What is durable execution, and what does it demand of your code?
Durable execution makes a program survive crashes by replaying it. That works only if your code is deterministic, which is a constraint people discover after adopting it.
On this page
- What we'll cover
- The illusion and how it works
- Determinism, the constraint
- What goes in an activity
- Versioning a running workflow
- What it is genuinely good at
- What it is not
- Testing something that replays
- The history, which grows
- Durable automation with CodeWords
- What to get right first
- Frequently asked questions
- Related reading
Durable execution lets you write a long-running process as ordinary sequential code — call this, wait a week, call that — and have it survive process restarts, deployments, and crashes. The program appears to be running for a week; the process hosting it is not.
The mechanism is replay. When execution resumes, the engine runs your function again from the beginning, feeding back the recorded results of everything that already happened rather than redoing it. That is elegant, and it imposes a constraint on how your code may be written that is easy to violate accidentally.
What we'll cover
The illusion and how it works
Your code reads sequentially. Call a service, wait for an approval, wait thirty days, call another service — written as one function rather than as a state machine spread across handlers.
Every external interaction is recorded in an event history: what was called, what came back.
On resume, the function runs again from the top. Calls that already happened return their recorded results immediately, without contacting anything, until execution reaches the point it had reached before.
Then it continues normally. From the code's perspective nothing happened; from the infrastructure's perspective the process died and a new one picked up.
Waiting costs nothing. A workflow paused for thirty days holds no thread, no connection, and no memory — it is a row in storage.
Determinism, the constraint
The whole model depends on replay producing the same decisions, which means your workflow code must be deterministic.
No direct clock reads. Calling the system clock gives a different answer on replay, which can send execution down a different branch than it took originally. Use the engine's time functions, which are recorded.
No random values. Same reason.
No direct network or database calls from workflow code. Every external interaction goes through the recorded mechanism.
No unordered iteration. Iterating a structure with non-deterministic ordering can produce different sequences across replays.
No reliance on process state — global variables, caches, environment differences between the original run and the replay.
Violating this produces a nondeterminism error, or worse, silently different behaviour. It is the single largest source of confusion for teams adopting durable execution, and it is not intuitive until the model clicks.
What goes in an activity
The escape hatch, and understanding it resolves most of the confusion above.
Activities are where the real work happens — network calls, database writes, anything with a side effect or a nondeterministic result.
They are called from workflow code and their results are recorded, which is what makes replay safe.
They are retried independently, with their own policy, without rerunning the workflow's earlier steps.
They must be idempotent, because a retry after a timeout may repeat work that already succeeded — the engine cannot know whether a call that timed out was received.
Keep workflow code thin. Orchestration, sequencing, and decisions in the workflow; everything else in activities. Teams that get this right rarely meet a determinism problem.
Versioning a running workflow
The problem unique to long-running code, and the one that arrives around month three.
Instances outlive deployments. A workflow running for six weeks will be replayed against whatever code is deployed at the time.
Changing the code changes the replay. If the new version makes different calls in a different order, replaying an old instance against it produces a history mismatch.
Engines provide versioning mechanisms — a marker that lets code branch on which version an instance started under.
Old branches must be kept until every instance that started on them has finished, which for a long workflow is a long time.
Plan for the accumulation. A workflow changed six times has six branches, and pruning them requires knowing no instances remain on the old paths.
Additive changes at the end are safe; reordering or removing steps in the middle is where instances break.
What it is genuinely good at
Processes that wait. Approvals, delivery windows, trial periods, anything measured in days.
Multi-step sequences with compensation. When step four fails and steps one to three must be undone in order, this is considerably easier to express here than anywhere else.
Work that must not be lost. The history is persisted, so a crash mid-process resumes rather than restarts.
Complex orchestration in readable form. The alternative — a state machine implemented across queue handlers and database columns — is harder to read and much harder to change.
Visibility. You can ask where a specific instance is, which a hand-built equivalent almost never provides.
What it is not
Worth being clear, since it gets proposed for things it does not suit.
Not a queue. It orchestrates; it does not distribute high volumes of independent work efficiently.
Not a data pipeline. Moving large volumes through workflow code is the wrong shape; call something built for it from an activity.
Not low latency. The persistence and recording add overhead that matters for a request in a user's browser.
Not free operationally. Most durable execution systems are a substantial service to run, or a vendor dependency.
Not a substitute for design. A poorly understood process expressed in durable code is still a poorly understood process.
Testing something that replays
Test workflows and activities separately. Activities are ordinary code and test ordinarily; workflows test against mocked activity results.
Test the replay explicitly. Most engines provide a way to replay a recorded history against new code, which is how you catch a determinism break before deploying it.
Keep histories from production as test fixtures, particularly for long-running workflows, since they are the cases that will be replayed against your change.
Test the failure paths. A step failing after three succeeded is the scenario that motivates the whole architecture and rarely appears in a test suite.
Test a version transition, with an instance that started on the old code, because that is the change most likely to break something already running.
The history, which grows
An operational detail that arrives later and is worth knowing in advance.
Every activity call and every signal is appended to the instance's history, which is what makes replay possible.
Long workflows accumulate large histories, and replay cost grows with them — a workflow with tens of thousands of recorded events is slow to resume.
Most engines cap history size, and a workflow that exceeds it fails rather than degrading.
Loops are the usual cause. A workflow polling every minute for a month has recorded forty thousand events for what is conceptually one wait.
Continue-as-new is the standard remedy: the workflow completes and immediately starts a fresh instance carrying its state forward, truncating the history.
Design for it where a workflow is long-lived or loops, rather than discovering the limit in production — the failure arrives at an unpredictable point and looks like nothing else.
Durable automation with CodeWords
CodeWords handles the durability rather than exposing it: describe a process that spans days and waits for people, and the state, retries, and resumption are part of how the automation runs. It connects to more than 3,000 integrations.
- Open CodeWords and start a new automation.
- Describe the process in plain language to Cody, the automation builder: the steps in order, what waits for what, and where a person decides.
- Describe the exceptions: a step that fails, an approval that never arrives, an input that arrives twice.
- Connect the systems each step touches, once.
You describe the outcome; Cody builds it, connects it, and deploys it. The free plan covers light use, with Pro at $39 per month and Business at $100 per month as usage grows; details are on the pricing page.
What to get right first
The separation between workflow code and activities: orchestration and decisions in the workflow, every side effect and every nondeterministic value in an activity. Teams that internalise this early rarely meet a determinism error; teams that do not spend their first month debugging replays.
Two habits make the difference. Make every activity idempotent from the start, since a timed-out call will be retried and the engine cannot know whether the original succeeded. And keep a production history as a test fixture per workflow, because replaying it against a change is the only reliable way to catch a determinism break before it reaches running instances.
Frequently asked questions
How does a workflow survive a crash?
By replaying. The engine runs your function again from the beginning, returning recorded results for everything that already happened, until execution reaches where it left off — then continues normally.
Why must workflow code be deterministic?
Because replay must make the same decisions. A clock read, a random value, or an unordered iteration can send a replay down a different branch than the original took, which breaks the reconstruction.
Where do network calls go?
In activities. Their results are recorded, so replay returns the recorded value rather than calling again. Keeping workflow code thin and pushing everything else into activities avoids nearly every determinism problem.
Do activities need to be idempotent?
Yes. A call that times out will be retried, and the engine cannot know whether the original was received. This is the same at-least-once reasoning that applies to any distributed call.
What happens when I change a workflow's code?
Running instances are replayed against the new code, which can produce a history mismatch. Use the engine's versioning mechanism and keep old branches until every instance that started on them has finished.
Is this the same as a workflow engine?
It is one kind. The distinguishing feature is that the process is written as ordinary sequential code rather than as a graph or a configuration, with durability achieved through replay rather than through explicit state transitions.
When is it the wrong tool?
High-volume independent work, data movement, and anything latency-sensitive. It orchestrates processes; it is not a queue, not a pipeline, and the persistence overhead matters for a request somebody is waiting on.
Why did a long-running workflow suddenly fail?
Probably its history hit the engine's size limit. Every activity call is appended, so a workflow that polls in a loop accumulates enormous histories. Continue-as-new truncates it by starting a fresh instance carrying the state forward.
Does replay get slower over time?
Yes, in proportion to the recorded history, since resumption replays all of it. A workflow with tens of thousands of events is noticeably slow to resume, which is the other reason to avoid tight loops in workflow code.
Can a running workflow be sent new information?
Yes — engines provide signals for exactly that, which is how an approval or a cancellation reaches an instance already in flight. Signals are recorded like anything else, so they replay correctly.
Should every long process use durable execution?
No. If restarting from the beginning after a failure is acceptable and nothing waits on a person or an external event for days, the operational cost of the engine buys very little.