Durable Execution for Agents: Temporal, Vercel Workflows, Inngest, Restate and DBOS

For engineers and engineering leaders who run LLM agents that take minutes to days: multi-step tool loops, human approvals, scheduled jobs. Eight modules on when a durable-execution engine is worth adopting, which one, and how to write agent code so that replay, retries and idempotency hold when a non-deterministic model sits in the loop. Built from the vendors’ own documentation and pricing pages as they read in October 2026.

8 modules
24 quizzes
47 flashcards
93 primary sources
~85 min

Research current to 14 October 2026. Every price, limit and quotation was read from a live vendor page or the vendor’s documentation source on that date, except where the text says an archived copy or a third-party report was used.

How to read this course

Quotations are word for word from the named documentation page. Prices and limits carry the date they were read, because every vendor in this course changed its pricing page at least once in the twelve months before writing. Where a vendor’s site could not be reached, the text says whether the figure came from the vendor’s own documentation repository, from an archived copy with its capture date, or from a named third party. Callouts labelled My position are argument, not sourced fact. Comparisons that only a vendor makes about a competitor are labelled as the vendor’s claim. Every URL cited is listed under Primary sources at the end.

Course Modules

  1. The failure you are buying insurance againstFoundations
  2. The determinism contractMechanics
  3. Idempotency, retries and things you cannot undoMechanics
  4. Waiting: approvals, signals and timersPatterns
  5. The engines compared, October 2026Landscape
  6. Cost and limitsEconomics
  7. Observability and operationsOperations
  8. Decision clinic and reference architectureDecisions
1

The failure you are buying insurance against

A 40-minute agent run dies at step 31. What happens next depends entirely on the substrate underneath it.
Learning objectives
  • Define durable execution precisely, in terms of an event history, deterministic replay and step boundaries, and tell it apart from a queue that retries
  • Trace what a plain process, a queue plus worker, a cron plus bookmark and a durable engine each do after the same crash
  • Name the two things durability does not protect an agent from
  • State when a cron plus a database bookmark is the right answer and what forces the move to an engine

What actually fails

A classic request handler lives for milliseconds. An agent run lives for minutes, hours or days: it calls a model, reads the answer, runs a tool, calls the model again, waits for a person, sleeps until Monday, and repeats. The process hosting that loop will not survive the run. Deploys restart containers. Spot instances get reclaimed. Serverless functions have ceilings: Vercel’s function limits page, updated 24 August 2026, puts the default duration at 300 seconds, the Pro and Enterprise maximum at 800 seconds, and an extended maximum of 1,800 seconds in beta. The same page sends anyone who needs “unlimited execution time” to Vercel Workflows instead.

The vendors say the demand is real. Temporal’s funding announcement of 14 September 2026 put it plainly: “Customers are asking to run agents that work for days, weeks, or months, and this is becoming the new normal.” The same post reported “more than 4,300 paying customers, up 139% year over year” and that “In August alone, the platform processed 1.9 trillion billable actions, up more than 350% year over year.” Those are vendor-reported numbers, but the direction is not in dispute: long-running agent loops have become an infrastructure category.

So picture the concrete failure this course is about. A research agent has made 30 model calls and 30 tool calls over 40 minutes. The context it carries is now large. On step 31 the container dies. What happens next?

Four substrates, one crash

SubstrateWhat happens after the crashWhat gets redoneWhat gets double-done
Plain process (a loop in a script, a serverless function, a pod)Nothing. The run is gone. Someone notices later, or nobody does.Everything, by hand, if anyone restarts itAny tool side effect from steps 1 to 30 that the restart repeats
Queue plus worker (a job message, a worker that picks it up, a retry policy)The message becomes visible again and another worker starts the job from the beginningAll 30 model calls and all 30 tool callsEvery side effect from the first attempt, unless each tool was made idempotent by hand
Cron plus bookmark (a scheduled job that records how far it got in a database row)The next scheduled tick reads the bookmark and resumes from the last recorded positionOnly the work since the last bookmark writeWhatever happened between the last bookmark write and the crash
Durable engine (Temporal, Vercel Workflows, Inngest, Restate, DBOS)The engine notices the worker is gone, re-runs the workflow code against its recorded history, and resumes at step 31Only step 31Only a side effect inside step 31 that completed before the result was recorded

The third row is underrated and the fourth row is the product. A bookmark is a durable engine with one step boundary that you placed yourself. An engine is a bookmark after every step, placed by the framework, with the bookkeeping for retries, timers and waits done for you. That framing matters because it tells you what you are paying for: not magic, but a step ledger you did not have to write.

A precise definition

Each vendor describes the same mechanism in its own words, and the words are worth reading because they define what the engine guarantees.

Primary sources — the vendors’ own definitions, read 14 October 2026

Temporal: the Event History is “a complete, ordered log of everything that has already happened in a Workflow” and “the source of truth for everything that happens in the Workflow.” Vercel Workflows: “All inputs and outputs are recorded in an event log. If a deploy or crash happens, the system replays execution deterministically from where it stopped.” Inngest, on steps: each step “Will not be re-executed if it has already been successfully executed.” DBOS: “once a step completes and is checkpointed, it is never re-executed.” Restate: on replay, a handler that reaches a recorded action “will skip execution and will inject the response it finds in the journal.”

Put together, durable execution means three things. First, an event history: an append-only record of every step input and output, every timer and every external signal, stored outside the process. Second, deterministic replay: after a failure the engine re-drives the workflow code and feeds it the recorded results, so the code reaches the same point it was at without redoing the recorded work. Third, step boundaries: the code is divided into the orchestration that is replayed and the units of work whose results are recorded. Everything in this course follows from those three ideas.

There are two implementation families, and Inngest’s own documentation draws the line. It describes Temporal as using “a deterministic replay model where your entire workflow function is re-executed from the beginning on each step, relying on an internal event history to skip completed work,” and itself as using “a step-based memoization model where each step runs once, its result is persisted, and subsequent executions skip completed steps by injecting their stored results.” That is a vendor’s framing of a competitor, so read it as positioning. In practice both families re-run your function and substitute recorded results; the difference is in how strictly the orchestration code must behave between steps, which is module 2.

The step boundary is the unit of insurance

Draw a box around each step. Inside the box, the engine promises nothing except that it will try again: the code runs at least once, and if the process dies after the side effect but before the result is recorded, it will run again. Between boxes, the engine promises everything: results are recorded, the orchestration is replayable, and a crash costs you at most the step in flight.

For an agent this has one consequence that dominates the rest. The expensive thing in an agent loop is the model call. It costs money per token, it takes seconds, and it returns a different answer each time. If the model call is inside a step, a crash costs you one call. If it is outside a step, in the orchestration code, a crash costs you every call since the start, and worse, as module 2 shows, the replay may not even converge. Temporal’s documentation for its OpenAI Agents SDK integration states the design directly: model calls “retry durably and are not repeated during Workflow replay.” Inngest’s agent tool-loop guide shows the two versions side by side and labels the bare call: “Not durable — if this fails, you lose all prior work.”

When a bookmark beats an engine

Not every agent needs an engine. A daily-brief agent that wakes once, fetches sources, writes a summary and posts it is a cron job. The scheduled agents course on this site builds exactly that with a bookmark, a post-then-record rule and a budget cap, and it is right to. The run is short, it has one side effect, and the side effect can be made idempotent with a key. Adding an orchestration engine to that job adds a dependency and a programming model for no durability you did not already have.

What forces the move is a wait. The moment a run has to pause for a person, pause for a webhook, or sleep until next week while keeping its place, a cron plus bookmark becomes a hand-written state machine with a polling loop. Module 4 is about why engines make waits cheap and bookmarks make them painful. The second forcing function is fan-out: once one run spawns twenty sub-tasks whose results must be joined, bookkeeping the bookmark by hand stops being fun. The third is retry with compensation: when a failed step must undo three earlier side effects, you want the saga machinery from module 3 rather than a bespoke undo table.

My position

A cron plus a bookmark is enough until you need a wait. Teams reach for a durable engine too early because the demo is impressive and too late because by then the hand-rolled state machine has a name and an owner. The test is mechanical: if any run of your agent must pause for longer than your process is allowed to live, or must survive its own deploy, you need an engine. If every run is a short, idempotent, post-then-record job, you need a bookmark and a budget. The strongest objection is that engines are cheap now, so why not start there. The answer is that the determinism contract in module 2 is a real tax on how your team writes code, and you should pay it when the wait appears, not before.

What durability does not buy you

Two things, and they are the things people most often assume they are getting.

First, durability does not make the agent right. A workflow that replays perfectly will replay a wrong plan perfectly. Evaluation, routing and the evaluator-optimizer loop belong to the harness layer above the substrate; the dynamic workflows course covers those patterns. Nor does durability stop a prompt injection: a hijacked agent with a durable engine is a hijacked agent that cannot be killed by a restart. The containment controls in the securing agents course sit beside the engine, not inside it.

Second, durability does not make tool side effects exactly-once. The engine records a step’s result after the step returns. If the process dies between the external call and the recording, the step runs again. Every engine in this course says so in its own words, and module 3 is about what you do with that.

Think about it: the 40-minute run dies at step 31. Before reading on, write down what is re-executed under each of the four substrates, and which tool side effects can happen twice.

Plain process: nothing is re-executed unless a human restarts it, and then everything is, so every side effect from steps 1 to 30 can happen twice. Queue plus worker: the whole job re-runs from step 1 automatically; the same double-execution risk, now without a human in the loop to notice. Cron plus bookmark: work since the last bookmark write is re-executed; if the bookmark is written after each step, only step 31 is redone and only its side effect can double. Durable engine: the orchestration code is replayed against the history, steps 1 to 30 are served from the record, step 31 runs again, and only a side effect inside step 31 that completed before its result was recorded can double. Notice that the bookmark-per-step design and the engine give the same answer. The engine’s value is that it also gives that answer for timers, waits, fan-out and retries without you writing the ledger.

Takeaways
  • Agent runs outlive the processes that host them. Serverless ceilings are measured in minutes; agent runs are measured in hours and days.
  • Durable execution is an event history plus deterministic replay plus step boundaries. Everything else is bookkeeping.
  • Inside a step you get at-least-once. Between steps you get replay. Put the expensive, non-deterministic model call inside a step.
  • A cron plus a bookmark is a one-boundary engine. Move to a real engine when you need waits, fan-out or compensation.
  • Durability does not make the agent correct, safe or exactly-once. Those are other layers.
2

The determinism contract

Why workflow code must be deterministic, what that forbids, why every model call is a step, and what happens to in-flight runs when you ship new code
Learning objectives
  • Explain why replay requires deterministic orchestration code and list what that forbids
  • Argue, with the vendors’ own words, that every model call and tool call must be a step or activity
  • Describe how replay differs between Temporal, Vercel Workflows, Inngest, Restate and DBOS
  • Choose between patching, versioning and run-pinning when code changes under a running agent
  • Count the replayed steps and re-billed tokens for a naive design against a correct one

The rule, in each vendor’s words

Replay works by re-running your orchestration code and handing it recorded results in order. That only works if the code asks for the same things in the same order every time it runs. Every engine states this rule, and the statements are strikingly similar.

Primary sources — determinism requirements, read 14 October 2026

Temporal’s Workflow Definition page: “all operations that do not purely mutate the Workflow Execution’s state should occur through a Temporal SDK API,” and in a tip aimed at this course’s readers: “To handle non-deterministic operations like API calls, LLM/AI invocations, database queries, and other external interactions, put them in Activities.” Vercel’s Workflow SDK documentation: workflow functions “Must be deterministic to allow resuming after failures,” because “the workflow code gets re-run multiple times during its lifecycle.” DBOS: “The workflow function must be deterministic,” with database access, third-party API calls, random numbers and the local time belonging “in a step instead of directly in the workflow function.” Inngest: “Any non-deterministic logic (such as DB calls or API calls) must be placed within a step.run() call.” Restate: “Always immediately await ctx.run, before doing any other context calls.”

What happens when you break the rule is also documented. Temporal: “If a generated Command doesn’t match what it needs to in the existing Event History, then the Workflow Execution returns a non-deterministic error.” Vercel’s error table lists REPLAY_DIVERGENCE: “A replay could not consume the event log deterministically, usually because of non-deterministic workflow code.” DBOS raises DBOSStepNondeterminismError when “a single execution of a workflow recorded different results for the same step.” These are not warnings. They are the run failing after it survived the crash, which is the worst possible outcome: you paid for durability and got a stuck run.

What is forbidden, and the replay-safe alternative

In orchestration codeWhy it breaks replayReplay-safe alternative
Reading the clockA different time on replay changes a branchWorkflow-context time (Temporal); Vercel’s sandbox fixes Date so “the values don’t change across replays”; or read time inside a step
Random numbers and UUIDsDifferent value on replay, different pathCapture once in a step; Restate’s ctx.rand is “seeded… with the invocation ID, so it always returns the same value on retries”; Vercel fixes Math.random but warns seeded values “are not secret”
Network calls, database reads, file I/OExternal state differs between runsAlways a step or activity
Environment variables and config lookupsChange between deploysPass as workflow input or read inside a step
Threads, unmanaged concurrencyOrdering of results variesEngine-provided parallelism (Promise.all over steps, Temporal’s async primitives)
A model callSame prompt, different answer, different tool choiceAlways a step or activity, without exception

Vercel’s approach is worth noting as the exception that proves the rule. Its workflow functions run in a sandbox without full Node.js access, so the compiler can fix the non-deterministic primitives for you. The documentation says: “The sandboxed environment that workflows run in already ensures determinism.” That removes the clock and random pitfalls, but it cannot remove the network, and it cannot make a model deterministic. The model call still has to be a step.

Why the model call is the worst offender

Clocks and random numbers break replay by accident. A model call breaks it by nature. Ask the same model the same question twice and you will get two different answers; ask it which tool to call and you may get two different tools. If the model call lives in orchestration code, a replay after a crash asks the model again, gets a different plan, and tries to match that plan against a history recorded from the first plan. Temporal’s non-deterministic error and Vercel’s replay divergence are what that looks like.

The agent frameworks that integrate with these engines have all reached the same design. Temporal’s OpenAI Agents SDK integration documents that “Agent orchestration… runs inside the Workflow, while model calls run as Activities,” and that model calls “retry durably and are not repeated during Workflow replay.” Its Google ADK integration, as the AI cookbook index describes it, is built so that “every LLM call and every I/O tool call runs as a durable activity.” Vercel’s durable-agent guide introduces WorkflowAgent from @ai-sdk/workflow, which “runs the agent loop inside a workflow, persists state across step boundaries, and lets tool executions marked with "use step" retry automatically.” Inngest’s tool-loop guide wraps the model call in step.run("think", …) and each tool in its own step.run so that “a failing tool doesn’t re-run the LLM call.”

// Inngest agent tool loop (TypeScript), from the vendor guide, trimmed
while (!done && iterations < MAX_ITERATIONS) {
  iterations++;
  const llmResult = await step.run(`think`, async () =>
    anthropic.messages.create({ model, max_tokens: 4096, messages, tools }));
  const toolCalls = llmResult.content.filter((b) => b.type === "tool_use");
  if (toolCalls.length === 0) { done = true; return { response: text, iterations }; }
  messages.push({ role: "assistant", content: llmResult.content });
  for (const toolCall of toolCalls) {
    const result = await step.run(`tool-${toolCall.name}`, async () =>
      executeTool(toolCall.name, toolCall.input));
    toolResults.push({ type: "tool_result", tool_use_id: toolCall.id, content: result });
  }
  messages.push({ role: "user", content: toolResults });
}

Notice the step names. Inngest hashes the step ID to look up the recorded result, and the versioning guide explains that for steps inside loops “the SDK combines the step ID with an occurrence counter.” That is how a loop with the same literal name on every iteration still gets a distinct record per pass. Temporal matches by command sequence rather than by name, which is why reordering commands is a versioning event there.

How replay actually works, engine by engine

The five engines differ in where the replay happens and what gets re-executed.

EngineRecovery mechanism, per its docsWhere orchestration code runs
TemporalRe-runs the Workflow code from the start; Commands are matched against the Event History; Activity results are reused rather than recomputedYour Workers, polling Task Queues
Vercel Workflows“the system replays execution deterministically from where it stopped”; the workflow function “compiles into a route that orchestrates execution”; a replay attempt longer than 240 seconds may abort the runYour Vercel Functions, driven by Vercel Queues
Inngest“Each step in your function is executed as a separate HTTP request”; the function is re-invoked with the state of previous executions and the SDK injects memoized resultsYour compute, invoked over HTTPS by Inngest
RestateThe server “retries by invoking the handler again with the latest journal”; recorded actions are skipped and their responses injectedYour services, proxied by the Restate Server
DBOS“The workflow is called again with its checkpointed inputs. As the workflow re-executes, it checks before each step if that step’s output is checkpointed in Postgres”Your process; the library talks to your Postgres

Two practical consequences. The replay cost grows with the history. Vercel’s pricing page notes that runs exceeding 2,000 events or 1 GB “have slower replay times” and recommends child workflows; its event-sourcing documentation says a run “is worth splitting well before it fails: split into child workflows once a run would grow past a few thousand events.” Temporal logs a warning at 10,240 events and terminates the execution past 51,200, which is why its Continue-As-New exists: “Temporal will tell your Workflow when it’s approaching performance or scalability problems.” An agent loop with thousands of iterations needs a plan for history growth from day one.

The second consequence is latency between steps. Each step boundary is a round trip to the engine: a Workflow Task in Temporal, an HTTP request in Inngest, a queue message in Vercel, a Postgres write in DBOS. DBOS’s comparison page claims its checkpoint “typically takes 1-2ms” while a Temporal step dispatch “takes tens to hundreds of ms.” That is a vendor comparing itself to a competitor, so treat the ratio as a claim and measure your own. The point stands independently: step granularity is a latency decision, and module 8 returns to it for the 30-second SLA case.

Code changes under a running agent

Agents run for days. You deploy several times a day. The question of what happens to an in-flight run when its code changes is not theoretical, and the engines take three different stances.

Patch markers. Temporal’s Python SDK uses workflow.patched("id"), which “inserts a marker into the Event History”; during replay the Worker “will fail the Workflow task when the Workflow code doesn’t produce the same patch marker.” Once old executions have drained you call deprecate_patch, and only when “pre-patch Workflows have left retention” may you remove both calls. DBOS has the same shape: DBOS.patch("use-baz") “returns True for new workflows… and False for old workflows,” followed by deprecate_patch and removal.

# Temporal Python patching, from the vendor docs
if workflow.patched("my-patch"):
    self._result = await workflow.execute_activity(
        post_patch_activity, schedule_to_close_timeout=timedelta(minutes=5))
else:
    self._result = await workflow.execute_activity(
        pre_patch_activity, schedule_to_close_timeout=timedelta(minutes=5))

Pin the run to its code. Vercel Workflows pins by default: “Workflow runs are pinned to the deployment that starts them,” so a run sleeping for two days “does not suddenly resume into the new implementation.” To move a run you cancel it and rerun on the latest deployment, or build a self-upgrading loop where each run “does a bounded piece of work, then starts the next run on the latest deployment and exits,” passing deploymentId: "latest". Temporal’s Worker Versioning, generally available since the Replay conference announcements of 6 May 2026, “pins running Workflows to the Worker version that started them.” DBOS versions too: “When DBOS tries to recover workflows, it only recovers workflows whose version matches the current application version,” and recommends blue-green deployments that drain old versions. Restate’s CLI output says the same about its invocations: those “that made progress on a deployment cannot move to newer deployments automatically.”

Match by step identity. Inngest does neither. On resume “Inngest uses the current code and reuses results from completed steps whose IDs still match.” Adding a step runs it when reached; changing a step’s ID “treats it as a new step and runs it”; reordering returns saved results by ID with a possible warning. The guide’s own caution is the one that matters for agents: changing a step ID on a step that writes to another system “can repeat a payment, email, or resource creation.”

The agent-specific trap

Prompt changes are code changes. If your orchestration decides which tools to expose based on a prompt template, and you edit the template, a replaying Temporal or Vercel run may now issue different commands than its history recorded. Keep prompts and tool lists as step inputs or recorded values, not as constants baked into the orchestration function, and version them like code. Vercel’s pinning spares you the divergence but not the surprise: the pinned run finishes on yesterday’s prompt.

Worked exercise: the price of a naive design

Worked exercise: count the replayed steps and the re-billed tokens. Inputs: a run has completed 30 model calls averaging 30,000 input tokens and 1,500 output tokens each, plus 30 tool calls. It crashes during call 31. Design A puts model calls in orchestration code; Design B makes every model call and tool call a step. Estimate the re-billed tokens and cost at Claude Opus 5.5 and Claude Sonnet 5.5 list prices, then open.

Prices read from Anthropic’s pricing page on 14 October 2026: Claude Opus 5.5 at $4 per million input tokens and $20 per million output tokens; Claude Sonnet 5.5 at $2 and $10.

Design A (model calls in orchestration code). On replay the engine has no record of the 30 model results, so the code calls the model 30 more times before reaching the crash point. Re-billed input: 30 × 30,000 = 900,000 tokens. Re-billed output: 30 × 1,500 = 45,000 tokens. On Opus 5.5: 0.9 × $4 = $3.60 plus 0.045 × $20 = $0.90, total $4.50 per crash. On Sonnet 5.5: $1.80 plus $0.45, total $2.25 per crash. Those are the best case. The likely case is that one of the 30 replayed calls picks a different tool, the command stream no longer matches the recorded tool calls, and the run dies with a non-deterministic or replay-divergence error, which means you paid the $4.50 and still lost the run.

Design B (every call a step). Replay reads 30 model results and 30 tool results from the history and re-runs only call 31. Re-billed tokens: one call, 30,000 input and 1,500 output, which is $0.15 on Opus 5.5 and $0.075 on Sonnet 5.5. The orchestration cost of recording those 60 steps is a rounding error: at Vercel’s $0.02 per 1,000 events with three events per step, 60 steps are 180 events, or $0.0036; at Temporal Cloud’s $50 per million actions, 60 activity executions are $0.003.

The ratio. Design A re-bills about 30 times the tokens of Design B on every crash, and risks a stuck run. The engine’s fee for preventing that is three orders of magnitude smaller than the tokens it saves. Module 6 scales this to a 10,000-tool-call run.

My position

Every model call is a step. Every tool call is a step. No exceptions for “cheap” calls, no exceptions for calls you think cannot fail. The strongest objection is overhead: each step is a round trip and a serialized payload, and payload limits are real. Temporal Cloud caps a payload at 2 MB per request, Inngest caps a step’s returned data at 4 MiB and run state at 32 MiB, Vercel allows 50 MB. The answer is not fewer steps but smaller payloads: pass references, not blobs. Temporal’s cookbook has a claim-check recipe for “offloading large payloads to S3,” and its External Payload Storage, announced 6 May 2026, is described as “especially useful for agentic and AI data processing applications.” An agent’s growing message history is exactly the payload that needs this treatment.

▶ Animated recap · 1:41
Two hosts walk through how replay re-runs workflow code against its recorded history, what has to move into a step, and what a model call in workflow code costs when the replay diverges. Best watched after the worked exercise above.
Takeaways
  • Replay re-runs your orchestration code and feeds it recorded results. The code must therefore ask for the same things in the same order, every time.
  • Clocks, randomness, I/O and config are accidental non-determinism. A model call is essential non-determinism. It is always a step.
  • Every engine fails loudly on divergence. That failure happens after the crash you survived, so test for it with replay tests before shipping.
  • Code changes under running agents are handled by patch markers, by pinning runs to a version, or by matching step IDs. Know which one your engine does before your first deploy.
  • Payload limits argue for smaller step payloads, not fewer steps. Use claim-check references for growing message histories.
3

Idempotency, retries and things you cannot undo

At-least-once is the default everywhere. Keys, poison inputs, backoff, sagas, and the model that answers differently on retry
Learning objectives
  • State each engine’s default retry policy and what it implies for a tool that sends an email
  • Derive an idempotency key for a side-effecting agent tool and explain where the key must be checked
  • Classify failures into retryable, poison and compensable, and route each to the right primitive
  • Design a saga for an agent action that cannot be undone, including the lost-response case
  • Explain why a model call and a side-effecting tool call must never share a step

At-least-once is the default, and the vendors say so

Every engine in this course retries a failed step, and every one of them tells you that the consequence is your problem. Temporal’s Activities page: “We recommend that it be idempotent, so retries can be processed without duplicate side effects.” DBOS’s architecture page: “Steps should be idempotent, meaning it should be safe to retry them multiple times.” Vercel’s errors page: when a retried step “performs external side effects (payments, emails, API writes), ensure those calls are idempotent,” and, in the section on backend connection failures, the sentence every tool author should frame: “A connection failure does not prove that the backend rejected a write: it may have accepted it before the response was lost.”

The defaults differ in how hard they retry, which changes how many duplicates a non-idempotent tool can produce.

EngineDefault retry behaviour for a stepHow to stop retryingRead
TemporalInitial interval 1 second, backoff coefficient 2.0, maximum interval 100 times the initial, maximum attempts unlimited (“The default is unlimited”)List the error type in the policy’s non-retryable types (“By default, none are specified”); Workflows themselves “do not retry by default”14 Oct 2026
Vercel Workflows“steps retry up to 3 times on arbitrary errors” (4 attempts); steps are “enqueued immediately after a failure”; set maxRetries per stepThrow FatalError; use RetryableError with retryAfter for backoff; serialization failures skip retries14 Oct 2026 (SDK docs source)
InngestSteps retried on thrown errors; the steps guide’s sample comments “Can also be retried up to 4 times”; attempts configured per functionThrow NonRetriableError (used in AgentKit’s human-in-the-loop sample)14 Oct 2026 (docs source)
Restate“the ctx.run function is retried infinitely on failure” unless a retry policy is setThrow TerminalError; with a maximum attempts policy the block “fails with a TerminalError” when exhausted14 Oct 2026 (docs source)
DBOSSteps re-executed on recovery; retries configurable per stepRaise from the step; mark steps idempotent14 Oct 2026 (docs source)
LangGraph“Retries are opt-in. A node retries only when it has a retryPolicy configured”; defaults once set: max_attempts 3, initial_interval 0.5 secondsOmit the policy, or return false from retry_on14 Oct 2026 (docs source)

Read the first and fourth rows again. Temporal and Restate retry forever by default. A tool that posts a comment to a pull request, wrapped in a step with no retry policy and no idempotency key, will post that comment every time the step times out after the comment landed. That is not a bug in the engine. It is the engine doing what its documentation says it does.

Where the key comes from

An idempotency key is a value that identifies the logical operation, not the attempt. Inngest’s idempotency guide gives the clearest decomposition of where to deduplicate, and it maps directly onto an agent.

Primary source — Inngest idempotency guide, read 14 October 2026

Three places to deduplicate: “At the event producer” with a stable event id that “Inngest uses… to avoid starting functions again for a duplicate event received within 24 hours”; “At the function” with an idempotency expression that “prevents another execution with the same value for 24 hours”; and “At the external service” by sending “a stable operation key to a payment, email, database, or provisioning API,” because “A request can succeed before Inngest records the step result. A retry can then call the API again. Event and function deduplication do not remove that risk.” And the rule for the key itself: “Do not use a new retry attempt ID as the external idempotency key.”

The other engines give you the first two layers under different names. Restate accepts an idempotency-key request header and “will then deduplicate requests with the same idempotency key, and will only execute the handler once.” DBOS: “An assigned workflow ID acts as an idempotency key,” so a repeated ID runs the workflow once, with a workflow_id_reuse_policy of reject if you want duplicates to fail loudly. Temporal’s billing page notes that “De-duplicated Workflow starts that share a Workflow ID do not count as an Action,” which tells you the server deduplicates starts by ID too. None of these protect the third layer. The external service must see a key.

For an agent tool, derive the key from the step’s identity and the operation’s arguments, never from the attempt. A workable recipe: hash the run ID, the step ID (including its loop occurrence) and a canonical form of the tool input. Two attempts of the same step produce the same key; two different tool calls in the same run do not. Pass that key to the external API that supports one, and for APIs that do not, check for the resource first using a deterministic name that embeds the key.

# DBOS: workflow ID as idempotency key, from the vendor tutorial
with SetWorkflowID("very-unique-id"):
    example_workflow()

# Restate: idempotent invocation over HTTP, from the vendor docs
curl localhost:8080/GreeterService/greet \
  -H 'idempotency-key: ad5472esg4dsg525dssdfa5loi' \
  --json '"Hi"'

Poison inputs and backoff

Three classes of failure need three different responses, and the engines give you a primitive for each.

Transient failures retry with backoff. A rate-limited model API is the canonical case. Vercel’s RetryableError takes a retryAfter duration or date, and its advanced example computes exponential backoff from getStepMetadata().attempt. Temporal’s default policy already doubles the interval each attempt and caps it at 100 seconds. Its AI cookbook has a recipe for “passing retry information from HTTP headers to Temporal’s retry mechanisms,” which is the right way to honour a provider’s retry-after header.

Poison inputs must not retry. A tool asked to open a file that does not exist, a model asked for a schema it cannot produce, a 404 from an API: retrying burns attempts and, under an unlimited policy, burns them forever. Throw the terminal error type: FatalError in Vercel, TerminalError in Restate, NonRetriableError in Inngest, a type listed in Temporal’s non-retryable error types. Vercel also classifies serialization failures as deterministic: a step whose arguments cannot be serialized “fails like a step whose body threw a FatalError” and skips the retry loop. For an agent, a model output that fails schema validation is a poison input to the next step, and the right move is to feed the error back to the model in a new step rather than retry the failed one.

Structural failures end the run. Vercel’s MAX_EVENTS_EXCEEDED fires when a run reaches the per-run event ceiling; Temporal terminates past 51,200 events. No retry policy fixes those. They are design errors, solved by Continue-As-New or child workflows.

Things that cannot be undone

Some agent actions have no idempotent form because the world has already changed: an email was sent, a pull request was merged, a payment was captured, a message was posted in a channel where people read it. For these you need two mechanisms: a gate before the action, which is module 4, and a compensation after it if a later step fails, which is the saga pattern.

Temporal’s saga design page is the most precise statement of the pattern: “You implement each step as a local transaction with a corresponding compensation transaction,” and “If any step fails, you execute compensation transactions in reverse order to undo the effects of all completed steps.” Two details separate a saga that works from one that fails on the day it matters.

Primary sources — Temporal saga pattern and recovery guide, read 14 October 2026

On timing: “In all SDKs, compensations are registered before Activity execution and run in reverse order of registration,” and therefore “The compensation must be idempotent and handle cases where the forward Activity never executed (no-op).” The recovery guide explains why with a lost-response case: “The Activity throws because it never received a response — yet the booking exists on the vendor side.” Pre-registering “guarantees cancelAppraisal runs during rollback either way; idempotency makes it a safe no-op when the side effect never actually landed.” On failure of the compensation itself: “If a compensation fails, log the error and continue executing remaining compensations,” and “In production, alert for manual intervention on persistent compensation failures.”

Vercel’s documentation shows the same rollback loop but registers the compensation after the forward step succeeds: “Only enqueue a compensation after its forward step succeeds.” The two vendors disagree, and Temporal’s reasoning wins for agents. A tool that opened a pull request and then lost the network response has opened the pull request. If the compensation is registered only on success, the rollback never closes it. Register first, make the compensation a no-op when the forward action did not land, and you cover both cases.

// Vercel Workflow SDK rollback loop, from the vendor docs (trimmed)
export async function placeOrderSaga(orderId: string) {
  "use workflow";
  const rollbacks: Array<() => Promise<void>> = [];
  try {
    await reserveInventory(orderId);
    rollbacks.push(() => releaseInventory(orderId));
    await chargePayment(orderId);
    rollbacks.push(() => refundPayment(orderId));
  } catch (e) {
    for (const rollback of rollbacks.reverse()) { await rollback(); }
    throw e;
  }
}

Restate bakes compensation into cancellation: “For cancellations, Restate will gracefully stop the handler by executing all compensation actions,” whereas “For kills, Restate will immediately stop the handler without executing any compensation actions.” That distinction is the operational form of the saga: an operator who cancels a runaway agent gets the undo; an operator who kills it does not, and must clean up by hand.

For an agent, the practical taxonomy is short. Actions with a native undo (close the pull request, delete the draft, void the uncaptured payment) get a compensation step registered before the forward call. Actions with no undo (the email, the merged PR, the captured payment, the message a human already read) get a gate before them and a compensating communication after them: a follow-up email, a revert PR, a refund, a correction post. Decide which bucket each tool is in when you write the tool, not during the incident.

The model that answers differently on retry

Here is the failure that is specific to agents and that none of the vendor pages address head-on. A step calls the model, the model decides to call the send_email tool, the step’s code sends the email, and the process dies before the step result is recorded. The step retries. The model is asked again, decides this time to call search_docs instead, and the run continues. The history now records a search. The email that went out is recorded nowhere. No compensation will ever fire for it, because the saga does not know it happened.

The rule that prevents this is simple: a model call and a side-effecting tool call never share a step. The model call is one step whose recorded result is the tool choice and its arguments. The tool call is a second step whose idempotency key is derived from that recorded choice. A retry of the model step before its result is recorded is harmless because it has no side effect. A retry of the tool step is harmless because the key deduplicates it. Inngest’s tool-loop guide enforces exactly this split, and Temporal’s OpenAI Agents integration does it structurally by routing I/O tools through activity_as_tool() while keeping deterministic tools in the workflow.

A second, subtler form: do not retry a model step because you dislike its answer. A retry is for failures of the call, not failures of judgement. If the output fails validation, record it, and let the orchestration decide to ask again in a new step with the error in context. That is the evaluator-optimizer loop from the dynamic workflows course, and it belongs in the orchestration, where it is replayable, not in the retry policy, where it is invisible.

My position

Every tool with a side effect carries a key derived from the step’s identity, and the step records the external resource’s ID as the very first thing it does after the call returns. That second clause is the one teams skip. The strongest objection is that many third-party APIs do not accept idempotency keys. True, and the fallback is a deterministic resource name that embeds the key plus a check-before-create. It costs one extra read per tool call. Compared with explaining to a customer why an agent sent the same email three times, one read is cheap.

Think about it: design the idempotency scheme for an agent tool that opens a GitHub pull request. The API has no idempotency-key header. What do you check, what do you name, and what do you record?

Derive a key from the run ID, the step ID and a hash of the tool input (repository, base branch, title, body digest). Use the key in the head branch name, for example agent/<key>, so the resource name is deterministic. Before creating, list open pull requests from that head branch; if one exists, return its number and URL as the step result instead of creating another. If none exists, create it. Record the pull request number as the first line of the step’s result so a crash after creation but before the record is caught by the next attempt’s check. Register a compensation before the forward call that closes any open pull request from that head branch and is a no-op when none exists. Keep the model call that decided to open the pull request in its own earlier step, so a retry here can never re-ask the model. Finally, treat “merge” as a separate, gated tool: opening is undoable, merging is not.

Takeaways
  • Every engine retries steps at least once, and Temporal and Restate retry forever by default. The duplicate side effect is your responsibility by the vendors’ own words.
  • Deduplicate at three layers. Only a key sent to the external service survives the crash between a successful call and its recording.
  • Transient failures get backoff, poison inputs get a terminal error, structural failures get a redesign.
  • Register compensations before the forward call, make them idempotent no-ops, and alert when they fail.
  • A model call and a side-effecting tool call never share a step. Validation failures are a new step with the error in context, not a retry.
4

Waiting: approvals, signals and timers

Human-in-the-loop gates, external events, sleeps that last for days without a running process, deadlines and escalation, and how all of it maps onto agent permission gates
Learning objectives
  • Explain why durable waits consume no compute and why that is the feature that forces engine adoption
  • Map each engine’s wait primitive (signal, hook, event, awakeable, message, interrupt) to the approval pattern
  • Design an approval with a deadline, a default outcome and an escalation path
  • Connect durable waits to agent permission gates in managed harnesses and in your own
  • Choose between a timer, a schedule and a cron for recurring agent work

Why waits force the engine

A wait is where a plain process and a durable engine diverge completely. A process that waits is a process that is running, billed and killable. A durable workflow that waits is a row in a database. Every vendor leads with this.

Primary sources — the cost of waiting, read 14 October 2026

Temporal: “Workers consume no additional resources while waiting for a Timer to fire,” and timers are persisted so that “even if your Worker or Temporal Service is down” when the period ends, the workflow resumes once they return. Vercel: sleep “will pause the workflow and consume no resources, resuming automatically when the time expires,” with no limit on sleep duration. Inngest: “A Function paused by a sleeping Step doesn’t affect your account capacity; i.e. it does not count against your plan’s concurrency limit,” with sleeps “up to a maximum of one year.” Restate, on serverless: “you don’t pay for the time the handler is sleeping.” DBOS: “DBOS saves the wakeup time in the database so that even if the workflow is interrupted” it still wakes on schedule.

For an agent this is the difference between an approval step that costs nothing for a week and an approval step that holds a container, a connection and a model context in memory for a week. It is also what lets one engine host thousands of paused agent runs on a handful of workers: Temporal’s timers page notes that because waiting is free, one Worker can await a very large number of timers at once.

The wait primitives, engine by engine

Each engine exposes two or three ways for the outside world to wake a workflow. The names differ; the shape is the same: the workflow records that it is waiting for something identified by a token, suspends, and resumes with a payload when that token is resolved.

EnginePrimitiveHow it resumesDeadline
TemporalSignal (asynchronous), Update (synchronous, returns a value), Query (read-only); workflow.wait_condition blocks on stateClient, CLI or another workflow sends the Signal or Update; the condition is “evaluated on every state transition”timeout on wait_condition, raising asyncio.TimeoutError
Vercel WorkflowsdefineHook / createHook for typed in-app events; createWebhook for an HTTP callback URLapprovalHook.resume(token, payload) from any route; hooks “can receive multiple payloads while active”Race the hook against sleep; hook token size 255 bytes; token conflicts raise HookConflictError
Inngeststep.waitForEvent with a match or if expressionAny producer sends the event; “you can resume many runs from a single event”timeout; resolves null when nothing arrives
RestateAwakeable (“the callback (task token) pattern”); durable promises on workflowsExternal process resolves or rejects over HTTP (/restate/awakeables/<id>/resolve) or via the SDKCombine with durable timers
DBOSDBOS.send / DBOS.recv messages; set_event / get_event for publishing statusAny code, including a plain HTTP handler, calls DBOS.send(workflow_id, message, topic); delivery is exactly-once from a workflowtimeout_seconds on recv, returning None
LangGraphinterrupt(payload) inside a nodeRe-invoke the graph with Command(resume=value), which becomes the return value of the interruptNone built in; the thread simply stays checkpointed

Two engine-specific cautions from the documentation. Inngest’s wait-for-event page warns that the wait “begins listening for new events from when the code is executed. This means that events sent before the function is executed will not be handled by the wait,” and notes a “lookback” feature is coming; the usage-limits page lists an event lookback period of one hour on the Free plan and three days on Pro. Design the approval UI to send its event only after the wait is registered, or carry the decision in a store the workflow can also read. Vercel’s hooks reserve their token while active, and a second run trying to create a hook with the same token gets a hook_conflict event rather than a hook; derive tokens from the run, not from the business object, if two runs can wait on the same object.

The approval pattern with deadlines and escalation

Temporal’s approval design pattern and Inngest’s human-in-the-loop guide describe the same three-outcome shape, and both are explicit that every outcome needs handling.

# Temporal approval pattern (Python), from the vendor design-patterns page
@workflow.signal
def submit_approval(self, data: ApprovalData) -> None:
    self.approval_data = data

# inside the run method
try:
    await workflow.wait_condition(
        lambda: self.approval_data is not None,
        timeout=timedelta(seconds=timeout_seconds),
    )
except asyncio.TimeoutError:
    self.status = "TIMEOUT"

Temporal’s page carries the decision context in the Signal payload, the decision, the approver and comments, rather than a bare boolean, and its escalation variant first waits the initial timeout, sends a notification Activity, then waits again with an extended timeout of 24 hours in the sample. Inngest’s guide lists four timeout strategies in a table: auto-reject for “High-risk actions (delete, deploy, send to external),” auto-approve for “Low-risk, time-sensitive actions,” escalate for actions that “must get a response,” and retry notification when a “Human might have missed the first message.” Its escalation sample waits four hours, pings an escalation contact, and waits four more.

// Inngest human-in-the-loop (TypeScript), from the vendor guide, trimmed
const approval = await step.waitForEvent("wait-for-approval", {
  event: "agent/approval.response",
  match: "data.approvalId",
  timeout: "24h",
});
if (!approval) { return { status: "timed_out" }; }        // TIMEOUT
if (approval.data.approved) {
  const result = await step.run("execute-action", async () =>
    performAction(approval.data));
  return { status: "approved", result };                   // APPROVED
}
return { status: "rejected", reason: approval.data.reason }; // REJECTED

Vercel’s version uses a typed hook and a loop over received events, so an approver can send “changes” with notes and the workflow revises and re-asks, which is the right shape for a draft-review cycle.

// Vercel Workflows approval hook, from the vendor concepts page, trimmed
export const approvalHook = defineHook<{ decision: 'approved' | 'changes'; notes?: string }>();

export async function aiApprovalWorkflow(topic: string) {
  'use workflow';
  const draft = await generateDraft(topic);
  const events = approvalHook.create({ token: 'draft-123' });
  for await (const event of events) {
    if (event.decision === 'approved') { await publishDraft(draft); break; }
    const revised = await refineDraft(draft, event.notes);
    await publishDraft(revised);
  }
}
// elsewhere: await approvalHook.resume('draft-123', { decision, notes });

Mapping waits onto agent permission gates

An agent permission gate is an approval wait with the tool call as the thing being approved. The managed harnesses have started to expose exactly this, and the mapping is worth making explicit because it tells you what to build yourself when you are not on a managed harness.

Vercel’s agent approval guide describes a tool marked needsApproval: true that suspends the run, and notes that the flag “also accepts an async function that decides per call from the tool’s input.” On resume, the guide says, “a suspended tool call resumes with the exact message history, tool outputs, and pending decisions” as they stood before the pause, and that “Approvals are recorded as workflow events.” Inngest’s AgentKit shows an ask_developer tool whose handler calls step.waitForEvent with a four-hour timeout, “pausing the execution of the AgentKit network.” Claude Managed Agents handles tool confirmation as session events: the sessions documentation lists user.tool_confirmation among the events a session accepts, and the session status table says a session is idle when the “Agent is waiting for input, including user messages or tool confirmations.” LangGraph’s interrupt is the same idea for a graph node: the interrupt payload describes the proposed action, and Command(resume=...) carries the human’s answer back into the node.

What unites these is that the gate lives in the substrate’s wait primitive, not in the model’s prompt. A prompt that says “ask before sending email” is advice the model can ignore under injection. A tool whose execution path is a durable wait cannot be bypassed by text. The securing agents course makes the architectural argument; the managed agents course covers the product side of permission policies. Here the point is narrower: the durable wait is the mechanism that makes a gate both unskippable and free to hold open.

Design rules that follow. Gate on the recorded tool choice from the model step, so the approver sees exactly what will run and the run cannot substitute a different call after approval. Put the approver’s identity in the payload, as Temporal’s pattern does, so the history is an audit trail. Give every gate a deadline and a default, and make the default for anything irreversible a rejection. Escalate by waiting again, not by looping on a shorter timer.

Timers, schedules and crons for recurring agents

A recurring agent is a wait with a calendar. The engines give you three shapes.

A timer inside a long-lived workflow: sleep until next Monday, do the work, sleep again. Temporal’s cloud limits allow a timer duration of up to 100 years; Vercel’s sleep has no limit; Inngest’s is one year, or seven days on the Free plan. The catch is history growth: a workflow that loops weekly for a year accumulates events, which is why Vercel’s versioning page models such loops “as a sequence of runs,” each starting the next on the latest deployment, and why Temporal has Continue-As-New.

A schedule object that starts a fresh run each time. Temporal Schedules cost three actions per execution; DBOS scheduled workflows “run your workflows exactly-once per time interval” and are “automatically enqueued to their owning application’s latest version”; Restate’s timers page says “Cron jobs are not yet a built-in feature of Restate” and points to a guide for building one from durable timers and virtual objects.

A managed scheduled deployment. Claude Managed Agents’ scheduled deployments take a cron expression and an IANA timezone with minute granularity, record every attempt as a deployment run with a session_id or an error.type, apply “jitter of up to 15% of the interval between runs, with a minimum of 5 seconds and a maximum of 9 minutes,” do not backfill missed triggers after an unpause, and allow 1,000 deployments per organization. The same page carries the daylight-saving warning every cron user eventually learns: wall-clock times that do not exist on a spring-forward day “are not triggered” and times that occur twice on a fall-back day “fire twice.”

The choice follows the scheduled agents course logic: a schedule that starts short, idempotent runs is the bookmark design and is right for most recurring work; a long-lived timer loop is right only when the run must carry state forward that is awkward to re-derive, and then it needs a Continue-As-New or next-run boundary.

My position

An approval is a durable wait with a deadline and a default outcome, enforced by the substrate, and never a prompt instruction. The strongest objection is latency: a durable wait is a round trip through the engine, and a chat agent with a 30-second SLA cannot afford a queue hop on every tool call. The answer is that the gate is not on every tool call. It is on the handful of tools that are irreversible, and those are precisely the ones where a few hundred milliseconds of engine latency is irrelevant next to the minutes a human takes to decide. Module 8 draws the line for the support-agent case.

Think about it: a weekly research agent drafts a report on Monday and must not publish without approval. The approver has 48 hours; after that a backup approver gets 24 hours; after that the draft is discarded. Sketch the waits and the defaults before opening.

Model call to draft is a step; the draft is the recorded result. Notify the primary approver in a step with an idempotency key, then wait on an approval event or signal correlated by the run ID with a 48-hour timeout. On timeout, a step notifies the backup approver, then wait again on the same correlation with a 24-hour timeout. On the second timeout, record the outcome as discarded and end the run; the default for publishing is reject because publishing cannot be undone. On approval, a step publishes with a key derived from the run and draft hash; on “changes” with notes, a new model step revises and the wait repeats with a fresh deadline. Put the approver’s identity and comments in the event payload so the history is the audit trail. Hosting: a scheduled deployment or cron starts a fresh run each Monday; the 72 hours of waiting cost nothing, and the run survives any deploy in between because it is pinned or patched per module 2.

Takeaways
  • A durable wait costs nothing to hold open. That is the feature that forces agents off crons and onto engines.
  • Every engine has a token-based wait: signal, hook, event, awakeable, message or interrupt. Correlate by run identity.
  • Every gate has three outcomes. Give each a handler, a deadline and a default; irreversible actions default to reject.
  • A permission gate is an approval wait on a recorded tool choice. Put it in the substrate, where text cannot bypass it.
  • Recurring agents are schedules that start short runs, unless state must carry forward, in which case the loop needs a Continue-As-New boundary.
5

The engines compared, October 2026

Temporal, Vercel Workflows, Inngest, Restate and DBOS as their own documentation describes them, plus the durability-lite tier: LangGraph checkpointers, Claude Managed Agents sessions and OpenAI Agents SDK sessions
Learning objectives
  • Describe the five programming models and where each one runs your code
  • Compare hosting, pricing unit, run limits, versioning, languages and observability from vendor documentation with dates
  • Name what each engine added for agents in 2026
  • Explain what the durability-lite tier persists and what it does not
  • Match a workload to an engine and defend the choice

The landscape in one paragraph

Five products sell durable execution for application code in October 2026, and they differ more in where the engine lives than in what it does. Temporal is a server that your Workers poll, available self-hosted or as Temporal Cloud, with SDKs its developer page lists for “.NET, Go, Java, PHP, Python, Ruby, Rust, and TypeScript.” Vercel Workflows is the hosted form of the open-source Workflow SDK, generally available on Vercel since 16 April 2026, where “each step runs as its own function invocation on Fluid compute” and the state lives in Vercel’s managed persistence. Inngest is an event-driven platform that invokes your functions over HTTP and memoizes each step; its README names SDKs for TypeScript/JavaScript, Python, Go and Kotlin/Java, with the server “available under the Server Side Public License” with delayed Apache 2.0 publication and the SDKs under Apache 2.0. Restate is a single Rust binary that proxies invocations to your services and journals their progress, with SDKs for TypeScript, Java and Kotlin, Python, Go and Rust, licensed under the Business Source License 1.1. DBOS is a library: “DBOS provides lightweight durable workflows built on top of Postgres,” MIT-licensed, for TypeScript, Python, Go and Java, with an optional control plane called Conductor.

Money followed the category this year. Temporal announced on 14 September 2026 a $550 million Series E at a $12.55 billion valuation, led by Lightspeed. Restate’s own site could not be read for this course, but Forkast and Remio reported a $20 million Series A led by Singular on 30 September 2026; treat that as reported, not verified. Vercel’s GA post framed the product as eliminating a category: “Workflows eliminates the orchestrator entirely,” and described dedicated orchestration services as “distributed systems you pay for on top of your core application compute.” That sentence is the thesis of the hosted-in-your-platform camp, and DBOS makes the same argument from the library side: “The only point of failure in DBOS is Postgres.”

Programming model and hosting

EngineProgramming model, per its docsWhere orchestration and steps runLanguagesLicense and hosting
TemporalWorkflow Definitions and Activities; Commands matched against an Event History on replay; Signals, Queries, Updates, Timers, Child Workflows, SchedulesYour Workers poll Task Queues; Temporal Server or Temporal Cloud stores history. Serverless Workers on AWS Lambda announced 6 May 2026 as pre-release.NET, Go, Java, PHP, Python, Ruby, Rust, TypeScript (docs, 14 Oct 2026)Self-host or Temporal Cloud; 99.9% SLA, 99.99% with HA options
Vercel Workflows"use workflow" and "use step" directives compiled by a build plugin; event-sourced run log; sleep, hooks, webhooks, streamsWorkflow and step code run as Vercel Functions, dispatched by Vercel Queues; state in managed persistence pinned to one region per runTypeScript/JavaScript; Python via the vercel Python SDK (docs, 14 Oct 2026)Open-source Workflow SDK (workflow 5.2.2 on npm; recent versions list Apache-2.0) with Worlds for local, Vercel, Postgres or custom backends; hosted on Vercel
Inngeststep.run, step.sleep, step.waitForEvent, step.invoke, step.ai; functions triggered by events, crons or webhooks; memoized step resultsYour compute, invoked over HTTPS (“serve” for serverless, “connect” for workers); Inngest Cloud or a self-hosted serverTypeScript/JavaScript, Python, Go, Kotlin/Java (README, 14 Oct 2026)Server under SSPL with delayed Apache 2.0; SDKs Apache 2.0; cloud plans Hobby, Pro, Enterprise
RestateServices, Virtual Objects with per-key K/V state and single-writer concurrency, and Workflows whose run handler executes once per ID; ctx.run journals side effects; awakeables and durable timersYour services behind the Restate Server, which “runs as a single binary with zero dependencies”; Lambda and other serverless targets supportedTypeScript, Java, Kotlin, Go, Python, Rust (docs source, 14 Oct 2026)BSL 1.1 for the server; Restate Cloud (“Free to start”) and BYOC, per the archived cloud page of 30 Sep 2026
DBOS@DBOS.workflow() and @DBOS.step() decorators; every workflow input and step output checkpointed to a Postgres system database; queues, messages, events, streamsIn your own process; recovery is coordinated by Conductor or by the single node at startupPython, TypeScript, Go, Java (docs and pricing FAQ, 14 Oct 2026)DBOS Transact library, MIT; Conductor control plane paid; DBOS Cloud hosting optional

Two structural facts fall out of that table. First, only Temporal and Restate put a long-lived server on the critical path of every step; Vercel and Inngest put a queue and an HTTP hop there; DBOS puts a Postgres write. That is the latency axis module 8 uses for the 30-second SLA case. Second, the hosting choice is mostly made for you by where your code already runs: an application on Vercel has Workflows one import away, a Postgres shop has DBOS one decorator away, and a polyglot organization with platform engineers has Temporal’s eight SDKs.

Pricing unit, limits, versioning, observability

EnginePricing unitRun length and history capVersioning in-flight runsObservability
Temporal CloudActions: $50 per million pay-as-you-go, down to $25 per million self-serve; active storage $0.042 per GB-hour, retained $0.00105 per GB-hour; Business from $500 per month (read 14 Oct 2026)Timers up to 100 years; Event History warns at 10,240 events, terminates at 51,200 events or 50 MB; Continue-As-New to go longer; retention 1 to 90 daysPatch markers (patched / deprecate_patch) or Worker Versioning, GA May 2026, which pins runs to the Worker version that started themEvent History per execution in the UI; replay from history; Braintrust integration (20 Jan 2026) maps every Workflow and Activity to a span; OpenMetrics support
Vercel WorkflowsEvents $0.02 per 1,000; data written $0.50 per GB; data retained $0.50 per GB-month; Hobby includes 50,000 events; functions and queues billed separately (page updated 16 Sep 2026)No maximum run or sleep duration; 25,000 events and 10,000 steps per run; 50 MB payload; 2 GB per run; replay attempt must finish within 240 secondsRuns pinned to their deployment by default; cancel and rerun on latest, or self-upgrading runs with deploymentId: "latest"Dashboard trace viewer redesigned 23 Jun 2026; time-between-steps measurement 5 Aug 2026; local npx workflow web; retention 1, 7 or 30 days by plan
InngestExecutions, where “one run uses 6 executions (the run itself + 5 steps)” for a five-step function; Hobby $0 with 50k; Pro from $99 per month with 1M included, pay-as-you-go to 20M (archived pricing page, 9 Sep 2026)1,000 steps per function; sleep up to one year; maximum run length 30 days Free, 90 days Basic, 366 days Pro; run state 32 MiB; step output 4 MiB (archived limits page, 30 Aug 2026)Step-ID matching: completed steps with the same ID return saved results; new IDs run; major rewrites get a new function ID with a cutover triggerTraces and logs retained 24 hours Free, 7 days Pro, 90 days Enterprise on the pricing page; Dev Server with rerun-from-step; step.ai records prompts and tokens
RestateCloud is “Usage-based pricing with volume discounts” and “Free to start” per the archived cloud page; the pricing page itself did not render in the archived copy, so no unit price is stated hereNo per-invocation numeric caps found in the documentation read; ctx.run retries infinitely by defaultInvocations bound to the deployment they progressed on and “cannot move to newer deployments automatically”UI and CLI invocation inspection with journal progress and retry status; OpenTelemetry traces exported to your platform
DBOSLibrary free; Conductor Pro $99 per month with 1M checkpoints and $50 per extra million; Teams $499 per month with 10M checkpoints and $40 per extra million (archived pricing page, 24 Aug 2026)No per-workflow step cap stated in the docs read; durable sleep persisted in PostgresPatch markers (DBOS.patch) or application versions with blue-green rollout; recovery only to matching versionsConductor dashboards, workflow fork from a step, Prometheus-compatible metrics; SQL introspection of system tables

What each engine added for agents in 2026

Every vendor shipped agent-specific features this year, and reading them together shows the category converging on the same four needs: model calls as steps, streaming out of a durable run, approval gates, and large payloads.

Temporal’s Replay conference announcements of 6 May 2026 included Workflow Streams, “durable streaming that uses Temporal’s Signal & Update primitives” for LLM tokens and live updates; External Payload Storage, “especially useful for agentic and AI data processing applications”; Standalone Activities; and integrations with Google ADK and the OpenAI Agents SDK, the latter now generally available with a pre-release sandbox integration in which “all sandbox operations — LLM model API calls, the full sandbox lifecycle, running commands — execute as Temporal activities.” Its AI cookbook lists recipes for Claude and OpenAI tool-calling loops, a durable MCP server, Strands, the Vercel AI SDK, a human-in-the-loop agent, a claim-check pattern and a multi-agent deep-research system.

Vercel shipped WorkflowAgent in @ai-sdk/workflow (2.0.72 on npm as of 14 October 2026), which runs the AI SDK agent loop inside a workflow with tools as steps, resumable streams through getWritable(), and needsApproval gates. Its guides cover a durable code agent that runs generated tests in a Sandbox microVM and a research agent, and the platform added run-region pinning on 20 July 2026.

Inngest offers step.ai.infer(), which “offloads the inference request to Inngest’s infrastructure, pausing your function execution until the request finishes,” so a serverless function is not billed while the model thinks; step.ai.wrap() to turn an existing SDK call into a step with prompt and token observability; and AgentKit (0.13.2 on npm), a TypeScript framework for “multi-agent networks with deterministic routing and rich tooling via MCP” that gains retries, offloading and traces when deployed with Inngest. Its documentation now has a “Durable Agents” section with the tool-loop, human-in-the-loop and sub-agent delegation guides quoted throughout this course.

Restate positions Virtual Objects for “Stateful agents, actors, and digital twins: e.g. AI chat sessions,” where per-key state and single-writer concurrency give each conversation a serialized, durable mailbox. Its cloud page headline in the 30 September 2026 archive is “The Durable Runtime for your Agents and Backends,” and search results point to blog posts on OpenAI Agents SDK and Google ADK integrations in January 2026, which this course could not fetch directly.

DBOS ships a Hacker News research agent as a documented example in Python and TypeScript, publishes a case study with Yutori on “Large-scale, Durable Agentic AI,” and its workflow streaming API, where writes from a workflow “happen exactly-once” and writes from a step “at-least-once,” is built for streaming model output out of a durable run.

The durability-lite tier

Below the engines sits a tier of tools that persist an agent’s conversation but not a replayable step ledger. They are often enough, and knowing exactly what they do and do not give you prevents both over- and under-engineering.

ToolWhat persistsCrash in the middle of a tool callWaits
LangGraph checkpointersGraph state at every super-step, per thread; InMemorySaver for experiments, SqliteSaver, PostgresSaver, MongoDB and Redis savers for productionDepends on the durability mode: "exit" saves only when the graph exits, so “you cannot recover from system failures (like process crashes) mid-execution”; "async" has “a small risk” of a lost checkpoint; "sync" writes “every checkpoint before continuing.” Node retries are opt-ininterrupt() pauses the thread; resume with Command(resume=…); no deadline primitive
Claude Managed Agents sessions“Claude Managed Agents is stateful by design: sessions are long-running, resume cleanly after pauses, and store conversation history, sandbox state, and outputs server-side.” Event history “is persisted server-side and can be fetched in full”Statuses include rescheduling, “Transient error occurred, retrying automatically”; the harness owns the loop, so you do not write replay code and cannot inspect a step ledger as you would a Temporal historyTool confirmations and user messages leave the session idle at no model cost; session budgets pause work with budget_reached; scheduled deployments for cron
Claude Agent SDK sessionsTranscript on disk; continue resumes the most recent session, resume takes a session ID, fork “creates a new session that starts with a copy of the original’s history” (archived docs, 9 Oct 2026)Resuming returns “with full context from wherever the session left off”; the interrupted turn itself is not replayed as stepsPermission prompts and hooks within a query; no durable timer
OpenAI Agents SDK sessions“Sessions store conversation history for a specific session”; SQLite, SQLAlchemy, Redis, MongoDB, Dapr and server-side Conversations implementationsNot described; the docs cover resuming after an approval pause with the same session instance, not recovery from a process failure. Temporal’s integration notes that SQLiteSession is unsupported there because it stores history in a local fileApproval pauses resumed with the same session; no durable timer

The pattern across the tier: the conversation survives, the in-flight step does not. For a chat agent whose every tool is idempotent and whose longest wait is a user typing, that is enough, and the Claude Agent SDK course and managed agents course show how far the managed tier goes. The moment a run must sleep for days, fan out and join, or compensate, you are back in engine territory, and the cleanest designs put a managed session inside an engine step rather than pretending the session is the engine. Temporal’s sandbox integration is one worked example of exactly that composition.

Versions as read on 14 October 2026

PackageVersionRegistry
@temporalio/workflow / temporalio1.24.0 / 1.34.0 (released 30 Sep 2026)npm / PyPI
workflow / @ai-sdk/workflow5.2.2 / 2.0.72npm
inngest / @inngest/agent-kit / inngest (Python)4.24.0 / 0.13.2 / 0.5.19npm / npm / PyPI
@restatedev/restate-sdk / restate_sdk1.17.3 / 1.0.5npm / PyPI
@dbos-inc/dbos-sdk / dbos5.2.11 / 3.2.0 (released 29 Sep 2026)npm / PyPI
@langchain/langgraph / langgraph1.5.0 / 1.2.15 (released 12 Oct 2026)npm / PyPI
openai-agents0.23.1 (released 2 Oct 2026)PyPI
My position

Pick by where your code already runs, then check the two constraints that actually bite: history growth and language. An application on Vercel should use Workflows and split long agent loops into child runs before 2,000 events. A Postgres shop that will not add infrastructure should use DBOS and buy Conductor the day it runs more than one executor. A polyglot organization with a platform team should use Temporal and budget engineering time for the determinism contract. A TypeScript team that already thinks in events and needs concurrency, throttling and debounce as first-class features should use Inngest and design around the 1,000-steps-per-function limit. A team building stateful per-user agents with strict latency should look hard at Restate’s Virtual Objects. The strongest objection is that this ignores feature depth, and Temporal’s depth is real. The answer is that an engine your team can operate beats a deeper engine it cannot, and the vendors have converged enough on the agent features in this module that depth rarely decides.

Think about it: match four workloads to a substrate before opening. (1) A Next.js SaaS adding a background agent that drafts onboarding emails with a human review. (2) A Django monolith on RDS Postgres that must add nightly data-repair agents and refuses new infrastructure. (3) A bank with Java, Go and Python services building a document-processing agent that waits days for signatures. (4) A per-customer support agent that must keep conversation state and answer in under a second.

(1) Vercel Workflows: the code already runs on Vercel, WorkflowAgent gives tools-as-steps, and a hook is the review. Split the loop into child runs if it grows past a few thousand events. (2) DBOS: decorators on existing functions, checkpoints in the Postgres already there, scheduled workflows for nightly runs, Conductor when a second executor appears. (3) Temporal: three languages on one history, Signals for signatures, timers for the days of waiting, Worker Versioning for the long tail of in-flight documents, and an enterprise support tier. (4) Restate Virtual Objects, or a durability-lite session with idempotent tools: per-key state delivered with the request and single-writer semantics fit a mailbox per customer, and a sub-second budget rules out a queue hop per message. Reasonable people would also put (4) on Inngest with concurrency keys per customer, or on DBOS if its checkpoint-latency claim holds in your measurement. The point of the exercise is that none of the four answers is “the best engine”; each is “the engine nearest the code.”

Takeaways
  • The five engines differ mainly in where the engine lives: a polled server, a queue plus functions, an HTTP-invoked platform, a journaling proxy, or a library over Postgres.
  • Pricing units are actions, events plus bytes, executions, or checkpoints. Limits that bite agents are history size, steps per run and payload size.
  • Every vendor shipped agent features in 2026: model calls as steps, durable streams, approval gates, large-payload offloading.
  • The durability-lite tier persists the conversation, not the step. Compose a managed session inside an engine step when you need both.
  • Choose the engine nearest your code and your team’s operating ability, then design for history growth from day one.
6

Cost and limits

History caps, payload limits, events per run, what a 10,000-tool-call agent costs on each pricing model, and why the orchestration bill rounds to zero next to the tokens
Learning objectives
  • Read each engine’s pricing unit and limits from its own page and know the date you read them
  • Estimate the orchestration cost of a 10,000-tool-call agent run on each pricing model
  • Compute the token cost of the same run at published Claude prices and the ratio between the two
  • Place budget caps at the substrate so a runaway loop stops spending
  • Name the hidden costs that the pricing unit does not show

The pricing units, with dates

EngineWhat you pay forNumbers as readDate and source
Temporal CloudActions: workflow starts, activity starts and retries, timers, signals, queries, updates, schedules; plus storageFrom $50 per million actions pay-as-you-go, down to $25 per million self-serve, further by commit; active storage $0.042 per GB-hour; retained storage $0.00105 per GB-hour; Business plan the greater of $500 per month or 10% of usage, including 2.5M actions; $150 of free credits for 90 days14 Oct 2026, temporal.io/pricing and the Actions reference
Vercel WorkflowsEvents, data written, data retained; functions and queues billed separately$0.02 per 1,000 events; $0.50 per GB written; $0.50 per GB-month retained; Hobby includes 50,000 events and 1 GB written per month; a step that succeeds first time “produces three events”Page updated 16 Sep 2026, read 14 Oct 2026
InngestExecutions: “a single durable function run plus each step inside it”; concurrent steps; events; span dataHobby $0 with 50k executions and 5 concurrent steps; Pro from $99 per month with 1M executions included, “Pay as you go up to 20M,” 100 concurrent steps then $25 per 25; Enterprise custom; overage billed “at tiered rates” without a listed priceArchived copy of inngest.com/pricing captured 9 Sep 2026
Restate CloudUsage-based on actions“Free to start — no credit card” and “Usage-based pricing with volume discounts”; no unit price readable. A third party, DevTune, listed on 10 July 2026 a free tier of 50k actions per month and overage from $25 per million; unverified against Restate’s own pageArchived copy of restate.dev/cloud captured 30 Sep 2026
DBOSLibrary is free; Conductor meters checkpoints (“workflows, steps, transactions”) and seatsPro $99 per month: 2 seats, 3 apps, 1M checkpoints, $50 per extra million; Teams $499 per month: 10 seats, 10 apps, 10M checkpoints, $40 per extra million; Enterprise custom with self-hosted ConductorArchived copy of dbos.dev/pricing captured 24 Aug 2026
Claude Managed AgentsModel tokens at list price, session running time, web searchesRunning time $0.08 per hour; web searches $10 per 1,000; budgets enforced “between model requests, not mid-request”14 Oct 2026, session budgets page

The limits that bite an agent loop

LimitTemporal CloudVercel WorkflowsInngest
History or events per runWarning at 10,240 events or 10 MB; execution terminated past 51,200 events or 50 MB25,000 events per run; 10,000 steps per run; 2 GB total entity storage; slower replay past 2,000 events1,000 steps per function; run state 32 MiB
Payload per step2 MB per request, including each argument and return value; 4 MB per gRPC message50 MB per input or output4 MiB returned by a step; event size 256 KiB Free to 3 MiB Pro
Signals and hooks10,000 Signals and 2,000 Updates per execution; 2,000 concurrent incomplete ActivitiesHook token 255 bytes; 200 hook creations per secondEvent lookback 1 hour Free, 3 days Pro
Run duration and sleepTimer up to 100 years; retention 1 to 90 days after closeNo limit on run duration or sleep; replay attempt 240 seconds; step bounded by function limits (300 s default, 800 s max, 1,800 s beta)Sleep up to 1 year (7 days Free); run length 30 days Free, 90 days Basic, 366 days Pro; step timeout up to 2 hours
Read14 Oct 202614 Oct 2026Archived 30 Aug 2026; note the limits page lists a Basic plan that the September pricing page does not

Restate and DBOS publish no comparable per-run caps in the documentation read for this course, and DBOS’s only hard number in this area is Conductor’s executor timeout: “By default, the executor timeout is 60 seconds, so Conductor waits 60 seconds after an executor disconnects before recovering its workflows.” That number matters more than it looks: it is the floor on how long a crashed agent stays dead before another executor picks it up.

Read the first row as a design constraint. An agent that loops 10,000 times with a model call and a tool call per iteration produces 20,000 steps. On Temporal that is several tens of thousands of events, past the termination limit several times over, so the loop must Continue-As-New periodically; the OpenAI Agents integration page recommends exactly that for long conversations. On Vercel it is 60,000 events against a 25,000 cap and 20,000 steps against a 10,000 cap, so the loop must become a chain of child workflows. On Inngest it is twenty functions of a thousand steps each, or a fan-out. None of this is hard. All of it must be designed before the first production run, because the failure mode is a dead run at step 9,999.

Token prices, read 14 October 2026

ModelInput per million tokensOutput per million tokensCache read per millionBatch (50% off)
Claude Opus 5.5$4$20$0.20$2 / $10
Claude Sonnet 5.5$2$10$0.10$1 / $5
Claude Haiku 5.5 (prompts up to 100,000 tokens)$0.10$0.50$0.01$0.05 / $0.25
Claude Haiku 5.5 (prompts over 100,000 tokens)$0.50$2.50$0.05$0.25 / $1.25

Source: Anthropic’s pricing page, which also states that cache hits on Opus 5.5 and Sonnet 5.5 “are priced at 0.05x the base input price.” The inference economics course goes deeper on token costs; here the prices exist to be set against the orchestration fee.

Worked exercise: the 10,000-tool-call agent

Worked exercise. Inputs: one agent run of 10,000 iterations, each iteration one model call and one tool call, so 20,000 steps. Each model call averages 20,000 input tokens and 1,000 output tokens. No step retries. Estimate (a) the token bill on Sonnet 5.5, Opus 5.5 and Haiku 5.5, (b) the orchestration bill on Temporal Cloud, Vercel Workflows, Inngest and DBOS Conductor, and (c) the ratio. Then open.

(a) Tokens. Input: 10,000 × 20,000 = 200,000,000 tokens, or 200 million. Output: 10,000 × 1,000 = 10,000,000, or 10 million.

  • Sonnet 5.5: 200 × $2 = $400 input; 10 × $10 = $100 output; $500.
  • Opus 5.5: 200 × $4 = $800; 10 × $20 = $200; $1,000.
  • Haiku 5.5 (all prompts under 100,000 tokens): 200 × $0.10 = $20; 10 × $0.50 = $5; $25.
  • With prompt caching at 90% cache hits on Sonnet 5.5: 180M × $0.10 = $18 plus 20M × $2 = $40 for input, plus $100 output, $158. On Opus 5.5: 180M × $0.20 = $36 plus 20M × $4 = $80, plus $200, $316.

(b) Orchestration.

  • Temporal Cloud. Actions: one workflow start plus 20,000 activity starts, plus a handful of Continue-As-New starts to stay under the history limit; call it 20,005. At $50 per million: 20,005 × $0.00005 = $1.00. At the $25 self-serve floor: $0.50. Storage is extra but small: a run that holds 200 MB of active history for ten hours is 2 GB-hours at $0.042, or $0.08.
  • Vercel Workflows. Events: 3 × 20,000 = 60,000, so the run must be split into at least three child runs for the 25,000-event cap and at least two for the 10,000-step cap; say four child runs of 5,000 steps. Events: 60,000 × $0.02 per 1,000 = $1.20. Data written: if each step writes 40 KB of input and output, 20,000 × 40 KB = 800 MB, or $0.40; retained seven days on Pro, 0.8 GB × 7/30 = 0.19 GB-month, or $0.09. Orchestration total about $1.70, before function compute, which is billed on active CPU and provisioned memory and depends on how long each step waits on the model.
  • Inngest. Executions: the 1,000-step cap means twenty functions, each one run plus 1,000 steps, so 20 × 1,001 = 20,020 executions. On Pro that is 2% of the 1M included for $99; at the plan’s blended included rate of $99 per million, about $1.98 of the subscription. Overage is priced at unlisted tiered rates.
  • DBOS with Conductor Pro. Checkpoints: 20,000 step outputs plus two per workflow, 20,002. That is 2% of the 1M included for $99, about $1.98 at the blended rate, or $1.00 at the $50-per-million overage rate. Without Conductor the library is free and the cost is 20,002 Postgres writes.

(c) The ratio. Orchestration costs between $0.50 and $2 for the run. Tokens cost $25 on Haiku, $158 to $500 on Sonnet, $316 to $1,000 on Opus. The engine’s fee is roughly 0.05% to 0.6% of the token bill on Opus, 0.1% to 1.3% on Sonnet, and 2% to 8% on Haiku. Even on the cheapest model the engine is a rounding error. What the engine actually sells is not its fee but the Design B saving from module 2: one crash in a Design A run re-bills up to the whole token column.

What the ratio tells you

Three conclusions follow from the arithmetic, and each one changes a decision.

First, never choose an engine on its per-step price. The spread between the most and least expensive orchestration above is about a dollar on a run that costs hundreds in tokens. Choose on hosting fit, limits, language and operability, as module 5 argued.

Second, the engine’s job is to protect the token column. Every crash under a naive design re-bills tokens; every retry of a step that wraps both a model call and a tool call re-bills a model call. Step granularity is a cost control, not an overhead. The loop engineering course treats the loop’s shape; this module adds that the shape determines the re-bill.

Third, prompt caching changes the shape of the exercise more than the engine does. Moving from no caching to 90% cache hits cut the Sonnet run from $500 to $158. Caching depends on a stable prefix, and a replayed step that reconstructs the message history byte-for-byte is exactly what keeps the prefix stable. Durable execution and caching are allies.

Budget caps belong in the substrate

A 10,000-iteration loop that should have stopped at 50 is the agent-era version of a fork bomb, and the only reliable stop is a cap enforced below the model’s reasoning. The substrates offer three shapes.

A hard dollar cap on a managed session. Claude Managed Agents session budgets are “a hard ceiling on the session’s list cost”; the platform “stops issuing new model requests once that running total reaches max_list_cost,” the session pauses and goes idle with budget_reached, and because enforcement is between requests the final cost “can land a fraction past the cap.” Deployments copy the cap onto every run they start. The page also distinguishes this from the Messages API’s task budgets, “which are advisory, token-denominated budgets the model uses to self-regulate.” Advisory budgets are for pacing; hard budgets are for stopping.

# Claude Managed Agents: hard budget at session creation, from the vendor docs
session = client.beta.sessions.create(
    agent=agent.id,
    environment_id=environment.id,
    budget={
        "type": "limit",
        "max_list_cost": {"amount": "125", "currency": "USD"},  # cents, as a string
    },
)

A counter in workflow state. On an engine, keep the running token total in the orchestration state, add each model step’s recorded usage to it, and exit the loop when it crosses the line. Because the total is derived from recorded step results it is replay-safe and survives the crash. Inngest’s tool-loop guide shows exactly this with a 500,000-input-token ceiling and an iteration cap, and adds a stuck-loop detector that breaks when the last three tool calls repeat. Temporal has no native dollar budget; the counter is the pattern.

A cap on the trigger. For scheduled agents, cap at the schedule: the scheduled agents course puts a per-run budget on a daily brief, and Managed Agents deployments do the same with a per-run budget object. A cap per run bounds the blast radius of one bad day; a cap per month bounds the quarter.

The costs the unit price hides

Compute for steps is billed by whoever runs the step. On Vercel that is function active CPU and provisioned memory; on Temporal it is the Workers you operate or, since May 2026 in pre-release, Lambda; on Inngest and Restate it is your own compute; on DBOS it is your process and your Postgres. A model call that waits 30 seconds for a response is 30 seconds of a function or worker doing nothing, which is why Inngest’s step.ai.infer() offloads the request so “you don’t pay for function execution while waiting,” and why Vercel bills active CPU rather than wall time.

Retention is a cost and a capability. Temporal Cloud retains closed histories for 30 days by default, configurable from 1 to 90; Vercel keeps run data for 1, 7 or 30 days by plan; Inngest keeps traces for 24 hours on Hobby and 7 days on Pro; DBOS Conductor manages retention per application. If your audit requirement is longer than the retention, export: Vercel’s own guide suggests writing run history to Blob “as a final step for audits that outlast retention.”

People are the largest hidden cost. The determinism contract is a tax on every engineer who touches the orchestration, and the review burden of “is this call a step” never goes away. That tax is the strongest honest argument for the durability-lite tier where it suffices.

My position

Put a hard spending cap below the model on every long-running agent, and make it a stop, not a warning. A managed session budget, a replay-safe token counter, or a per-run cap on the schedule; any of the three, but one of them, and set at a level that would hurt if hit ten times in a day. The strongest objection is that caps truncate legitimate long tasks. They do, and the fix is a resume-with-raised-cap path, which Managed Agents provides by updating the budget and which an engine provides by a signal that raises the counter’s ceiling. A paused run with a human deciding whether to spend more is the correct state for an agent that has already spent more than you expected.

Takeaways
  • Pricing units are actions, events plus bytes, executions and checkpoints. Read them with the date; every vendor changed its page this year.
  • History caps and steps-per-run limits force long agent loops into Continue-As-New or child runs. Design that in before the first production run.
  • A 10,000-tool-call run costs about a dollar or two to orchestrate and hundreds to a thousand dollars in tokens. The engine protects the token column; it does not compete with it.
  • Put a hard cap below the model: a session budget, a replay-safe counter or a per-run cap, with a resume path for legitimate overruns.
  • The hidden costs are step compute, retention beyond your audit window, and the engineers who must honour the determinism contract.
7

Observability and operations

Run histories as traces, replay as a debugger, correlating a model trace with a workflow history, alerting on stuck runs, migrations and draining, testing with time-skipping, and local development
Learning objectives
  • Use a run history as the primary trace of an agent and replay as its debugger
  • Correlate a model-call span with the workflow step that made it, in both directions
  • Define “stuck” for a durable agent run and alert on it
  • Deploy, drain and roll back without stranding in-flight runs
  • Test orchestration with time-skipping and replay, and run the whole stack locally

The history is the trace

An agent’s run history is a better trace than anything you will get from a logging library, because it is the engine’s own record of what happened, in order, with the inputs and outputs that the engine needed to replay. Every vendor has built its debugging surface on that record.

Vercel’s dashboard records, per its documentation, “Every step, input, output, sleep, and error inside a workflow,” and the redesigned trace viewer of 23 June 2026 lets you “click into any step to see its inputs, outputs, and run metadata” and zoom any section of the timeline. The 5 August 2026 addition measures the gap between any two steps, which the changelog says “can be useful for spotting delayed starts and queue waits when a run takes longer than its steps account for.” That gap is exactly where an agent’s wall-clock time hides: not in the model call, but in the queue hop between the model step and the tool step. The run data is sensitive, which is why the docs note that “Team owners can read decrypted Workflow run data” and offer a “Workflow Run Data Viewer” permission for others.

Temporal’s Event History is viewable per execution and, as module 2 covered, replayable: the Python Replayer recreates a run from its history and “Replay succeeds only if the Workflow Definition is compatible with the provided history.” Restate’s CLI shows the journal as progress: restate invocations describe prints each recorded call with a check mark, the current status such as backing-off with the retry count and next retry time, and a reminder that the invocation “is bound to run on deployment” it started on. DBOS Conductor provides “dashboards of all active and past workflows and all queued tasks as well as real-time workflow visualization,” and lets an operator “pause any workflow execution, start any stopped or enqueued workflow, or restart any workflow from a specific step.” That last capability, which DBOS calls fork, is the operational superpower of a step ledger: “if a large number of billing workflows fail overnight due to an outage in a payment API, you can use fork to restart them all from the payment step after the outage is resolved.” Inngest’s Dev Server offers the same move for agents, letting you “edit prompts and rerun from the step” when the model call was made with step.ai.wrap and JSON-serializable arguments.

Claude Managed Agents keeps its own ledger: “Event history is persisted server-side and can be fetched in full,” and the session object carries usage.list_cost and usage.active_seconds. What it does not give you is a step ledger you can replay in your own process, which is the trade of the managed tier.

Correlating a model trace with a workflow history

A durable run and an LLM observability tool each see half the story. The run history knows which step ran, how many times, and what it returned. The model trace knows the prompt, the tokens, the latency and the cost. The join key is the run identity, and the integrations that exist in 2026 are all built on that join.

Primary source — Temporal and Braintrust, 20 January 2026

“every Temporal Workflow and Activity becomes a Braintrust span.” The integration provides a “Full trace hierarchy: From client request through every Workflow step,” with inputs, outputs, token counts and latency for each LLM call, the prompt version each execution used, and cost per query, while on the Temporal side you keep “Workflow executions: every research request with full Event History,” activity retries and execution state. The setup is a plugin on the Worker and the Client: plugins=[BraintrustPlugin()].

Inngest’s step.ai.wrap() records the model call as a step “augmenting the observability of your Inngest Functions with information such as prompts and tokens used,” so the join is built in. Vercel’s WorkflowAgent writes ModelCallStreamPart chunks to the run’s persistent stream, and the run error code is exposed “as an OpenTelemetry span attribute (workflow.error.code).” Restate “exposes traces via OpenTelemetry” with an invocation ID that “lets you correlate logs and metrics.” DBOS Conductor exposes metrics “from a Prometheus-compatible endpoint” and, for sensitive workloads, a metadata-only mode in which “only workflow and step metadata (but not data, like workflow or step inputs or outputs) is sent to Conductor.”

If you are not using a vendor integration, do the join by hand with two rules. Every model-call span carries the run ID, the step ID and the attempt number as attributes, so a trace can be found from a history. Every model step’s recorded result carries the provider request ID and the span ID, so a history can be found from a trace. The agent tracing course covers the span conventions; the addition here is that the step result is the right place to persist the link, because it is the one record the engine guarantees to keep.

Stuck runs and what to alert on

A durable run cannot crash in the old sense, so the failure mode moves. The new failure is the run that is alive and not progressing: retrying forever against a poison input, waiting for an event nobody will send, paused at a budget nobody noticed, or pinned to a deployment that no longer exists. Define “stuck” as a run older than its longest legitimate wait with no registered wait in its history, and alert on that definition per workflow type.

The engines surface the states you need. Vercel lists REPLAY_TIMEOUT, MAX_DELIVERIES_EXCEEDED and DEPLOYMENT_MISMATCH among its run error codes, and npx workflow inspect runs --status running --json lists live runs. Restate shows backing-off with “Retried 141 time(s)” in its own CLI example, which is what a poison input looks like under an infinite retry policy. Inngest’s sleeps page documents a known limitation worth an alert of its own: runs sleeping longer than the plan’s trace retention “are still sleeping and will resume as scheduled, even if they’re not visible in the Function Runs list,” so a dashboard that shows nothing does not mean nothing is waiting. DBOS Conductor marks a disconnected executor DEAD after the 60-second default timeout and recovers its workflows elsewhere; Teams plans add “Custom workflow alerting.” Managed Agents sessions report rescheduling for transient retries and budget_reached when a cap pauses work, and the status docs warn that “idle alone doesn’t mean that the work is done” when a workflow run is open.

Four alerts cover most agent incidents: the age of the oldest running run per workflow type against its expected duration; the retry count of any single step above a threshold, which catches poison inputs before the bill does; any compensation failure, which per Temporal’s saga guidance warrants “manual intervention on persistent compensation failures”; and the count of runs paused at a budget or approval beyond their deadline. Each of those is a query over the history or the status field, not a log search.

Migrations, draining and rollback

Deploying under live agent runs is a scheduled operation, and the engines’ versioning models from module 2 turn into runbook steps here.

Before a deploy, list the in-flight runs by version. DBOS gives that query directly, shown below, with the guidance “When deploying a new version of your code, launch new processes running your new code version, but retain some processes running your old code version” while they drain. Temporal’s Worker Versioning, GA since May 2026, keeps in-flight workflows on their original version so a rollout can proceed while old Workers finish. Vercel pins runs to deployments and warns about the consequence on rollback: “a rollback does not stop runs on the deployment you rolled back from. A run whose steps keep failing on that deployment is retried there… until the run fails or is cancelled.” And the sentence that should be in every runbook: “runs pinned to a deleted deployment never complete or fail on their own, so cancel them as well.”

# DBOS: which runs are still on the old version? From the vendor docs
active = DBOS.list_workflows(
    app_version="1.0.0",
    status=["ENQUEUED", "DELAYED", "PENDING"],
)
if not active:
    print("Safe to retire version 1.0.0")

Inngest’s migration guidance is the most hands-on, because its step-ID model means old runs resume on new code: test both paths in the Dev Server by starting a run, pausing it with step.sleep(), changing the code, resuming, and checking “which saved results it reuses and which steps it runs.” For a rewrite, deploy a second function with mutually exclusive triggers and a cutover timestamp, and remove the original “Once those runs finish.” Restate’s deployment binding means the same thing operationally: keep the old deployment registered until its invocations complete.

For agents specifically, treat a prompt or tool-list change as a version bump and run the drain procedure. A pinned run finishing on an old prompt is acceptable; a replaying run diverging on a new prompt is an incident.

Testing with time-skipping and replay

Two test types matter for durable agents and both are cheap once the engine is in place.

Time-skipping tests the waits. Temporal’s Python test suite starts a time-skipping server with start_time_skipping() where “Timers, which include sleeps and conditional timeouts, are fast-forwarded except when Activities are running,” and env.sleep(seconds) advances the clock manually. A 48-hour approval timeout and its 24-hour escalation run in milliseconds, with mocked Activities that “should have the same signature as the real implementation.” Inngest’s equivalent is the Dev Server paused-run procedure above.

# Temporal Python: time-skipping and replay tests, from the vendor docs
async with await WorkflowEnvironment.start_time_skipping() as env:
    await env.sleep(3)  # advance time by 3 seconds

replayer = Replayer(workflows=[YourWorkflow])
await replayer.replay_workflow(WorkflowHistory.from_json(history_json_str))

Replay tests test the determinism contract. Export histories of real runs, including runs with model calls, and replay them against the current orchestration code in CI. If the orchestration asks for a different command than the history recorded, the test fails before production does. Temporal’s versioning page says to “incorporate Replay Testing” to check whether a patch is needed; the same histories double as golden tests for the agent’s control flow, because the recorded model outputs are fixtures that never change. This is the cheapest regression suite an agent team will ever own, and it is free once every model call is a step.

Local development is a solved problem on every engine. Vercel: npx workflow web opens “a local dashboard showing all workflow runs and their status, as well as a trace viewer,” with a bundled local backend that needs no setup. Inngest: npx inngest-cli dev runs the Dev Server. Temporal: temporal server start-dev. Restate: the server “runs as a single binary with zero dependencies” locally. DBOS: a local Postgres. The agent-specific addition is to record model step outputs in development and replay them in tests, so the suite never calls a model.

My position

Replay is the debugger and the run ID is the correlation key. Every orchestration change ships with a replay test against histories from production, and every model span carries the run and step that made it. The strongest objection is that histories contain customer data and prompts, which makes a CI fixture a data-handling problem. True, and the answer is the pattern DBOS calls metadata-only and Vercel enforces with viewer permissions: redact payloads in exported histories, keep the step structure, and replay against that. A determinism test needs the shape of the history, not its contents.

Think about it: it is 3 a.m. and a dashboard shows 40 agent runs older than six hours on a workflow whose longest legitimate wait is one hour. Write the first five runbook steps before opening.

One: pull the histories of three of the forty and look at the last recorded event; a step with a high attempt count is a poison input, a registered wait with no deadline is a lost signal, a DEPLOYMENT_MISMATCH or version error is a stranded deploy, and a budget pause is a cap. Two: if it is a poison input, stop the bleeding by cancelling the retry loop for those runs (Restate cancel runs compensations, kill does not; Vercel and Temporal cancel from the UI or CLI) and record the input for a terminal-error fix. Three: if it is a lost signal, check whether the approval event was sent before the wait registered (Inngest’s documented race) or to a hook token another run owned (Vercel’s hook_conflict), then resend the signal with the correct correlation. Four: if it is a stranded deploy, find which deployment or version the runs are pinned to, and either restore it long enough to drain or cancel and rerun on latest with the same inputs. Five: before touching anything irreversible, check which side effects each run has already recorded, so that a rerun starts after the last external write, not before it. Then fix the alert that let forty runs age six hours before anyone looked.

Takeaways
  • The run history is the primary trace. Every vendor’s debugging surface, from Vercel’s trace viewer to DBOS’s fork, is built on it.
  • Join model traces to histories on run identity in both directions, and persist the link in the step result.
  • Durable runs do not crash; they get stuck. Alert on age, per-step retries, compensation failures and paused runs past deadline.
  • Deploys are drains. List in-flight runs by version first, keep old versions alive until they finish, and cancel runs pinned to deleted deployments.
  • Time-skipping tests the waits; replay tests the determinism contract. Recorded model outputs make the suite free and model-free.
8

Decision clinic and reference architecture

Three scenarios decided, the positions to defend with their strongest objections answered, a layered reference architecture, and a 30-day adoption plan
Learning objectives
  • Choose a substrate for a coding agent, a weekly research agent and a 30-second-SLA support agent, and say why
  • List what to wrap in steps and what to keep out for each scenario
  • Defend seven positions against their strongest objections
  • Draw the reference architecture as layers with a named owner for each
  • Execute a 30-day adoption plan that ends with replay tests and a runbook

Scenario A: a coding agent that opens pull requests

The shape: a user asks for a change; the agent reads the repository, edits, runs tests in a sandbox, iterates, and opens a pull request. Inngest’s tool-loop guide sizes it: “a coding agent that reads, edits, and tests might need 15–20” iterations. Runs last minutes to an hour. The irreversible actions are push, pull-request creation and merge.

Substrate. If the application is on Vercel, Vercel Workflows with Sandbox: Vercel’s own guide builds exactly this loop, with generateCode, generateTests and executeInSandbox as steps, the generated code running “in an ephemeral Firecracker microVM with its own filesystem and network,” and a FatalError for non-retryable failures. If the organization is polyglot or already runs Temporal, Temporal with its OpenAI Agents SDK sandbox integration, where “LLM model API calls, the full sandbox lifecycle, running commands — execute as Temporal activities” and a Worker crash mid-turn means “Temporal replays the workflow and resumes from the last completed state.” Either way the loop is short enough that history limits are not a concern; a 20-iteration run is well under any cap.

Steps. Every model call; every sandbox command batch, because a test run is slow, flaky and worth retrying; repository reads, because they are I/O; pull-request creation, with the idempotency scheme from module 3’s exercise. Not steps. Prompt assembly and output parsing. Vercel’s guide keeps its stripCodeFences helper and the loop control out of steps, and that is right: they are deterministic functions of recorded results. Gate. Merge, always; push to a shared branch, usually. Compensation. Close the pull request, registered before creation. Budget. An iteration cap in the loop and a token counter in state. The coding agents course covers the agent itself; this is the substrate underneath it.

Scenario B: a weekly research agent with a human approval

The shape: every Monday, gather sources, synthesize a report, send it for review, wait up to 48 hours, publish or discard. Runs are mostly waiting. The irreversible action is publishing.

Substrate. This is the case the scheduled agents course solves with a cron plus a bookmark, right up to the approval. The approval is the forcing function from module 1, so pick one of three. A Claude Managed Agents scheduled deployment with a per-run budget, if you want the harness hosted and the approval handled through its tool-confirmation events; the deployment records every attempt, and the budget copies onto each session. Inngest, if the team is TypeScript and event-driven: a cron trigger, step.run for fetch and synthesis, step.waitForEvent with a 48-hour timeout, a second wait for escalation, and a run length well inside the plan limits. DBOS, if the team is on Postgres: a scheduled workflow that runs “exactly-once per time interval,” DBOS.recv with a timeout for the approval, and set_event to publish status to a review page.

Steps. Each source fetch, with large documents offloaded by reference so step payloads stay under the engine’s limit; each model call; the send-for-review notification with a key; the publish with a key. Not steps. Formatting and templating. Gate. Publish, with reject as the timeout default. Budget. Per run, on the schedule.

Scenario C: a customer-support agent with a 30-second SLA

The shape: a conversation, many short turns, tools that look up orders and occasionally change them, an answer expected within seconds. The irreversible actions are refunds, cancellations and outbound messages.

Substrate. Not a queue-hopping engine on the hot path. A step boundary is a round trip, and DBOS’s comparison page, which is a vendor’s claim about a competitor, puts a Temporal step dispatch at “tens to hundreds of ms” against its own “1-2ms” Postgres write; whatever the true numbers are in your environment, measure them before putting ten steps between a question and its answer. The conversation itself belongs to the durability-lite tier: a managed session, an Agent SDK session, or a LangGraph thread with a PostgresSaver in sync mode if you can afford the write per super-step. Make every read tool idempotent and every write tool keyed, and the conversation survives a restart at the cost of one lost turn.

Then spawn durable work for the long tail. A refund is not a 30-second operation; it is a saga with a gate. Start it as a workflow from the chat turn and return a reference, the way DBOS’s checkout example starts a workflow under an idempotency key and returns a payment ID through get_event. Restate’s Virtual Objects are the natural fit if you want per-customer durable state with single-writer semantics on every turn: “Stateful agents, actors, and digital twins: e.g. AI chat sessions” is the vendor’s own example, and its invocations are pushed over streaming connections rather than polled. Gate. Refund and outbound message. Budget. Per conversation. The managed agents course and the realtime course cover the conversational layer.

Positions to defend

PositionStrongest objectionAnswer
Every model call is a stepStep overhead and payload limits: 2 MB on Temporal Cloud, 4 MiB per step on Inngest, 50 MB on VercelShrink payloads, not step counts. Claim-check large context by reference; Temporal’s External Payload Storage exists for this. A crash under the alternative re-bills every call since the start and risks divergence.
Do not put the agent loop in a serverless function without an engineFunctions now run 800 seconds, or 1,800 in beta; most agent runs fitDuration is not the problem; deploys and waits are. A function dies on deploy with no history, and it cannot wait for a human. Vercel’s own limits page routes “unlimited execution time” to Workflows.
A cron plus a bookmark is enough until you need waitsEngines are cheap and the demo is impressive; start thereThe determinism contract is a tax on every engineer forever. Pay it when the wait, the fan-out or the compensation appears.
Gates live in the substrate, not the promptA durable wait on every tool call wrecks latencyGate only irreversible tools. Human decision time dwarfs engine latency on exactly those tools.
Register compensations before the forward callVercel’s docs say to enqueue a compensation only after the forward step succeedsThe lost-response case: the action landed, the response did not. Temporal’s guide pre-registers and makes the compensation an idempotent no-op; that covers both outcomes.
A hard spending cap sits below the modelCaps truncate legitimate long tasksPause, do not kill, and provide a resume-with-raised-cap path. A human deciding whether to spend more is the right state for an overrun.
Choose the engine nearest your codeFeature depth differs, and Temporal’s is deepestAn engine the team can operate beats a deeper one it cannot, and the vendors converged on the agent features in 2026. Orchestration fees never decide; they are a few percent of tokens at most.

The reference architecture

Six layers, each with a named owner and a question that decides whether it is done.

LayerWhat lives hereDone when
1. HarnessThe agent’s control flow: chains, routing, orchestrator-worker, evaluator-optimizer, from the dynamic workflows course; iteration caps; stuck-loop detectionThe loop is a deterministic function of recorded step results and nothing else
2. SubstrateThe engine (Temporal, Vercel Workflows, Inngest, Restate, DBOS) or the durability-lite session, chosen by where the code runsA kill of the process mid-run resumes at the step in flight, demonstrated in staging
3. StepsModel calls, tool calls, compensations; each with a stable ID, a payload under the engine’s limit, and for side effects a key derived from step identityNo step contains both a model call and a side effect; every side-effecting step records the external ID first
4. GatesDurable waits on recorded tool choices for every irreversible action, with deadline, default and escalationEvery gate has three handled outcomes and the irreversible default is reject
5. ObservabilityRun ID on every model span, trace link in every model step result, alerts on age, retries, compensation failures and paused runs; replay tests in CIA stuck run can be diagnosed from its history alone within five minutes
6. BudgetsA hard cap per run or session below the model, a per-schedule cap, a resume pathThe cap has been hit in staging on purpose and the resume path exercised

Security sits beside the stack rather than in it: sandboxes, egress control and credentials from the securing agents course apply to the step runtime, and the gate layer is where the two courses meet. The harness shape comes from the opinionated harness course; the substrate’s job is to make whatever shape you chose survive.

A 30-day adoption plan

Days 1 to 7: inventory and bookmark. List every agent in production with its longest run, its longest wait, its irreversible tools and its hosting. Any agent with no wait longer than its process lifetime gets a bookmark, a post-then-record rule and a per-run budget, and leaves the engine conversation. Pick one agent with a real wait as the pilot. Choose the engine nearest its code by the module 5 rule.

Days 8 to 14: the pilot, every call a step. Move the pilot’s loop into the engine with every model call and tool call as a step, stable step IDs, and payloads under the limit by reference. Kill the process mid-run in staging and watch it resume. Write the first replay test from that staging history.

Days 15 to 21: keys, gates and compensations. Give every side-effecting tool a key derived from step identity. Put a durable wait with a deadline and a reject default in front of every irreversible tool. Register a compensation before each undoable action and make it an idempotent no-op. Test the timeout and escalation paths with time-skipping.

Days 22 to 30: operations. Add the run ID to every model span and the trace link to every model step result. Stand up the four alerts. Write the deploy-and-drain runbook for your engine’s versioning model and rehearse a rollback with runs in flight. Set the hard budget, hit it on purpose, and exercise the resume path. Put the replay suite in CI. Then, and only then, migrate the second agent.

My position

Durable execution is the correct substrate for any agent that waits, and the wrong first project for a team that has never written replay-safe code. Start with one agent, one engine, one wait, and insist on the two tests that make the contract real: a kill mid-run that resumes, and a replay against a production history that passes. The strongest objection is that this is slow, and that a platform team could wrap the engine so product teams never see the determinism contract. Wrappers help and the vendors’ agent SDKs are good ones; but someone on the team must understand why a model call cannot live in orchestration code, because the day the wrapper leaks, that person is the one who reads the history and finds the divergence.

Self-assessment: answer yes or no to ten questions about one agent you run today, then open to score.

1. Can you name its longest wait and whether that exceeds its process lifetime? 2. Is every model call a recorded step? 3. Is every tool call a recorded step, separate from the model call that chose it? 4. Does every side-effecting tool send a key derived from step identity? 5. Does every irreversible tool sit behind a durable wait with a deadline and a reject default? 6. Is every undoable action paired with a compensation registered before the call? 7. Have you killed the process mid-run in staging and watched it resume? 8. Does a replay test run in CI against a production history? 9. Can you find the model trace for any step from the run history, and the run from any trace? 10. Is there a hard spending cap below the model with a resume path?

Scoring. Ten yes answers: you are done, migrate the next agent. Seven to nine: you have a durable agent with a gap; the missing items are usually 6, 8 and 9, in that order. Four to six: you have a retrying agent; module 2 and 3 are the work. Three or fewer: you have a script, and the first question to answer is whether it needs a wait at all, because if it does not, a bookmark and a budget will get you to six yes answers in a week.

Takeaways
  • Coding agents: short loops, sandbox commands as steps, gate the merge, compensate by closing the PR.
  • Research agents: a cron plus bookmark until the approval, then the engine or managed deployment nearest your code, with publish gated and reject on timeout.
  • Support agents: keep the conversation on a lite session with keyed tools, and spawn durable sagas for the long tail; measure step latency before putting steps on the hot path.
  • Seven positions, each with its objection answered. The ones that get challenged in review are compensations-first and gates-in-substrate; have the lost-response case and the injection argument ready.
  • Thirty days: inventory, pilot with every call a step, keys and gates and compensations, then operations. Two tests make it real: the mid-run kill and the replay.