Concepts and Vocabulary
The shared vocabulary of durable execution, defined once here and used by every deep-dive in this catalog. The field has no agreed terminology: the same idea is a step in Inngest and Cloudflare Workflows, an activity in Temporal and Azure Durable Functions, a journal entry in Restate, an oplog entry in Golem, and an operation output in DBOS. This page fixes one name per idea and records what each system calls it.
Last reviewed: September 12, 2026.
The core idea
Durable execution is the technique of making a long-running program survive the death of the process running it, by recording the results of its effects and re-deriving everything else. It is not checkpointing: nothing of the program's stack, heap, or local variables is saved. What is saved is a journal of answers, and the program is expected to ask the same questions again.
A durable program (the catalog's umbrella term for what systems variously call a workflow, an orchestration, or a durable function) is therefore split in two:
| Part | Property required | Persisted? |
|---|---|---|
| The program | deterministic between effects | no — re-executed from scratch |
| Its effects | may be arbitrary, may fail, may touch the outside world | yes — result recorded |
IMPORTANT
Every system in this catalog that replays enforces the same asymmetry: the program must be a pure function of the journal, and everything impure must go through the effect boundary. Systems differ almost entirely in how that boundary is named, identified, and policed.
Program and effect
Durable program
The re-executed function. Temporal and Azure Durable Functions call it an orchestration or orchestrator function; Effect and Cloudflare a workflow; DBOS a workflow function; Restate a handler (of a service, virtual object, or workflow); Golem a worker (an instance of a WebAssembly component); Vercel marks one with the "use workflow" directive. Inngest calls it a function and re-invokes it over HTTP for every step.
Activity (or step)
One journaled effect. The boundary across which nondeterminism, I/O, and failure are allowed. Named Activity.make in Effect, step.run in Inngest, step.do in Cloudflare, ctx.run in Restate, @DBOS.step() in DBOS, an activity function in Temporal and Azure, and a "use step" function in Vercel. In Golem the boundary is implicit: every WASI host call is journaled, with no user-visible step construct.
The defining property is the same everywhere: an activity's result is recorded, so on replay the activity does not run — its recorded value is returned in place of executing it.
Journal (or history, event log, oplog)
The append-only record of what the activities answered. Temporal calls it the event history (a sequence of HistoryEvent protobufs owned by the server); Azure and Dapr/DTFx the history (TaskScheduled, TaskCompleted, TimerFired, …); Restate the journal (commands and notifications); Golem the oplog; DBOS the operation_outputs table; Inngest the op stack, keyed by hashed step id.
A journal entry is conventionally a pair, not a single record: an intent written before the effect and a result written after it. Temporal's ActivityTaskScheduled/ActivityTaskCompleted and Restate's RunCommand/RunCompletion are the same shape, and it is the shape write-ahead logging has required since ARIES: the log record describing a change reaches stable storage before the change does.
Replay
Re-running the durable program from its first instruction, answering each activity from the journal until the journal is exhausted, after which execution goes live again. Most systems expose a flag for the phase (IsReplaying in Azure, Dapr and Netherite, context.df.isReplaying in the JavaScript SDK) so that logging and other unjournaled side effects can be suppressed during it.
Suspension
Ending the process deliberately while the program is logically still running: at a long timer, an external event, or a child workflow. Because there is no continuation to save, suspension is just "stop, and replay later" — Effect models it as a self-interrupt that persists a Suspended outcome, Restate as a first-class protocol state, and Temporal as simply not scheduling another workflow task. The contrast case is Trigger.dev, which suspends by checkpointing the operating-system process instead.
Identity and matching
Step identity
How a replayed call is matched to its recorded answer. This is the single sharpest axis of variation in the catalog, and it takes three forms:
| Scheme | Key | Systems | Breaks when |
|---|---|---|---|
| Positional | an ordinal counter assigned in order | Temporal, Azure, Dapr/DTFx, DBOS, Restate, Netherite | a step is inserted, removed, or reordered |
| Named | a developer-supplied string (+ attempt) | Inngest, Cloudflare, Effect | two steps share a name; a rename silently re-executes |
| Derived | a hash of parent id and call site | Resonate (promise ids), Golem (idempotency keys) | the derivation captures something that legitimately moves |
No system in the catalog makes the arguments part of the key, and only Restate compares them at all (as a mismatch check, per command type, not as the key). Helland is the one source arguing that it should be a function of the whole request: a retry that differs in its arguments is a different request, not a repeat of the same one.
Divergence (nondeterminism error, journal mismatch)
The error raised when the replayed program asks for something the journal does not have at that position. Temporal's is [TMPRL1100]; Restate's is JOURNAL_MISMATCH (RT0016), with a per-handler policy of retry, pause or fail; DBOS raises DBOSUnexpectedStepError; DTFx raises NonDeterministicOrchestrationException, which in Dapr stalls the instance rather than corrupting it.
WARNING
Divergence detection is partial in every system surveyed. All of them compare the step's identity and usually its name; none compares its arguments as a matter of course, so a changed argument to an already-journaled step is invisible. Inngest's own SDK specification mandates a warning when step order changes, and its current engine never raises one.
Idempotency key
A caller-supplied unique id that makes a repeated request a lookup rather than a second execution. It appears as the workflow/run id (DBOS's workflowID, Temporal's workflow id, Cloudflare's instance id), as an explicit key on an invocation (Restate), as the primary key of a durable promise (Resonate), or as a storage-level uniqueness constraint (Effect's UNIQUE (message_id)). Helland names the general requirement: the recipient must remember what it has already processed, and must return the same answer to a duplicate.
Guarantees
At-least-once, exactly-once, effectively-once
An activity that has run but whose result has not yet been journaled will run again after a crash. That window cannot be closed, only narrowed, so activity execution is at-least-once in every replay system in this catalog.
What can be exactly-once is the recorded outcome: the journal admits one answer per step. Burckhardt et al. prove this for the internal state of a durable function, and explicitly exclude external calls from the result, because "duplication of external calls (unlike internal calls) is observable". DBOS's documentation states the split plainly: steps get at-least-once guarantees, workflow outcomes are persisted exactly-once.
Effectively-once is the composite property the field actually delivers: at-least-once execution plus idempotent effects. It is a property of the effects, not of the engine.
Determinism
The requirement that the program produce the same sequence of activity calls given the same sequence of answers. Systems enforce it along a spectrum:
| Degree | Mechanism | Systems |
|---|---|---|
| By construction | every nondeterministic input is a logged event; state transition is a pure fold | Netherite, Burckhardt's calculus |
| By substitution | the runtime replaces the clock, RNG, timers, and scheduler with journaled versions | Temporal, Azure, Restate |
| By sandbox | the host language's nondeterministic APIs are patched or removed | Temporal (TypeScript v8 isolate) |
| By static analysis | an analyzer flags forbidden calls in orchestrator code | Azure (.NET only) |
| By discipline | documented rules, detected after the fact if at all | Inngest, DBOS, Cloudflare |
The substituted primitives recur across systems: a journaled clock (Azure's currentUtcDateTime is the orchestration episode's start timestamp; Dapr's NewGuid is a UUID v5 derived from journal-stable inputs) and a seeded random (Restate's ctx.rand).
Writers, integrity and intervention
Fencing token
A value that identifies a writer's claim and increases monotonically, so a write carrying a stale one can be refused even if the writer has not noticed it lost its claim. Temporal's shard RangeID, Restate's LeaderEpoch and Netherite's partition ownership are fencing tokens; a plain lock is not, because a process holding an expired lock still believes it holds one.
Lease
A time-bounded claim on work or on a partition, refreshed while held and reclaimable once stale. Effect's shard locks and Inngest's queue items are leased. A lease keeps processes apart; it does not by itself make an individual write conditional, which is why the careful designs have both.
Conditional append (expected version)
An append that succeeds only if the record is in the state the writer believes it is in. KurrentDB's ExpectedVersion, Temporal's DBRecordVersion, [Orleans]'s e-tag and Marten's version guard are all this. Note that an optimistic version check is not sufficient under a weak isolation level — Marten documents the race where two writers both pass the check.
Atomic multi-record append
Writing several records as one unit: an intent and its result, or a step and its compensation registration. Free for anything over a transaction (Marten, Akka/Pekko's AtomicWrite, Inngest's single Redis script) and a build-it-yourself problem for anything appending to a file.
Durability barrier
An operation that blocks until the record is durable, exposed to the program rather than managed for it. Golem's oplog-commit is the only instance in this survey: it takes a replication count and returns when the log has reached it.
Dead letter (parking, quarantine)
Somewhere to put a record or a run that cannot be processed, so it neither blocks the system nor disappears. KurrentDB parks a message on its own stream with a replay operation; [Orleans] quarantines retired state for a grace period with resurrection supported; Marten skips and dead-letters while running but pauses during a rebuild. Inngest argues the category away, keeping failed runs failed and re-running them in bulk after a fix.
Fork, reset, redrive, rewind
The family of operations that resume a run from somewhere other than where it stopped. Temporal's reset takes an event id and a policy for which external inputs are reapplied afterwards; Golem reverts to an oplog index or undoes the last N invocations; AWS Step Functions redrives from the failed state; DBOS forks from a step. rr generalises it: execution runs backwards by jumping to an earlier checkpoint and replaying forward (deterministic record and replay).
Cancel versus terminate
Two different ways to stop a run. Cancelling delivers something the program can observe, so its cleanup and compensation execute; terminating stops it without a turn. Temporal and Restate keep both; Dapr has only the abrupt one, which means an operator stop there cannot run rollback.
Failure and rollback
Compensation
A semantic undo: an action that reverses the business effect of a completed step, rather than restoring a previous state. The concept and its guarantee come from Sagas (1987), which pairs each sub-transaction T1..Tn with a compensating transaction C1..Cn-1 and runs the compensations in reverse on failure.
Almost no engine in this catalog provides it. Effect's withCompensation is the exception, registering a scope finalizer that runs LIFO on failure. Everywhere else — Temporal, Dapr, Azure, DBOS, Restate, Inngest — the documented answer is a pattern: keep a list of closures, reverse it in a catch. The compensation calculi supply the laws that pattern is silently relying on, including the result that compensations of concurrent steps are themselves concurrent and do not depend on the observed interleaving.
Forward recovery
Retrying the failed step instead of undoing the completed ones. Sagas treats it as a first-class alternative, using save-points; the compensation calculi give it a separate operator. Most engines implement it as per-step retry policies with backoff, and reserve "failure" for the case where retries are exhausted.
Growth, evolution and observation
Snapshot, checkpoint, continue-as-new
Replay cost grows with journal length, so every system bounds it. Netherite and Akka/Pekko write periodic state snapshots beside the log. Temporal, Azure and Dapr instead offer continue-as-new: end the current execution and start a fresh one with the carried-over state as its argument, truncating the history to a single entry. Golem is the one system that can snapshot the program, not just its state, via a component-exported save/load pair.
Versioning
Running new code against a journal written by old code. The approaches are: inline patch markers (Temporal's GetVersion/patched, DBOS's DBOS.patch(), Dapr's IsPatched), pinning an in-flight execution to the code version that started it (Restate's PinnedDeployment, Temporal's Worker Versioning, Azure's versioned orchestrations), or side-by-side deployment with drain. In the event-sourcing tradition the equivalent is an upcaster (Marten, Akka/Pekko's EventAdapter): a function that reads an old event shape into the new one.
Projection
A read model derived by folding the journal, kept separate from the journal itself and carrying the offset it reflects. Standard in event sourcing (Kurrent's catch-up subscriptions, Marten's async daemon and its mt_event_progression table, Akka/Pekko's persistence-query), and the same mechanism Netherite uses internally when it validates that folding an event onto a freshly deserialized state equals folding it onto the live one.
Journal versus world
When the recorded journal and the current state of the outside world disagree, which is authoritative?
Every replay engine surveyed answers the journal, and can do so only because it forbids the program from observing the world outside an activity. The world is then reached exclusively through at-least-once effects that the receiver is expected to deduplicate. ARIES is the one source that reconciles rather than dictating: redo is applied conditionally, by comparing the log record's sequence number against the one stamped on the page itself. Helland supplies the framing for when reconciliation is impossible — memories, guesses and apologies — and the compensation calculi state the underlying constraint directly: the real world cannot be check-pointed.
Testing vocabulary
Replay test. Run new code against a stored journal and assert it does not diverge. Temporal's WorkflowReplayer makes this a CI gate against real production histories.
Crash-at-every-index. Truncate the journal after each entry, resume, and assert the final journal equals the uninterrupted one. No system surveyed ships this; Burckhardt's Lemmas 6.6 and 6.7 state it as a property, and the example program in this catalog demonstrates it in about 200 lines of D.
Deterministic simulation testing. Own the scheduler, clock, network and disk; draw fault schedules from a seed; replay a failing seed exactly. See deterministic simulation testing for FoundationDB, TigerBeetle and Antithesis, and deterministic record and replay for the process-level tradition that precedes it.
Sources
Each term above is grounded in the deep-dive it links to; those pages carry the primary citations. The terminology-fixing sources for this page are:
- Sagas — compensation, backward and forward recovery
- ARIES — write-ahead logging, repeating history, compensation log records
- Helland — idempotence, uniquifiers, the outside world
- Burckhardt et al. — the formal replay semantics and its exactly-once boundary