LangChainLangChain 1.4 · Python 3.10+
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
46 small wins to finish your pathNext lesson →

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.

Example
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

python
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 thread

Adding 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.

python
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 step

Naming the thread

Name the conversation with a thread_id in the config. Every call that passes this config shares one saved history.

python
ravi = {"configurable": {"thread_id": "ravi-1"}}   # one conversation, named ravi-1

Asking 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.

python
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 questions

Two calls on one thread

The same pieces in one file. The second call passed one new message and got back eight.

Example
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.

Example
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.

Example
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 checkpointerCheckpointer + thread_id
What the agent seesOnly the messages you passThe saved thread plus the new message
Across callsStarts fresh each timeContinues the same conversation
Telling conversations apartYou keep each list yourselfthread_id keys each one
Where it livesNowhere after the callThe 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.
Watch out. 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.
Try it yourself
  • 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.