AI Blog

Temporal vs Restate vs Inngest vs DBOS: where the agent’s transcript lives

All four resume a crashed run from its last completed step, so that is not the decision. An agent’s durable record is a transcript that grows with every turn, not a handful of small step results — and the engines differ on where that growth is stored, what ceiling it hits, and whether your model call is allowed to sit in replayed code. Temporal terminates a workflow at 51,201 events or 50 MB of history; Inngest caps a step output at 4 MB and run state at 32 MB; Restate and DBOS push the growth into storage you operate. Decide on that, then on billing shape, and the feature tables stop mattering.

By Agentic AI Wiki 14 min read

All four of these resume a crashed run from its last completed step, which means the feature everyone shops for is the one that cannot decide anything. What separates them for an agent is that an agent's durable record is not a handful of small step results — it is a transcript that grows on every turn, and each engine has a different answer to who absorbs that growth. Temporal terminates a workflow at 51,201 events or 50 MB of history. Inngest caps a step's output at 4 MB and a run's state at 32 MB. Restate and DBOS push the growth into storage you operate. Choose on that first; the feature tables agree with each other anyway.

At a glance

Four projects, four genuinely different shapes — and the shape, not the feature list, is what you are buying.

ProjectShapeDurable record lives inLicence
TemporalServer cluster plus worker processes you runEvent history in the Temporal serviceMIT; Temporal Cloud is the managed option
RestateA single Rust binary with a distributed logJournal plus an embedded key-value storeSDKs MIT, runtime BSL
InngestManaged platform that calls your functions over HTTPStep state held by the platformCommercial platform, generous free tier
DBOSA library inside your own processCheckpoint rows in your existing PostgresOpen-source library (DBOS Transact)
Where each engine keeps the durable record of a run Four columns. Temporal keeps event history in a server cluster backed by Cassandra or a SQL store, operated by you or by Temporal Cloud. Restate keeps a journal and embedded key-value state in a single Rust binary using a distributed log. Inngest keeps step state in its managed platform and calls your functions back over HTTP. DBOS keeps checkpoints in the Postgres database your application already uses, as a library running inside your own process. Where the durable record of a run is kept Temporal Restate Inngest DBOS THE RECORD Event history Every command and result, append-only Journal + KV state Durable log, plus state on virtual objects Step state Each step’s output, held by the platform Postgres rows Checkpoints in the DB your app already has WHAT YOU RUN Server cluster Self-hosted (MIT) or Temporal Cloud One Rust binary Distributed log; MIT SDKs, BSL runtime Nothing Managed platform calls your functions over HTTP Nothing new A library inside your own process WHO ABSORBS THE GROWTH The engine Hard ceiling; the run is terminated Your storage Payloads compressed near the size limit The platform Per-step and per-run size limits apply Your Postgres Your table growth, your vacuum A long agent run grows the record on every turn; the column you pick decides who notices first.
A long agent run grows the record on every turn; the column you pick decides who notices first.

Restate is written in Rust, ships as one binary, and records events in a distributed log built on a virtual-consensus design derived from Flexible Paxos, with SDKs for TypeScript, Python, Java/Kotlin, Go and Rust. Temporal came out of the team that built Cadence at Uber and is the oldest and most operationally proven of the four. Inngest is the only one where the correct answer to "what do we deploy" is nothing, and it is the only one shipping a first-party agent framework — AgentKit, with agents composed into networks, a router choosing who runs next, shared network state, and MCP servers as tools. DBOS is the only one that adds no infrastructure at all: decorate a function, and its checkpoints land in the Postgres you already operate.

The ceiling nobody plans for

Published per-run ceilings on durable state A horizontal bar chart of the published ceiling on durable state per run. Temporal terminates a workflow whose event history passes 50 megabytes or 51,201 events. Inngest caps function run state at 32 megabytes. Restate publishes no fixed per-run ceiling and compresses payloads as they approach the limit. DBOS publishes no ceiling because checkpoints are rows in the Postgres database you operate. The two open-ended bars are drawn with a dashed edge to show they are bounded by storage rather than by the engine. Published ceiling on durable state, per run 0 16 MB 32 MB 48 MB 64 MB Temporal 50 MB history, or 51,201 events — run terminated Inngest 32 MB function run state; 4 MB per step output Restate no fixed published ceiling; payloads compressed near the limit DBOS no ceiling; checkpoints are rows in your own Postgres Solid bars are enforced by the engine. Dashed bars run to the edge of your storage instead.
Solid bars are enforced by the engine. Dashed bars run to the edge of your storage instead.

In a conventional workflow — charge a card, send an email, update a row — durable state is a few kilobytes and nobody thinks about it again. An agent inverts that. Every turn appends a model response, and every tool call appends a result, and those results are the fat ones: a page of scraped HTML, a query returning four hundred rows, a file the agent read. If you journal them, the durable record grows roughly with the transcript, which grows roughly with the square of the step count once you account for re-sending.

The published numbers are worth memorising because they arrive as production incidents rather than as warnings:

  • Temporal. A single payload warns at 256 KB and errors at 2 MB. Event history warns at 10,240 events or 10 MB, and the service terminates the workflow at the 51,201st event or 50 MB with a non-retryable error. There is also a 4 MB cap on a single Workflow Task transaction, which a workflow can hit by scheduling many modestly sized activities at once even though no individual payload was large.
  • Inngest. Data returned by a step is capped at 4 MB, and total function run state cannot exceed 32 MB.
  • Restate. No fixed per-run ceiling is published in the same way; version 1.5 added automatic payload compression as payloads approach the size limit, which particularly helps when running on AWS Lambda.
  • DBOS. No engine ceiling, because the checkpoints are rows in your database. What you inherit instead is table growth, index bloat and a vacuum story.

Two conclusions follow, and the second one is the useful one. First, engine-enforced ceilings are not a defect — they are a forcing function that makes you externalise the transcript early, which you should be doing anyway. Second, and this is the part that decides architectures: store the transcript by reference from the first commit, whichever engine you pick. Write tool results and messages to object storage or a table, journal the key and a short summary, and the ceiling never becomes an event. Teams that journal payloads by value hit the wall at month six, and the migration is not a config change — it is a rewrite of every step signature in the codebase, performed under the pressure of runs that are already failing.

Where the model call is allowed to sit

Three execution models and where the LLM call is allowed to sit Three columns. Replayed deterministic code, used by Temporal, re-executes the workflow function on recovery, so the model call must live in an activity and streaming cannot originate in workflow scope. Journaled ordinary code, used by Restate and Inngest, records each durable step's result and replays it, so the constraint is that step results must be serialisable and stable. Decorated in-process functions, used by DBOS, checkpoint to your own Postgres from inside your application, so there is no separate worker fleet and the failure domain is your app. What the recovery mechanism asks of your code Replayed deterministic code Temporal The workflow function is re-executed on recovery. The model call must be an activity; no clocks, no random, no streaming in workflow scope. Journaled ordinary code Restate · Inngest Each durable step’s result is recorded and replayed. Normal functions; the rule is that step results serialise and stay stable across a retry. Decorated in-process DBOS Decorators checkpoint to the Postgres you already run. No worker fleet, no extra hop — and the failure domain is your own application process. Only the first column changes how you have to write the agent loop.
Only the first column changes how you have to write the agent loop.

Recovery mechanisms differ in a way that shows up as a constraint on your source code, not as a line in a comparison table. Temporal recovers by replaying workflow code against the recorded history, which is why workflow functions must be deterministic: no wall-clock reads, no unguarded randomness, no I/O. That is not a burden in itself — it is a well-understood discipline with a decade of tooling behind it — but for an agent it has a specific consequence. The model call is I/O, so it lives in an activity, and anything you wanted to do with a token stream cannot originate in workflow scope. Streaming to a user from a Temporal agent is a separate channel you build, not a return value you await.

Restate and Inngest journal instead of replaying your control flow in the same sense: you write ordinary functions and mark the durable steps, and a recovered run reads completed steps back from the journal. The discipline that remains is subtler and catches people anyway — a step's result has to serialise and has to stay stable, so a step that returns a live client object, a generator, or a value that embeds the current time will misbehave on the retry path rather than the happy path.

DBOS is the least invasive of the four: decorators on ordinary functions, checkpointing to Postgres, no separate service in the request path. The trade is that durability now shares a failure domain with your application and a capacity budget with your primary database. That is an excellent trade for a Python or TypeScript team already running Postgres, and a poor one for a workload whose durable state would double the size of an already-strained database.

All four are being wired into agent frameworks rather than competing with them: DBOS and Restate both document Pydantic AI integrations, Inngest ships AgentKit directly, and Temporal's pairing with graph-based orchestrators is the subject of its own deep dive. The durability layer is not your agent framework; it is the thing that survives underneath it.

Billing shape versus a loop that mints steps

The pricing question is not "what does it cost per month", it is "what does my program's shape do to the meter". An agent loop is unusually good at manufacturing the unit that gets billed.

Inngest bills per execution, where an execution is a function run plus each step inside it — a function with five step.run() calls consumes six. That is entirely reasonable for an event-driven workflow with a fixed shape. For an agent that runs until it decides to stop, the step count is a variable the model controls, and cost becomes a function of task difficulty in a way that is hard to forecast and easy to have a bad week with. The free tier is 50,000 step runs a month with paid plans starting around $20, so the arithmetic is easy to do in advance — do it with your p99 step count, not your median.

Temporal self-hosted is MIT-licensed and free in the sense that only the cluster costs money, which for many teams is the honest cheapest option and for others is a Cassandra or SQL persistence layer nobody wanted to own; Temporal Cloud converts that into a managed bill. DBOS is the outlier: the durable-execution layer is a library, so the marginal infrastructure cost of durability is the storage and load your checkpoints add to a database you were already paying for. Restate sits between them — one binary to run, or a managed offering.

Whichever you pick, the cost model that matters is the one in agent cost control: the model tokens dominate, and the durability layer's bill is a rounding error unless its unit happens to be the thing your loop generates without limit. On Inngest, it is.

When to pick which

SituationPickBecause
Runs that live for days or weeks, polyglot teams, an existing platform groupTemporalThe most operationally proven, the most explicit control over retries and timeouts, and a decade of tooling — at the cost of a cluster and the determinism discipline
Python or TypeScript team already running Postgres, wants durability without new infrastructureDBOSA library in-process; no cluster, no extra hop, checkpoints in a database you already operate and back up
TypeScript-first, serverless, wants an agent framework in the boxInngestNothing to deploy and AgentKit ships with it — model the step count first, because that is the billing unit
Wants a lightweight self-hosted runtime, multiple languages, low latencyRestateOne Rust binary, journal plus per-object state, and an unusually clean programming model — the youngest ecosystem of the four
Transcript is large and will stay largeAny, with the transcript stored by referenceThe ceiling is only a problem for teams who journal payloads by value

What should not decide it: which one has a nicer diagram of retries, and which one your framework's tutorial happened to use. All four retry, all four resume, all four do timers and signals. The differences that survive contact with production are the three above — where the growth goes, what the recovery model asks of your code, and what the meter counts.

FAQ

Do I need durable execution for an agent at all?

Not for a request-scoped agent that finishes inside one HTTP request and can simply be retried from the top. You need it the moment a run outlives a process — long tasks, human approvals in the middle, scheduled and triggered work — because then a crash is data loss and a retry is a duplicate side effect. That threshold, not the framework, is the reason to adopt one.

Does durable execution make my agent deterministic?

No, and expecting it to is the most common misunderstanding here. These engines make recovery deterministic: the steps that already completed are replayed from the record rather than re-run. The model call inside a step is still a sample from a distribution, so a resumed run is exactly as reproducible as the original was — which is to say, not very.

Will Temporal's 50 MB history limit actually bite me?

Only if you journal payloads by value. A run that stores tool results in object storage and journals a key and a summary will not approach either the size or the event ceiling in any realistic agent workload. A run that puts scraped pages and query results directly into activity return values will, and the terminate is non-retryable.

Can I stream tokens to a user from a durable workflow?

Yes, but not as the return value of the durable step. Under a replay model like Temporal's, the model call is an activity and streaming cannot originate in workflow scope, so the usual design is to stream out of the activity on a side channel — a queue, a websocket, a pub/sub topic — while the durable record captures the final result. Plan the channel; do not discover it late.

Is Restate's BSL runtime a problem?

The SDKs are MIT and the runtime is BSL, which is the now-common shape intended to stop a hyperscaler reselling the service rather than to restrict ordinary self-hosting. Whether that clears your policy is a legal question with a clear answer at most companies; Temporal's MIT server is the option that avoids the conversation entirely.

Further reading

On this wiki:

Project sources: