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, as if nothing happened.

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, a report being assembled, 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, the execution’s entire memory of where it was. A retry can start the function again, but it starts from the top with the in-flight state gone; whatever the first attempt already did, the second attempt doesn’t know about.

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.

Notice what this removes: recovery no longer depends on anything the process knew. The state that drives recovery was never in the process; it lives with the promises. A new process, on any machine, can read the last promise that was kept and continue from exactly 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; the work is not repeated. 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

process_order(order)promise created
├─ charge_card(order)✓ resolved · result recorded
└─ reserve_inventory(order)✕ the process dies here

Resumption = Invocation + Replay

process_order(order)re-invoked — new process
├─ charge_card(order)↺ replayed · recorded result returned
├─ reserve_inventory(order)▸ executes for the first time
└─ send_confirmation(order)▸ continues to the end
The charge step completed before the crash, so its recorded result stands in for the work on replay. Execution continues from where it stopped.

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, which is also 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, not just crashing. 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 thirty-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.

process_order.pyPython · same model in every SDK
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 without any process holding its place, then resumes. 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 instead of starting over. 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 just a function.

Concretely: 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. The pattern that makes agents hard to operate — long, stateful, unattended — is the pattern durable execution was built for.

A protocol, not a product

Durable promises need storage that offers durability and an atomic compare-and-swap. Any storage with those two properties can host them, which is why durable execution is best understood as a protocol rather than a product. The protocol is written down: distributed-async-await.io specifies the programming, execution, and system models in the open.

Resonate is an 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. Durable execution isn’t a platform you move into; it’s a capability your existing stack gains.

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.

Protocol · server · SDKs · tools — 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.