LangChain LangGraph
Module 1 Module 2 Module 3 Module 4 Module 5 Module 6
Module 1 · Basic Introduction
1 1-1 Simple Graph
2 1-2 Chain
3 1-3 Router
4 1-4 Agent
5 1-5 Agent Memory
6 1-6 Deployment
SUM Module Summary
HomeLangGraphModule 1 · Basic Introduction1-5 Agent Memory
📓 Notebook 📖 Explained
LANGGRAPH MODULE 1 · LESSON 5

Agent with Memory: Equip Agent with persistent memory
checkpointer · MemorySaver · Thread ID

The Agent in the previous lecture has "amnesia" every time it talks - after the invoke() is executed, the State disappears.
This lecture uses checkpointer to allow the Agent to remember the conversation history and truly realize multiple rounds of dialogue.

1Problem: Stateless Agent cannot conduct multiple rounds of dialogue

In the previous lecture (1-4), we built a complete ReAct Agent: it can call tools in a loop until the problem is solved. But it has a fatal flaw——Each invoke() is a completely new conversation, don’t remember anything said before.

Reproduction problem

First ask:"Add 3 and 4."——Agent calls add(3, 4) and returns 7.
Then ask again:"Multiply that by 2."——What is "that"?
The Agent without memory has no idea that "that" is 7!It might randomly guess, say multiply(2, 2) = 4, and be completely wrong.

Why is this happening? State life cycle

State in LangGraph isAn object temporarily created each time invoke(). The execution process is as follows:

State life cycle (without checkpointer)

📥
invoke() first call
Create a new State = {messages: [HumanMessage("Add 3 and 4.")]}, after the diagram is executed...
🗑️
State is discarded
All conversation history (records with add(3,4)=7) disappear from memory without any persistence
📥
invoke() second call
Created againbrand newState = {messages: [HumanMessage("Multiply that by 2.")]}, LLM has no idea what "that" refers to

Essentially, this is because LangGraph's graph implementation ispure functional——Input comes in, output goes out, no side effects are retained. This is a good thing in most cases (predictable, testable), but multi-turn dialogue scenarios require state persistence.

2Solution: checkpointer checkpoint mechanism

Checkpointer(checkpointer) is the persistence layer of LangGraph. Its responsibility is: after each node of the graph is executed,Automatically serialize and save the current State to the storage backend

Core idea

checkpointer lets the graph "leave a footprint" at every step. The next time you invoke(), you no longer start from a blank page, but load all the last footprints back first, and then continue execution on this basis.

What does checkpointer do (three things)

1
Save
After each node is executed, the current complete State is serialized (such as JSON) and stored in key-value storage. key isthread_id + step_number
2
Load
When a new invoke() is called, first find the latest checkpoint of the conversation based on thread_id, deserialize it into a State object, and load it into the graph.
3
Merge
Append the new input (the new HumanMessage) to the loaded history State and start the current round of execution.

MemorySaver: The simplest checkpointer implementation

from langgraph.checkpoint.memory import MemorySaver

# MemorySaver 把所有 checkpoint 存在 Python 进程内存里
# 本质上是一个字典:{thread_id: {step: state_snapshot}}
memory = MemorySaver()

# 编译时把 checkpointer 传进去
react_graph_memory = builder.compile(checkpointer=memory)

MemorySaverUses an in-memory dictionary to store all checkpoints, zero configuration, out-of-the-box, suitable for development phases and unit testing. The disadvantage is that all data is lost after the process is restarted.

3Thread ID: distinguish between different conversation sessions

An Agent may serve multiple users at the same time, and the conversation history of each user must be isolated.Thread IDIt is the "conversation session ID", which is equivalent to the primary key in the database.

Different Thread IDs correspond to different independent conversations

thread_id = "1"
(User Alice)
Add 3 and 4. → 7
Multiply that by 2. → 14 ✓
Now add 10. → 24 ✓
thread_id = "2"
(User Bob)
What is 100 / 4? → 25
Add 5 to that. → 30 ✓
thread_id = "42"
(User Carol)
Multiply 6 and 7. → 42
Divide that by 3. → 14 ✓

Complete conversation history of three threadsIndependent storage, no interference with each other. Alice's "that" is 7, Bob's "that" is 25, and Carol's "that" is 42.

Thread ID is decided by you

LangGraph does not automatically generate thread_id. you need to be inconfigPassed in, usually generated by your application layer (such as UUID, user ID, Session ID). Multiple requests from the same user use the same thread_id, and different users use different thread_ids.

import uuid

# 方式一:固定字符串(测试/demo 时常用)
config = {"configurable": {"thread_id": "1"}}

# 方式二:UUID(生产环境,每个用户 session 一个)
thread_id = str(uuid.uuid4())
config = {"configurable": {"thread_id": thread_id}}

# 方式三:与业务 ID 绑定(最常见的实践)
config = {"configurable": {"thread_id": f"user_{user_id}_session_{session_id}"}}

4Core code: only one parameter missing

Adding memory to Agent, the code changes are extremely small——Just pass in checkpointer when compile(), all the rest of the code remains unchanged.

Memoryless version (1-4 Agent)

from langgraph.graph import (
    StateGraph, MessagesState,
    START, END
)
from langgraph.prebuilt import (
    ToolNode, tools_condition
)

# 无 checkpointer
react_graph = builder.compile()

# 每次 invoke 都是全新对话
messages = react_graph.invoke(
    {"messages": messages}
    # 没有 config,无法区分对话
)

Memory version (Lectures 1-5)

from langgraph.graph import (
    StateGraph, MessagesState,
    START, END
)
from langgraph.prebuilt import (
    ToolNode, tools_condition
)
from langgraph.checkpoint.memory import MemorySaver

memory = MemorySaver()

# 传入 checkpointer ← 唯一改动!
react_graph_memory = builder.compile(
    checkpointer=memory
)

config = {"configurable": {"thread_id": "1"}}

# 必须传 config,带上 thread_id
messages = react_graph_memory.invoke(
    {"messages": messages},
    config   # ← 告诉图用哪个 thread
)

Full code: two-turn conversation example

from langchain_core.messages import HumanMessage
from langgraph.checkpoint.memory import MemorySaver

# ─── 初始化 Checkpointer ───
memory = MemorySaver()
react_graph_memory = builder.compile(checkpointer=memory)

# ─── 配置:使用 thread_id = "1" ───
config = {"configurable": {"thread_id": "1"}}

# ─── 第一轮对话 ───
messages = [HumanMessage(content="Add 3 and 4.")]
result = react_graph_memory.invoke({"messages": messages}, config)
# LLM 调用 add(3, 4) → ToolMessage("7") → AIMessage("The sum is 7.")
# ✅ Checkpointer 把这整个 State 保存到 thread "1" 的 checkpoint

# ─── 第二轮对话:注意只传了新的问题 ───
messages = [HumanMessage(content="Multiply that by 2.")]
result = react_graph_memory.invoke({"messages": messages}, config)
# ✅ LangGraph 先加载 thread "1" 的历史,再追加新消息
# LLM 看到完整历史,正确理解 "that" = 7
# → 调用 multiply(7, 2) → ToolMessage("14") → AIMessage("The result is 14.")

print(result["messages"][-1].content)
# 输出:"The result is 14."

5How to "connect" two invokes - complete life cycle

To truly understand the memory mechanism, you need to know what steps each invoke() goes through internally.

The first time invoke("Add 3 and 4.", config={"thread_id": "1"})
1
Find history: LangGraph goes to MemorySaver to find the checkpoint with thread_id="1".not found(First time), starting with an empty State.
2
Initialize State:State = {messages: [HumanMessage("Add 3 and 4.")]}
3
Execute agent node: LLM processes the message and returns AIMessage(tool_calls=[add(3,4)]).Checkpoint saving step 1
4
Execute tools node: ToolNode executes add(3,4)=7 and appends ToolMessage("7").Checkpoint saving step 2
5
Execute the agent node again: LLM sees the tool results and returns AIMessage("The sum of 3 and 4 is 7.").Checkpoint Save Step 3 (Final State)
6
tools_condition judgment: The latest news has no tool_calls and returns END. The execution ends and the result is returned.
↓ User sees answer 7 and sends second question
The second time invoke("Multiply that by 2.", config={"thread_id": "1"})
1
Find history: LangGraph goes to MemorySaver to find the latest checkpoint of thread_id="1".Found it!Load an existing complete State.
2
Merge new input: Put the new HumanMessage("Multiply that by 2.")AppendGo to the end of the history message list. There are now 5 messages.
3
Execute agent node: LLM sees the complete 5 message history, understands "that" = 7, and returns AIMessage(tool_calls=[multiply(7,2)]). Checkpoint save.
4
Execute tools node: multiply(7, 2) = 14, append ToolMessage("14"). Checkpoint save.
5
Execute the agent node again: LLM returns AIMessage("The result is 14."). Checkpoint saves the final State (now 7 messages).
6
Finish: Correct answer 14 is returned to the user.
Key understanding

The second time you invoke() you only pass in[HumanMessage("Multiply that by 2.")], but LangGraph internally quietlyAppend it to the history of 5 messages, what LLM actually sees is 6 messages (5 historical + 1 new issue). That’s the nature of memory—the continual accumulation of a list of messages.

6Comparison demonstration: with memory vs without memory

The following visually shows what LLM actually received during the second round of invoke().messagesWhat a difference a list makes.

No memory version
messages seen by LLM in the second round

Only 1 message, no context at all
HumanMessage Multiply that by 2.
LLM’s Dilemma
What is "that"? Don't know at all. Maybe guess multiply(2, 2) = 4, or ask the user for clarification.

There is a memory version
messages seen by LLM in the second round

6 messages in total, complete conversation history
HumanMessage Add 3 and 4.
AIMessage (tool_call) add(a=3, b=4)
ToolMessage 7
AIMessage The sum of 3 and 4 is 7.
Second round of new additions
HumanMessage Multiply that by 2.
LLM completely understand
"That" is obviously what the previous AIMessage said 7. Call multiply(7, 2) = 14.

Complete message list after the second round of execution (with memory version)

After the two rounds of dialogue are executed, there are a total of messages in the State of thread "1"7 items

# thread_id="1" 的最终 messages 列表:
[
    HumanMessage(content="Add 3 and 4."),
    AIMessage(content="", tool_calls=[{"name": "add", "args": {"a": 3, "b": 4}}]),
    ToolMessage(content="7", name="add"),
    AIMessage(content="The sum of 3 and 4 is 7."),
    HumanMessage(content="Multiply that by 2."),
    AIMessage(content="", tool_calls=[{"name": "multiply", "args": {"a": 7, "b": 2}}]),
    ToolMessage(content="14", name="multiply"),
    AIMessage(content="The result of multiplying 7 by 2 is 14."),
]

7checkpointer workflow diagram

The figure below shows the graph with checkpointer inSingle invoke() internalHow to automatically save State after each node.

Checkpoint saving is automatically triggered after each node is executed.

START
agent node
LLM call
tools node
Execution tool
agent node
LLM organizes answers
END
Save Checkpoint #1
{msgs:[Human, AIToolCall]}
Save Checkpoint #2
{msgs:[..., ToolMsg]}
Save Checkpoint #3
{msgs:[..., AIFinal]}
MemorySaver
Storage backend
thread_id: "1"
step_0: State{1 item}
step_1: State{2 items}
step_2: State{3 items}
step_3: State{4 items} ← Latest
The next time invoke() is called, LangGraph is loaded directlystep_3 (latest checkpoint), append the new message and continue execution.
Time Travel

Because each step has a checkpoint, LangGraph supports the "time travel" function: you can load any historical checkpoint and re-execute from that point in time (the subsequent history will be overwritten). This is a powerful tool for debugging and error correction, and will be specifically explained in Module 3.

8MemorySaver vs Production Grade Storage

MemorySaverOnly suitable for development stage. Production environments require a checkpointer implementation that can be persisted to disk or an external database.

Checkpointer Storage backend Applicable scenarios persistence
MemorySaver Python memory (dictionary) Local development, unit testing, Demo ❌ Process restart is lost
SqliteSaver SQLite file Single-machine deployment, lightweight production ✅ Persist to file
PostgresSaver PostgreSQL Multi-instance deployment, high concurrent production ✅ Persistence to database
RedisSaver Redis High-performance, cache-first scenario ✅ Configurable persistence
Custom implementation Any storage Special needs (such as MongoDB, S3) Depends on implementation

All checkpointer implementations follow the same interface (BaseCheckpointSaver), so when switching from MemorySaver to PostgresSaver,Zero changes to business code, just replace the checkpointer object passed in at compile time.

# 开发阶段
from langgraph.checkpoint.memory import MemorySaver
graph = builder.compile(checkpointer=MemorySaver())

# 生产阶段(替换一行,其余代码不变)
from langgraph.checkpoint.postgres import PostgresSaver
with PostgresSaver.from_conn_string("postgresql://user:pass@host/db") as checkpointer:
    graph = builder.compile(checkpointer=checkpointer)
    # 同样的 invoke(),同样的 config,完全一致的行为
Note: LangGraph Platform

If you useLangGraph Platform(Cloud hosting service, see Module 6), the platform will automatically manage checkpointer, you don't even need to configure it manually - just use thread_id directly, and the underlying persistence is completely transparent to you.

9The necessity of Config parameters

Even if you pass checkpointer, if it is not provided when invoke()config(or no thread_id), LangGraph willReport an error. This is a mandatory constraint by design.

# ❌ 错误:有 checkpointer 但没有 config
react_graph_memory.invoke({"messages": messages})
# 抛出 ValueError: "thread_id" is required in config["configurable"]
# when a checkpointer is used

# ✅ 正确:提供 config 和 thread_id
config = {"configurable": {"thread_id": "1"}}
react_graph_memory.invoke({"messages": messages}, config)

Why is it mandatory? Because there is no thread_id, checkpointer doesn't know at all:

Other uses of config

in the config dictionaryconfigurableNot only can it be placedthread_id, you can also put other runtime parameters (such as LLM temperature, user permissions, custom parameters, etc.). This is LangGraph's standard mechanism for implementing "runtime configuration", which avoids the confusion of stuffing all parameters into State.

Query history State

With checkpointer, you can also query the historical State of a thread at any time without re-executing the graph:

# 获取某个 thread 的当前(最新)State 快照
snapshot = react_graph_memory.get_state(config)
print(snapshot.values["messages"])   # 查看完整消息历史
print(snapshot.next)               # 查看下一步将执行哪个节点

# 获取某个 thread 的全部历史 checkpoints(时间旅行用)
for checkpoint in react_graph_memory.get_state_history(config):
    print(checkpoint.config)          # 该 checkpoint 的 config(含 checkpoint_id)
    print(checkpoint.values["messages"])  # 该时刻的消息列表

10Summary: What did you learn from this lecture?

This lecture is based on the complete ReAct Agent in 1-4, usingminimal code changesImplemented persistent multi-round dialogue memory.

conceptOne sentence summary
The source of statelessness LangGraph's State is an object that is temporarily created each time invoke() is executed and discarded after execution.
Checkpointer The State snapshot is automatically saved after each node is executed, and is automatically loaded and restored the next time invoke() is executed.
MemorySaver Memory version of checkpointer, data will be lost after process restart, suitable for development and testing
Thread ID The unique identifier of the conversation session. Multiple invoke()s with the same thread_id share and accumulate history.
Configuration parameters Each time invoke() must be passed in{"configurable": {"thread_id": "xxx"}}
connection mechanism The second time invoke() loads the historical State first and then appends new messages. LLM sees the complete context.
production switch Replace MemorySaver with PostgresSaver/SqliteSaver, with zero changes to the business code
Preview of the next lecture: Deployment

1-6 will introduce how to deploy LangGraph Agent as a service (LangGraph Server) that can be accessed externally. You'll see how checkpointer works in the cloud and interacts with the Agent via the REST API and SDK.

Module 1 Learning Path
1-1 Simple Graph 1-2 Chain 1-3 Router 1-4 Agent 1-5 Memory ← You are here 1-6 Deployment