LangGraph Checkpointer: Persist and Resume Agent State (2026)
Make a LangGraph agent durable with a checkpointer: attach SqliteSaver in one line, crash the graph on purpose, and resume from disk without re-running the Claude call.
On this page
Quick answer (2026)
A LangGraph checkpointer saves your graph's state after every step, so an agent can pause, survive a crash, and resume exactly where it stopped instead of starting over. You attach one with builder.compile(checkpointer=...) and address each run with a thread_id. Use InMemorySaver while developing, SqliteSaver for a single machine, and PostgresSaver in production. This tutorial builds a two-node graph, kills it on purpose, and resumes it from disk without re-running (or re-paying for) the completed Claude call.
What you need
You need Python 3.10+, an Anthropic API key in ANTHROPIC_API_KEY, and three packages. The checkpointer classes live in their own installable packages, which trips people up.
Install the graph, the SQLite checkpointer, and the Claude binding:
pip install langgraph langgraph-checkpoint-sqlite langchain-anthropic
InMemorySaver ships inside langgraph itself. SqliteSaver and PostgresSaver are separate installs: langgraph-checkpoint-sqlite and langgraph-checkpoint-postgres. If you have never assembled a graph before, build one first in our LangGraph graph-agent tutorial, then come back to make it durable. Everything below follows the LangGraph persistence docs (2026) and the langgraph repo.
The graph we are going to make durable
Two nodes. generate calls Claude once, the expensive step. publish sends the result somewhere that can fail: a rate limit, a deploy hiccup, a flaky webhook. We will make publish fail the first time on purpose.
# app.py
import os, sqlite3
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.sqlite import SqliteSaver
from langchain_anthropic import ChatAnthropic
llm = ChatAnthropic(model="claude-sonnet-5", max_tokens=300)
class State(TypedDict):
topic: str
draft: str
published: bool
def generate(state: State) -> dict:
print("generate: calling Claude (this one costs money)")
msg = llm.invoke(f"Write a one-line changelog entry for: {state['topic']}")
return {"draft": msg.content.strip()}
def publish(state: State) -> dict:
# Simulate a downstream failure the FIRST time only.
if not os.path.exists("publish.ok"):
open("publish.ok", "w").close()
raise RuntimeError("publish API timed out")
print("publish: shipped ->", state["draft"])
return {"published": True}
builder = StateGraph(State)
builder.add_node("generate", generate)
builder.add_node("publish", publish)
builder.add_edge(START, "generate")
builder.add_edge("generate", "publish")
builder.add_edge("publish", END)
Nothing here is durable yet. If publish throws, the Claude call in generate is gone, and re-running pays for it again.
Add a checkpointer in one line
Attach a checkpointer at compile time. That is the whole change.
conn = sqlite3.connect("checkpoints.db", check_same_thread=False)
checkpointer = SqliteSaver(conn)
graph = builder.compile(checkpointer=checkpointer)
Now LangGraph writes a checkpoint after every super-step into checkpoints.db. Because it is SQLite on disk, those checkpoints outlive the process. That is the part InMemorySaver cannot do.
thread_id: the key that makes resume work
A checkpointer stores state per thread_id. The same id means "continue this run"; a new id means "start fresh." You pass it in the config on every call.
config = {"configurable": {"thread_id": "changelog-42"}}
Reuse changelog-42 and you resume that exact run. This same mechanism is how a checkpointer gives a chat agent per-conversation memory, and how human-in-the-loop pauses resume after a human replies.
Crash it, then resume without re-paying for Claude
First run. generate calls Claude and checkpoints the draft, then publish raises and the script dies.
if __name__ == "__main__":
graph.invoke(
{"topic": "add dark mode toggle", "published": False},
config,
)
generate: calling Claude (this one costs money)
RuntimeError: publish API timed out
The process is gone. But checkpoints.db holds the state right after generate. Run the SAME script again, this time resuming instead of starting over:
# second run: pass None to continue the existing thread from its last checkpoint
graph.invoke(None, config)
publish: shipped -> Added a dark mode toggle to settings.
Read that output carefully: there is no second generate: calling Claude line. LangGraph resumed at the failed publish step, reused the saved draft, and never re-ran or re-billed the Claude call. On a real pipeline where step one is a twenty-cent model call or a ten-minute job, that is the difference between a cheap retry and redoing everything.
Passing None as the input is the resume signal. It means "no new input, continue thread changelog-42 from where it stopped."
Inspect every checkpoint
The saved state is not a black box. get_state shows the current snapshot and what is still pending; get_state_history walks every checkpoint newest-first.
snap = graph.get_state(config)
print(snap.values) # {'topic': ..., 'draft': ..., 'published': True}
print(snap.next) # () when done; ('publish',) right after the crash
for st in graph.get_state_history(config):
print(st.config["configurable"]["checkpoint_id"], "->", st.next)
snap.next is the proof. Right after the crash it reads ('publish',), which is why the resume re-ran publish and left generate alone. Each entry in the history is a StateSnapshot you can also resume or fork from for time travel.
InMemory vs SQLite vs Postgres: pick by where you run
Same interface, three backends. Swap the one line; the graph does not change.
Scroll to see more
| Checkpointer | Import | Install | Use it for |
|---|---|---|---|
InMemorySaver | from langgraph.checkpoint.memory import InMemorySaver | ships with langgraph | dev, tests, single process. Lost on exit. |
SqliteSaver | from langgraph.checkpoint.sqlite import SqliteSaver | langgraph-checkpoint-sqlite | one machine, survives restarts. |
PostgresSaver | from langgraph.checkpoint.postgres import PostgresSaver | langgraph-checkpoint-postgres | production, many workers. |
For Postgres, create the tables once with .setup():
from langgraph.checkpoint.postgres import PostgresSaver
checkpointer = PostgresSaver.from_conn_string("postgresql://localhost/agents")
checkpointer.setup() # one time: creates the checkpoint tables
graph = builder.compile(checkpointer=checkpointer)
InMemorySaver looks fine in a notebook, but it cannot do the crash-and-resume above, because the state dies with the process. If you want durability, you need SQLite or Postgres. Nothing about the checkpointer is Claude-specific either. Swap ChatAnthropic for any LangChain chat model and the persistence works the same.
Verify your setup (break it on purpose)
Do not trust a checkpointer you have not watched recover. Three checks:
- Delete
publish.ok, runapp.pyonce, watchpublishraise. Confirmcheckpoints.dbnow exists on disk. - Run
app.pyagain with the samethread_id. Confirm it printspublish: shippedand NOT a secondgenerateline. That is resume working. - Change
thread_idtochangelog-43and run again. It re-runsgeneratefrom scratch, because a new thread is a new run. That is proof the id, not the file, scopes the state.
If step two re-calls Claude, you either changed the thread_id or forgot to pass None on the resume call.
Limitations and open questions
- A checkpoint stores your graph state, not external side effects. If
publishalready sent an email before it failed, resuming will send it again. Make failure-prone nodes idempotent, or gate them on state you persist. SqliteSaveris single-machine. The moment you run two workers, move toPostgresSaver; concurrent writers against one SQLite file will bite you.- Checkpoints accumulate. A long thread keeps every super-step. For high-volume production, plan retention and cleanup rather than letting the table grow forever.
- State is stored serialized. Keep large blobs like files or images in a store or object storage and keep only references in graph state.
Related reading: building a LangGraph graph agent and human-in-the-loop with LangGraph.
Written by
Ren OkabeRen Okabe builds and breaks agent systems, then writes down the runnable version. Principal-engineer voice, code first.
Frequently asked questions
What is a checkpointer in LangGraph?
A checkpointer is a persistence layer that saves your graph's state as a checkpoint after every super-step. It lets an agent pause, survive a crash, and resume from the last saved point instead of restarting. You attach one with builder.compile(checkpointer=...) and address each run with a thread_id.
What is the difference between a checkpointer and a store in LangGraph?
As of 2026, a checkpointer persists a single thread's graph state for short-term, thread-scoped memory: conversation continuity, human-in-the-loop, time travel, and fault tolerance. A store persists application-defined data outside the graph state and across threads, for long-term memory. Most agents use a checkpointer first and add a store only when they need cross-thread memory.
How do I resume a LangGraph run after a crash?
Use a durable checkpointer such as SqliteSaver or PostgresSaver, then re-invoke the graph with the same thread_id and None as the input: graph.invoke(None, config). LangGraph loads the last checkpoint and continues from the step that failed, without re-running the steps that already completed.
Does InMemorySaver survive a process restart?
No. InMemorySaver keeps checkpoints in memory only, so they are lost when the process exits. It is ideal for development and tests, but it cannot resume after a crash-and-restart. For that you need SqliteSaver on a single machine or PostgresSaver in production.
Do I need to call setup() on a LangGraph checkpointer?
Call .setup() once for the database-backed savers to create their tables. It is required for PostgresSaver. InMemorySaver needs no setup, and SqliteSaver creates its tables on first use, so a .setup() call is not needed for the in-memory or SQLite paths in the basic flow shown here (2026).
Related tutorials
LangGraph Tutorial: Build a Graph Agent in Python (2026)
A hands-on LangGraph tutorial for 2026: build the same tool-using AI agent two ways, with the five-line create_agent helper and as an explicit StateGraph you can customize. Fully runnable Python.
LangGraph Human-in-the-Loop (2026): Pause an Agent for Approval with interrupt()
A runnable LangGraph human-in-the-loop tutorial for 2026: pause an agent mid-run with interrupt(), review the proposed action, then approve, edit, or reject it with Command(resume=...). Includes the checkpointer requirement and the re-run-from-the-top gotcha that breaks most tutorials.
Agentic Workflow Patterns in Python (2026)
Five reusable agentic workflow patterns, built from scratch in Python with runnable code and a start-simple rule.
