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.
# LLM emits a tool callresponse = llm.chat(...)tool_call = response.tool_calls[0]# Agent crashes. Restarts. Retries.result = ledger.run(tool_call, handler=charge) # runs onceWorks with OpenAI, Anthropic, LangChain, and more
Agents retry. Side effects multiply.
Real incidents from teams running AI agents in production.
| Date | Incident | Impact |
|---|---|---|
| Jan 2025 | Claude Code stuck in recursion loopX post by @alexalbert__ | ~$16K1.67B tokens |
| 2025 | Agent workflow retried on timeout, duplicated side effectsHacker News discussion | $47KAPI costs |
| Jul 2025 | Duplicate ledger deductions on API accountsOpenAI status page | Mass 429sService degradation |
| ? | Your agent retries on timeout → your customer charged twiceYou, eventually | TBD |
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
With agent-ledger
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.You approved $100. The agent charged $10,000.
That's what happens when approvals aren't tied to the exact payload.
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.
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.
# 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
)# 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