Durability¶
Build agents that survive process crashes, pause for human approval for days, and never re-fire side effects on resume.
from fastaiagent import Chain, Resume, interrupt
def approve_refund(state):
if state["amount"] > 10_000:
decision = interrupt(
reason="manager_approval",
context={"amount": state["amount"]},
)
return {"approved": decision.approved}
return {"approved": True}
# First run — suspends and exits cleanly.
result = chain.execute({"amount": 50_000}, execution_id="refund-abc")
assert result.status == "paused"
# Hours, days, or a server restart later, in a different process:
result = await chain.aresume(
"refund-abc",
resume_value=Resume(approved=True, metadata={"approver": "alice"}),
)
assert result.status == "completed"
What v1.0 ships¶
- Crash-proof agents. A real
SIGKILLin the middle of a 5-node chain. The next process call resumes at the last successful checkpoint. - Pause for human approval. For days.
interrupt()suspends a workflow cleanly. The process can exit. Hours later, an HTTP request, a CLI command, or a click in the local UI resumes it. - SQLite locally. Postgres in production. Same SDK. The
CheckpointerProtocol lets you swap one constructor argument and get multi-process / distributed durability — same semantics, same atomic resume claim. - Multi-agent durability. Hierarchical
agent_pathso a paused tool inside a worker inside a supervisor renders assupervisor:planner/worker:auditor/tool:approve_refundeverywhere — in checkpoints, in the/approvalsUI, and in resume errors. - Built-in side-effect protection. The
@idempotentdecorator caches a function's result for the lifetime of an execution — wrap yourcharge_customeronce, never double-charge again.
Pick a path¶
| Goal | Read |
|---|---|
| Get a paused-and-resumed chain running in 5 minutes | Quickstart |
| Understand why side effects double-fire on resume — and how to fix it | Side effects & idempotency |
| Apply durability to common production shapes | Patterns |
Wire interrupt() into Agent / Swarm / Supervisor |
Multi-agent durability |
| Choose between SQLite and Postgres for production | Checkpointers |
| Look up exact signatures, types, return shapes | API reference |
| Coming from LangGraph? | Migrating from LangGraph |
How it works in one paragraph¶
When a node calls interrupt(reason, context), the chain executor catches
the signal, persists an interrupted checkpoint and a row in
pending_interrupts in one transaction, and returns
ChainResult(status="paused"). The Python process can exit. To resume,
any process with access to the same checkpoint store calls
chain.aresume(execution_id, resume_value=Resume(...)). The resumer
atomically deletes the pending row (Postgres DELETE … RETURNING *,
SQLite BEGIN; SELECT; DELETE; COMMIT), re-enters the suspended node
with the resume value injected via a ContextVar, and interrupt()
returns the value instead of raising. Concurrent resumers — a
double-clicked Approve button, two webhook deliveries — see
AlreadyResumed. Same machinery powers Agent / Swarm / Supervisor; the
only difference is the segments their agent_path carries.