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
Ravi asks about one order and then another. Without a checkpointer the second call has no idea the first happened, because the conversation you pass in is all the agent has.
Each call starts fresh
Two invokes with no checkpointer. 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"])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
Give the 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 shop_model import ShopModel
from tools import lookup_order
from langgraph.checkpoint.memory import InMemorySaver
agent = create_agent(ShopModel(), tools=[lookup_order],
checkpointer=InMemorySaver()) # save after every stepNaming the thread
Name the conversation with a thread_id in the config. Every call that passes this config shares one saved history.
ravi = {"configurable": {"thread_id": "ravi-1"}} # one conversation, named ravi-1Asking two questions on one thread
Ask two questions on that thread. The second call passes one new message; everything saved under the id comes back with it.
agent.invoke({"messages": [{"role": "user", "content": "Where is A17?"}]}, ravi)
result = agent.invoke({"messages": [{"role": "user", "content": "And C40?"}]}, ravi)
print(len(result["messages"])) # 8, not 4
print([m.text for m in result["messages"] if m.type == "human"]) # both questionsTwo calls on one thread
The same pieces in one file. The second call passed one new message and got back eight.
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"])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)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"]))What the checkpointer saved
- Without a checkpointer each invoke returns its own four messages, so the second call never saw A17.
- 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, such as Postgres or SQLite; each is a separate package with the same interface.Related
- Previous: Two tools 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.