LangChain Conversation Memory -- Checkpointer
By default, each agent.invoke() is independent, and the Agent does not remember what was said before.
Checkpointer (checkpoint saver) allows the Agent to remember conversation history, enabling true multi-turn conversations.
Problems without Checkpointer
First, let's look at the situation without Checkpointer:
Example
load_dotenv()
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
model=model,
system_prompt="You are the assistant of EXAMPLE Tutorial.",
)
# Round 1
result1 = agent.invoke({
"messages": [HumanMessage(content=My name is Xiaoming)]
})
print(fRound 1: {result1['messages'][-1].content})
# Round 2 — Agent doesn't remember the content of the first round!
result2 = agent.invoke({
"messages": [HumanMessage(content=What's my name?)]
})
print(fRound 2: {result2['messages'][-1].content})
Output:
Round 1: Hello Xiaoming! Nice to meet you. Round 2: Sorry, I don't have your information, and I don't know what your name is.
Using Checkpointer to Remember Conversations
After adding Checkpointer, conversations under the same thread_id are automatically linked:
Example
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage
# Create an in-memory Checkpointer
checkpointer = InMemorySaver()
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
model=model,
checkpointer=checkpointer, # Pass in the Checkpointer
system_prompt="You are the assistant of EXAMPLE Tutorial.",
)
# Use thread_id to identify the conversation thread
config = {"configurable": {"thread_id": "user-001"}}
# Round 1
result1 = agent.invoke(
{"messages": [HumanMessage(content="My name is Xiaoming, I am learning Python")]},
config=config,
)
print(fRound 1: {result1['messages'][-1].content})
# Second round — using the same thread_id, the Agent remembered!
result2 = agent.invoke(
{"messages": [HumanMessage(content="What is my name? What am I learning?")]},
config=config,
)
print(fRound 2: {result2['messages'][-1].content})
Output:
Round 1: Hello Xiaoming! Python is a great language to start with. How can I help you? Round 2: Your name is Xiaoming and you are learning Python. Is there anything specific I can help with?
thread_id is the key. Conversations under the same thread_id are continuous, while conversations under different thread_id values are completely isolated. This lets you serve multiple users with a single Agent instance.
How Checkpointer Works
Checkpointer automatically saves a state snapshot (checkpoint) after each Agent execution. The next time the same thread_id is used, the state is automatically restored from the most recent checkpoint.
Detailed workflow:
- Call agent.invoke() and pass in config (containing thread_id)
- The Agent checks whether a checkpoint exists for that thread_id
- If it exists, load historical messages, append new messages, and continue
- After execution, automatically save a new checkpoint
Example
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage
checkpointer = InMemorySaver()
agent = create_agent(
model=init_chat_model("deepseek:deepseek-v4-flash", temperature=0),
checkpointer=checkpointer,
system_prompt="You are the assistant of EXAMPLE Tutorial.",
)
config = {"configurable": {"thread_id": "demo-001"}}
# Simulate multi-turn conversation
questions = [
My name is Xiaoming,
"I am learning Python",
"Help me summarize information about me",
]
for i, q in enumerate(questions, 1):
result = agent.invoke(
{"messages": [HumanMessage(content=q)]},
config=config,
)
# Check checkpoint status
state = agent.get_state(config)
print(f"\nAfter round {i}:")
print(f" Message count: {len(state.values.get('messages', []))}")
print(f" Next step: {state.next}")
print(f" Reply: {result['messages'][-1].content[:80]}...")
Output:
After round 1: Message count: 2 Next step: () Reply: Hello Xiaoming! Nice to meet you. After round 2: Message count: 4 Next step: () Reply: Python is a very popular programming language, simple and easy to learn. After round 3: Message count: 6 Next step: () Reply: Based on our conversation, here is your info: your name is Xiaoming, and you are learning Python.
Checkpointer Types
| Type | Storage Location | Persistence | Installation | Applicable Scenarios |
|---|---|---|---|---|
| InMemorySaver | Memory | No (lost after program exits) | Built-in | Development debugging, unit testing |
| SqliteSaver | SQLite database | Yes | langgraph-checkpoint-sqlite | Standalone deployment, small applications |
| PostgresSaver | PostgreSQL | Yes | langgraph-checkpoint-postgres | Production environment, multi-instance sharing |
If the Agent usesainvoke()asynchronous calls, you need to switch to the corresponding async versionAsyncSqliteSaver / AsyncPostgresSaver, the usage is similar to the sync version, except it needs to be paired withasync withto use.
SqliteSaver Example
InMemorySaverOnly saved in memory, data is lost after the program exits. If you want conversations to be persisted, you can useSqliteSaver。
First-time use requires installing SQLite Checkpointer:
pip install langgraph-checkpoint-sqlite
Example
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage
model = init_chat_model(
"deepseek:deepseek-v4-flash",
temperature=0,
)
# Must use the with statement to enter; the database connection will be closed automatically upon exit
with SqliteSaver.from_conn_string("conversations.db") as checkpointer:
agent = create_agent(
model=model,
checkpointer=checkpointer,
system_prompt="You are the assistant of EXAMPLE Tutorial.",
)
config = {"configurable": {"thread_id": "user-001"}}
result = agent.invoke(
{
"messages": [
HumanMessage(content=Hello)
]
},
config=config,
)
print(result["messages"][-1].content)
SqliteSaver saves checkpoints generated by each conversation in the SQLite database. Even if the program exits or restarts, as long as you continue using the same database file and
thread_id, the Agent can restore the previously saved conversation state. Note:withThe database connection will close after the code block ends, so logic involving multiple Agent calls should be placedwithinside the block; for long-running services (such as web applications), it is recommended to hold the connection during the application lifetime, or use the async version ofAsyncSqliteSaver。
Managing Conversation Threads
Viewing Conversation State
Example
state = agent.get_state(config)
print(f"Next step: {state.next}") # () indicates idle
print(f"Message count: {len(state.values.get('messages', []))}")
# View conversation history
for msg in state.values.get("messages", []):
print(f" [{msg.type}] {str(msg.content)[:60]}")
Creating a New Thread
Example
config_alice = {"configurable": {"thread_id": "alice"}}
config_bob = {"configurable": {"thread_id": "bob"}}
# Alice's conversation
agent.invoke(
{"messages": [HumanMessage(content="I am Alice")]},
config=config_alice,
)
# Bob's conversation—completely independent, doesn't know what Alice said
agent.invoke(
{"messages": [HumanMessage(content=# Verify isolation)]},
config=config_bob,
)
"Alice's conversation message count: {len(alice_state.values['messages'])}"
alice_state = agent.get_state(config_alice)
bob_state = agent.get_state(config_bob)
print(f"Bob's conversation message count: {len(bob_state.values['messages'])}")
print(fUpdating State — Manually Modifying Conversations)
Updating State — Manually Modifying Conversations
Example
Example
"(User upgraded to VIP membership)"
agent.update_state(
config,
{
"messages": [
SystemMessage(content=# Subsequent conversations will include this inserted message)
]
}
)
The parameters of update_state() are processed through the add_messages reducer (for the messages field), so new messages are appended rather than overwritten. If you want to clear history and start over, the simplest way is to use a new
AI thinking...; if you really need to delete some historical messages, you can pass the corresponding
thread_id(fromRemoveMessage) to precisely remove the specified messages.langchain.messagesOther Extensions