记忆设计
窗口之外持久的东西 — 记忆类型分类、何时写/何时召回、结构化笔记作为持久的规划状态,以及记忆如何失效
为什么上下文窗口不是记忆
上下文窗口是工作记忆,不是存储。有三个性质让它不适合做长期记忆:
- 有上界 — 再大也会满。任何”都留着就行”的策略,规模一大就崩。
- 压缩时有损 — 摘要保留大意,丢失细节。你三个会话之后需要的那个具体信息,往往是压缩最先丢的。
- 按会话切分 — 新对话从零开始。没有外部机制,会话 A 学到的东西在会话 B 里就不存在。
真正的记忆系统活在窗口之外。读写都是 Agent 通过工具显式执行的操作。窗口是 cache,记忆是 disk。
这个框架很重要,因为它改变了设计问题。“Agent 该记住什么?“变成了:
- 什么被写进外部存储?
- 什么触发召回?
- 任一时刻载入多少?
- 过期记忆怎么被清掉?
每个问题的正解,取决于记忆的类型。
记忆分类学
认知科学区分几种记忆系统 — 情景、语义、程序、工作。这套分类用在 Agent 设计上同样好使,因为四种类型的读写模式确实不同。
| 类型 | 装什么 | 何时写 | 何时读 | 例子 |
|---|---|---|---|---|
| 情景 | 事件:发生了什么、何时、和谁 | 显著事件后(完成、出错) | 相似情境再次出现时 | ”上个月的发布因为 X 挂了” |
| 语义 | 事实、偏好、规则 | 用户陈述或示范时 | 任何决策中相关时 | ”用户偏好 Tailwind 胜过 CSS-in-JS” |
| 程序 | 做某事的方式 — 技能、手册 | 学到一个可靠流程后 | 任务匹配已知流程时 | ”这个团队写 commit message 的风格” |
| 工作 | 当前任务状态 | 任务进行中 | 整个任务期间;完成时丢弃 | 打开的文件清单、当前计划、待办 |
工作记忆属于上下文窗口之内(用结构化笔记强化 — 见下文)。其他三种都属于窗口之外的持久存储。
这套分类不是让你必须搭四个独立存储。它是一个分类工具:当你要存一条信息,先说清它是哪种,读写策略就自动来了。
为什么分得这么细
- 情景要按相似度召回。 “类似情境以前出现过吗?“是搜索问题,不是常载入问题。关键字或向量检索合适。
- 语义便宜到可以常载入。 用户偏好通常很小、到处适用。放进系统提示词骨架,常比一次召回往返更划算。
- 程序常常像技能。 “部署到 staging 的手册”想在用户当前任务匹配这个流程时被召回 — 不是永远载入。
- 工作记忆是可丢弃的。 任务之间丢了没成本;硬要持久化反而制造一堆过期状态。
混淆类型是常见失败。比如把情景式的”这一个 PR 出过问题 Y”塞进常载入的语义记忆里 — 20 个 PR 之后,系统提示词被不相关的战争故事塞满。
在上下文 vs 外置
“留在窗口里”和”外置”之间的界线是模糊的。一条实用规则:
重新推导代价高、检索代价低的,都外置。
重新推导代价高:深思熟虑后的决定、用户陈述的偏好、项目专属约定、通过工具发现的环境事实。
检索代价低:小、稳定、能被 Agent 已知的关键字或路径索引到。
模糊地带是任务进行中的对话状态 — 打开的文件、当前假设、待决事项。这属于工作记忆。两个选择:
- 留在窗口 — 简单,但会被压缩吃掉。
- 外置为结构化笔记 — 耐用,但有摩擦。
短任务留在窗口;长任务早点外置(见下文”结构化笔记”)。
写入策略
Agent 何时该写入记忆?三个触发器:
| 触发器 | 信号 | 写什么 |
|---|---|---|
| 用户明示 | ”记住……”、“从现在起……”、“下次……” | 近乎原话,保留用户自己的表述 |
| 纠正 | ”不,不要做 X”、“其实正确答案是 Y” | 规则 + 原因 — 让边界情况以后能判断 |
| 确认成功 | 用户无异议接受,尤其是非显然的选择 | 做法 + 情境(“情境 S 下,X 奏效”) |
第三个触发器最常被漏。只存纠正,教会 Agent 避开什么,却没教它该重复什么 — 久了 Agent 变得过度谨慎,丢失了用户已经验证过的直觉。确认成功同等权重地存下来。
不该写的:
- 能从当前代码/文档推导出的事实(
git log是权威的,别快照) - 易逝的任务状态(当前分支、打开的文件)
- 已经在 CLAUDE.md 式的项目指令文件里的内容
- 用户一时气话但没重申的
信号胜过覆盖
写入时为信号密度优化,不是完整性。一条记忆会在几个月里被反复读。每个字都重要。好的记忆条目:
- 以规则或事实开头
- 包含为什么 — 用户陈述或可推断的理由
- 包含如何应用 — 它在什么情境下生效
- 短到能一眼读完
坏的记忆条目复述对话发生了什么。流水账腐烂得飞快。
召回策略
检索是多数记忆系统悄悄失败的地方。两种常见模式:
Eager(常载入)
一份记忆索引进系统提示词。Agent 每轮都看到”有 12 条记忆,标题分别是……”。只有感觉相关时才取完整内容。
适合:小的、语义型记忆。稳定偏好、项目约定、用户身份。
不适合:情景琐事。一份列出 200 条过往事故的系统提示词就是噪音。
Lazy(按需)
Agent 有一个 recall_memory(query) 工具。感觉记忆相关时调用;工具返回匹配的条目。
适合:大存储、情景记忆、相关性逐例判断的场景。
不适合:Agent 自己不知道该问的记忆。用户两周前说过”一律用英式英语”,Agent 压根不去 recall — 它就会写美式英语而且感觉良好。
混合
常见的甜蜜点是 索引常载入,内容按需取:Agent 始终看到索引(标题 + 一句钩子),完整内容按需拉。骨架保持精简,Agent 又有词汇知道什么东西存在可召回。
召回预算
一次召回多少条是个调参。返回 20 条把上下文灌满噪音;返回 1 条会漏掉”两条记忆同时适用”的情况。合理默认是 3-5 条,按相关度排序。如果存储大到排序本身很难,考虑 LLM-side-query(用一个小模型根据用户当前轮决定取哪些)。
“不知道该问”的失败
最微妙的召回失败是:Agent 本该召回一条记忆,却压根没去调那个工具。从 Agent 的视角看,什么都没出错 —— 它自信地基于默认值回答了。从用户的视角看,Agent 无视了之前给过的指引。这类失败报告率低,因为双方都看不到缺失的那条记忆。
四种机制可以降低”不知道该问”的风险,按从重到轻排列:
-
常载入索引。 记忆索引住在系统提示词里;Agent 每轮都看到”有 X、Y、Z 几条记忆”。它仍然要选择去召回,但词汇至少在场。索引较小(≤50 条)时最有效。
-
系统提示词里的触发线索。 点明必须召回(不是可选)的情境:“回答项目约定相关问题前,先召回标签为
convention的记忆。” 这把判断题变成了规则题。 -
按任务分类自动前置。 一个轻量级分类器给每轮用户输入打主题标签;标题匹配标签的记忆在 Agent 看到这一轮之前就被前置进上下文。召回从自愿变成不自愿 —— Agent 没机会忘,因为已经做完了。
-
LLM-side-query 路由。 一个更便宜、更快的模型读用户输入,决定取哪几条记忆;结果作为上下文注入主模型。适合存储大到前三种手调规则不再可行的场景。
常见递进:先 (1) + (2);观察到手动召回漏率明显时加 (3);存储超出主模型能可靠自选的规模时上 (4)。
结构化笔记
工作记忆和结构化笔记解决的是同一个问题的不同规模:
- 工作记忆是 Agent 工作时留在窗口里的任务状态 —— 当前计划、刚碰过的几个文件、待办 todo。短任务够用。
- 结构化笔记是工作记忆外溢到磁盘的版本 —— 当窗口靠不住了(任务太长、可能被压缩、会话可能中断)时启用。
两者不是不同类型的记忆,而是同一类型在不同耐久度上的形态。一旦 Agent 无法再信任窗口”还会装着它需要的东西”,笔记就接管了工作记忆的角色。
因此 结构化笔记是记忆的一种特例:Agent 在任务过程中写半持久的状态文件,之后跨越上下文重置回来再读。
Agent 维护一份
NOTES.md文件,工作中持续更新。上下文重置后,它读笔记然后继续。 — Anthropic, Effective Context Engineering for AI Agents
典型的结构化笔记:
- 待办清单 — 完成的、下一步的、阻塞的
- 进度日志 — 试过什么、什么奏效的流水
- 架构决策 — 任务中的设计选择 + 理由
- 未决问题 — 下次 checkpoint 要问用户的事
为什么管用:文件是无损的、始终可用、读之前不消耗注意力预算。Agent 通过外置到磁盘,可以跨上下文重置携带任意长度的任务状态。
两条设计选择决定结构化笔记好不好用:
完成的不可变性。 一旦一个 todo 被标记为 done,就不该悄悄还原。如果 Agent 重新规划时清单看上去丢了一条 done — 视为 bug — 要么 done 保留,要么显式删除。这能防住”Agent 忘了自己已经做过 X 又做了一遍”。
写的纪律。 笔记只有保持最新才有用。最好的约定是:每次有意义的动作之后都写一次,不是在任务末尾才补。任务中途的上下文重置必须能读到正确的笔记,否则它们比没用还糟。
结构化笔记是最常被提到的、解锁真正长时任务的单一技术。写读磁盘文件的 Agent 常常比”硬塞窗口”的 Agent 表现好 — 即便窗口理论上够大。
失效
记忆会过时。三种机制让记忆库保持诚实:
| 机制 | 何时适用 | 如何做 |
|---|---|---|
| 验证 | 记忆引用了具体文件、flag、函数 | 在照它行动之前,grep / 检查这个东西还在 |
| 纠正 | 召回的记忆和当前观察矛盾 | 相信观察;更新或删除记忆 |
| 过期 | 记忆有时间限定(截止期、sprint 状态) | 写入时把相对日期转成绝对日期;过期后衰减 |
常见失败是不验证就照过期记忆行动。例子:记忆说”用 createFoo() 辅助函数”,但一次重构把它改名成了 makeFoo()。Agent 召回后不验证就照做 —— 写了错代码,还把原记忆加进脑内的”可信列表”,错误就此复利。
一个好默认:记忆中出现的具体标识符,是”写入时存在”的声明。在照它行动之前先验证。
反模式
设计时的错 —— 记忆设计里常见的作者级失误。对应的运行时失败见 上下文管理 → 失败模式。
| 反模式 | 为什么伤 |
|---|---|
| 记忆 = 对话记录 | 写”今天我们聊了 X”,召回价值为零。提炼洞察。 |
| 只写纠正 | Agent 变得过度谨慎。也要存已验证的成功做法。 |
| 无上界的存储 | 不剪枝,索引会长到没法用。设上限并轮换。 |
| 写代码里已有的项目状态 | git log、文件内容、架构图是权威的。不要重复。 |
| 没有”为什么”的记忆 | 一条没理由的规则,边界情况无法判断。永远带上动机。 |
| 模糊的召回查询 | ”相关记忆”这种查询返回噪音。用贴近任务的具体关键字。 |
与其他支柱组合
记忆不是独立工作的:
-
提示词设计决定记忆索引如何在骨架中呈现、召回工具以什么描述暴露给模型。一个被描述成”取旧笔记”的召回工具,被调用的频率,远低于一个被描述成”为本用户和本项目检索过往决策、偏好与规则”的工具。
-
上下文管理决定召回后的记忆在对话推进中如何保留或修剪。一条召回后变得不相关的记忆,应该和其他工具结果一起老化出去,而不是永久把窗口撑大。压缩策略也必须给记忆条目留位置 — 关键的保留、被遗忘的丢掉。
记忆常常是 Agent 最先长出自己能力上限的一根支柱。一套设计良好的记忆系统,能把”一次性助手”变成一个真正随时间学习的 Agent — 在你没有它之前,再多 prompt 调参也到不了那一步。
如何度量 (Measuring It)
记忆是三支柱中最难评测的,因为收益延迟兑现 —— 一次正确的写入可能三周后召回时才生效。沿这几个轴度量:
- 写入正确性 —— 抽样看一批写入:是否准确、不重复、范围合适?手动抽样;规模大时用模式匹配。
- 召回精度 / 召回率 —— Agent 是否取回了对当前轮实际相关的记忆?用一个你知道 ground truth 的合成测试集来测。
- 召回遗漏 —— “该召回但没召回”的静默失败。更难量。代理指标:去掉相关记忆后跑原来用过记忆能跑通的任务,看回归。
- 过期率 —— 召回的记忆里,引用已不存在事物的比例。高于 ~10% 说明失效纪律在松懈。
- 库规模增长 —— 只增不减的记忆库是负债。跟踪每周新写入 vs 每周剪枝。
实用评测:保留一段合成回归对话 —— 一段脚本化的多轮对话,依赖较早轮写入的记忆。定期跑一遍能浮现用户在野外才会碰到的记忆回归。
相关阅读
- 提示词设计 —— 记忆索引和召回工具的描述都住在系统提示词里。那里怎么呈现,决定了 Agent 到底用不用记忆。
- 上下文管理 —— 控制召回的记忆如何和其他上下文一起老化。一条变得不相关的记忆应该被修剪,不该永久撑大窗口。
来源
- Effective Context Engineering for AI Agents — Anthropic, 2026
- MemGPT: Towards LLMs as Operating Systems — Packer et al., 2023,记忆层级的经典框架
- Generative Agents: Interactive Simulacra of Human Behavior — Park et al., 2023,Agent 语境下的情景/语义记忆