Naive agent loops die on minute 29 of a 30-minute job. The model picks the right tool, the pod gets rescheduled, the deploy hits halfway through — and the user gets nothing, because nothing was written down. Durable execution is the runtime that makes any orchestration framework actually survive production: every step journaled, the next process picks up exactly where the previous one died. As of late June 2026, four engines compete on that primitive with architecturally opposite bets — Temporal ships code-as-workflow with a history event journal, Inngest sells DX-first event-driven step functions, Restate bets on virtual objects with per-object journals, and Cloudflare Workflows stitches durable execution into the Workers edge runtime.
At a glance
Four engines, four answers to the same question: how do you keep a long-running process resumable when the underlying machine is allowed to vanish at any moment.
| Engine | Approach | Self-hosted? | Pricing shape |
|---|---|---|---|
| Temporal | Workflow-as-code + history event journal | Yes (MIT) or Temporal Cloud | Per-action on Cloud; infra cost self-hosted |
| Inngest | Event-driven step functions, dev-server-first | Yes (self-hosted server) or Inngest Cloud | Per-step on Cloud, with a generous free tier |
| Restate | Virtual objects + per-object journal, exactly-once | Yes (single Rust binary) or Restate Cloud | Per-invocation on Cloud; single-binary self-host |
| Cloudflare Workflows | V8-isolate engine inside the Workers runtime |
No — managed only on Cloudflare | Bundled with Workers: CPU-ms + requests + storage |
Snapshot: 2026-06-23. These engines change fast; verify against current docs before standardizing on any one.
Workers runtime.Temporal
Workflow-as-code
Temporal's central abstraction is the workflow: a regular function in Go, Java, Python, TypeScript, .NET, Ruby, or PHP that calls other functions ("activities") to do work. You write what looks like ordinary procedural code — await activity.charge_card(), await workflow.sleep(timedelta(hours=24)) — and the runtime makes that code crash-proof. The workflow function must be deterministic: same inputs, same code, same outputs. That constraint is what lets Temporal re-execute the function from the top on a new worker and arrive at the same state. Activities are the side-effecting pieces — HTTP calls, model invocations, database writes — with at-least-once retry semantics of their own.
This is "durable execution at maximum control": you author the control flow yourself in a language you already use, and Temporal supplies the machinery for retries, timeouts, cancellation, and resume. No DSL, no YAML. The trade is the discipline — non-deterministic constructs (random, system time, raw network) have to go through Temporal's APIs so they land in history, and that learning curve is real.
History event journal
Every step is recorded in a history — a per-workflow-execution append-only log held in Temporal's database (Cassandra, PostgreSQL, or MySQL). The history contains everything needed to reconstruct state: which activities ran, what arguments they got, what they returned, when timers fired, which signals arrived. When a worker crashes mid-workflow, a new one replays the history to the exact line that was executing — then resumes.
Workflow code and durable state are physically separated. Workers are stateless; the Temporal service is the source of truth. You can run thousands of workers, kill any of them, and lose nothing. The cost of that separation is operational: the service wants Cassandra or Postgres, a history shard count chosen up front, and a worker pool sized to your concurrency. Temporal Cloud takes that off your hands at per-action pricing; self-hosted, it is the heaviest of the four to operate.
Durable timers, queries, signals
Three primitives ride on top of the history journal. Durable timers are workflow.sleep() calls that survive process death — a workflow that sleeps for 30 days does not hold a worker; it parks in history and a worker wakes it up when the timer fires. Signals let external systems push events into a running workflow (approval clicked, webhook arrived) without polling. Queries let external systems read current state without disturbing the run. Together these are why long-running agent jobs — a research agent that runs for an hour, an approval flow that waits days for a human — feel native here rather than bolted on.
Inngest
Function-as-workflow with step-level retries
Inngest's abstraction is the function: a TypeScript, Python, Go, or Kotlin handler triggered by an event, a schedule, or another function. Inside, the unit of durability is the step — you wrap side effects in step.run("name", async () => ...), step.sleep("wait-24h", "24h"), or step.waitForEvent("approval", ...), and the runtime journals each step's result. If the function crashes between steps, the next invocation replays the function from the top, but each completed step.run short-circuits and returns its cached result. Same replay-to-resume model as Temporal; smaller surface area.
That smaller surface is the point. Where Temporal asks you to learn workflow-vs-activity, history shards, task queues, and worker pools, Inngest mostly asks you to remember to wrap side effects in step.run. Retries are per-step, flow control (rate limits, per-tenant concurrency, throttling) is a few lines on the function, and there are no idempotency keys to pass around — the step name is the idempotency key. The trade is less control over the orchestration shape.
Dev-server-first DX
npx inngest-cli@latest dev spins up a local server with a dashboard at localhost:8288 that shows every event, every function run, every step in every run, plus an in-browser replay button. Trigger a function, watch each step execute live, see exactly which step failed and what it returned, re-run a single failed step without re-running the whole function. No separate APM to wire up — the trace is the dashboard, and Inngest Cloud is the same UI pointed at production data.
That tight inner loop is genuinely uncommon in this space. The cost: Inngest's mental model is built around events. If your application is not event-driven — a long-running synchronous request-response service, say — you end up faking it.
Agent kit integration
Inngest ships an Agent Kit: a thin TypeScript layer that wraps step-based durability around agent loops. Each model call is a step.run, each tool invocation is a step.run, and the trace shows the whole agent's tool-call history in the same dashboard as ordinary functions. The point is not that you couldn't build this on bare Inngest; it is that the framework opinion exists, making "wrap LangGraph in durable execution" a few lines rather than a weekend. For teams on LangGraph, CrewAI, or the OpenAI Agents SDK that want production durability without standing up Temporal, Agent Kit is the lowest-friction path.
Restate
Virtual objects + RPC
Restate's abstraction is the virtual object: a single-writer stateful entity keyed by an identifier (a user ID, an order ID, an agent session ID). Each instance owns its own consistent key/value state and serializes its own handler invocations — two concurrent calls to cart-42.add_item() queue rather than race. You invoke handlers over HTTP RPC, and the runtime keeps the object's state and journal locally so handler invocations are exactly-once with respect to the journal, not at-least-once with retries on top. The mental shift from Temporal: durability is not centralized in a workflow execution; it is distributed across object instances, each with its own focused journal.
That shape maps unusually well onto agent workloads where the natural unit of state is "this session" or "this user's thread" rather than "this 30-step pipeline." Each agent session is its own virtual object; its memory, tool-call log, and pending interrupts live in that object's state; multiple agents do not contend for a single workflow history.
Journal exactly-once
The claim that distinguishes Restate is exactly-once handler execution. Temporal and Inngest promise at-least-once activity / step execution and ask you to make those operations idempotent. Restate's runtime journals each handler's effect before acknowledging it externally — a handler that increments a counter increments it exactly once even if the network drops the response, because the next attempt sees the journaled outcome and returns it without re-running.
The honest caveat: exactly-once applies to effects the runtime can journal — calls back into Restate, state writes inside an object, outbound calls Restate brokers. Side effects to systems Restate does not mediate (a third-party API with no idempotency token) still need the usual care. Restate just shrinks the surface where you have to think about it.
No idempotency keys required
For ordinary cases inside Restate's world — object-to-object calls, object-to-workflow calls, scheduled invocations — you do not pass idempotency keys around in user code. The journal entry's identity is implicit in the call graph, and the runtime handles deduplication. That is a meaningful ergonomic difference from Temporal, where activity authors carry the idempotent-semantics burden, and from Inngest, where the step.run name is the key you have to choose. Restate's bet is that the runtime can carry that burden without leaking it back to the developer — and for code that lives entirely inside Restate's world, it largely does.
Cloudflare Workflows
V8-isolate runtime
Cloudflare Workflows runs inside the same V8 isolate runtime as Workers — no containers, no per-invocation Linux process boot, just a JavaScript isolate started in a few milliseconds at the edge. A workflow is a TypeScript class with a run(event, step) method; inside it you call step.do("name", async () => ...), step.sleep("name", "24 hours"), or step.waitForEvent("name", { ... }) — the same step-as-durability-unit shape Inngest popularized, executed in Cloudflare's isolate runtime instead of a Node or Python worker process.
The architectural payoff is cold-start: a workflow that sleeps for a week resumes in milliseconds, not seconds, because the isolate model does not need to boot a container. The ceiling is the same isolate constraints any Workers app already lives with — CPU-time budgets per invocation, no arbitrary native dependencies, no long-lived TCP sockets within a single step.
Integrated with Workers / Queues / D1
Workflows is not a standalone product; it is one primitive in a stack. A workflow's steps can call other Workers, enqueue messages into Queues, read and write D1 (Cloudflare's serverless SQLite), put objects in R2, and invoke Workers AI — all inside the same control plane, with shared auth and observability. For an agent that does retrieval against Vectorize, calls a tool that hits D1, and writes results to R2, the round trip is one bill, one dashboard, one deploy. The cost of that integration is its inverse: there is no self-hosted Workflows server, no on-prem option, no "move my workflow logic to AWS next year."
GA in 2025
Cloudflare Workflows reached general availability on April 7, 2025, after roughly a year in open beta, with a waitForEvent primitive for human-in-the-loop pauses, scaled concurrency, and Cloudflare Agents SDK integration landing in the same release. Pricing folds into the Workers paid plan: CPU-milliseconds, requests, and (starting September 15, 2025) workflow state storage — and crucially, sleep time and time spent waiting for events do not bill CPU, which matters for any workflow that genuinely waits days for an external signal.
Cross-cutting comparison
Programming model
The four sit on a spectrum from "code I already write" to "platform-shaped functions." Temporal is the most code-native: workflows are ordinary functions in seven SDK languages, control flow is plain if / for / await, and the discipline is determinism rather than API surface. Inngest and Cloudflare Workflows both adopt the step-function shape — a handler wrapped around step.run / step.do calls the runtime journals individually — Inngest leaning event-driven, Cloudflare leaning into the Workers runtime. Restate is the outlier: virtual objects are not a workflow shape but a stateful-entity shape, closer to the actor model than step functions, where the unit of durability is the object instance rather than the workflow execution.
Determinism story
All four replay to resume, but they ask for different determinism contracts. Temporal asks the most: the workflow function must be deterministic top to bottom, and non-deterministic operations (time, random, IO) have to go through Temporal APIs so they get journaled — a workflow that calls Date.now() directly will break on replay. Inngest and Cloudflare Workflows are more forgiving because only the code inside step.run / step.do wrappers gets re-executed; the rest of the function is glue. Restate's exactly-once handler journal sidesteps the question for ordinary cases — the handler runs once, the result is cached, and replay returns the cached value. In practice: Temporal codebases grow lint rules to catch non-determinism; Inngest and Cloudflare ones just need developers to remember to wrap side effects; Restate codebases barely think about it for journaled effects.
Pricing shape
Four engines, four billing units. Temporal Cloud charges per-action (workflow start, activity execution, signal, query) plus storage and active-execution time; the open-source server is free but you pay for the Cassandra / Postgres cluster, the worker fleet, and the on-call. Inngest Cloud bills per-step with a generous free tier; self-hosted, you pay for the server's compute. Restate Cloud bills per-invocation; the self-hosted single Rust binary has the lightest operational footprint of the four — no external database. Cloudflare Workflows folds into the standard Workers paid plan: CPU-milliseconds, requests, and storage, with sleep and wait-for-event time excluded from CPU billing. The non-obvious properties: a Cloudflare workflow that mostly sleeps costs almost nothing, and a Temporal workflow with many small activities can be surprisingly expensive at scale.
Operational footprint
Self-hosting cost varies by more than an order of magnitude. Temporal self-hosted is heaviest: a multi-component service that wants a real database, a chosen shard count, a worker pool, and meaningful operator attention — battle-tested at OpenAI, Salesforce, and DoorDash, but not a weekend project. Inngest self-hosted is moderate: one server binary plus your application workers. Restate self-hosted is the lightest of the three self-hosting options: a single Rust binary that bundles journal storage. Cloudflare Workflows has no self-hosted option — you pay Cloudflare to operate it, full stop. For regulated data residency or air-gapped environments, Cloudflare is off the table and Restate's single-binary footprint is probably the lightest defensible choice.
When to pick which
| Use case | Pick Temporal if… | Pick Inngest if… | Pick Restate if… | Pick CF Workflows if… |
|---|---|---|---|---|
| Long-running LLM workflows | You want maximum control and multi-language SDKs, with durable timers, signals, and queries as first-class primitives. | You want step-level retries and a dashboard that traces every model and tool call without a separate APM. | Each LLM session is one virtual object and you want exactly-once handler semantics, not at-least-once-with-idempotency. | You already run on Cloudflare and want the workflow next to Workers AI, D1, and Vectorize. |
| Agent tool-call retries | Activities are the natural fit, with per-activity retry policies separated from the workflow's deterministic shell. | Each tool call wraps in step.run; Agent Kit ships the framework for TypeScript. |
Each tool invocation is a handler on the agent's virtual object; the journal handles dedup without an idempotency key. | step.do wraps each tool call; millisecond cold-start means short retries do not pay a container-boot tax. |
| Event-driven SaaS | Workable, but workflow-as-code is heavier than this case rewards. | Native fit — events trigger functions, functions sleep until the next event, the dashboard shows the whole funnel. | Workable if user-keyed virtual objects map to your domain (one onboarding object per user). | Native fit — events from Queues or webhooks trigger workflows; waitForEvent handles the pauses. |
| Edge-deployed agents | Self-hosted multi-region is possible but operationally serious; not the natural fit. | Self-host close to your users, but the platform is not built around edge primitives. | The single-binary model can be deployed at the edge; multi-region coordination is your problem. | Native fit — runs at whichever Cloudflare PoP serves the request, no extra config. |
| Regulated data residency | Self-host on the database and region you choose; Cloud also offers region-pinned deployments. | Self-hosted server in your region; Cloud offers region-pinned plans. | Single-binary self-hosted is the lightest footprint to keep entirely in your environment. | Off the table — workflow state lives on Cloudflare's infrastructure. |
| Polyglot stack | Strongest fit — SDKs for Go, Java, Python, TypeScript, .NET, Ruby, and PHP share one workflow service. | SDKs for TypeScript, Python, Go, and Kotlin/Java; smaller language surface than Temporal. | SDKs for TypeScript, Java/Kotlin, Python, Go, and Rust; viable in most polyglot shops. | TypeScript / JavaScript only inside the Workers runtime; off the table for Java / Python / Go services. |
FAQ
Is durable execution different from a job queue?
Yes, meaningfully. A job queue (Sidekiq, BullMQ, SQS-plus-workers) gives you at-least-once execution of a single job and leaves multi-step coordination, retry policies, and resume-after-crash to you. Durable execution adds the ability to write a multi-step process as a single function — with sleeps, waits-for-events, parallel branches, conditional logic — and have the runtime guarantee it survives crashes by journaling each step. You can build durable execution on a queue (people did, for years), but the engines here ship it as the primitive rather than something you assemble.
Do I need this if I use LangGraph checkpoints?
Sometimes. LangGraph's checkpointer gives state durability for the agent graph itself — crash mid-graph, resume from the last node. That covers single-process resume. It does not give you durable timers (sleep 24 hours and survive), durable wait-for-event (pause until a webhook arrives), exactly-once side effects across retries, or multi-region resume. If your agent runs end-to-end in a single process in under a minute, LangGraph checkpoints are probably enough. If it sleeps, waits for humans, fans out across services, or has to survive deploys, you want a durable execution engine underneath.
What about AWS Step Functions / Durable Functions?
AWS Step Functions has been GA since 2016, with a JSON state-machine DSL (ASL) and tight AWS-service integration. Azure Durable Functions has been GA since 2017 with workflow-as-code via async/await. Both are credible if you are already deep on AWS or Azure. We left them out of the head-to-head because they are the safe-default answers in each hyperscaler's ecosystem; the four in this post are where the design space is actively moving in 2026.
Can I run all four self-hosted?
Three of the four. Temporal ships an MIT-licensed server (you supply Cassandra, PostgreSQL, or MySQL). Inngest ships an open-source self-hosted server. Restate ships as a single Rust binary. Cloudflare Workflows has no self-hosted edition — it runs only on Cloudflare's platform, by design.
What's the cold-start cost?
Cloudflare Workflows wins this axis decisively: V8 isolates start in single-digit milliseconds, and a sleeping workflow resumes essentially without warm-up. Inngest and Temporal run worker processes that are usually long-lived, but a newly scheduled worker pays a container-or-process-boot cost in the hundreds of milliseconds to seconds. Restate's runtime is one Rust binary that is usually warm; reloading an object instance from the journal is fast for ordinary state sizes. For a workflow that mostly sleeps and occasionally wakes, Cloudflare's model is the cheapest in both wall-clock latency and billable time.
Which one survives a region outage?
All four claim multi-region durability; operator burden varies. Temporal Cloud offers multi-region replication; self-hosted Temporal can do it but you operate the Cassandra / Postgres replication yourself. Inngest Cloud and Restate Cloud run multi-region by default on paid plans; the self-hosted equivalents are your responsibility. Cloudflare Workflows runs on the global network — state is replicated across PoPs and a region failure is largely invisible to your code, the strongest position on this axis and also the one you can do least to influence.
Further reading
On this wiki:
- LangGraph vs CrewAI vs Claude Managed Agents vs OpenAI Agents SDK — the orchestration-frameworks comparison that pairs with this one. Orchestration frameworks decide what the agent does next; durable execution engines keep that decision alive across process death. The two layers compose:
LangGraphnodes call Temporal activities, Inngest steps wrap CrewAI tool calls, and the OpenAI Agents SDK's runner loop can sit inside a Cloudflare Workflow. - The Agent Loop — the perceive-decide-act cycle every agent runs. Durable execution is what keeps that loop resumable across the failure modes a naive
while Trueignores. - Planning and Termination — how an agent decides what to do next and how to stop. Durable timers and wait-for-event primitives are what let the planner pause cleanly without holding a worker.
Project sources:
- Temporal — docs at docs.temporal.io, source at github.com/temporalio/temporal.
- Inngest — docs at inngest.com/docs, source at github.com/inngest/inngest.
- Restate — docs at docs.restate.dev, source at github.com/restatedev/restate.
- Cloudflare Workflows — developer docs; GA announcement on the Cloudflare blog (April 7, 2025).