Durable workflow histories are executable recovery contracts
Durable workflow histories are executable recovery contracts. I do not treat a history as an audit trail that happens to help during an incident. It is the authoritative input from which another worker must reconstruct the same state, recognize completed work, and decide the next command. If that record cannot drive recovery after the original process is gone, it is operational evidence, not durable execution.
The contract starts with what the history records. Workflow start, activity scheduling and completion, timers, signals, and other state transitions are appended rather than folded into one mutable row. I can derive current state from that ordered account without trusting a worker’s heap or a hand-maintained checkpoint flag. The useful question after failure is therefore not “what did the dead process remember?” but “what durable facts had the service accepted?”
Calling the history executable is literal. On recovery, workflow code runs again against recorded events and emits commands such as scheduling an activity, starting a timer, or continuing as new. During replay, commands already represented in history are matched and skipped; their recorded results are supplied instead of repeating completed activities. The first command without a corresponding event identifies where forward execution can resume. Recovery is ordinary execution constrained by prior facts, not a separate repair program.
That design makes determinism a compatibility obligation. Given the same inputs and history, workflow code must produce the same command sequence. I keep wall-clock reads, randomness, network calls, and file I/O outside workflow logic or use SDK mechanisms that record a value once for replay. A nondeterminism error is useful because it refuses to invent continuity when current code no longer explains the durable record.
The history also draws a necessary line around effects. Workflow code expresses intent; activities perform external I/O; the service records the resulting transitions. Replaying the workflow must not resend an activity whose completion is already known. Activity retries are a different boundary: an attempt can fail after the remote system acted but before completion was recorded. I still need stable idempotency keys, validation, or an explicit at-most-once policy. History makes uncertainty visible; it does not repeal distributed-systems ambiguity.
A recovery contract binds code versions as well as data. A deployment that changes command order can make an old history unintelligible to new workflow code. I therefore treat worker build IDs, patch markers, and replay tests as migration machinery. Old executions can remain routed to compatible workers while new executions start on new code. The release unit is not just a binary; it is a binary plus the set of histories it promises to interpret.
Long-lived execution adds a lifecycle constraint. Every activity, timer, signal, and decision grows the history, so an indefinitely recurring workflow cannot pretend one log is unbounded. Continue-as-new closes the current execution, carries explicit state into a new run, and starts a fresh history while retaining the workflow identity clients use. I see that handoff as contract renewal: preserve the state needed to continue, but do not make every prior event part of every future recovery.
There is one precise concession: for a short, stateless operation whose only safe recovery is to restart from its original input, a replayable workflow history can cost more than it protects. The claim begins when work spans processes, waits on timers or human signals, or crosses consequential external effects. At that boundary, a mutable status row and application logs cannot jointly substitute for an ordered record that the runtime itself consumes.
I design the history before I design the happy path. I ask which events prove an effect completed, which values must be recorded rather than recomputed, which code versions can replay each run, and when history rolls forward into a new execution. Those answers define what recovery is allowed to do. Interruption becomes a state transition because the past constrains the next action after everything ephemeral has disappeared.