Checkpointer
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: 29 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.
Remembering a name with MemorySaver
Without memory the video's agent forgets between invokes. Told "Hello my name is Krish" and then asked "What is my name", it cannot say; in the video's run it even makes a tool call before apologizing. The fix is a checkpointer. MemorySaver, from langgraph.checkpoint.memory, saves the state after every node. It goes into compile(checkpointer=memory).
Every invoke then passes a config with a thread_id, the id of one conversation. On thread "1" the second question gets the name back.
The video imports MemorySaver, which is another name for InMemorySaver, the name the docs and the shop's code below use. The video's graph with memory runs below on Groq with both of its tools: tool, the TavilySearch from ToolNode and tools_condition, which needs langchain-tavily and TAVILY_API_KEY, and multiply. It is a complete script: save it as its own file, such as memory_graph.py, and set GROQ_API_KEY and TAVILY_API_KEY first (add the load_dotenv() lines at the top if they are in .env).
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph,START,END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode, tools_condition
from langchain.chat_models import init_chat_model
from langchain_tavily import TavilySearch
from langgraph.checkpoint.memory import MemorySaver
class State(TypedDict):
messages:Annotated[list,add_messages]
llm=init_chat_model("groq:openai/gpt-oss-120b")
tool=TavilySearch(max_results=2)
## Custom function
def multiply(a:int,b:int)->int:
"""Multiply a and b
Args:
a (int): first int
b (int): second int
Returns:
int: output int
"""
return a*b
tools=[tool,multiply]
llm_with_tool=llm.bind_tools(tools)
## Node definition
def tool_calling_llm(state:State):
return {"messages":[llm_with_tool.invoke(state["messages"])]}
## Grpah
builder=StateGraph(State)
builder.add_node("tool_calling_llm",tool_calling_llm)
builder.add_node("tools",ToolNode(tools))
## Add Edges
builder.add_edge(START, "tool_calling_llm")
builder.add_conditional_edges("tool_calling_llm",tools_condition)
builder.add_edge("tools","tool_calling_llm")
memory = MemorySaver()
graph=builder.compile(checkpointer=memory)
config={"configurable":{"thread_id":"1"}}
response=graph.invoke({"messages":"Hi my name is Krish"},config=config)
print(response['messages'][-1].content)
response=graph.invoke({"messages":"Hey what is my name"},config=config)
print(response['messages'][-1].content)Hello Krish! Nice to meet you. How can I assist you today? Your name is Krish.
The second invoke sent only "Hey what is my name". The checkpointer handed back the first turn from thread "1", so the model read the name from the conversation. The shop's version below adds a second thread that has no such history.
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"The graph has one node that calls the model. Build it one piece at a time.
The imports
Start with the imports: the graph pieces, the in-memory checkpointer, the model builder, and the message types.
from langgraph.graph import MessagesState, StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver # saves state per thread
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage, SystemMessageThe model node
Define one node that sends the conversation so far to the model and appends its reply.
model = init_chat_model("groq:openai/gpt-oss-120b", temperature=0) # uses your GROQ_API_KEY
SYSTEM = SystemMessage("You are the support assistant for a small online shop. Answer in one short sentence.")
def reply(state: MessagesState):
return {"messages": [model.invoke([SYSTEM] + state["messages"])]}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
Tell the model your name on turn 1, ask for it on turn 2 on the same thread, then ask again on a new thread. Print how many messages each state holds and the model's answer.
cfg = {"configurable": {"thread_id": "1"}}
graph.invoke({"messages": [HumanMessage("My name is Ravi.")]}, cfg) # turn 1
out = graph.invoke({"messages": [HumanMessage("What is my name?")]}, cfg) # turn 2, same thread
print(len(out["messages"]), out["messages"][-1].content)
other = {"configurable": {"thread_id": "2"}} # a new thread
new = graph.invoke({"messages": [HumanMessage("What is my name?")]}, other)
print(len(new["messages"]), new["messages"][-1].content)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.chat_models import init_chat_model
from langchain.messages import HumanMessage, SystemMessage
model = init_chat_model("groq:openai/gpt-oss-120b", temperature=0) # uses your GROQ_API_KEY
SYSTEM = SystemMessage("You are the support assistant for a small online shop. Answer in one short sentence.")
def reply(state: MessagesState):
return {"messages": [model.invoke([SYSTEM] + state["messages"])]}
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("My name is Ravi.")]}, cfg) # turn 1
out = graph.invoke({"messages": [HumanMessage("What is my name?")]}, cfg) # turn 2, same thread
print(len(out["messages"]), out["messages"][-1].content)
other = {"configurable": {"thread_id": "2"}} # a new thread
new = graph.invoke({"messages": [HumanMessage("What is my name?")]}, other)
print(len(new["messages"]), new["messages"][-1].content)4 Your name is Ravi. 2 I’m sorry, but I don’t have your name on record.
Why thread 1 remembered and thread 2 did not
- Turn 1 saved a human and an AI message under thread "1".
- Turn 2 used the same thread_id, so it loaded those two first. The model saw "My name is Ravi." in the conversation and answered from it, and the state ended with four messages.
- Thread "2" had nothing saved, so it started with only the new question: two messages, and the model had no name to give.
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
- Next: Store
- Reference: Persistence
- Call
graph.get_state(cfg)and read the saved messages for thread "1". - Compile the same graph without the checkpointer and run the two turns again. What does the model answer on turn 2?
You understood something today that you didn't yesterday.