Agent Orchestration Explained
← /learn · 03

Checkpoints and interrupts

A checkpoint after every super-step: a run that crashes and resumes, a human interrupt, an edit, and time travel that forks the thread, on a timeline of checkpoints.

Loading the animation…

Concept

With a checkpointer, LangGraph saves a checkpoint after every super-step: the state, the nodes due to run next, and, as tasks finish, their writes. A thread's checkpoints form a history you can list, resume and branch. The animation plays five sessions on the engine's runtime; each is also one of the conformance sessions run in real LangGraph.

  • A crash, then resume. load fans out to enrich and score; the worker running enrich dies. No checkpoint is written for that step, but score had finished, and its writes are kept on the last checkpoint as pending writes. invoke(None) resumes: only enrich runs again. The side-effects list shows the price: enrich called its API twice (3 effects in all for 5 node runs).
  • A human interrupt. The ask node calls interrupt(…) with the draft; the run pauses and returns the question. Its sibling check finished, and is kept. When the human answers with Command(resume="yes"), ask runs again from its first line, so the notification it sent before the interrupt goes out twice. LangGraph's documentation says exactly this: the node restarts from the beginning, and code before interrupt should be idempotent.
  • A static interrupt and an edit. Compiled with interrupt_before=["execute"], the run stops before a risky step. A person corrects the plan with update_state(…, as_node="planner"), which writes a new update checkpoint as if planner had written the change, and the run continues from it (resuming never stops at the same static interrupt twice).
  • Time travel. update_state on an earlier checkpoint starts a branch: the old run stays in the history, and the new one runs calculator instead of search. Replaying an earlier checkpoint (invoke(None, config) with its checkpoint_id) copies it to a fork checkpoint and runs the nodes after it again: LangGraph's time-travel page says replay "re-executes nodes", it does not read them from a cache.

Maths

A checkpoint is ck=(vk,Ak,Pk)c_k = (v_k, A_k, P_k): the state after step kk, the tasks due next and the pending writes of those that have finished. A step either completes, giving ck+1c_{k+1}, or stops on a failure or an interrupt, leaving ckc_k with more pending writes. Resuming runs only Ak∖done(Pk)A_k \setminus \mathrm{done}(P_k) and applies the union, as in the equation; so a step whose tasks have side effects pays them once per attempt of each unfinished task, and an interrupted node pays everything before its interrupt call once per resume.

The history is a tree. update_state on cjc_j adds a child c′c' with step j+1j+1 whose state is vjv_j with the edit applied through the reducers; replaying cjc_j adds fork(cj)\mathrm{fork}(c_j) and runs from it. The thread's "latest" checkpoint is the newest one, wherever it hangs, so the next invoke(None) continues the newest branch: in the fork session, 8 checkpoints over 3 operations.

Code

Resuming: a task whose result is already on the checkpoint is not run again; time travel copies the checkpoint into a fork (cut from the vendored engine, graph.ts):

if (first && reuse && rec.result !== null) {
  keyed.push([key, t, clone(rec.result)]);
  continue;
}
else cur = this.put(base.id, base.step + 1, "fork", base.values, base.pull, base.barriers, base.ready, base.sends);

The interrupt in the real LangGraph node the conformance check builds (conformance/langgraph_graphs.py):

out[op["key"]] = interrupt(op["payload"])