Short-term memory with a checkpointer
A checkpointer is a saver that stores the agent's conversation after each step, so a later call on the same thread continues where the last one left off.
Last updated: 27 Sep, 2026 · LangChain 1.4
Remembering a name across two calls
A model on its own remembers nothing between calls. The video shows it first: the chatbot is told "Hello my name is Krish" and replies "Nice to meet you", and asked "What is my name" right after, it does not know, because nothing from the earlier call is kept. LangGraph's answer is a checkpointer. MemorySaver is an in-memory checkpoint saver: it stores a checkpoint in a Python dictionary as each node runs, so a later call can read the earlier conversation back. The checkpointer is passed when the graph is compiled.
Each conversation also needs a thread id, passed in a config as {"configurable": {"thread_id": ...}}. It must be unique, one per session or user, and in a full application the session keeps it. With the checkpointer in place, the bot is told "Hi my name is Krish" and answers "Nice to meet you, Krish!". A second call with the same config asks "Hey what is my name", and the bot answers "Your name is Krish." Nothing in the second question mentions the name; it came from the saved thread. The video builds this chatbot by hand as a LangGraph graph; LangGraph is the library create_agent runs on. With create_agent it takes three changes: import InMemorySaver (the current name of MemorySaver), pass checkpointer=, and pass a config with a thread_id:
from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver
agent = create_agent("groq:openai/gpt-oss-120b", checkpointer=InMemorySaver())
config = {"configurable": {"thread_id": "1"}}
agent.invoke({"messages": "Hi my name is Krish"}, config)
response = agent.invoke({"messages": "Hey what is my name"}, config)
print(response["messages"][-1].text)Your name is Krish. 😊
One agent can hold many conversations at once, one per thread. The shop version below shows it.
Now the shop. First see what goes wrong without a checkpointer: Ravi asks about one order, then another.
Each call starts fresh
Use the shop agent in agent.py from create_agent: the agent loop (not the weather agent at the top of that lesson), which has no checkpointer. Two invokes: each returns its own four messages, and the second never saw A17.
first = agent.invoke({"messages": [{"role": "user", "content": "Where is A17?"}]})
second = agent.invoke({"messages": [{"role": "user", "content": "And C40?"}]})
print(len(first["messages"]), len(second["messages"]))
print([m.text for m in second["messages"] if m.type == "human"])4 4 ['And C40?']
The InMemorySaver checkpointer
from langgraph.checkpoint.memory import InMemorySaver
# save state per thread
agent = create_agent(model, tools=[...], checkpointer=InMemorySaver())
config = {"configurable": {"thread_id": "ravi-1"}} # name the conversation
agent.invoke({"messages": [...]}, config) # continues that threadAdding a checkpointer
This lesson's agent answers order questions with lookup_order, the tool built in Tools: a function the model can call. Start the file with it.
from langchain.tools import tool
ORDERS = {"A17": "shipped on 3 March", "C40": "waiting for stock"}
@tool
def lookup_order(order_id: str) -> str:
"""Look up an order's shipping status by its id, such as A17."""
status = ORDERS.get(order_id)
return f"{order_id} {status}." if status else f"{order_id} is not an order we have."Give the same agent a checkpointer. InMemorySaver keeps the saved state in a Python dictionary and reads it back at the start of the next call.
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langgraph.checkpoint.memory import InMemorySaver
agent = create_agent(init_chat_model("groq:openai/gpt-oss-120b", temperature=0), tools=[lookup_order], # uses your GROQ_API_KEY
system_prompt="You are the support assistant for a small online shop. Answer in one or two short sentences, using only what the tools returned.",
checkpointer=InMemorySaver()) # save after every stepTwo calls on one thread
Name the conversation with a thread_id in the config; every call that passes this config shares one saved history. Ask two questions on that thread. The second call passes one new message, and everything saved under the id comes back with it: eight messages, not four.
ravi = {"configurable": {"thread_id": "ravi-1"}}
agent.invoke({"messages": [{"role": "user", "content": "Where is A17?"}]}, ravi)
result = agent.invoke({"messages": [{"role": "user", "content": "And C40?"}]}, ravi)
print(len(result["messages"]))
print([m.text for m in result["messages"] if m.type == "human"])8 ['Where is A17?', 'And C40?']
Reading a thread without running it
get_state reads what the checkpointer saved for a thread without running the agent. Its values are the state.
state = agent.get_state(ravi)
print(len(state.values["messages"]))
print(state.values["messages"][-1].text)8 C40 is currently waiting for stock.
Another thread starts empty
A second thread_id in the same checkpointer holds only its own messages. Mei's thread starts empty even though Ravi's is saved alongside it.
mei = {"configurable": {"thread_id": "mei-1"}}
result = agent.invoke({"messages": [{"role": "user", "content": "Hello"}]}, mei)
print(len(result["messages"]))2
What the checkpointer saved
- With a checkpointer and one thread_id the second call passed one new message and got eight back: the saved history plus the new question and answer.
- get_state reads what a thread saved without running the agent; here it is the eight messages, ending with the C40 answer.
- A second thread_id starts empty. Mei's thread holds only her two messages even though Ravi's is in the same checkpointer; each thread's history is kept apart.
Passing history yourself vs a checkpointer
| No checkpointer | Checkpointer + thread_id | |
|---|---|---|
| What the agent sees | Only the messages you pass | The saved thread plus the new message |
| Across calls | Starts fresh each time | Continues the same conversation |
| Telling conversations apart | You keep each list yourself | thread_id keys each one |
| Where it lives | Nowhere after the call | The saver: memory, or a database |
When to use a checkpointer
- A chat assistant that must remember earlier turns in the same session.
- Any multi-turn flow where the user refers back to something said before.
InMemorySaver is gone when the program ends. For anything that must survive a restart, use a checkpointer backed by a database: langgraph-checkpoint-sqlite or langgraph-checkpoint-postgres, each a separate package with the same interface.Related
- Previous: Several tool calls at once
- Next: Trimming and removing messages
- Reference: Short-term memory
- Invoke the checkpointed agent without the config and read the error.
- Print
agent.get_state(mei).values["messages"]after Mei's call. - Ask a third question in Ravi's thread and predict the message count before you run it.
This is what real progress feels like.