DeepAgents(十六):Agent 的长短记忆
Agent 的长短记忆
模型每次调用都是干净的。你上一轮说的话,它这一轮一个字都看不到。
想让它记住,就得在外面搭一层东西。搭之前先把 2 件事分开:这次对话里要记住的,和下次还想记住的。
一、模型自己不带记忆
先跑一遍裸的。不挂会话状态,也不挂任何外部存储,连着问 2 轮。
第 1 轮模型收到 2 条消息,1 条 system,1 条 user。第 2 轮还是这 2 条,上一轮报过的名字,这一轮 system 里一个字都搜不到。
「记住」这件事,框架不给你加,模型自己就没有。
二、短期记忆活在一条会话线上
挂上会话状态再看。同一条会话线跑 2 轮,模型收到的消息从 2 条涨到 4 条,多出来的是上一轮的问答。
换一条会话线,立刻掉回 2 条。切回原来那条,历史又接上,再跑一轮变成 6 条。
所以短期记忆的边界很硬:它认会话线,不认人。 同一个用户开一条新线,它照样当你是新的。
第 2 道约束是窗口。会话越长,历史越占地方,于是有 3 条常见做法:裁剪,只留最近 N 条;删除,精确删掉某几条;摘要,把旧消息压成一段。Deep Agents 默认内置摘要,窗口用到 85% 触发。
第 3 道约束是进程。会话状态放内存里,服务重启就没了;放库里,重启照样接得上。这一条决定了短期记忆能不能扛住发布。
三、长期记忆必须落到外部存储
想让记忆活过会话线,就只能把它放到模型上下文之外。
我把 3 条偏好写进本机 Postgres,然后开一条全新的会话线提问。按问题做向量检索,前 2 条是「编辑器偏好:vim」,相似度 0.67,和「身份:老程,搜索架构师」,相似度 0.41。
再狠一点,把整个进程杀掉,重新起一个进程查库,同一条偏好还在。
这就是长期记忆的实质:它是一套外部存储加一套取用规则。取用规则可以是启动时全量塞进提示词,也可以是每次按问题检索几条,这 2 种付法差得很远。
四、按内容再分成 4 类
认知心理学把人的记忆分成 4 类,Agent 领域把这个分法接了过来:工作记忆、情景记忆、语义记忆、程序记忆。这 4 类在 DeepAgents 里各有各的落点。
| 类型 | 记什么 | DeepAgents 里的落点 | 怎么退场 |
|---|---|---|---|
| 工作记忆 | 当前任务临时要用的信息 | 会话状态的消息列表、StateBackend 的 files 通道 |
会话结束就清 |
| 情景记忆 | 具体发生过的事,带时间和顺序 | StoreBackend 一个 namespace,按时间往下写 |
按时间衰减 |
| 语义记忆 | 剥离了时间的事实与偏好 | StoreBackend 另一个 namespace,对账后的事实 |
按重要性淘汰 |
| 程序记忆 | 一件事该怎么做 | AGENTS.md 常驻,SKILL.md 按需读 |
换文件就是换版本 |
StateBackend 和 StoreBackend 真正的差别是重启之后还在不在。前者把文件放进 agent state 的 files 键,类注释写的就是 ephemeral;后者按 namespace 分区,注释写的是跨所有线程都在。
程序记忆有 2 种装法。MemoryMiddleware 把 AGENTS.md 整份拼进系统提示,每轮都带着走;SkillsMiddleware 只把技能名和描述放进去,正文等真的要用时再 read_file 读。一个稳、贵,一个省、但要先付一次读文件的成本。
分类的价值在于:它决定了这条记忆该怎么退场。 不分类,你就只能对所有记忆用同一套淘汰规则,结果要么误杀硬事实,要么留一堆垃圾。
五、整个系统其实只有 3 个动作
记忆系统的骨架大体都一样:输入进来,拆到 3 个动作上,每个动作后面挂自己的实现和存储。添加、搜索、管理,换哪套技术栈都是这 3 件。
落到 DeepAgents 上,3 件事各有位置。写,Agent 自己调 write_file,或者你在图外面调 store.put;取,读文件由 backend 按路径分派,语义查询要自己接 store;管,框架里没有现成的,得自己写个定时任务。
这里有一处容易误解:这 3 个分支不是框架帮你判断的。 里面没有「操作类型判断」那一步,写和取是 2 处分开的代码,往哪落由路径前缀定。
CompositeBackend 的 default 是 StateBackend,routes 把 /memories/ 这个前缀指到 StoreBackend。写 /memories/x.md 就进库,写 /notes/x.md 就留在会话状态,路由是配出来的。
3 个动作里,检索最容易被低估。写入错了,下次还能改;检索错了,用户这一轮就直接收到错的答案。 而检索最难的地方在于:向量只回答「像不像」,答不了「哪条还算数」。
一条 3 天前的偏好和一条 200 天前的偏好,语义相似度可以只差 0.2,但前者才是真的。所以检索这一步必须有重排,光靠向量距离排,取回来的可能是半年前的旧答案。
写入和管理各有自己的一套。这篇先把零件清单定下来,让方案能落地。
六、4 个典型场景
用户偏好。 下次开口不用重新自我介绍,称呼、语气、禁忌都还在。
Agent 自我改进。 每次踩到的坑记下来,同一个错不犯第 2 次,这份记忆属于 Agent 自己,不属于哪个用户。
知识累积。 同一主题分几次聊完,第 3 次能接着前 2 次的资料往下走。
长任务推进。 一个任务要做几周,每次开工先读进度,收工再写回去。
前 2 个是记住人,量小、要求准。后 2 个是记住事,量大、要求能检索。这 2 类的表结构不一样,别混在 1 张表里。
七、定方案就是定 6 个维度
| 维度 | 要回答的问题 | 常见选项 | 产线默认 |
|---|---|---|---|
| 持续时间 | 活一次会话,还是活过所有会话 | 会话状态 / 外部存储 | 跨会话一律外部存储 |
| 信息类型 | 事实、经历,还是规程 | 语义 / 情景 / 程序 | 事实与经历分开存 |
| 作用域 | 一个人看,一个 Agent 看,还是全组织看 | 用户级 / Agent 级 / 组织级 | 默认用户级,共享的只读 |
| 更新策略 | 对话中写,还是对话结束后整理 | 热路径 / 冷路径 | 事实走热路径,归纳走冷路径 |
| 检索方式 | 开门全量搬,还是按问题取 | 全量注入 / 按需检索 | 量小全量,量大检索 |
| 权限 | Agent 能不能自己改 | 可写 / 只读 | 组织策略锁成只读 |
这 6 项定完,方案基本就定了。
反过来,很多记忆系统上线后越用越乱,是只想了第 1 项和第 3 项,剩下 4 项没定过。
八、「记忆就是一份文件」只在 2 个前提下成立
文件是最容易起步的做法:把内容写进一个 Markdown,启动时整份读进提示词,每轮都带着走。它成立的前提很窄,得同时满足量小、且每轮都要看。
先算一笔账。200 条短记忆,全量注入是 5274 字符、约 1319 token 一轮。
换成按问题检索 top-3,一轮 42 字符、约 11 token。差 120 倍。
连着跑 20 轮,一边是 26380 token,一边是 220 token。
再算上限。一条 82 字符的记忆约 21 token,128k 的窗口大约装 6095 条。用户量一上来,或者一个用户聊上几个月,这个数很容易破。
所以文件适合放那些小而稳、每轮都要看的东西:项目规矩、命名约定、发布前检查清单。属于某个人的、会越攒越多的记忆,得进库。
九、顺序反了要返工
先定要不要跨会话,这是 0 和 1 的差别,决定你要不要引入存储。
再定记哪几类内容,这决定表怎么设计。
然后定存哪:小规程放文件,人的偏好放关系库,大量经历放向量库。
最后定怎么取:全量注入,还是按问题检索。
记忆这件事,先回答「哪些要跨会话」,「用什么存」排在它后面。 这个问题答错,后面存得再讲究也是白搭。