Durable Execution
Design from failure boundaries, not from a preferred product. The goal is a
process whose durable state outlives any request, worker, deployment, or host
that happens to execute it.
Recognize The Problem
Consider durable execution when a process has one or more of these properties:
- It crosses multiple independently failing systems or side effects.
- It must wait minutes, days, or months without holding a process open.
- A crash must resume progress instead of restarting from the beginning.
- Retries must not duplicate charges, messages, infrastructure, or records.
- A callback, signal, approval, or external event determines what happens next.
- Operators need to inspect, cancel, repair, or otherwise influence a live run.
- Partial completion requires compensation rather than a database rollback.
- Existing runs must remain correct while code is deployed and changed.
Duration alone is not decisive. A five-second payment flow may need durability;
a two-hour disposable batch calculation may not. Ask whether correctness state
must survive a lost process and whether the process crosses irreversible failure
boundaries.
When uncertain, imagine a crash after every side effect and before every
acknowledgement. If reconstructing what happened would require guesses, the
process needs durable state somewhere.
Choose The Smallest Adequate Mechanism
| Mechanism |
Use when |
Limit |
| Request/response |
Work is short, bounded, and can fail with the request |
No progress after disconnect or process loss |
| Ordinary async/future |
Concurrency is local to one live process |
In-memory state disappears with the process |
| Job queue |
One independently retryable background operation is enough |
Multi-step progress and waits become application bookkeeping |
| Cron/scheduler |
Time is the trigger and each run is independent |
It is not per-instance orchestration |
| Database state machine |
States are few, transitions are explicit, and the team wants to own polling, locking, retries, and repair |
Operational machinery grows with every failure mode |
| Durable workflow |
Progress spans time or services and must resume, wait, retry, compensate, and remain inspectable |
Adds a runtime, replay rules, and history lifecycle |
Do not introduce a workflow engine merely because code has several functions.
Use one when it removes meaningful recovery state and failure handling that the
application would otherwise have to build and operate.
Establish The Process Contract
Before choosing APIs, write down:
- Identity: What business key identifies a run? What should a duplicate
start do?
- Invariants: What must never happen twice? What may happen at least once?
- Durable state: Which decisions and outputs are needed after a restart?
- Side effects: Where can the outside world change independently?
- Waits: Which timers, callbacks, approvals, or messages resume progress?
- Deadlines: Which step, attempt, and overall process timeouts apply?
- Failure policy: Which failures retry, pause for intervention, compensate,
or terminate?
- Operations: What must a user or operator be able to see and change?
- Evolution: How will old runs behave after a deployment?
- Retention: How much history and payload data can the process create?
Model explicit terminal states such as completed, failed, cancelled, and
compensated. "The worker stopped" is not a business state.
Separate Orchestration From Effects
Workflow code owns durable control flow. Keep it deterministic and replayable:
- branching and loops based on durable inputs and recorded results
- calls to activities, tasks, child workflows, timers, and durable waits
- compensation order and process-level state transitions
- handling signals, updates, cancellation, and deadlines
Activities or tasks own interaction with the nondeterministic world:
- network, database, filesystem, and subprocess calls
- random values, UUIDs, wall-clock time, environment reads, and secrets
- payment, email, infrastructure, browser, and LLM operations
- CPU-heavy work that should not block workflow-task execution
Do not call an API and then record that it happened as two unrelated operations.
The process can crash between them. Let the durable runtime record the scheduled
effect, and make the effect safe to retry.
Replay Discipline
Workflow code may execute again to reconstruct state. During replay:
- Never perform an unrecorded side effect.
- Never branch on current time, randomness, mutable globals, live configuration,
or a fresh database/API read.
- Use runtime-provided deterministic time, randomness, side effects, and version
markers where supported.
- Treat recorded activity results and received messages as immutable history.
- Keep logs and metrics replay-aware so replay does not duplicate telemetry.
Fetching mutable external state in an activity is valid. Persist the returned
snapshot as history and make the workflow decision from that recorded result.
Fetch again explicitly when a fresh decision is required.
Make Every Effect Retry-Safe
Assume an activity can complete externally while its acknowledgement is lost.
Most systems therefore provide at-least-once attempts, not magical exactly-once
effects.
For each effect:
- Derive an idempotency key from stable workflow/run/step identity.
- Pass that key to providers that support idempotency.
- Use unique constraints, upserts, compare-and-set, or an application receipt
table when the provider does not.
- Return and persist the provider's operation identifier.
- Reconcile ambiguous timeouts before issuing the effect again.
- Make cleanup and compensation idempotent too.
Classify errors before retrying:
| Failure |
Typical response |
| Timeout, rate limit, transient network/service error |
Bounded retry with backoff and jitter |
| Invalid input, forbidden request, missing permanent configuration |
Fail without retry or await correction |
| Unknown outcome after submission |
Reconcile by idempotency key or provider operation ID |
| Business rejection |
Record the outcome and follow business policy; do not disguise it as infrastructure failure |
| Exhausted transient retries |
Pause, compensate, or fail visibly according to the process contract |
Set schedule-to-start, start-to-close, heartbeat, and overall deadlines according
to the actual operation. A retry count without a time budget can outlive the
business deadline.
Model Interaction Explicitly
- Signals/messages report external facts and may arrive more than once.
Deduplicate with stable message IDs and define behavior for early or late
arrival.
- Updates/commands request a validated mutation and should report whether it
was accepted.
- Queries inspect state and must not mutate it.
- Timers represent durable time. Do not keep a thread or process sleeping.
- Cancellation is cooperative and should allow bounded cleanup.
- Termination is an emergency stop; do not assume normal cleanup runs.
Correlate callbacks to workflow identity plus a one-time or versioned token.
Authenticate the sender, persist receipt, and make callback handling idempotent.
Handle Partial Completion Deliberately
A saga is a sequence of committed local effects with compensations. It is not an
ACID transaction across services.
- Register compensation intent before or atomically with the forward effect.
- Compensate in the business-defined order, often reverse completion order.
- Retry compensation independently and expose failures to operators.
- Prefer semantic compensation such as refund or release over pretending an
external action never happened.
- Do not compensate a step whose outcome is still unknown; reconcile it first.
Use finally-style workflow cleanup for best-effort lifecycle work, but retain a
separate lease/reaper mechanism for resources that must eventually be reclaimed
after termination or runtime loss.
Bound Growth And Concurrency
- Use child workflows for independently owned lifecycles, isolated retry/cancel
policy, or large fan-out.
- Apply explicit concurrency limits; durable fan-out can overwhelm downstream
systems just as easily as ordinary concurrency.
- Use continue-as-new, history rollover, or an equivalent feature for perpetual
or high-message workflows.
- Keep large payloads outside history in durable object storage and record an
immutable reference plus integrity metadata.
- Treat task queues as routing boundaries, not as business identity.
Evolve Running Workflows Safely
Old executions may replay code written months ago. Before changing workflow
control flow:
- Determine whether the engine pins code, versions workflows, or records patch
markers.
- Keep old handlers available until no compatible runs remain, or migrate with a
supported reset/continue-as-new strategy.
- Make additive payload changes and define defaults for missing fields.
- Test replay against representative production histories before deployment.
- Separate backward-compatible worker rollout from irreversible data migration.
Never assume redeploying new code rewrites durable history.
Operate The Process As A Product
Every run should expose:
- stable workflow and run identity plus the business correlation key
- current status and meaningful business phase
- pending timer, message, activity, or child workflow
- attempt count, last failure, and next retry time
- timestamps and age in the current phase
- worker/task-queue availability when relevant
- cancellation, compensation, and terminal outcome
Diagnose from durable history before retrying or mutating anything. Prefer
documented control APIs for signal, update, cancel, retry, reset, or terminate;
do not edit runtime persistence directly.
Alert on symptoms that require action: overdue dispatch, retry exhaustion,
stalled waits beyond business deadlines, growing queue latency, failed
compensation, and history/payload growth. A merely long-running workflow is not
itself unhealthy.
For deeper design and operational checklists, read
references/design-patterns.md and
references/operations.md when those concerns are
part of the task.
Durable Supervision For AI Agents
Use a durable workflow to supervise an autonomous agent when the session spans
multiple tool calls, sandboxes, approvals, budgets, or restarts.
- Put every LLM call and tool call in an activity/task. Model output is
nondeterministic and must not run inside replayed orchestration code.
- Persist only the result needed for the next durable decision; place large
transcripts and artifacts in external storage with immutable references.
- Give tool calls stable operation IDs and enforce idempotency at effect
boundaries.
- Bound iterations, elapsed time, spend, and parallelism. A durable infinite
loop is still an infinite loop.
- Use signals/updates for human approval and cancellation, with explicit timeout
and rejection paths.
- Snapshot sandbox state when useful, but design for sandbox loss and restore.
- Keep credentials scoped to activities and sandbox providers, never durable
history.
- Ensure resource cleanup has both workflow-level finalization and an external
lease expiry/reconciler.
The workflow supervises intent and lifecycle; the agent remains an unreliable,
nondeterministic participant.
When Not To Use A Workflow Engine
Do not use one when:
- A single database transaction provides the required atomicity.
- One short, idempotent queued job with ordinary retries is sufficient.
- Work is stateless streaming or high-throughput transformation with no
per-instance lifecycle to recover.
- The operation is latency-critical and adding a durable scheduling boundary
provides no correctness value.
- Losing and recomputing the work is cheaper and simpler than persisting it.
- A small explicit state machine is already easy to own and operate.
- The team cannot support the runtime or honor its determinism/versioning model,
and the process does not justify that cost.
Do not force all application logic into workflows. Keep ordinary request
handling, pure computation, projections, dashboards, and domain services in
their natural boundaries.
Select An Implementation
Evaluate engines by execution model, language/runtime support, deployment and
data ownership, isolation, limits, observability, versioning, testing, and cost.
Do not choose from a feature checklist alone; prototype the hardest wait,
failure, replay, and upgrade path.
Read references/platform-selection.md when
selecting or comparing products. It includes vendor-neutral criteria and
starting points for Durable Workflow, Temporal, and Inngest.
Validate The Design
Before calling the process ready, prove these cases:
- Crash the worker after an external effect but before acknowledgement.
- Restart on another worker and confirm the process resumes correctly.
- Deliver the same callback or signal twice and out of order.
- Let a transient failure recover, then exhaust retries on a permanent failure.
- Cancel during an activity and during a durable wait.
- Fail a compensation and recover it operationally.
- Deploy changed workflow code and replay old histories.
- Run enough fan-out and history growth to reach realistic limits.
- Verify secrets and sensitive payloads do not appear in history or logs.
- Confirm operators can explain and safely resolve a stuck run.
The proof should exercise real restart and persistence boundaries, not only an
in-memory unit test.
1---2name: durable-execution3description: Recognize, design, implement, and operate long-running processes that must survive failures, retries, restarts, delays, callbacks, or human input. Use for payments, provisioning, onboarding, imports, approvals, external API coordination, autonomous agents, and when deciding between ordinary code, queues, cron, state machines, or a durable workflow engine.4license: MIT5---67# Durable Execution89Design from failure boundaries, not from a preferred product. The goal is a10process whose durable state outlives any request, worker, deployment, or host11that happens to execute it.1213## Recognize The Problem1415Consider durable execution when a process has one or more of these properties:1617- It crosses multiple independently failing systems or side effects.18- It must wait minutes, days, or months without holding a process open.19- A crash must resume progress instead of restarting from the beginning.20- Retries must not duplicate charges, messages, infrastructure, or records.21- A callback, signal, approval, or external event determines what happens next.22- Operators need to inspect, cancel, repair, or otherwise influence a live run.23- Partial completion requires compensation rather than a database rollback.24- Existing runs must remain correct while code is deployed and changed.2526Duration alone is not decisive. A five-second payment flow may need durability;27a two-hour disposable batch calculation may not. Ask whether correctness state28must survive a lost process and whether the process crosses irreversible failure29boundaries.3031When uncertain, imagine a crash after every side effect and before every32acknowledgement. If reconstructing what happened would require guesses, the33process needs durable state somewhere.3435## Choose The Smallest Adequate Mechanism3637| Mechanism | Use when | Limit |38| --- | --- | --- |39| Request/response | Work is short, bounded, and can fail with the request | No progress after disconnect or process loss |40| Ordinary async/future | Concurrency is local to one live process | In-memory state disappears with the process |41| Job queue | One independently retryable background operation is enough | Multi-step progress and waits become application bookkeeping |42| Cron/scheduler | Time is the trigger and each run is independent | It is not per-instance orchestration |43| Database state machine | States are few, transitions are explicit, and the team wants to own polling, locking, retries, and repair | Operational machinery grows with every failure mode |44| Durable workflow | Progress spans time or services and must resume, wait, retry, compensate, and remain inspectable | Adds a runtime, replay rules, and history lifecycle |4546Do not introduce a workflow engine merely because code has several functions.47Use one when it removes meaningful recovery state and failure handling that the48application would otherwise have to build and operate.4950## Establish The Process Contract5152Before choosing APIs, write down:53541. **Identity:** What business key identifies a run? What should a duplicate55 start do?562. **Invariants:** What must never happen twice? What may happen at least once?573. **Durable state:** Which decisions and outputs are needed after a restart?584. **Side effects:** Where can the outside world change independently?595. **Waits:** Which timers, callbacks, approvals, or messages resume progress?606. **Deadlines:** Which step, attempt, and overall process timeouts apply?617. **Failure policy:** Which failures retry, pause for intervention, compensate,62 or terminate?638. **Operations:** What must a user or operator be able to see and change?649. **Evolution:** How will old runs behave after a deployment?6510. **Retention:** How much history and payload data can the process create?6667Model explicit terminal states such as completed, failed, cancelled, and68compensated. "The worker stopped" is not a business state.6970## Separate Orchestration From Effects7172Workflow code owns durable control flow. Keep it deterministic and replayable:7374- branching and loops based on durable inputs and recorded results75- calls to activities, tasks, child workflows, timers, and durable waits76- compensation order and process-level state transitions77- handling signals, updates, cancellation, and deadlines7879Activities or tasks own interaction with the nondeterministic world:8081- network, database, filesystem, and subprocess calls82- random values, UUIDs, wall-clock time, environment reads, and secrets83- payment, email, infrastructure, browser, and LLM operations84- CPU-heavy work that should not block workflow-task execution8586Do not call an API and then record that it happened as two unrelated operations.87The process can crash between them. Let the durable runtime record the scheduled88effect, and make the effect safe to retry.8990### Replay Discipline9192Workflow code may execute again to reconstruct state. During replay:9394- Never perform an unrecorded side effect.95- Never branch on current time, randomness, mutable globals, live configuration,96 or a fresh database/API read.97- Use runtime-provided deterministic time, randomness, side effects, and version98 markers where supported.99- Treat recorded activity results and received messages as immutable history.100- Keep logs and metrics replay-aware so replay does not duplicate telemetry.101102Fetching mutable external state in an activity is valid. Persist the returned103snapshot as history and make the workflow decision from that recorded result.104Fetch again explicitly when a fresh decision is required.105106## Make Every Effect Retry-Safe107108Assume an activity can complete externally while its acknowledgement is lost.109Most systems therefore provide at-least-once attempts, not magical exactly-once110effects.111112For each effect:113114- Derive an idempotency key from stable workflow/run/step identity.115- Pass that key to providers that support idempotency.116- Use unique constraints, upserts, compare-and-set, or an application receipt117 table when the provider does not.118- Return and persist the provider's operation identifier.119- Reconcile ambiguous timeouts before issuing the effect again.120- Make cleanup and compensation idempotent too.121122Classify errors before retrying:123124| Failure | Typical response |125| --- | --- |126| Timeout, rate limit, transient network/service error | Bounded retry with backoff and jitter |127| Invalid input, forbidden request, missing permanent configuration | Fail without retry or await correction |128| Unknown outcome after submission | Reconcile by idempotency key or provider operation ID |129| Business rejection | Record the outcome and follow business policy; do not disguise it as infrastructure failure |130| Exhausted transient retries | Pause, compensate, or fail visibly according to the process contract |131132Set schedule-to-start, start-to-close, heartbeat, and overall deadlines according133to the actual operation. A retry count without a time budget can outlive the134business deadline.135136## Model Interaction Explicitly137138- **Signals/messages** report external facts and may arrive more than once.139 Deduplicate with stable message IDs and define behavior for early or late140 arrival.141- **Updates/commands** request a validated mutation and should report whether it142 was accepted.143- **Queries** inspect state and must not mutate it.144- **Timers** represent durable time. Do not keep a thread or process sleeping.145- **Cancellation** is cooperative and should allow bounded cleanup.146- **Termination** is an emergency stop; do not assume normal cleanup runs.147148Correlate callbacks to workflow identity plus a one-time or versioned token.149Authenticate the sender, persist receipt, and make callback handling idempotent.150151## Handle Partial Completion Deliberately152153A saga is a sequence of committed local effects with compensations. It is not an154ACID transaction across services.155156- Register compensation intent before or atomically with the forward effect.157- Compensate in the business-defined order, often reverse completion order.158- Retry compensation independently and expose failures to operators.159- Prefer semantic compensation such as refund or release over pretending an160 external action never happened.161- Do not compensate a step whose outcome is still unknown; reconcile it first.162163Use `finally`-style workflow cleanup for best-effort lifecycle work, but retain a164separate lease/reaper mechanism for resources that must eventually be reclaimed165after termination or runtime loss.166167## Bound Growth And Concurrency168169- Use child workflows for independently owned lifecycles, isolated retry/cancel170 policy, or large fan-out.171- Apply explicit concurrency limits; durable fan-out can overwhelm downstream172 systems just as easily as ordinary concurrency.173- Use continue-as-new, history rollover, or an equivalent feature for perpetual174 or high-message workflows.175- Keep large payloads outside history in durable object storage and record an176 immutable reference plus integrity metadata.177- Treat task queues as routing boundaries, not as business identity.178179## Evolve Running Workflows Safely180181Old executions may replay code written months ago. Before changing workflow182control flow:183184- Determine whether the engine pins code, versions workflows, or records patch185 markers.186- Keep old handlers available until no compatible runs remain, or migrate with a187 supported reset/continue-as-new strategy.188- Make additive payload changes and define defaults for missing fields.189- Test replay against representative production histories before deployment.190- Separate backward-compatible worker rollout from irreversible data migration.191192Never assume redeploying new code rewrites durable history.193194## Operate The Process As A Product195196Every run should expose:197198- stable workflow and run identity plus the business correlation key199- current status and meaningful business phase200- pending timer, message, activity, or child workflow201- attempt count, last failure, and next retry time202- timestamps and age in the current phase203- worker/task-queue availability when relevant204- cancellation, compensation, and terminal outcome205206Diagnose from durable history before retrying or mutating anything. Prefer207documented control APIs for signal, update, cancel, retry, reset, or terminate;208do not edit runtime persistence directly.209210Alert on symptoms that require action: overdue dispatch, retry exhaustion,211stalled waits beyond business deadlines, growing queue latency, failed212compensation, and history/payload growth. A merely long-running workflow is not213itself unhealthy.214215For deeper design and operational checklists, read216[references/design-patterns.md](references/design-patterns.md) and217[references/operations.md](references/operations.md) when those concerns are218part of the task.219220## Durable Supervision For AI Agents221222Use a durable workflow to supervise an autonomous agent when the session spans223multiple tool calls, sandboxes, approvals, budgets, or restarts.224225- Put every LLM call and tool call in an activity/task. Model output is226 nondeterministic and must not run inside replayed orchestration code.227- Persist only the result needed for the next durable decision; place large228 transcripts and artifacts in external storage with immutable references.229- Give tool calls stable operation IDs and enforce idempotency at effect230 boundaries.231- Bound iterations, elapsed time, spend, and parallelism. A durable infinite232 loop is still an infinite loop.233- Use signals/updates for human approval and cancellation, with explicit timeout234 and rejection paths.235- Snapshot sandbox state when useful, but design for sandbox loss and restore.236- Keep credentials scoped to activities and sandbox providers, never durable237 history.238- Ensure resource cleanup has both workflow-level finalization and an external239 lease expiry/reconciler.240241The workflow supervises intent and lifecycle; the agent remains an unreliable,242nondeterministic participant.243244## When Not To Use A Workflow Engine245246Do not use one when:247248- A single database transaction provides the required atomicity.249- One short, idempotent queued job with ordinary retries is sufficient.250- Work is stateless streaming or high-throughput transformation with no251 per-instance lifecycle to recover.252- The operation is latency-critical and adding a durable scheduling boundary253 provides no correctness value.254- Losing and recomputing the work is cheaper and simpler than persisting it.255- A small explicit state machine is already easy to own and operate.256- The team cannot support the runtime or honor its determinism/versioning model,257 and the process does not justify that cost.258259Do not force all application logic into workflows. Keep ordinary request260handling, pure computation, projections, dashboards, and domain services in261their natural boundaries.262263## Select An Implementation264265Evaluate engines by execution model, language/runtime support, deployment and266data ownership, isolation, limits, observability, versioning, testing, and cost.267Do not choose from a feature checklist alone; prototype the hardest wait,268failure, replay, and upgrade path.269270Read [references/platform-selection.md](references/platform-selection.md) when271selecting or comparing products. It includes vendor-neutral criteria and272starting points for Durable Workflow, Temporal, and Inngest.273274## Validate The Design275276Before calling the process ready, prove these cases:2772781. Crash the worker after an external effect but before acknowledgement.2792. Restart on another worker and confirm the process resumes correctly.2803. Deliver the same callback or signal twice and out of order.2814. Let a transient failure recover, then exhaust retries on a permanent failure.2825. Cancel during an activity and during a durable wait.2836. Fail a compensation and recover it operationally.2847. Deploy changed workflow code and replay old histories.2858. Run enough fan-out and history growth to reach realistic limits.2869. Verify secrets and sensitive payloads do not appear in history or logs.28710. Confirm operators can explain and safely resolve a stuck run.288289The proof should exercise real restart and persistence boundaries, not only an290in-memory unit test.