Concepts

Durable execution

Why a long agent picks up where it stopped instead of starting over: each step's result is recorded the moment it completes, and replay skips it.

A workflow makes progress by running its handler again and again. Duraton records the result of every step the moment it completes, so each pass replays the finished steps from their recorded results and executes only the next unfinished one.

const charge = await ctx.step.run("charge", () => chargeCard(order));
await ctx.step.sleep("settle", "1h");
const ship = await ctx.step.run("ship", () => createShipment(charge));

Interrupt this run after charge - a crash, a deploy, an hour of sleep - and the next pass replays charge from its recorded result without calling chargeCard again, then continues at ship. The card is charged exactly once.

When a step fails

Only the failed step retries. Every step before it already has a recorded result, so it is not re-executed. If charge succeeds and ship throws, Duraton retries ship alone (see Retries).

When work must go inside a step

The code between steps runs on every pass. Only work inside a step is recorded and replayed.

Anything with a side effect, or a result that can change between passes - network calls, database reads, randomness, reading the clock - must live inside a step, or it repeats on every pass and its value drifts:

const drifts = Date.now();                                  // re-read on every pass
const stable = await ctx.step.run("now", () => Date.now()); // recorded once, replayed after

The code between steps must be deterministic

Given the same recorded step results, the orchestration around your steps - if branches, loops, building arguments, choosing step ids - must reach the same next step every pass. This is a requirement, not a preference: a branch that takes a different path on a later pass asks for a step that has no recorded result at that position, and the run's recorded history no longer describes the code that produced it.

Never branch on a value the handler reads outside a step. if (Math.random() > 0.5) and if (Date.now() > deadline) decide differently on each pass. Record the value in a step first, then branch on the recorded result.

See the durable execution example running end to end in Examples.

On this page