What is Durable Execution?
Durable execution: interruption-agnostic code resulting in interruption-tolerant execution.
Write the code as if failure doesn’t exist. The execution survives failure anyway. Your process can crash, restart, or move to another machine, and the execution (the running instance of your code) continues from where it stopped.
Why executions break
Distributed applications live longer than any single process. Deploys roll, machines get recycled, containers are rescheduled onto other nodes, timeouts fire and workloads move. Meanwhile the work itself often needs minutes, hours, or months to finish: an order being processed, an agent halfway through a task.
A function’s call stack lives in the memory of one process. When the process dies, the stack dies with it: every local variable, every pending call, and where it was. A retry can start the function again, but it starts from the top with the in-flight state gone, so the second attempt repeats whatever the first one already did.
Durable execution moves the execution’s memory of where it was out of the process.
The mental model: functions and promises
Durable execution rests on two concepts you already use: functions and promises. A durable promise is a promise with a home outside any process — written down the moment it’s made, settled when it’s done, kept even if the process making it doesn’t finish.
The durable promise is the unit of durability. When a function is invoked, the invocation is recorded as a promise. When the function completes, the result is recorded by resolving that promise. Every execution leaves this trail — promises created for what was started, promises resolved for what finished.
That makes a durable promise a checkpoint. At any moment, the promises record exactly how far the execution got, independent of the process that was executing. The process can die. The promise is still kept.
Recovery depends only on the promises. A new process, on any machine, reads the last promise that was kept and continues from there.
Invocation and Resumption
Your code and the system meet in two interactions: Invocation and Resumption. Resumption is Invocation plus Replay.
Invocation is the ordinary case. A function call creates a promise, the function runs, and the promise resolves with the result.
Resumption is the recovery case, built from the same parts. Say process_order charges a card, reserves inventory, and sends a confirmation. The card gets charged and that promise resolves. Then the process dies.
The execution isn’t lost; it’s interrupted. process_order is invoked again, by a new process, possibly on another machine, and execution starts from the top of the function. Replay takes over. When execution reaches the charge step, the system finds a resolved promise and returns the recorded result immediately. When execution reaches the reservation step, its promise is unresolved — so it executes, for the first time. From there, the execution continues to the end.
Invocation — the execution begins
Resumption = Invocation + Replay
Resumption = Invocation + Replay. Re-invoke the function, replay what’s recorded, execute what isn’t. Suspend, checkpoint, resume — recovery is the same motion as the happy path. That is what makes it testable: Resonate exercises crash-and-resume behavior with deterministic simulation testing.
Replay protects steps that completed: their promise resolved before the failure. A step still in flight when the process died has no recorded result, so it runs again on resumption. That narrows retries from the whole function down to the one interrupted step — and a step with an external side effect, like a charge against a card network, should still be idempotent.
The same mechanics cover waiting. A durable timer or a pending human approval suspends the execution; when the timer fires or the answer arrives, resumption picks it back up. Whether the interruption was a crash, a deploy, or a seven-day wait, recovery is one mechanism.
Distributed Async Await: the code you write
If durable promises are the model, Distributed Async Await is the programming interface: Async Await, extended across process boundaries. Functions and promises, made durable and distributed.
async def process_order(ctx: Context, order: dict):
await ctx.run(charge_card, order)
await ctx.run(reserve_inventory, order)
await ctx.sleep(timedelta(days=7))
return await ctx.run(send_followup, order)Each await ctx.run(...) records a durable promise, so each step is a checkpoint. The ctx.sleep is a durable timer: the execution waits seven days, then resumes. The promise holds its place. And the same await reaches across processes: ctx.rpc awaits a function running on another worker, with a durable promise in between.
Read the code again with failure in mind. It contains no retry logic, no state machine, no recovery branch. Durability is a property of the execution, not the code.
Durable execution for AI agents
An agent is a long-running, multi-step process: a loop that calls a model, acts on the result, calls a tool, waits (sometimes on another agent, sometimes on a human), and goes around again. Steps take seconds or days, any of them can fail, and losing the loop’s state midway means losing the work done so far.
That is the shape durable execution suits. Each model call and tool call resolves to a promise, so a crashed agent resumes with its history intact. A durable timer lets an agent wait a day for an approval without holding a process open. Async Await spans services, agents, and human tasks alike — the agent loop is a function.
Wrap each step of the loop in ctx.run and the loop becomes a durable workflow. An agent that fans out twenty tool calls, waits on a human review, then writes up its results is a function whose promises record every one of those steps. Kill its process at step fourteen, and the resumed execution replays thirteen recorded results, re-enters step fourteen, and keeps going.
Durable execution is a protocol
Most infrastructure arrives as a product: one implementation, one vendor. Durable execution arrives as a protocol. It asks two things of storage — durability, and an atomic compare-and-swap — and any storage with both can hold durable promises, which is why more than one implementation of it can exist. The rules are written down in the open: distributed-async-await.io specifies the programming, execution, and system models.
Resonate is one implementation of that protocol — a durable execution engine backed by your databases, connected by your queues and transports. An SDK in your app, one binary beside it. The server and the SDKs are Apache 2.0.
What is a durable execution framework?
A durable execution framework is the software that implements the model: an SDK that gives your functions durable promises, and a runtime that stores those promises and re-invokes interrupted executions. The same category is sometimes called a durable workflow engine, and the durable workflows it runs are ordinary functions whose executions outlive any single process. With Resonate, the framework is an SDK in your language plus a server that ships as a single binary. For how the pieces fit together, see why Resonate.
Do I need new infrastructure to run durable execution?
No. Durable promises need durable storage with an atomic compare-and-swap, and the databases you run provide both. Compare-and-swap is what resolves a promise exactly once, even when two recovering processes race to complete the same step. The Resonate server stores its state in SQLite or Postgres, and messages move over the transports you use today.
What languages can I use?
Resonate ships SDKs for TypeScript, Python, Rust, Go, and Java. A durable execution in Rust rests on the same promises as one in Python or TypeScript, and each language’s quickstart starts from the model in its own idiom.
Server · SDKs — all Apache 2.0
Run your first durable execution.
A quickstart in your language, on your machine — a workflow that survives a kill and picks up where it left off.