Checkpointer: memory across turns
A checkpointer saves the state after every step. Give a run a thread_id and the next run on the same thread continues where the last one left off.
Last updated: 27 Sep, 2026 · LangGraph 1.2
Without a checkpointer every run starts from nothing. With one, a conversation carries across turns, which is what makes a chat feel like it remembers you.
Compiling with a checkpointer
from langgraph.checkpoint.memory import InMemorySaver
graph = builder.compile(checkpointer=InMemorySaver())
config = {"configurable": {"thread_id": "1"}}
graph.invoke(inputs, config) # saved under thread "1"This tiny graph has one node that always replies "ok". The piece to watch is the checkpointer, which saves the state so a second run on the same thread keeps the first run's messages. Build it one piece at a time.
The imports
Start with the imports: the graph pieces, the in-memory checkpointer, and the message types.
from langgraph.graph import MessagesState, StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver # saves state per thread
from langchain.messages import HumanMessage, AIMessageThe reply node
Define one node. It ignores the input and returns a single AI message, which keeps the example small.
def reply(state):
return {"messages": [AIMessage("ok")]} # always answers "ok"Building the graph
Build the graph and compile it with a checkpointer. That one argument is what turns on memory.
builder = StateGraph(MessagesState)
builder.add_node("reply", reply)
builder.add_edge(START, "reply")
builder.add_edge("reply", END)
graph = builder.compile(checkpointer=InMemorySaver()) # memory turned onadd_edge(START, "reply")runs the node first.compile(checkpointer=InMemorySaver())saves the state after every step.
Running two turns
Run two turns with the same thread_id and print how many messages the state holds.
cfg = {"configurable": {"thread_id": "1"}} # same thread = same memory
graph.invoke({"messages": [HumanMessage("hi")]}, cfg) # turn 1
out = graph.invoke({"messages": [HumanMessage("again")]}, cfg) # turn 2, same thread
print(len(out["messages"]))The checkpointer in a run
The same pieces in one file.
from langgraph.graph import MessagesState, StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver
from langchain.messages import HumanMessage, AIMessage
def reply(state):
return {"messages": [AIMessage("ok")]}
builder = StateGraph(MessagesState)
builder.add_node("reply", reply)
builder.add_edge(START, "reply")
builder.add_edge("reply", END)
graph = builder.compile(checkpointer=InMemorySaver())
cfg = {"configurable": {"thread_id": "1"}}
graph.invoke({"messages": [HumanMessage("hi")]}, cfg) # turn 1
out = graph.invoke({"messages": [HumanMessage("again")]}, cfg) # turn 2, same thread
print(len(out["messages"]))Why the count reached four
- Turn 1 saved a human and an AI message under thread "1".
- Turn 2 used the same
thread_id, so it loaded those two, appended a new human and a new AI message, and ended with four. - A different
thread_idwould have started fresh with no history.
With and without a checkpointer
| No checkpointer | With checkpointer | |
|---|---|---|
| Each run | Starts from nothing | Continues its thread |
| Memory | None across runs | Per thread_id |
| Needs | Nothing | A thread_id in config |
When you need a checkpointer
- Any chat that should remember earlier turns.
- Any run you want to pause and resume, which needs the state saved.
InMemorySaver keeps checkpoints in RAM and loses them on restart. For anything real use SqliteSaver or PostgresSaver; the saving code is identical.Related
- Previous: create_agent: the short way
- Next: Store: memory across conversations
- Reference: Persistence
- Run turn 2 with a new
thread_id. How many messages now? - Call
graph.get_state(cfg)and read the saved messages.
You understood something today that you didn't yesterday.