作为一个 39 岁的技术人,我最近在啃 DeepAgents 框架的长期记忆章节时,发现了一个很有意思的设计------Agent 的记忆不是像人类大脑那样"自然存在"的,而是通过文件系统 + 存储后端 + 路由机制这套组合拳来实现的。上一章我们学完了 Skills(可复用的能力包),但 Skills 解决的是"Agent 会做什么",而本章的 Memory 解决的是"Agent 记得什么"。今天我们就来把这个机制扒开揉碎,讲得明明白白。
一、DeepAgents 中的记忆机制是怎么实现的?
1.1 记忆机制的整体架构
DeepAgents 将记忆作为一等公民------Agent 以文件形式读写记忆,你用 Backend 控制这些文件存储在哪里。整个流程可以概括为三步:
| 步骤 | 做什么 | 类比 |
|---|---|---|
| 1. 准备存储与文件 | 配置 Backend 和 Store,预置记忆文件 | 装修房子,准备好书架 |
| 2. 加载记忆 | memory= 指定文件路径,内容进入系统提示词 |
入住时把书摆上书架 |
| 3. 更新记忆(可选) | 通过提示词约定写入规则,Agent 调用 edit_file 更新 |
读完后往书架上添新书 |
python
# memory= 是读取配置:指定哪些文件的内容会被注入系统提示词
# skills= 是程序性记忆:先注入元数据,正文由 Agent 按需读取
agent = create_deep_agent(
model=model,
memory=["/memories/preferences.md"], # 加载用户偏好
skills=["/skills/"], # 加载技能包
backend=..., # 控制文件存在哪里
)
关键细节 :在 DeepAgents 0.7.10 中,缺失的记忆文件会被跳过,不会自动创建 ,缺失文件的路径也不会作为已加载记忆注入提示词。所以要固定偏好的写入位置,需要同时约定写入路径------仅声明
memory=不能保证 Agent 使用这个文件名。
1.2 memory= vs backend=:它们各自管什么?
这是很多初学者容易混淆的地方。我们用一张表说清楚:
| 参数 | 职责 | 类比 | 如果不配置会怎样 |
|---|---|---|---|
memory= |
读配置:告诉框架"启动时把哪些文件的内容加载到系统提示词里" | "请帮我把书架上那本《用户手册》拿给我看" | Agent 启动时不加载任何已有记忆 |
backend= |
存储配置:告诉框架"文件实际存在哪里、怎么路由" | "书架是实木的还是金属的、放在哪个房间" | 文件存在默认的 StateBackend(对话结束就没了) |
它们的关系是这样的:
memory=["/memories/preferences.md"]
↓ 告诉框架要加载这个文件
↓ 框架去 backend 中找这个路径
↓
backend = CompositeBackend(
routes={"/memories/": StoreBackend(...)}
↑ 告诉框架 /memories/ 开头的文件存在 StoreBackend 里
)
memory= 负责**"加载什么",backend= 负责"存在哪、怎么找"**。两者配合,缺一不可。
二、Agent 的两种记忆:短期记忆和长期记忆的应用场景区别
人类有短期记忆和长期记忆------你记得今天的对话内容(短期),也记得你的名字和偏好(长期)。Agent 也一样,但需要不同的技术来实现。
2.1 短期记忆(Thread-scoped)
定义:同一个对话线程(thread)内持久化,对话结束后消失。
实现方式 :默认的 StateBackend 将文件存在 LangGraph 的 Agent State 中,通过 Checkpointer 机制保证同一 thread 内多轮对话不丢失。
应用场景:
- 当前任务的中间结果(比如草稿、临时笔记)
- 对话上下文("你刚才说了什么")
- 本次会话中产生的临时文件
类比:就像你的工作桌面------当前任务的资料都摊在上面,但下班清理后就干净了。换一个 thread_id,桌面就清空了。
2.2 长期记忆(Cross-thread)
定义:跨不同对话线程保留的信息,不随对话结束而消失。
实现方式 :通过 StoreBackend 存储在持久化存储中(内存、PostgreSQL、LangSmith 平台等)。
应用场景:
- 用户的偏好设置("我喜欢简洁的代码风格")
- 项目的背景知识("我们用 React + TypeScript")
- 累积的研究成果(多次对话中逐渐收集的资料)
- Agent 从反馈中学到的改进指令
类比:就像你的书房书架------不管今天聊什么,书架上的书都在那里,明天来还在。
2.3 两者的对比
| 维度 | 短期记忆 | 长期记忆 |
|---|---|---|
| 作用域 | 单个 thread(对话线程) | 跨所有 thread |
| 底层存储 | Agent State(内存中的状态字典) | Store(持久化存储) |
| 生命周期 | thread 结束即消失 | 永久保留(除非手动删除) |
| 实现机制 | Checkpointer | StoreBackend + CompositeBackend |
| 典型用途 | 对话上下文、临时文件 | 用户偏好、项目知识、Agent 经验 |
| 类比 | 工作桌面 | 书房书架 |
三、DeepAgents 中短期记忆的实现机制和记忆管理策略
3.1 Checkpointer:短期记忆的基础
Checkpointer 是 LangGraph 的短期记忆机制。它的工作原理很简单:
- 每次 Agent 执行完一步,自动保存当前状态(消息历史、文件系统状态、任务清单等)
- 下次调用时,如果
thread_id相同,自动恢复上次的状态 - 开发用
MemorySaver(内存,重启丢失),生产用PostgresSaver(数据库,持久化)
来看实际代码演示:
python
from langgraph.checkpoint.memory import InMemorySaver
checkpointer = InMemorySaver()
agent = create_deep_agent(
model=model,
checkpointer=checkpointer,
)
# 同一个 thread_id 内,Agent 记得之前的对话
config = {"configurable": {"thread_id": "conversation-001"}}
agent.invoke({"messages": [{"role": "user", "content": "我叫张三"}]}, config=config)
agent.invoke({"messages": [{"role": "user", "content": "我叫什么名字?"}]}, config=config)
# Agent 能回答"你叫张三"
# 换一个 thread_id,Agent 不记得了
config2 = {"configurable": {"thread_id": "conversation-002"}}
agent.invoke({"messages": [{"role": "user", "content": "我叫什么名字?"}]}, config=config2)
# Agent 不知道你是谁
实际运行效果:
Thread 1 ID: conversation-001
Thread 2 ID: conversation-002
→ 两个 thread 之间状态完全隔离,互不干扰
Thread 1 保存了: 我叫张三
Thread 2 是空的: []
→ 换 thread 后,Agent 不记得之前对话的内容
关键限制 :Checkpointer 只在同一个 thread_id 内有效。不同的对话(不同 thread_id)之间,状态完全隔离。
3.2 短期记忆的管理策略
随着对话越来越长,消息历史可能超出 LLM 的上下文窗口。LangChain 提供了三种应对策略:
| 策略 | 做法 | 适用场景 |
|---|---|---|
| Trim(裁剪) | 只保留最近 N 条消息,丢弃更早的 | 简单粗暴,适合不需要历史上下文的场景 |
| Delete(删除) | 用 RemoveMessage 精确删除特定消息 |
需要选择性清理(如删除敏感信息) |
| Summarize(总结) | 用 LLM 将旧消息压缩为摘要 | 需要保留历史语义,是最推荐的方式 |
在 DeepAgents 中,Summarize 策略已经自动内置 (SummarizationMiddleware)。create_deep_agent() 在已知模型窗口大小时,默认到 85% 触发;缺少窗口信息时使用固定 token 阈值。
如果你需要自定义裁剪逻辑,可以用 LangChain 的 @before_model 中间件:
python
from langchain.messages import RemoveMessage
from langgraph.graph.message import REMOVE_ALL_MESSAGES
from langchain.agents import AgentState
from langchain.agents.middleware import before_model
from langgraph.runtime import Runtime
@before_model
def trim_messages(state: AgentState, runtime: Runtime) -> dict | None:
"""只保留最近几条消息,防止上下文溢出。"""
messages = state["messages"]
if len(messages) <= 3:
return None # 不需要裁剪
first_msg = messages[0] # 保留第一条(通常是系统消息)
recent = messages[-3:] # 保留最近 3 条
return {
"messages": [
RemoveMessage(id=REMOVE_ALL_MESSAGES),
first_msg,
*recent,
]
}
agent = create_deep_agent(
model=model,
middleware=[trim_messages],
)
@before_model是 LangChain 的中间件装饰器------它在每次模型调用之前执行,可以修改传给模型的消息。对应地还有@after_model(模型调用之后执行)。
3.3 进阶:自定义 AgentState
LangChain 允许你扩展默认的 AgentState,添加自定义字段:
python
class CustomAgentState(AgentState):
user_id: str # 用户 ID
preferences: dict # 用户偏好
agent = create_agent(
model=model,
state_schema=CustomAgentState,
checkpointer=checkpointer,
)
工具可以通过 ToolRuntime 读写这些自定义状态字段:
python
@tool
def get_user_info(runtime: ToolRuntime) -> str:
"""查询当前用户信息。"""
user_id = runtime.state["user_id"] # 从 Agent State 中读取
return f"用户ID: {user_id}"
@tool
def update_preferences(new_theme: str, runtime: ToolRuntime):
"""更新用户偏好设置。"""
from langgraph.types import Command
current_prefs = runtime.state.get("preferences", {})
current_prefs["theme"] = new_theme
return Command(update={"preferences": current_prefs})
关键点:
runtime.state是读状态,Command(update={...})是写状态。这样工具不仅能返回结果给模型,还能直接修改 Agent 的短期记忆。
四、DeepAgents 中的长期记忆:路径路由与用户隔离
4.1 长期记忆都有哪些?
DeepAgents 的长期记忆通过 Store 存储,主要包括:
| 记忆类型 | 存储内容 | 作用域 | 典型文件路径 |
|---|---|---|---|
| Agent 级记忆 | Agent 从多次对话中积累的知识 | 所有用户共享 | /memories/AGENTS.md |
| 用户级记忆 | 个人偏好、私有笔记 | 单个用户 | /memories/preferences.md |
| 组织级记忆 | 合规策略、公司政策 | 全组织共享 | /policies/compliance.md |
| 项目记忆 | 技术栈、架构文档 | 项目组成员 | /memories/project/tech-stack.md |
| 研究记忆 | 研究笔记、参考资料 | 研究者 | /memories/research/sources.md |
| 情景记忆 | 过去的完整对话记录 | 单个用户 | 通过 Checkpointer 搜索 |
4.2 如何分清不同用户的长期记忆?------namespace 机制
DeepAgents 通过 namespace(命名空间) 来隔离不同用户、不同 Agent 的记忆。
python
# Agent 级记忆:所有用户共享
namespace = ("my-coding-agent", "memories")
# 用户级记忆:按用户隔离
namespace = ("user-123", "memories") # 用户 A
namespace = ("user-456", "memories") # 用户 B
实际运行效果:
Agent 级记忆(所有用户共享):
Namespace: ('my-coding-agent', 'memories')
内容: ## Agent 知识库
- 本项目使用 React + TypeScript
- 代码规范:ESLint + Prettier
用户 A 的私有记忆:
Namespace: ('alice', 'memories')
内容: # Alice 的偏好 - 深色主题 - 中文注释
用户 B 的私有记忆:
Namespace: ('bob', 'memories')
内容: # Bob 的偏好 - 浅色主题 - 英文注释
关键区别:
Agent 级: namespace = (assistant_id, 'memories') → 所有用户读同一份
用户级: namespace = (user_id, 'memories') → 各用户隔离
4.3 什么是路径路由机制?
路径路由 是 CompositeBackend 的核心能力。它的思想很简单:不同的文件路径,路由到不同的后端存储。
python
from deepagents.backends import CompositeBackend, StateBackend, StoreBackend
backend = CompositeBackend(
default=StateBackend(), # 默认路由:临时文件
routes={
"/memories/": StoreBackend(
namespace=lambda rt: (rt.context.user_id, "memories"),
),
"/policies/": StoreBackend(
namespace=lambda rt: (rt.context.org_id,),
),
},
)
4.4 路径路由在长期记忆中起到什么作用?
路径路由解决了三个核心问题:
1. 透明的存储切换 :Agent 操作文件的方式完全一样------都是调用 write_file、read_file。区别只在于路径前缀:
write_file('/workspace/draft.txt', '草稿') → StateBackend (临时,对话后消失)
write_file('/notes.txt', '笔记') → StateBackend (临时,对话后消失)
write_file('/memories/preferences.md', '偏好') → StoreBackend (持久化)
write_file('/policies/compliance.md', '合规') → StoreBackend (持久化)
2. 存储空间的隔离 :/memories/ 和 /policies/ 路由到不同的 namespace,互不干扰。
3. 路由前缀的自动剥离:
Agent 看到的虚拟路径: /memories/preferences.md
实际 Store 中的 key: /preferences.md(路由前缀 /memories/ 被自动剥离)
实际 Store 中的 namespace: (user_id, 'memories')
大坑提醒 :如果 Store key 写成
/memories/preferences.md(带路由前缀),CompositeBackend 在返回结果时还会补一次/memories/,最终暴露成错误的/memories/memories/preferences.md。Store key 不应该包含路由前缀!
五、四种实用场景
通俗讲解四种场景的使用方式
场景 1:用户偏好记忆(preferences.md)
一句话总结:让 Agent 记住"我喜欢什么"。
怎么用:
- 配置
memory=["/memories/preferences.md"] - 在 system_prompt 中约定:"当用户明确要求记住偏好时,用
edit_file更新/memories/preferences.md" - 用户说"记住我的偏好",Agent 就会调用
edit_file写入
实际运行效果:
[场景 1] 用户偏好记忆 --- preferences.md
→ 每次对话 Agent 都能记住并使用用户偏好
场景 2:自我改进的 Agent(AGENTS.md)
一句话总结:让 Agent 随着时间"越用越聪明"。
怎么用:
- 配置
memory=["/memories/AGENTS.md"] - 约定 Agent 从用户反馈中学习:"当用户指出错误时,记录到 AGENTS.md"
- 随着时间推移,AGENTS.md 积累越来越多经验
实际运行效果:
[场景 2] 自我改进的 Agent --- AGENTS.md
→ Agent 随时间积累知识,越来越'懂'这个领域
示例文件内容:
markdown
## 从反馈中学到的经验
- 2026-01-15: 用户希望减少解释,直接给代码
- 2026-01-20: 用户希望错误信息附带修复建议
场景 3:知识库累积(project/*.md)
一句话总结:让 Agent 跨多次对话逐渐构建项目知识库。
怎么用:
- 按项目子目录组织文件:
/memories/project/tech-stack.md - 每次对话,Agent 读取已有内容,追加新信息
- 新对话启动时,Agent 加载完整的项目知识
实际运行效果:
[场景 3] 知识库累积 --- project/tech-stack.md
→ 跨多次对话逐渐构建项目知识库
示例文件内容:
markdown
## 项目技术栈
- 前端: React 18 + TypeScript
- 后端: FastAPI + Python 3.12
- 数据库: PostgreSQL 16
- 部署: Docker + K8s
场景 4:研究项目持续推进(research/*.md)
一句话总结:让大型研究任务可以"分多次对话"持续推进。
怎么用:
- 用多个 memory 路径加载不同研究文件
- Agent 启动时加载所有文件,了解当前进度
- 每次对话更新对应的文件
python
agent = create_deep_agent(
model=model,
memory=[
"/memories/research/sources.md", # 参考资料
"/memories/research/notes.md", # 研究笔记
"/memories/research/report.md", # 研究报告
],
...
)
实际运行效果:
[场景 4] 研究项目持续推进 --- 多个 memory 文件
/research/sources.md ✓
/research/notes.md ✓
/research/report.md ✓
→ Agent 启动时加载所有文件,每次对话更新进度
核心使用原则
| 原则 | 说明 |
|---|---|
| 按主题拆分文件 | 不要把所有记忆塞进一个大文件,拆分成 preferences.md、tech-stack.md、sources.md 等 |
| memory= 加载,提示词约定写入 | memory= 负责读取,system_prompt 负责约定写入规则 |
| 持久化路径要有意义 | 用 /memories/project/tech-stack.md 而不是 /memories/file1.md |
六、组织记忆和情景记忆:场景与实现
6.1 组织级记忆(Organization-level Memory)
用在什么场景:
- 公司的合规政策("不得披露内部定价")
- 全组织共享的知识库
- 安全规则和行为准则
核心特点:
- 跨所有用户和 Agent 共享
- 通常设为只读(防止恶意用户注入攻击)
- 由应用代码(而非 Agent)填充内容
如何实现:
python
agent = create_deep_agent(
model=model,
memory=[
"/memories/preferences.md", # 用户级(可读写)
"/policies/compliance.md", # 组织级(只读)
],
backend=CompositeBackend(
default=StateBackend(),
routes={
"/memories/": StoreBackend(
namespace=lambda rt: (rt.context.user_id, "memories"), # 用户级
),
"/policies/": StoreBackend(
namespace=lambda rt: (rt.context.org_id,), # 组织级
),
},
),
)
从应用代码中填充组织级记忆:
python
from langgraph_sdk import get_client
from deepagents.backends.utils import create_file_data
client = get_client(url="<DEPLOYMENT_URL>")
await client.store.put_item(
(org_id,),
"/compliance.md",
create_file_data("""## 合规政策
- 不得披露内部定价
- 金融建议必须附加免责声明
"""),
)
安全提醒:如果一个用户能写入另一个用户读取的记忆,恶意用户可以注入指令。共享策略应该用只读模式------通过应用代码填充,不让 Agent 写入。
6.2 情景记忆(Episodic Memory)
用在什么场景:
- 回忆"上次是怎么解决这个问题的"
- 搜索过去的完整对话记录
- Agent 回溯上次调试过程,直接跳到可能的根因
和语义记忆的区别:
- 语义记忆 :记"是什么"(事实和偏好)→
/memories/preferences.md - 情景记忆:记"发生了什么"(完整经历)→ 过去的对话历史
如何实现:
DeepAgents 的 Checkpointer 天然支持情景记忆------每次对话都被完整持久化。要让过去的对话变得可搜索,可以包装一个搜索工具:
python
from langgraph_sdk import get_client
from langchain.tools import tool, ToolRuntime
client = get_client(url="<DEPLOYMENT_URL>")
@tool
async def search_past_conversations(query: str, runtime: ToolRuntime) -> str:
"""搜索过去的对话以获取相关上下文。"""
user_id = current_user_id(runtime)
threads = await client.threads.search(
metadata={"user_id": user_id},
limit=5,
)
results = []
for thread in threads:
history = await client.threads.get_history(thread_id=thread["thread_id"])
results.append(history)
return str(results)
这对执行复杂多步任务的 Agent 尤为有用------比如代码 Agent 可以回溯上次调试过程,直接跳到可能的根因。
6.3 记忆的六个维度全景
官方文档将记忆系统拆分为六个可独立配置的维度:
| 维度 | 核心问题 | 选项 |
|---|---|---|
| 持续时间 | 保留多久? | 短期(单次对话)/ 长期(跨对话) |
| 信息类型 | 记什么? | 情景记忆 / 程序性记忆(Skills)/ 语义记忆(事实) |
| 作用域 | 谁能看? | 用户级 / Agent 级 / 组织级 |
| 更新策略 | 何时写入? | 对话中(默认)/ 对话间(后台整合) |
| 检索方式 | 如何读取? | 启动加载(memory=)/ 按需读取(Skills) |
| 权限控制 | Agent 能写吗? | 读写(默认)/ 只读(共享策略) |
这六个维度互相独立,你可以自由组合。比如:
- "用户偏好" = 长期 + 语义记忆 + 用户级 + 对话中写入 + 启动加载 + 读写
- "合规政策" = 长期 + 语义记忆 + 组织级 + 应用代码写入 + 启动加载 + 只读
七、从开发到生产:Store 的升级路径
| 阶段 | Store 类型 | 特点 |
|---|---|---|
| 开发阶段 | InMemoryStore |
零配置,快速迭代,重启丢失 |
| 生产阶段 | PostgresStore |
真正持久化,可伸缩,需要 pip install langgraph-checkpoint-postgres |
| LangSmith 部署 | 平台自动配置 | 无需手动管理,平台自动提供持久化存储 |
记忆文件格式(deepagents >= 0.5 v2 格式)
json
{
"content": "第一行\n第二行\n第三行",
"encoding": "utf-8",
"created_at": "2024-01-15T10:30:00Z",
"modified_at": "2024-01-15T11:45:00Z"
}
不要手写底层 JSON!Agent 外部(后端服务、初始化脚本)预填记忆时应使用
create_file_data辅助函数。
小结
本章我们深入剖析了 DeepAgents 的记忆系统:
- 短期记忆靠 Checkpointer,管同一个 thread 内的状态持久化
- 长期记忆靠 Store + CompositeBackend,跨 thread 保留信息
memory=是读配置,backend=是存储配置,两者配合才完整- 路径路由让 Agent 用统一方式操作文件,底层自动路由到正确的存储
- namespace 实现了用户级、Agent 级、组织级的隔离
- 四种实用场景告诉你:通用记忆能力在实际中怎么用
- 组织记忆 用只读模式防注入,情景记忆用搜索工具回溯历史
记忆机制的本质就是:把"记忆"变成"文件",把"存储"变成"后端",把"隔离"变成"路由规则"。理解了这个底层逻辑,你就能灵活应对各种记忆需求。
下一章,我们将学习 Human-in-the-Loop------如何为敏感操作添加人工审批,构建安全的人机协作流程。