记忆设计

窗口之外持久的东西 — 记忆类型分类、何时写/何时召回、结构化笔记作为持久的规划状态,以及记忆如何失效

为什么上下文窗口不是记忆

上下文窗口是工作记忆,不是存储。有三个性质让它不适合做长期记忆:

  • 有上界 — 再大也会满。任何”都留着就行”的策略,规模一大就崩。
  • 压缩时有损 — 摘要保留大意,丢失细节。你三个会话之后需要的那个具体信息,往往是压缩最先丢的。
  • 按会话切分 — 新对话从零开始。没有外部机制,会话 A 学到的东西在会话 B 里就不存在

真正的记忆系统活在窗口之外。读写都是 Agent 通过工具显式执行的操作。窗口是 cache,记忆是 disk。

这个框架很重要,因为它改变了设计问题。“Agent 该记住什么?“变成了:

  • 什么被写进外部存储?
  • 什么触发召回?
  • 任一时刻载入多少?
  • 过期记忆怎么被清掉?

每个问题的正解,取决于记忆的类型


记忆分类学

记忆分类学 (Memory Taxonomy) 四种记忆类型 —— 读写模式和存储位置各不相同 窗口之外 —— 持久存储 (Persistent Store) 情景 (Episodic) 事件 —— 什么 / 何时 / 和谁 显著事件后 相似情境再次出现时 "上个月的发布 因为 X 挂了" 按相似度召回 语义 (Semantic) 事实、偏好、规则 用户陈述时 任何决策中相关时 "用户偏好 Tailwind 胜过 CSS-in-JS" 便宜到可以常载入 程序 (Procedural) 技能、手册 —— 怎么做 X 学到可靠流程后 任务匹配流程时 "这个团队写 commit message 的风格" 按任务匹配召回 窗口边界 (window boundary) 窗口之内 —— 工作记忆 (Working Memory) 工作 (Working) 当前任务状态 —— 计划、打开文件、待办 任务进行中 整个任务期间;完成时丢弃 可丢弃 —— 硬持久化反而制造过期状态 窗口不可靠时才外置 → 结构化笔记 结构化笔记 外置的工作记忆 何时 窗口装不住时: 长任务 / 可能被压缩 / 会话可能中断 todo.md · progress.md · decisions.md · open-questions.md 经验法则: 重新推导代价高、检索代价低的都外置;其余留在窗口里。

认知科学区分几种记忆系统 — 情景、语义、程序、工作。这套分类用在 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 到底用不用记忆。
  • 上下文管理 —— 控制召回的记忆如何和其他上下文一起老化。一条变得不相关的记忆应该被修剪,不该永久撑大窗口。

来源

这页有帮助吗?