Development Choices

Durable Execution for Long-Running Agent Workflows

Author
Gregory Mostizky Software Engineer
Published
Section
AI Agents
Length
8 min read3 sources cited

Durable execution persists the position of a running workflow so a crash resumes from the last completed step rather than restarting the sequence. It is bought with a determinism constraint on workflow code, configured per activity for retries and timeouts, and is worth adopting only when losing a run midway is unacceptable.

What durable execution is

A durable workflow engine persists the position of a run so that a crash resumes from the last completed step instead of restarting the whole sequence. The orchestration logic is ordinary code, but the engine records every step it completes, and on restart it rebuilds the run’s state from that record and continues from where the process died.

For agent workloads, the run in question is usually a chain of model calls, tool invocations, and waits — the kind of sequence where step 14 of 20 dying means throwing away every token spent on steps 1 through 13.

How the position is persisted

The mechanism is an event history plus replay. The engine appends each completed step’s result to a durable log. When a worker picks the run back up, it re-executes the workflow function from the top, but every call that already has a recorded result returns that recorded value immediately instead of doing the work again. Execution fast-forwards through the completed prefix and only does real work once it reaches the first step with no recorded result. Temporal’s workflow documentation describes this execution model and the guarantees it provides.

This is why the distinction between workflow code and activity code exists. Workflow code is the orchestration — the branching, the sequencing, the loop over a list of tool calls. Activity code is the part that touches the outside world. Only activity results go in the history.

The constraint you pay for it

Durability is bought with a constraint: workflow code must be deterministic and replayable, which rules out reading the clock, generating random values, or calling the network outside an activity. Replay only reconstructs the correct state if re-running the function produces the same sequence of decisions it produced the first time. A Date.now() in workflow code returns a different value on replay, a branch taken on that value goes the other way, and the engine’s reconstructed state no longer matches what actually happened.

The practical consequences are specific:

The cost is not just the initial rewrite. It is a standing tax on every change, because editing a workflow function that has runs in flight can break the replay of those runs — most engines require versioning gates around changes to already-deployed workflow logic. Teams weighing this against a plain process should read it alongside the trade-offs in serverless functions versus long-running hosts for agent workloads, since the hosting model and the durability model constrain each other.

When it is worth the constraint

The threshold where it becomes worth the constraint is when a single run is long enough or expensive enough that losing it midway is unacceptable — not when the code merely has several steps. Step count is the wrong trigger. A five-step sequence that completes in 400ms and costs a fraction of a cent can simply be retried whole; the determinism tax buys nothing there.

What moves a workload over the line is the cost of a lost run:

Agent runs that call out to sandboxed tools sit in this category more often than they look, because the network egress controls on those sandboxes add latency and failure modes that lengthen the run.

Retries, timeouts, and heartbeats are per activity

Retry policy, timeout, and heartbeat are configured per activity rather than globally, because a model call and a database write fail in entirely different ways. A single global policy has to be wrong for one of them.

The shapes differ concretely:

Temporal’s retry policy reference documents the parameters — initial interval, backoff coefficient, maximum interval, maximum attempts, and the non-retryable error types list. That last one matters most in practice: a 400 from a model provider for a malformed request will never succeed, and retrying it burns the budget that a genuinely transient failure needed.

Retries also interact badly with load. Marc Brooker’s analysis of retries and backoff makes the point that retries amplify traffic exactly when a dependency is already struggling, and that backoff alone does not fix it — a client-side budget or circuit breaker is what bounds the amplification. Per-activity configuration is what lets you set that budget differently for a rate-limited model endpoint than for an internal service, which matters when a routing layer is already shifting traffic between models on failure.

What a plain queue already covers

A plain queue with idempotent handlers covers a large share of what teams reach for a workflow engine to do. If the unit of work is a single step, or a short chain where each step can be enqueued by the previous one, then at-least-once delivery plus a handler that is safe to run twice gives you crash recovery without any determinism constraint on your code.

The pieces that gets you: the broker holds the message until it is acknowledged, so a crashed consumer means redelivery rather than loss; idempotency keys mean redelivery does not duplicate the side effect; a dead-letter queue catches what never succeeds. That is durable in the sense most teams mean.

What it does not give you is the state between steps. There is no place to hold “we are on iteration 7 of the agent loop, with these three tool results accumulated” other than a row you maintain yourself — and once you are maintaining that row, adding compensation logic and a timer table, you have started writing a workflow engine with none of the testing. The honest boundary is roughly: chains short enough that a per-message idempotency key is the whole state, stay on the queue.

What to check next

Sources

  1. Temporal's workflow documentation docs.temporal.io
  2. Temporal's retry policy reference docs.temporal.io
  3. analysis of retries and backoff brooker.co.za

See also