📑 本页目录(点开跳转)
04b · Agent 记忆
⏱ 62 分钟 | ⭐⭐ 会话结束之后,什么留下来
🎯 一句话
上下文工程管的是这一次对话里注意力怎么花;记忆管的是这一次结束之后什么值得留下来、下一次怎么找回来。
记忆是 Agent 自己写的可写状态,RAG 是别人写好的只读语料。 读法几乎一样,写法完全不同——这一章大半篇幅都花在写入侧。
🧭 一、先划三条边界
「记忆」这个词特别容易和旁边三样东西糊成一团。先把边界划清楚,后面才不会绕。
边界一:和第 4 章上下文工程
第 4 章的四件武器(压缩 / 外置笔记 / 子 Agent / 渐进式披露)回答的是 「这一个会话里,有限的注意力预算怎么花」。记忆回答的是另一个问题: 「这个会话没了之后,什么应该还活着」。
| 你遇到的问题 | 归谁管 | 在哪一章 |
|---|---|---|
| 这一轮该带哪些 token 进去 | 上下文工程 | 第 4 章 |
| 历史太长了怎么压 | 上下文工程(压缩) | 第 4 章 |
| 会话断了,怎么接着干这个任务 | 工作记忆 | 本章 + 第 12 章 |
| 下周他再来,还记不记得他要中文回答 | 长期记忆 | 本章 |
| 上次这个工具为什么失败了 | 情景记忆 | 本章 |
🔑 一句话分界:上下文工程管「进」,记忆管「留」。 第 4 章的「结构化笔记(外置记忆)」其实就是记忆的一种实现,只是那一章从省上下文的角度讲; 这一章从跨会话的角度重讲一遍。同一件东西的两个面。
边界二:和 RAG ⭐(最容易混,也是面试最爱问的一条)
| RAG(第 11 章) | 记忆(本章) | |
|---|---|---|
| 数据是谁写的 | 别人——产品手册、工单库、代码仓库 | ⭐ Agent 和用户在交互中自己写的 |
| 读写属性 | 只读语料,Agent 不改它 | 可写状态,每一轮都可能新增或推翻 |
| 一条数据错了 | 答错这一次 | ⭐ 错到你手动删掉为止 |
| 谁能看见 | 全体用户共享一份 | 按用户/项目隔离,串了就是事故 |
| 产生速度 | 跟文档更新节奏(天、周) | 每一轮对话都可能产生新的 |
| 新旧冲突 | 新版文档直接覆盖旧的 | 新旧会同时存在,得自己裁决 |
| 检索技术 | 向量 + BM25 + 重排 | 几乎完全一样 |
🔑 压成一句:检索技术可以完全一样,治理规则完全不一样。 你可以把第 11 章那一整套(混合检索、RRF 融合、Cross-Encoder 重排)原封不动搬来做记忆检索, 一行都不用改。真正的新问题全在写入侧:什么该写、谁来写、旧的怎么办、别人的能不能看见。
⚠️ 一个很常见的错答:「记忆就是把聊天记录喂进 RAG」。 聊天记录是原料,不是记忆。原始对话是流水账(充满代词、临时上下文、说了又改的话), 而一条记忆必须是能脱离原文独立成立的事实。把流水账直接向量化,你会得到一个检索出来全是 「嗯好的」「那就这样吧」的库。
边界三:和长上下文
「窗口 100 万 token 了,把所有历史都带上不就有记忆了?」——不行,四个理由:
| 理由 | 说明 |
|---|---|
| 成本 | 每轮带全部历史 vs 带 5 条相关记忆,差几百倍(和第 11 章那笔账同源) |
| 腐烂 | 塞满 ≠ 用得好(第 4 章) |
| 会话是会断的 | 上下文不是持久化。进程重启、换设备、隔一周再来,窗口再大也是空的 |
| ⭐ 不能忘 | 长上下文只能让它「不忘」,不能让它「忘掉该忘的」 |
最后一条最本质:用户去年的地址、上个月已经作废的方案,在长上下文里和最新信息平等地占着注意力。 记忆系统的价值有一半在删除权——这一点长上下文永远给不了。
🗂️ 二、四层记忆
① 短期记忆:当前会话
是什么:当前会话的 messages 数组——就是第 1 章里那个「唯一状态」。
存在哪:只在上下文窗口里,别的地方都没有。
活多久:一轮到一次会话,进程重启即消失。
坏掉的样子:会话越长越健忘,也就是第 4 章的上下文腐烂。
💡 短期记忆不需要你设计,它是免费自动的。你要做的只有一件事:控制它的体积(第 4 章)。
② 工作记忆:当前任务的中间状态
是什么:这个任务做到哪了、下一步是什么、哪些方案试过了不行、关键路径、报错原文。
存在哪:⭐ 上下文之外的文件或表——progress.json、todo.json、decisions.md。
这就是第 4 章的「外置笔记」,也是第 12 章 harness 的地基。
活多久:和任务一样长。任务结束就该归档,不该混进长期记忆。
坏掉的样子:会话重启后重复劳动、把已经推翻的方案再试一遍——第 12 章「五头怪」里的状态丢失。
⚠️ 这是最常被跳过的一层。 很多人一想到「给 Agent 加记忆」就直奔长期记忆, 结果做出一个记得住用户是左撇子、却记不住自己十分钟前刚试过什么的 Agent。 工作记忆救的是任务成败,长期记忆救的只是体验。 先做前者。
③ 长期记忆:跨会话的事实与偏好
是什么:稳定的、和某个具体任务无关的东西。分三类:
| 类别 | 例子 |
|---|---|
| 关于用户 | 「回答用中文」「别用 emoji」「我是后端,不用给我解释 HTTP」 |
| 关于世界 | 「生产库叫 prod-main」「发版走周四」「部署环境是 K8s」 |
| 关于自己 ⭐ | 「这个 API 的分页从 0 开始」「这个仓库跑测试要先起 docker」——Agent 自己踩出来的操作经验 |
存在哪:少量、结构化、要精确命中的放 KV 或关系库;多、模糊、要语义检索的放向量库。 活多久:跨会话,直到被推翻或过期。 坏掉的样子:⭐ 把一次性的东西记成了永久偏好——用户说了一次「这次简短点」,从此半年惜字如金。
④ 情景记忆:做过什么、结果如何
是什么:一条条带时间戳的事件——什么时候、做了什么、用了哪个工具、成功还是失败、花了多少。 存在哪:只追加的事件日志(第 13 章「大脑与双手解耦」里的 Session 就是这个)。 活多久:留档期由合规决定,不由 Agent 决定。
它有三个用处,缺一个都可惜:
- 让 Agent 不再犯同一个错(「上次带
force=true删炸过一次」) - 让你能复盘——第 14 章反复强调的「读转录」,读的就是它
- 事后审计与归因(第 16 章)
⚠️ 情景记忆和长期记忆的关键区别: 情景记忆记的是发生过什么(不可改写),长期记忆记的是现在成立什么(可以被推翻)。 搞混了就会出现「用户三个月前说过喜欢深色主题」被当成「用户现在喜欢深色主题」用。
⭐ 正确关系:情景记忆是原料,长期记忆是从原料里提炼出来的结论, 而且提炼出来的每条结论都要留一个指回原料的指针——这是后面所有纠错手段的前提。
不是每层都要上
🔑 默认只有短期记忆。 加的顺序是:工作记忆 → 情景记忆 → 长期记忆。 情景记忆几乎总是值得的,因为它本来就是日志——你为了可观测性也要打(第 16 章)。 长期记忆放最后,因为它是唯一一个会把错误固化下来的层。
🛑 读到这里可以停 —— 前半章讲完了(约 30 分钟):记忆和上下文工程/RAG/长上下文的三条边界,以及四层记忆各自活多久、存在哪。 后半章还有:写入判据(什么该记、谁来决定写、一条记忆该多大) · 读出的分层策略与拼装顺序 · 遗忘与冲突裁决 · 记忆带来的三个新失败模式(含一个 4 个月才被发现的事故) 回来的时候不用重读,直接从下一节接着看就行。
✍️ 三、写入:什么该记
⭐ 记忆系统崩掉的头号原因不是记不住,是记太多。 一个塞了 800 条垃圾的记忆库,检索出来的 Top-5 全是废话,效果比没有记忆更差—— 因为它还挤掉了本该留给推理的 token。
三条写入判据(要同时满足)
① 跨会话仍然成立
「把这段翻译成英文」→ 不成立,是一次性指令
「我的母语是中文」 → 成立
② 未来还会被用到
临时算出来的中间值、这次的文件路径 → 不会
③ ⭐ 重新问一遍的代价 > 记错的代价
直接问用户只要 3 秒的事,别记
—— 记错了要跟着他半年
第 ③ 条最容易被忽略,也最能砍掉垃圾。能便宜地重新获取的信息,不要记。
不该进长期记忆的东西
| 别记 | 为什么 | 该放哪 |
|---|---|---|
| 当前任务的进度 | 任务一结束就是垃圾 | 工作记忆 |
| 用户的原话 | 是原料不是结论 | 情景记忆 |
| 模型自己的推测 💀 | 「用户好像是产品经理」——猜的东西一旦固化,之后全按错画像服务 | 别写;非要写就单独标 confidence 并单独一类 |
| 密码、密钥、身份证号 | 记忆库不是保险箱,而且它会被检索出来塞进提示词 | 不记(第 15 章) |
| 一次性的语气要求 | 「这次简短点」 | 短期记忆足够 |
谁来决定写
三种方式,实际系统通常混用:
| 方式 | 怎么做 | 优点 | ⚠️ 缺点 |
|---|---|---|---|
| 规则抽取 | 用 schema 抽固定字段(语言、时区、常用仓库) | 确定、可测、零幻觉 | 只能抓到你预先想到的 |
| 模型自判 | 每次会话结束问它「这次有什么值得长期记住的」 | 覆盖面广 | ⭐ 它系统性地倾向于多记,还会把推测写成事实 |
| 用户显式 | 用户说「记住这个」,或在设置里自己管 | 最准,而且用户有知情权 | 覆盖率低 |
🔑 一个稳的默认配置:规则抽取管结构化字段 + 模型自判但必须过一遍 「这句话能不能脱离本次对话独立成立」的过滤 + 长期记忆对用户可见可删。 第三条不只是合规要求——它是你唯一的兜底纠错通道。
写入粒度:一条记忆该多大
❌ 太大:「用户做数据平台,团队 8 人,用 Airflow,讨厌 YAML,上季度迁过 K8s」
→ 五个事实捆一起,作废其中一个就得重写整条
→ 检索时也会被无关部分带偏相似度
❌ 太小:「用户 · Airflow」
→ 丢了限定条件,一年后没人知道这句还成不成立
✅ 判据:一条记忆 = 一个可以被【独立推翻】的事实
每条记忆自带五样东西,少一样后面就有一类问题修不了:
# 一条长期记忆的最小结构 —— 这几个字段一个都不能省
import time, uuid, json
def make_memory(text, source_event_id, scope, confidence=0.9):
return {
"id": str(uuid.uuid4()),
"text": text, # ⭐ 必须能脱离原对话独立成立
"source": source_event_id, # ⭐ 指回情景记忆,出错时能追到原文
"scope": scope, # ⭐ 隔离键:哪个用户/哪个项目
"confidence": confidence, # 模型猜的要显著低于用户明说的
"created_at": time.time(),
"last_used_at": time.time(), # ⭐ 遗忘策略靠它
"use_count": 0,
"superseded_by": None, # ⭐ 被推翻时填新记忆 id,而不是删掉
}
m = make_memory("回答一律用中文", "ev_881", {"user": "u_123"})
print(json.dumps(m, ensure_ascii=False, indent=2))
什么时候写
| 时机 | 写哪一层 | 为什么是这个时机 |
|---|---|---|
| 每一轮结束 | 工作记忆 | 便宜,而且必须实时(随时可能断) |
| 会话结束 ⭐ | 长期记忆 | 这时候才看得出哪些东西真的重要,也避免一轮一轮写进重复内容 |
| 事件发生时 | 情景记忆 | 它本来就是日志 |
| 用户明说时 | 任何层 | 用户的显式指令优先级最高 |
📖 四、读出:怎么把记忆送回上下文
三种读法
| 读法 | 做法 | 什么时候用 | 代价 |
|---|---|---|---|
| 全量注入 | 把全部长期记忆拼进系统提示词 | 条数很少(几十条以内) | 一多就退化成第 4 章的腐烂问题 |
| 按需检索 | 拿当前问题去检索 Top-K 条 | 记忆多、且大部分与当前话题无关 | ⚠️ 检索不到就等于没有 |
| ⭐ 分层(推荐) | 极少量核心记忆常驻 + 其余按需检索 | 绝大多数生产系统 | 要维护「什么算核心」 |
🔑 为什么分层是默认答案:有一类记忆是每一轮都必须生效的 (说中文、不用 emoji、生产环境不许动),它们靠检索根本不可靠—— 用户问「帮我看下这段代码」时,「回答用中文」这条压根不会被检索命中。
约束型记忆必须常驻,并把总量压在 500 token 以内 (和第 8 章 Tool Search 那个常驻搜索工具是同一个量级)。 其余的事实型记忆才走检索。
拼装顺序:一个会烧钱的细节
回到第 4 章的缓存铁律——前缀逐字节相同才命中:
[系统提示词] ← 稳定
[常驻核心记忆] ← 稳定,放这里 ✅
[对话历史] ← 追加
[检索出来的记忆] ← 每轮都在变,必须放【最后】⭐
[当前问题]
两个理由:① 检索结果每轮都不同,放前面会把整段前缀的缓存打掉; ② 位置效应(第 3 章)——当前最相关的东西离问题越近越容易被用上。
记忆预算
⭐ 给记忆划一条硬预算,让它和工具定义、检索结果一起竞争,建议不超过上下文预算的 5%。 超了就是在拿「它记得」换「它想得清楚」——而后者更值钱。
⚠️ 别把记忆和 RAG 混成一次检索
很常见的做法:长期记忆和 RAG 文档塞进同一个向量库、同一次检索、同一个排序。别这么干。
两者的可信度和时效性完全不同(一条是用户三个月前的偏好,一条是上周更新的产品文档), 混在一起排序,你无法解释为什么某条被挤掉。
✅ 正确做法:两路分开检索、分两个区拼进提示词、各自标注来源。 出问题时你才能一眼看出该修哪边——和第 11 章「必须分开评估检索与生成」是同一个道理。
🧹 五、遗忘与冲突
为什么必须有遗忘
① 检索质量:噪声条目多了以后 Top-5 里全是过时的; ② 成本:每轮都在为不再成立的事实付 token; ③ ⭐ 最重要:错的记忆不遗忘,就等于永久生效。
💀 一个真实形态的事故
一个企业内部助理 Agent。用户在某次对话里说:「这个季度我们暂时不做移动端。」 模型判断这值得长期记住,但写进去的是「用户不负责移动端」——限定词被抹掉了。
| 发生了什么 | 此后凡是涉及移动端的问题,Agent 都主动跳过或建议「你可以问问别的同事」 |
| 为什么没被发现 | ⭐ 它不报错。 每一次回答单独看都合理,而且没有任何一条日志会写「我因为某条记忆而没做某事」 |
| 代价 | 拖了 4 个月,直到用户投诉「你为什么从来不给我看移动端的数据」才被发现 |
| 该补什么 | ① 写入时保留限定词(「这个季度」不能被抹掉)② 每条记忆带 TTL 或复核期 ③ ⭐ 在追踪里记录「这一轮用上了哪几条记忆」 |
🔑 第 ③ 条要单独记住:记忆是全链路里唯一一个能悄悄改变行为、却不在任何日志里露面的东西。 工具调用会留痕、检索会留痕、提示词会留痕,只有「因为记着某件事所以没做某件事」不会。
过期怎么判(三种策略叠加用)
| 策略 | 怎么做 | 适合 |
|---|---|---|
| TTL | 写入时定有效期,到期转「待复核」而不是直接删 | 明显有时限的(「这个季度…」) |
| 使用衰减 | 得分 = 重要性 × 时间衰减 × 使用频次,长期没被命中的降权到检索不出来 | 大量事实型记忆 |
| 容量上限 | 每个用户最多 N 条,超了淘汰得分最低的 | 兜底,防无限增长 |
⭐ 一条实践建议:淘汰用降权,不要物理删除。 降权可以回滚,删除不能;而且被删掉的记忆没留下「曾经存在过」的痕迹,日后排查会一头雾水。 物理删除只在两种情况下做:用户要求删除、合规要求删除 (见《数据这一关》19 章)。
新旧矛盾了怎么办
四步裁决,顺序不能换:
① 是不是【真冲突】?
「喜欢 Python」和「喜欢 Go」→ 可以同时成立,是补充
「在北京」和「在上海」 → 同一属性 + 互斥取值,才是冲突
⚠️ 跳过这一步的后果:把补充当冲突处理,记忆库越记越少
② 谁更新?
默认新的赢 —— 但带时间限定的旧记忆
不会被一条无限定的新记忆推翻
③ 谁更可信?
用户明说 > 规则抽取 > 模型推断
④ ⭐ 别删旧的,填 superseded_by 指向新的
留下这条链,你才能回答「它为什么突然改口了」
Memory 的 CRUD:Update 才是难的那个
| 操作 | 难在哪 |
|---|---|
| Create | 不难。难的是判断该不该 Create(第三节那三条判据) |
| Read | 不难。第 11 章那一套照搬 |
| Update ⭐ | ⭐ 最难。 你得先找出「这条新信息在改的是哪条旧记忆」——这本身就是一次检索,而且是召回率要求极高的检索:漏了就变成两条矛盾的记忆并存 |
| Delete | 技术不难,难在合规和可解释性(见上面那条建议) |
🔑 一个能判断系统成熟度的问题:问它「用户改了一个偏好,你的系统是新增一条还是改一条」。 答「新增」的,跑三个月后记忆库里会躺着五条互相矛盾的偏好,而且检索时它们会一起被召回。
💀 六、记忆带来的新失败模式
前五节讲怎么做对。这一节讲的是:加了记忆之后,你多了哪些原来没有的风险。
| 新风险 | 为什么记忆特别危险 | 怎么防 |
|---|---|---|
| 错误记忆固化 | 普通错误答完这轮就过去了;错误记忆会在之后每一轮继续参与决策 | 三条写入判据 + TTL + 追踪里记录用了哪几条 |
| 记忆污染 ⭐ | 第 15 章的提示注入是一次性的:这次读到恶意网页,这次被骗。但如果 Agent 把恶意内容写进了长期记忆,攻击就从「一次」变成「永久」——攻击者投毒一次,之后每次检索都在替他重新加载 | ⭐ 外部内容永远不能直接变成记忆:记忆只能从用户说的话和自己的执行结果里提炼;工具/网页返回的内容要显式标注为外部数据(第 15 章) |
| 跨用户串记忆 💀 | 一条记忆写进了错的 scope,A 的信息就会出现在 B 的回答里。这是数据泄露级别的事故,不是 bug | 隔离键必须由存储层强制(分库 / 分 namespace / 行级权限),不能只靠检索时加 filter——一次忘写就漏。测试里要有「用 B 的身份检索 A 的关键词,命中数必须是 0」这一条 |
| 评测不可复现 | 有记忆的 Agent 每次跑的初始状态都不同,第 14 章「每次试验从干净状态开始」被直接破坏 | 记忆层必须可重置、可注入固定初始状态;同时单独评一遍「带记忆」的表现,因为那才是线上的样子 |
| 隐私与留存 | 长期记忆是长期存着的个人数据,受留存期和删除权约束 | 见《数据这一关》19 章 |
🔑 整张表压成一句: 记忆把「一次性的错」变成了「持续性的错」,把「一次性的攻击」变成了「持久化的后门」。 所有防护手段的共同点只有一个——让每一条记忆都能被追到来源、被看见、被撤销。
🧭 七、你到底需不需要一个记忆系统
延续第 6 章的态度:先证明必要,别为了「看起来像个助理」而上记忆。
| 你观察到的症状 | 需要哪一层 |
|---|---|
| 一次会话里就开始忘事 | 不是记忆问题,是上下文问题(第 4 章) |
| 会话断了要从头来 | 工作记忆 |
| 用户每次都得重复交代同样的偏好 | 长期记忆——但先从规则抽取三五个固定字段开始,别一上来就上向量库 |
| 想知道它上次为什么那么做 | 情景记忆 = 日志,本来就该有 |
| 一次性任务、无状态接口 | ⭐ 都不需要 |
🔑 最小可用的记忆系统长什么样:一张表、几个字段,规则抽取三到五个固定偏好, 全量注入系统提示词,用户可见可删。没有向量库,没有模型自判,没有遗忘策略。 大多数产品在这一档就够了。撑不住了再往上加,加的顺序是: 模型自判写入 → 按需检索 → 遗忘策略 → 冲突裁决。
🔨 八、动手:给你的 Agent 做一次记忆审计
- 翻出一次「它突然变得很奇怪」的对话,问三个问题: 这一轮它带进去了哪几条记忆?其中哪条是三个月前的?哪条是模型自己猜的?
- 如果你答不上第 1 题 —— 恭喜,你找到要修的第一件事了: 先把「本轮用了哪几条记忆」打进追踪,别的都往后放
- 导出长期记忆库,按第三节的三条判据逐条过,统计不该记的占比
- 用另一个用户的身份去检索你自己的关键词,看命中数是不是 0
🕳️ 九、六个常见坑
| 坑 | 症状 | 修 |
|---|---|---|
| 什么都记 ⭐ | 检索 Top-5 全是废话,越用越笨 | 三条判据,尤其第 ③ 条「能便宜重新获取的不要记」 |
| 把推测记成事实 | 按一个错误画像服务半年 | 猜的单独标 confidence,或干脆不写 |
| 只做长期记忆不做工作记忆 | 记得住用户是左撇子,记不住自己十分钟前试过什么 | 先做工作记忆 |
| 记忆和 RAG 混一次检索 | 排序不可解释,出问题不知道修哪边 | 两路分开检索、分区拼装、各自标来源 |
| 只增不改 | 三个月后五条矛盾的偏好并存 | Update 而不是 Create,用 superseded_by |
| 隔离靠检索时加 filter 💀 | 一次忘写就跨用户泄露 | 隔离键下沉到存储层强制 |
🔗 这一章连到哪里
| 去哪 | 为什么 |
|---|---|
| 04 · 上下文工程 | 那一章决定「这一轮带哪些进去」,这一章决定「什么留下来」。「结构化笔记」那一节是两章的交界:从那边看是省 token,从这边看是工作记忆 |
| 11 · 检索与 RAG | 记忆的读出几乎可以照搬那一套(混合检索、RRF、Cross-Encoder 重排),真正不一样的是写入和治理 |
| 12 · Harness | 工作记忆的完整实现在那里:progress.json、从出错点恢复、五头怪里的「状态丢失」 |
| 15 · 安全:沙箱与提示注入 | 记忆污染 = 持久化的提示注入。要理解「为什么外部内容不能直接变成记忆」,得先看那一章的注入原理 |
| 14 · 评测 Evals | 有记忆之后,「每次试验从干净状态开始」这条会被破坏。评测环境必须能重置和注入记忆 |
| 数据这一关 19 · 隐私脱敏与留存 | 长期记忆是长期存着的个人数据。留存多久、怎么删、删到什么程度算删干净,按那一章办 |
✅ 检查点
- 上下文工程和记忆的一句话分界是什么?
- Memory 和 RAG 的本质区别是什么?哪一部分技术可以直接照搬,哪一部分完全不能?
- 长上下文为什么替代不了记忆系统?最本质的那条理由是什么?
- 四层记忆分别是什么?各存在哪?加的顺序建议是什么?
- 写入长期记忆的三条判据是什么?哪一条最容易被忽略?
- 「一条记忆该多大」的判据是什么?一条记忆要自带哪几个字段?
- 为什么读出要分层?什么样的记忆必须常驻、总量控制在多少?
- 新旧记忆冲突的四步裁决是什么?第一步为什么不能跳?
- 记忆污染和普通提示注入的区别是什么?防线在哪?
- 跨用户串记忆该怎么防?为什么「检索时加 filter」不够?
👀 答案
- 上下文工程管「进」(这一轮带哪些 token),记忆管「留」(会话结束后什么还活着)。第 4 章的「外置笔记」是两者的交界。
- RAG 是别人写好的只读语料,记忆是 Agent 自己写的可写状态;RAG 一条错只错这一次,记忆一条错会错到你手动删掉为止。检索技术可以完全一样(混合检索 / RRF / 重排照搬第 11 章),完全不一样的是写入与治理:什么该写、谁来写、旧的怎么办、谁能看见。
- 成本(差几百倍)、腐烂、会话是会断的(上下文不是持久化)、以及最本质的一条:长上下文只能让它「不忘」,不能让它「忘掉该忘的」——作废的信息会平等地占着注意力。记忆系统的价值有一半在删除权。
- 短期(messages,上下文窗口内,一次会话)、工作(任务中间状态,progress.json 等外置文件)、长期(跨会话的事实偏好,KV 或向量库,按用户 id 强制分区)、情景(做过什么,只追加的事件日志)。加的顺序:工作 → 情景 → 长期,长期放最后因为它是唯一会固化错误的层。
- ①跨会话仍成立 ②未来还会被用到 ③重新问一遍的代价 > 记错的代价。第 ③ 条最容易被忽略——能便宜地重新获取的信息不要记。
- 一条记忆 = 一个可以被独立推翻的事实(太大则作废一条要重写整条、检索还会被无关部分带偏;太小则丢限定条件)。字段:text、source(指回情景记忆)、scope(隔离键)、confidence、created_at / last_used_at、superseded_by。
- 因为约束型记忆靠检索不可靠——用户问「帮我看下这段代码」时,「回答用中文」这条根本不会被命中。所以约束型记忆必须常驻,总量压在 500 token 以内;事实型记忆才走检索。整个记忆预算建议不超过上下文的 5%。
- ①先判是不是真冲突(同一属性 + 互斥取值)②谁更新(但带时间限定的旧记忆不被无限定的新记忆推翻)③谁更可信(用户明说 > 规则抽取 > 模型推断)④不删旧的,填 superseded_by。第一步不能跳,否则会把「补充」当「冲突」处理,越记越少。
- 普通提示注入是一次性的(这次读到恶意内容这次被骗);记忆污染是持久化的后门——投毒一次,之后每次检索都在替攻击者重新加载。防线:外部内容永远不能直接变成记忆,记忆只能从用户说的话和自己的执行结果里提炼。
- 隔离键必须由存储层强制(分库 / 分 namespace / 行级权限)。只靠检索时加 filter 不够,因为一次忘写就泄露,而且这类漏写不会报错。测试要有「用 B 的身份检索 A 的关键词,命中数必须是 0」。
🛑 可以停在这里
⚡ 走神救援
上下文工程管「进」,记忆管「留」——第 4 章解决「这一轮带哪些 token」,这一章解决「会话没了之后什么还活着」。⭐Memory vs RAG:RAG 是别人写好的只读语料,记忆是 Agent 自己写的可写状态;RAG 一条错只错这一次,记忆一条错会错到你手动删掉为止;⭐检索技术可以完全照搬第 11 章(混合检索/RRF/重排),完全不一样的是写入与治理。⚠️「把聊天记录喂进 RAG」是错的——聊天记录是原料,记忆必须是能脱离原文独立成立的事实。长上下文替代不了它:成本差几百倍、腐烂、会话是会断的,最本质的是⭐长上下文只能让它「不忘」,不能让它「忘掉该忘的」——记忆系统的价值有一半在删除权。四层:短期(messages,会话内,免费自动,你只要控体积)/工作记忆(任务中间状态,progress.json 外置,⚠️最常被跳过——很多人做出记得住用户是左撇子、却记不住自己十分钟前试过什么的 Agent,工作记忆救任务成败,长期记忆只救体验)/长期(跨会话事实与偏好,KV 或向量库,按用户 id 强制分区)/情景(只追加的事件日志,原料)。⭐情景记忆记「发生过什么」不可改写,长期记忆记「现在成立什么」可被推翻;加的顺序是工作→情景→长期。写入三判据:跨会话仍成立、未来还会用到、⭐重新问一遍的代价 > 记错的代价(能便宜重新获取的不要记);💀别把模型的推测写成事实。粒度判据:一条记忆 = 一个可以被独立推翻的事实,自带 text/source/scope/confidence/时间/
superseded_by。读出分层:约束型记忆(说中文、生产不许动)必须常驻且压在 500 token 以内——靠检索不可靠;事实型才检索;检索结果必须放最后否则打掉前缀缓存;记忆总预算不超过上下文的 5%;⚠️别把记忆和 RAG 混成一次检索,两路分开、分区拼装、各标来源。遗忘:TTL/使用衰减/容量上限,⭐淘汰用降权不用物理删除。💀事故:用户说「这个季度暂时不做移动端」被记成「用户不负责移动端」,限定词被抹掉,Agent 此后 4 个月主动跳过移动端问题,它不报错、每次单看都合理——⭐记忆是全链路里唯一能悄悄改变行为却不留日志的东西,所以追踪里必须记「这一轮用了哪几条记忆」。冲突四步:先判是不是真冲突(跳过就会把补充当冲突,越记越少)→谁更新→谁更可信→填 superseded_by 别删。CRUD 里 Update 最难:得先检索出「在改哪条旧的」,漏了就两条矛盾并存。三个新风险:错误固化、⭐记忆污染=持久化的提示注入(投毒一次、之后每次检索都替攻击者重新加载)→ 外部内容永远不能直接变成记忆、💀跨用户串记忆——隔离键必须由存储层强制,只靠检索时加 filter 一次忘写就泄露,测试里要有「用 B 的身份检索 A 的关键词命中数必须是 0」。最后:先证明必要——最小可用版是一张表几个字段、规则抽取三五个偏好、全量注入、用户可见可删,没有向量库也没有模型自判。
下一节 👉 05-ClaudeCode实操-指令该放哪.md