AI agents retry. Side effects shouldn't.

When your agent crashes and retries, it doesn't know what it already did.
Double charges. Duplicate emails. No audit trail.

agent-ledger remembers.

Star on GitHub
$pip install agent-ledger
Python·Framework-agnostic·Apache-2.0

Agents retry. Side effects multiply.

Real incidents from teams running AI agents in production.

incidents.log
DateIncidentImpact
Jan 2025Claude Code stuck in recursion loopX post by @alexalbert__~$16K1.67B tokens
2025Agent workflow retried on timeout, duplicated side effectsHacker News discussion$47KAPI costs
Jul 2025Duplicate ledger deductions on API accountsOpenAI status pageMass 429sService degradation
?Your agent retries on timeout → your customer charged twiceYou, eventuallyTBD

The agents are already in production. The idempotency layer isn't.

agent-ledger helps prevent this.

Every tool call gets a deterministic key.

agent-ledger hashes workflow_id + tool name + args into a unique key. Before running, it checks: have I seen this key before?

Seen key →

Return recorded result

Handler skipped

New key →

Run once, record result

Then return it

Same call. Same key. Retries replay instead of re-execute.

Defense in depth: For APIs that support it (like Stripe), pass effect.idem_key downstream to extend idempotency end-to-end.

Three lines. That's the integration.

Without agent-ledger

5 retries → 5 charges
Crash → unknown state
"Did it run?" → ???

With agent-ledger

5 retries → handler runs once
Crash → resume from ledger
"Did it run?" → query the receipt
main.py
result = await ledger.run(
    ToolCall(
        workflow_id="order-123", 
        tool="stripe.charge", 
        args={"amount": 100, "currency": "usd"}
    ),
    handler=charge_customer,
)
# Retry this 100 times. Handler runs once.
Jul 2025: Replit agent deleted production database, fabricated data to hide it

You approved $100. The agent charged $10,000.

That's what happens when approvals aren't tied to the exact payload.

The problem with most HITL

Most human-in-the-loop tools approve the tool. "Yes, you can charge the customer."

But what if the agent retries with different args? Different amount? Different customer? Your approval still counts.

Intent-bound approvals

With agent-ledger, approval is bound to the exact payload hash. Change any argument? That's a new approval request.

No bait-and-switch. No drift. The approval is verified against the execution hash.

Approved
# You approved THIS exact call:
ledger.run(
    ToolCall(
        workflow_id="order-456",
        tool="stripe.charge",
        args={"amount": 100, "customer": "cus_123"}
    ),
    handler=charge_customer,
    requires_approval=True
)
Blocked
# Agent retries with different args?
# That's a NEW approval request.
args={"amount": 10000, "customer": "cus_123"}
#              ↑ Changed? Blocked.

The agent waits. You decide. Exactly what you approved.

Idempotent Execution

Same call, same result. Handler runs once. Finally.

Crash Recovery

Crash mid-run? New process resumes from the last successful step.

Audit Trail

Inputs, outputs, timing, status — all in Postgres. Debug failures in seconds.

Human Approvals

Payments, deploys, or any tool you flag — pauses for Slack or webhook approval.

Multi-Worker Safe

Multiple workers, same task? Only one executes. No race conditions.

Framework Agnostic

Works with any Python agent framework. No rewrites needed.

No sidecar. No SaaS. Just a library.

Let agents act—without losing control.

Built on agent-ledger, rune0 is the managed control plane for agent policies, approvals, and runtime enforcement in production.

Define who can approve what, enforce rules at runtime, and get an audit trail of every decision—configured as code.

Early access opening soon.

FAQ

Your agents will retry.

Make sure your side effects don't.

Apache-2.0 · Free forever · ~5ms overhead with Postgres