Labs/Showing agent workflows
06

Graphs, checkpoints, time travel

A multi-node agent graph executes. Watch state move through nodes, pause at an interrupt, then rewind to a checkpoint and fork a new branch.

GoalSee how graph runtimes expose state per step, and how a UI turns that into progress, review and replay.

nodes and edgescheckpointsinterruptsforkingtraces
checkpoint 00 / 04
Branch a · not started
1 · The graphnot started
STARTENDapprovedchanges requestedintakeresearchdraftreviewinterrupt: human reviewpublish
2 · Checkpoints1 branch · click one to inspect

branch a · queued

3 · State

Press play. Each step runs one node and saves a checkpoint.

4 · Time traveledit state, fork, replay

Run the graph first.

How it worksclick to collapse

A checkpoint per step. Graph runtimes such as LangGraph persist state after every super-step, keyed by thread. Each checkpoint holds the state values and which node runs next. That is everything a UI needs to draw progress: the active node, the path so far, the state at any point.

Interrupts are just a pause in the graph.A node can stop and wait for a value from outside. The thread sits at that checkpoint until something resumes it — which is why inbox-style UIs (LangChain's Agent Inbox is one) can list every paused thread for a person to approve, edit or answer.

Time travel is a fork.Editing state at an old checkpoint doesn't rewrite history; it writes a new checkpoint whose parent is the old one, and execution continues from there. Debuggers like LangGraph Studio expose exactly this. For your users it means undo, “try it another way” and side-by-side alternatives come almost free.

// pseudo-code: the shape of it, not one runtime's API
for await (const cp of graph.stream(input, { thread }))
  render(cp)                     // { id, node, next, state }

// paused at an interrupt: show the review UI
await graph.resume(thread, { decision: "approve" })

// time travel: edit state at an old checkpoint
const fork = await graph.updateState(thread, cp.id,
                                     { tone: "playful" })
for await (const c of graph.stream(null,
                 { thread, checkpoint: fork.id }))
  render(c)                      // a new branch

Names vary by runtime; check your framework's docs for the exact calls.