Checkpoints and resumption
A checkpoint is the state after every step, saved under a thread id. It turns a crashed run into something you can continue, and a finished run into something you can rewind.
A thread is a run you can name
Every invocation carries a thread_id. The checkpointer stores the state after each node under that id, so the next invocation with the same id starts from the last saved step rather than from the beginning.
That is what makes a long workflow practical. A request that takes thirty seconds and fails at step seven does not have to start again; it resumes at seven with the state it had.
A worked example: a refund workflow runs under thread order-1024. Steps one to six write a request, validate it, compute the amount and draft a message. Step seven calls the payment API and the process restarts. The next run loads the step-six checkpoint and runs the payment node with the amount and message in the state. Whether that is one refund or two depends on an idempotency key.
What gets saved
The state, the node that produced it, and the step number. Not the model’s internal state, not the tool’s connection. Everything the next node needs must be in the state, which is the same rule that made the graph resumable in the first place.
This is why payloads in the state are a liability. A checkpoint that carries a hundred pages of retrieved text is slow to write and expensive to store; a checkpoint that carries ids is small and fast.
A checkpoint is more than the values. It records which node produced them and which node comes next, which is what makes resumption a continuation rather than a replay. The model’s internal state, a database cursor, an open websocket — none of that is saved. If the next node needs it, it has to be in the state or recreated from it. The step number is what makes the history listable.
A checkpointer in LangGraph
from langgraph.checkpoint.memory import MemorySaver
graph = builder.compile(checkpointer=MemorySaver())
config = {"configurable": {"thread_id": "order-1024"}}
graph.invoke({"question": "Where is my order?"}, config)
# Later, same thread: continues from the saved state.
state = graph.get_state(config)
print(state.values, state.next)Save after every step
Run the graph, inspect the snapshots, then delete the last one and resume. The run should continue from the previous step, not the start.
Time travel is the same mechanism
Because every step is stored, you can list the history of a thread, load an earlier checkpoint, and continue from there. That is not a debugging curiosity: it is how you test an alternative route without re-running the expensive part.
It also makes “what did the state look like when it went wrong” answerable after the fact, which is the question that matters most in an incident review.
A worked rewind: list the thread, load the checkpoint after the outline step, change the outline, and invoke. The graph continues from the node after that checkpoint with the new outline; the later steps run again and produce a different draft. The original run is untouched, which is what makes this a branch rather than an edit. Loading a checkpoint does not delete the later ones; they remain as the original branch.
Use one thread per user-visible task, not per user. A thread that spans a whole session becomes a history nobody can read; one per task can be listed, resumed and deleted.
Checkpoint after the side effect?
A checkpoint saves the state after a node returns. If the node sent an email and the process died before the checkpoint was written, resumption runs the node again and sends a second email. That is the classic at-least-once problem, and the checkpointer does not solve it.
The fix is an idempotency key in the state: the email provider deduplicates on it, so the second call is a no-op. The key is generated before the side effect and saved with the state, not after. Any node that talks to the outside world needs one.
Storage is a real decision
An in-memory checkpointer is fine for a demo and useless across restarts. Postgres or Redis make a run survive a deploy, which is what “resumable” has to mean in production.
Retention matters too. Checkpoints accumulate, and a thread that is never cleaned up is a growing table. Decide how long a finished run is worth keeping before the first deployment, not after the table is large.
Retention is a data decision as well as a storage one. Checkpoints contain the user’s question, the retrieved passages and the draft, so they inherit whatever personal data the workflow handles. Decide how long a finished thread is kept, encrypt the store, and delete on request. A checkpoint table nobody prunes is a copy of your users’ data with no owner.
Rewind and branch
Run the workflow, rewind to the second checkpoint, and change the outline. The rest of the run continues from the new value — a different branch from the same history.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does a thread id identify?
