Someone told you a durable run “resumes at the exact line it crashed”. Then you watched the last node start again from its first line, tool calls and all, and the two statements refused to fit together.
That gap sits underneath durable execution for agent workloads. Your state is either a saved snapshot or a log of what already happened, and the API in front of you rarely says which. Two mechanisms follow from that fork, checkpoint-and-re-execute and journal-and-replay, and they charge different amounts after a crash. That difference is larger than the labels suggest, which is where what the runtime is actually promising starts to matter.
What is the difference between a checkpoint and a workflow event history?
A checkpoint is a snapshot of graph state, written at node or super-step boundaries into a store you configure. An event history is an append-only, ordered record of commands and activity results. The snapshot answers where you were; the log answers what you already did, and in what order.
A checkpointer writes snapshots of graph state. In LangGraph it is an object passed at compile time that persists state at each super-step into SQLite, Postgres, MongoDB or Cosmos DB, scoped to a thread_id and restored on the next invoke. Even a checkpointer that stores every super-step does not record which tool fired or what came back, and that gap is the whole difference.
In Temporal this is the event history: an append-only log of events, from WorkflowExecutionStarted to ActivityTaskScheduled to TimerFired. A history event is also not an event in the event-driven-architecture sense, despite the name.
The durability setting decides how much that snapshot is worth. LangGraph saves channel values at each super-step boundary, not the in-flight node’s progress, and its default mode writes asynchronously, so a hard crash can lose the newest checkpoint. That default assigns a window of loss rather than removing durability. sync makes the window explicit, and a measured disconnect test shows exit leaving no checkpoint at all.
Checkpoint-and-re-execute or journal-and-replay: which one re-runs more of your work?
What survives is settled; what has to happen twice is where the two camps part. Journal-and-replay memoises per unit (an activity, a ctx.run() call, a hashed step.run()), so a turn making twelve tool calls re-runs only the one that never finished. Checkpoint-and-re-execute reloads a snapshot and replays the whole node, so the same turn re-runs twelve. LangGraph and Mastra re-execute from a snapshot; Temporal, Restate, DBOS and Inngest replay from a journal.
The boundaries inside that camp diverge. Inngest memoises each step against a hash of the step name, so renaming a step creates one with no recorded result and it runs again. DBOS writes each step’s output into your own Postgres, in the same transaction. Temporal’s unit is the activity. Same family, three boundaries, three different bills.
The OpenAI Agents SDK sits outside both camps: a RunState save point durable at interruptions, unauthenticated on read, with no engine behind it.
Durable timers separate the two models. In Temporal a persisted timer fires whether or not anything was watching, so a two-day wait costs storage and no compute. A snapshot needs something awake to notice the deadline.
Replay cost tracks history length. Maxim Fateev attributes O(n) per replay to workers that cache workflow state, against O(n²) for services that invoke workers over HTTP, as a Restate versus Temporal comparison sets out. The direction is fair; the constants are disputed. Diagrid argues checkpointing is not durable execution, a case from a company selling the alternative, and who operates the store decides how far that goes.
The camp label predicts less than the boundary does.
Why does the interrupted node re-run from its first line when the thread resumes?
Resumption restores the node’s input state and calls the function again from its top. The interrupt was raised after the write, and no program counter was saved. In LangGraph that is interrupt() plus Command(resume=…): the suspension is persisted as a durable pause, and the resume value is applied at the next super-step. Microsoft states the general case plainly: recovery reenters the handler from its beginning, with no call stack restored.
The marketing line overstates it. “Resumes at the exact line” is closer to “replayed to the exact point”. An interrupt-resume can skip nodes that already finished, because their results are recorded. A crash-resume restarts the last node and any sibling whose result was not yet stored.
Pending writes, the stored results of nodes that finished inside a super-step, soften that: a super-step is transactional, and completed results are kept, so a resume redoes only the failed branch, though calls inside the super-step can still repeat. A conformance study (arXiv 2608.03836) measured a completed task re-running after a SIGKILL anyway.
Recovery can rewind what the run knew. Effects already sent stay sent, so a side effect before an interrupt needs an idempotency key or its own boundary, which is also why a deploy can still lose a running agent.
What is the difference between a thread checkpoint and a cross-thread memory store?
A thread checkpoint is the durable record that lets one run resume, keyed by thread. A cross-thread memory store is a searchable layer that should influence the next run. State answers whether this run may continue; memory answers what the next run should know. Only the first is a durability mechanism.
Temporal’s framing is the correction worth borrowing: memory restores what an agent knew, while the checkpoint records where the work stood. LangGraph separates them with per-thread checkpoints and a separate Store for cross-thread recall. Compaction is the lossy bridge, and where architectures break: once a summary replaces the transcript, the summary is the only record of what it dropped.
Graphiti and Zep, Mem0 and LangMem solve recall and provenance; resumption sits outside their scope. Graphiti’s still-open request for temporal versioning on node attributes shows the shape of the problem: a remembered fact you cannot trace back to a moment.
So the split lands on ownership. Whoever holds the checkpoint can resume a run; whoever holds the store can influence the next one, and they are rarely the same vendor. Which is why the mechanism matters less than the ownership question behind the mechanism.
Why the gap closes
The ownership split starts with what never got written down. The run did not resume at the line it crashed because no such line existed: snapshots record position, logs record fact, and only the log rebuilds anything.
Name the smallest unit your runtime memoises, then name who notices when a worker dies. If you cannot answer both, what you are holding is a save point, and a crash costs the width of that boundary. Either way, the boundary rather than the camp sets the bill. Return to the cluster hub.
Frequently Asked Questions
Does a checkpoint mean my agent’s work is safe if the process crashes?
No, not the work itself. A checkpoint records the state your graph had reached at a boundary, so a crash costs you everything the current node did after that boundary and before it. The function’s internal progress was never written down, which is why the node starts again at its first line rather than halfway through. Split the node into smaller steps and only the unfinished step repeats.
What happens if the crash lands before the newest checkpoint is written?
Then you lose that checkpoint and fall back to the previous boundary, which can mean repeating a whole super-step instead of one node. LangGraph’s default durability mode writes asynchronously, so the window is real. Switching to sync does not make the write free, it makes the window explicit, which is the trade you want for money-touching work.
What happens to a tool call that was in flight when the process died?
It runs again, from the top of its node. Nothing recorded the call being dispatched, only the state before the node began, so recovery restores that input and calls the function once more. The practical consequence is idempotency: give the tool an idempotency key, or move the side effect behind its own step boundary so a completed result is memoised.
Is “resume from the exact line it crashed” ever literally true?
Not literally, no. An interrupt-resume restarts the suspended node from its top and injects the value passed to Command(resume=...), which is why code before the interrupt should be idempotent. It can skip nodes that already finished, because their results are recorded. A crash-resume is harsher: the last node restarts, and any siblings that never landed in the same super-step restart with it.
Why doesn’t replay repeat the API calls that already succeeded?
Because the event history already holds the result, and replay injects it instead of calling the service again. Replay re-executes the orchestration code, reads the recorded outcome of each completed activity or step, and runs only the unit that never finished. That is also why recorded results must be deterministic: a branch that reads them differently on replay has nothing valid to inject.
Is a workflow event history the same as an event in event-driven architecture?
No. They share a word and little else. An event history is an internal, ordered log of commands and results that a workflow engine replays to rebuild its own state; it is not published to a broker, not consumed by subscribers, and not a schema you design. An event-driven event is a message other services react to. Confusing the two leads people to expect the history to behave like a bus.
How long can a durable workflow wait between steps?
For as long as the store keeps the record, in principle. A journal-based runtime writes a timer event and can wait two days, two weeks or a month with no process watching the clock, because the wait costs storage rather than compute. A snapshot-based runtime needs something alive to notice the deadline unless it hands scheduling to an external timer service.
Can a run resume on a different machine or after a new deploy?
Yes for the state, usually no for the code that reads it. Checkpoints and histories sit in a shared store, so another worker can load them. But replaying old history against new orchestration code is where runs break: a step renamed, reordered or removed leaves recorded results with no matching call. Deploys, more than machines, are the real risk, and versioning the code is the mitigation.
Does a longer event history make replay slower?
Replay does walk the history, so cost grows with its length. The exact shape is contested: Temporal co-founder Maxim Fateev attributes O(n) per replay to workers that cache workflow state, and O(n squared) to services that invoke workers over HTTP. Treat the direction as solid and the constants as disputed. Long-lived runs are usually capped with continue-as-new rather than left to grow.
Does renaming a step make it run again?
Yes, if the runtime keys the memoised result by the step’s name. Inngest hashes step.run() step names, so a new name means a different step with no recorded result, and it executes again. The same principle holds anywhere a boundary is identified by name, which is why step identifiers deserve the same care as database keys.
Can I use my checkpointer as long-term agent memory?
No. A checkpointer answers whether this run may continue; a memory store answers what the next run should know. LangGraph keeps them separate for that reason: per-thread checkpoints for resumption, a Store for cross-thread recall. Reusing the checkpoint as memory also ages badly, because what resumes a run is a position, not a fact you can search or cite.