作为一个 39 岁的技术人,我最近在啃 DeepAgents 框架的长期记忆章节时,发现了一个很有意思的设计——Agent 的记忆不是像人类大脑那样"自然存在"的,而是通过文件系统 + 存储后端 + 路由机制这套组合拳来实现的。上一章我们学完了 Skills(可复用的能力包),但 Skills 解决的是"Agent 会做什么",而本章的 Memory 解决的是"Agent 记得什么"。今天我们就来把这个机制扒开揉碎,讲得明明白白。
一、DeepAgents 中的记忆机制是怎么实现的?
1.1 记忆机制的整体架构
DeepAgents 将记忆作为一等公民——Agent 以文件形式读写记忆,你用 Backend 控制这些文件存储在哪里。整个流程可以概括为三步:
| 步骤 | 做什么 | 类比 |
|---|---|---|
| 1. 准备存储与文件 | 配置 Backend 和 Store,预置记忆文件 | 装修房子,准备好书架 |
| 2. 加载记忆 | memory=指定文件路径,内容进入系统提示词 | 入住时把书摆上书架 |
| 3. 更新记忆(可选) | 通过提示词约定写入规则,Agent 调用edit_file更新 | 读完后往书架上添新书 |
# 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(数据库,持久化)
来看实际代码演示:
fromlanggraph.checkpoint.memoryimportInMemorySaver 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中间件:
fromlangchain.messagesimportRemoveMessagefromlanggraph.graph.messageimportREMOVE_ALL_MESSAGESfromlangchain.agentsimportAgentStatefromlangchain.agents.middlewareimportbefore_modelfromlanggraph.runtimeimportRuntime@before_modeldeftrim_messages(state:AgentState,runtime:Runtime)->dict|None:"""只保留最近几条消息,防止上下文溢出。"""messages=state["messages"]iflen(messages)<=3:returnNone# 不需要裁剪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,添加自定义字段:
classCustomAgentState(AgentState):user_id:str# 用户 IDpreferences:dict# 用户偏好agent=create_agent(model=model,state_schema=CustomAgentState,checkpointer=checkpointer,)工具可以通过ToolRuntime读写这些自定义状态字段:
@tooldefget_user_info(runtime:ToolRuntime)->str:"""查询当前用户信息。"""user_id=runtime.state["user_id"]# 从 Agent State 中读取returnf"用户ID:{user_id}"@tooldefupdate_preferences(new_theme:str,runtime:ToolRuntime):"""更新用户偏好设置。"""fromlanggraph.typesimportCommand current_prefs=runtime.state.get("preferences",{})current_prefs["theme"]=new_themereturnCommand(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 的记忆。
# Agent 级记忆:所有用户共享namespace=("my-coding-agent","memories")# 用户级记忆:按用户隔离namespace=("user-123","memories")# 用户 Anamespace=("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的核心能力。它的思想很简单:不同的文件路径,路由到不同的后端存储。
fromdeepagents.backendsimportCompositeBackend,StateBackend,StoreBackend backend=CompositeBackend(default=StateBackend(),# 默认路由:临时文件routes={"/memories/":StoreBackend(namespace=lambdart:(rt.context.user_id,"memories"),),"/policies/":StoreBackend(namespace=lambdart:(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 随时间积累知识,越来越'懂'这个领域示例文件内容:
## 从反馈中学到的经验 - 2026-01-15: 用户希望减少解释,直接给代码 - 2026-01-20: 用户希望错误信息附带修复建议场景 3:知识库累积(project/*.md)
一句话总结:让 Agent 跨多次对话逐渐构建项目知识库。
怎么用:
- 按项目子目录组织文件:
/memories/project/tech-stack.md - 每次对话,Agent 读取已有内容,追加新信息
- 新对话启动时,Agent 加载完整的项目知识
实际运行效果:
[场景 3] 知识库累积 — project/tech-stack.md → 跨多次对话逐渐构建项目知识库示例文件内容:
## 项目技术栈 - 前端: React 18 + TypeScript - 后端: FastAPI + Python 3.12 - 数据库: PostgreSQL 16 - 部署: Docker + K8s场景 4:研究项目持续推进(research/*.md)
一句话总结:让大型研究任务可以"分多次对话"持续推进。
怎么用:
- 用多个memory 路径加载不同研究文件
- Agent 启动时加载所有文件,了解当前进度
- 每次对话更新对应的文件
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)填充内容
如何实现:
agent=create_deep_agent(model=model,memory=["/memories/preferences.md",# 用户级(可读写)"/policies/compliance.md",# 组织级(只读)],backend=CompositeBackend(default=StateBackend(),routes={"/memories/":StoreBackend(namespace=lambdart:(rt.context.user_id,"memories"),# 用户级),"/policies/":StoreBackend(namespace=lambdart:(rt.context.org_id,),# 组织级),},),)从应用代码中填充组织级记忆:
fromlanggraph_sdkimportget_clientfromdeepagents.backends.utilsimportcreate_file_data client=get_client(url="<DEPLOYMENT_URL>")awaitclient.store.put_item((org_id,),"/compliance.md",create_file_data("""## 合规政策 - 不得披露内部定价 - 金融建议必须附加免责声明 """),)安全提醒:如果一个用户能写入另一个用户读取的记忆,恶意用户可以注入指令。共享策略应该用只读模式——通过应用代码填充,不让 Agent 写入。
6.2 情景记忆(Episodic Memory)
用在什么场景:
- 回忆"上次是怎么解决这个问题的"
- 搜索过去的完整对话记录
- Agent 回溯上次调试过程,直接跳到可能的根因
和语义记忆的区别:
- 语义记忆:记"是什么"(事实和偏好)→
/memories/preferences.md - 情景记忆:记"发生了什么"(完整经历)→ 过去的对话历史
如何实现:
DeepAgents 的Checkpointer 天然支持情景记忆——每次对话都被完整持久化。要让过去的对话变得可搜索,可以包装一个搜索工具:
fromlanggraph_sdkimportget_clientfromlangchain.toolsimporttool,ToolRuntime client=get_client(url="<DEPLOYMENT_URL>")@toolasyncdefsearch_past_conversations(query:str,runtime:ToolRuntime)->str:"""搜索过去的对话以获取相关上下文。"""user_id=current_user_id(runtime)threads=awaitclient.threads.search(metadata={"user_id":user_id},limit=5,)results=[]forthreadinthreads:history=awaitclient.threads.get_history(thread_id=thread["thread_id"])results.append(history)returnstr(results)这对执行复杂多步任务的 Agent 尤为有用——比如代码 Agent 可以回溯上次调试过程,直接跳到可能的根因。
6.3 记忆的六个维度全景
官方文档将记忆系统拆分为六个可独立配置的维度:
| 维度 | 核心问题 | 选项 |
|---|---|---|
| 持续时间 | 保留多久? | 短期(单次对话)/ 长期(跨对话) |
| 信息类型 | 记什么? | 情景记忆 / 程序性记忆(Skills)/ 语义记忆(事实) |
| 作用域 | 谁能看? | 用户级 / Agent 级 / 组织级 |
| 更新策略 | 何时写入? | 对话中(默认)/ 对话间(后台整合) |
| 检索方式 | 如何读取? | 启动加载(memory=)/ 按需读取(Skills) |
| 权限控制 | Agent 能写吗? | 读写(默认)/ 只读(共享策略) |
这六个维度互相独立,你可以自由组合。比如:
- “用户偏好” = 长期 + 语义记忆 + 用户级 + 对话中写入 + 启动加载 + 读写
- “合规政策” = 长期 + 语义记忆 + 组织级 + 应用代码写入 + 启动加载 + 只读
七、从开发到生产:Store 的升级路径
| 阶段 | Store 类型 | 特点 |
|---|---|---|
| 开发阶段 | InMemoryStore | 零配置,快速迭代,重启丢失 |
| 生产阶段 | PostgresStore | 真正持久化,可伸缩,需要pip install langgraph-checkpoint-postgres |
| LangSmith 部署 | 平台自动配置 | 无需手动管理,平台自动提供持久化存储 |
记忆文件格式(deepagents >= 0.5 v2 格式)
{"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——如何为敏感操作添加人工审批,构建安全的人机协作流程。