记忆与持久化模式
问题
如果没有持久化记忆,Agent 在会话结束的瞬间就会丢失所有用户偏好、项目上下文和行为反馈。用户不得不每次会话都重复纠正("用 bun,不要用 npm"),Agent 也无法积累那些让它真正好用的工作知识。单一的扁平记忆存储同样行不通:指令级别的规则与临时工作笔记需要不同的持久性,而组织级别的约定必须与项目级别、用户级别的覆盖共存而不冲突。
这些问题出现在任何跨会话持久化的 Agent 运行时中——并非特定于某个具体实现。
黄金法则
按作用域和持久性分层
并非所有记忆都一样。指令级别的规则("始终使用 tab")应放在随项目一起版本控制的文件中。个人工作流偏好("用 VS Code 打开 diff")应放在跨所有项目的用户作用域存储中。临时工作笔记应放在 Agent 自行管理的自动记忆层中。将这些合并到单一存储中,就无法独立地共享、审计或覆盖每一层。至少设计三个层级:组织/团队、用户和项目——再加一个永不共享的本地覆盖层。
本地覆盖优先——永远如此
当同一主题在多个作用域中都有设置时,最本地的指令拥有最高优先级。组织级策略设定底线,用户级偏好缩小范围,项目级约定进一步缩小,本地覆盖文件拥有最终决定权。按优先级升序拼接各层,使最本地的内容出现在最后,获得模型最多的注意力。构建者必须理解这个层级关系,否则他们会设置一个全局规则并期望它占据主导地位,结果却被本地文件静默覆盖。
按类型分类自动记忆,而非按时间
自动记忆条目应各自携带一个取自小型固定分类体系的类型——用户身份、行为修正、无法从代码推导的项目上下文,以及稳定的参考事实。类型分类体系可以防止记忆存储退化为按时间堆积的垃圾堆,并使得系统化地审计、裁剪和提升条目成为可能。排除所有可从代码库或版本历史推导出的内容——保存它们浪费索引空间且很快过时。
两步保存:先写主题文件,再更新索引
每次记忆写入都是两步操作:首先将完整内容写入专用主题文件,然后在索引中追加一行指针。如果进程在两步之间崩溃,最坏的结果只是一个孤立的主题文件——索引保持一致。永远不要将记忆内容直接写入索引;索引是目录,不是文档。
索引是有界的常驻上下文;主题文件是按需加载的详情
记忆索引在每次对话中都会加载——其代价在每一轮都要支付。保持它的小体积:每个条目一行,设有行数和字节数硬上限,触发上限时附带截断警告。完整详情存储在主题文件中,Agent 按需调用。这种分离使上下文成本保持恒定,不受 Agent 已学习了多少知识的影响。
后台提取直接写入自动记忆
会话记忆提取应作为后台进程运行,在 Agent 产生最终响应后读取会话记录。提取器直接写入自动记忆条目——主题文件和索引更新——遵循与主 Agent 相同的两步保存不变式。这使提取不在关键路径上(不给用户可见的响应增加延迟),同时自主产生持久记忆。人类控制在跨层边界得到保障:将自动记忆条目提升为项目约定、个人指令或团队共享记忆是一个单独的提议和审查工作流,未经明确批准绝不会应用更改。
主 Agent 和提取 Agent 互斥
如果主 Agent 在某一轮中写入了记忆(因为用户明确要求它记住某事),后台提取器会完全跳过该轮并前移其游标。两个写入者在同一轮中针对同一记忆存储会产生冲突。按轮次而非按文件强制互斥。
将提取 Agent 沙箱化
后台提取器以受限权限运行:可以自由读取,但只能写入自动记忆目录,且不得执行任意命令。将其轮次数上限设为较小的数(如五次)以防止验证兔子洞。它与父级共享 prompt cache 以降低成本,但不能继承父级的完整写入权限。
适用场景
- 你的 Agent 跨会话持久化,且需要记忆用户偏好、项目上下文或行为修正。
- 多个作用域的指令共存(组织、用户、项目、本地),需要明确的优先级排序。
- Agent 应从会话中学习,而无需用户手动维护知识库。
- 你需要一个跨层审查流程,在记忆层级之间提议提升。
- 你的运行时支持后台工作,且希望提取不阻塞用户。
权衡
| 决策 | 收益 | 代价 |
|---|---|---|
| 四层以上作用域的分层记忆 | 每个作用域可独立共享、审计和覆盖 | 启动时需要发现和拼接更多文件 |
| 本地优先的优先级排序 | 用户无需修改共享文件即可覆盖 | 全局规则可能被静默覆盖——如果不在完整栈中测试会很意外 |
| 自动记忆的类型分类体系 | 可进行结构化裁剪、提升和审计 | 分类体系需预先定义;分类错误会降低质量 |
| 两步保存(先主题文件后索引) | 抗崩溃——索引永不指向缺失内容,孤立文件无害 | 每次保存两次写入;孤立主题文件的清理是独立的问题 |
| 有界索引配合按需主题文件 | 无论总记忆量多大,上下文成本保持恒定 | Agent 访问完整详情需要额外的检索步骤 |
| 通过 fork Agent 进行后台提取 | 不给用户可见响应增加延迟;共享 prompt cache | 提取与下一个用户轮次之间存在竞态窗口;失败的提取可能重试过时内容 |
| 按轮次互斥 | 主 Agent 与提取器之间无写入冲突 | 主 Agent 写入时提取被完全跳过——某些轮次不会产生提取 |
| 带轮次上限的沙箱化提取器 | 防止失控循环和意外副作用 | 可能错过需要更深推理的细微记忆 |
| 跨层提升采用提议而非自动写入 | 人类始终控制进入共享或版本控制记忆的内容 | 增加手动审查步骤;未审查的提议会积累 |
实现模式
- 定义一个小型固定的自动记忆类型分类体系(如用户身份、行为反馈、项目上下文、稳定参考),并记录每种类型应包含什么。排除所有可从代码库或版本历史推导的内容。
- 在 Agent 首次写入之前通过幂等创建步骤确保记忆目录存在。不要让 Agent 在运行时检查目录是否存在。
- 将记忆索引视为目录:每个条目一行,包含标题、主题文件链接和一行摘要钩子。对每个条目实施字符预算(约 150 个字符),以保持在索引的硬上限之内。
- 给每个主题文件加上结构化的 frontmatter,至少包含名称、描述和从分类体系中取得的类型字段。
- 实现两步保存不变式:先写主题文件,再追加到索引。永远不要颠倒顺序。
- 启动时通过遍历已知的位置层级(组织管理的、用户主目录、项目根目录及祖先目录、本地覆盖)来发现指令文件。按优先级升序拼接。
- 在指令文件中支持 include 指令,以便将大型配置由小片段组合而成。
- 仅在 Agent 产生最终响应且无待处理工具调用后才触发后台提取。如果主 Agent 在该轮中写入了记忆,跳过提取并前移游标。
- 将提取 Agent 的写入范围限制在自动记忆目录。允许广泛读取但拒绝任意命令执行。
- 将提取轮次上限设为较小的数以防止验证循环。合并并发提取请求,使同一时间只运行一个。
- 在进程关闭前,响应刷新之后、关闭保险计时器触发之前,排空正在进行的提取。
- 构建一个跨所有层审计并提议提升的审查机制(到项目约定、个人指令、团队共享记忆,或保留在自动记忆中)——但未经用户明确批准绝不应用更改。
- 提供环境变量或设置以完全禁用自动记忆,用于不需要持久化的场景(临时运行、CI、远程沙箱)。
踩坑指南
索引截断是静默触发的。 行数和字节数硬上限在读取时强制执行。长行条目(多句摘要而非一行钩子)可能在未超过行数上限时就触及字节上限。保持条目简短,将详情放在主题文件中。
优先级排序是反直觉的。 本地覆盖优于项目规则,项目规则优于用户规则,用户规则优于组织规则。如果你在用户级别注入一条规则并期望它占主导,项目根目录中的本地覆盖文件会静默胜出。始终在完整的指令文件栈中测试。
提取时机产生竞态窗口。 后台提取器在响应结束时触发,但用户可以在提取完成前开始下一轮。重叠保护会合并并发调用,游标仅在成功运行后前移——但失败的提取意味着那些消息下次会被重新考虑,可能产生重复提议。
可推导的内容不属于记忆。 架构、代码模式和版本历史都可以从项目本身重新推导。保存它们浪费索引空间且很快过时。类型分类体系应在设计上排除可推导内容,但 Agent 仍然会尝试保存它们,除非 prompt 明确禁止。
团队共享记忆依赖于自动记忆的启用。 如果自动记忆被禁用(通过环境变量或设置),团队记忆也会被禁用,因为共享层构建在相同的目录和索引基础设施之上。禁用一个会静默禁用两个。
不要将记忆用于会话内状态。 计划、任务列表和草稿板更适合用专门的会话内原语处理。记忆仅用于跨会话回忆。混用两者会让索引膨胀出大量在下一个会话就过时的临时内容。
孤立的主题文件无害但会积累。 如果进程在写入主题文件后、更新索引前崩溃,主题文件就变成了孤儿。孤儿不会损坏索引,但会占用磁盘空间。定期扫描并删除索引未引用的主题文件是合理的维护步骤。
Claude Code 实证
Claude Code 的记忆系统是这些原则在五个协作子系统中的生产实现:
四级指令层级。 Claude Code 在四个作用域发现 CLAUDE.md 文件:用于组织级策略的管理位置、用于个人指令的用户主目录位置、用于项目约定的项目根目录和祖先目录位置(包括 rules 子目录),以及永不签入版本控制的 CLAUDE.local.md 文件用于私有的项目级覆盖。文件按优先级升序拼接,使本地覆盖出现在 prompt 的最后。@include 指令允许从片段组合。建议的单文件字符上限防止任何单个指令文件占据过多上下文。
四类型自动记忆与有界索引。 自动记忆存储在项目目录下,包含一个 MEMORY.md 索引和各个主题文件。每个主题文件携带 YAML frontmatter,包含名称、描述和类型字段。四种类型——用户、反馈、项目和参考——在 prompt 中有文档说明,使 Agent 能一致地分类条目。索引上限为 200 行和 25,000 字节;当任一上限触发时会追加截断警告。目录在 prompt 构建之前幂等创建,使 Agent 永远不会遇到缺失路径。
团队记忆作为共享扩展。 当功能门控和启用检查都通过时,同一项目 slug 下的第二个目录被添加用于团队共享记忆。它使用相同的四类型分类体系和索引结构。团队记忆要求自动记忆已启用——通过环境变量禁用自动记忆也会禁用团队记忆。
带互斥机制的后台会话提取。 在每个查询循环结束时,一个 fork 出的子 Agent 检查会话记录并将记忆直接写入自动记忆目录。fork 与父级共享 prompt cache 以降低成本。如果主 Agent 在该轮中已写入记忆路径,提取器完全跳过并前移读取游标。提取器的工具权限受到限制:它可以自由读取、搜索和 glob,但只能写入自动记忆目录,且不能执行任意 shell 命令。五次的硬轮次上限防止验证兔子洞。并发提取请求被合并,同一时间只运行一个,尾随运行会接收活跃运行期间到达的消息。正在进行的提取在响应刷新后、关闭保险计时器触发前被排空。
提议而非自动写入的审查技能。 内置的 "remember" 技能跨所有记忆层审计并按操作分组提议提升:提升到项目约定、提升到个人指令、提升到团队记忆、清理、存疑或无需操作。它从不自主应用更改——它呈现结构化报告并等待用户明确批准后才修改任何文件。四个提升目的地对应四个指令文件作用域。