附录 C 横向对比:几种 agent 记忆的设计
本附录把 letta-code 的 MemFS 和几种常见的 agent 记忆方案放在一起比较。对其他系统只描述其公开的、稳定的设计思路,不涉及具体版本的 API 细节;需要精确信息时请以各项目的官方文档为准。
C.1 比较维度
一个记忆系统可以从下面几个问题来刻画:
- 存储介质与单位:数据库字段?向量?文件?一条“记忆”多大?
- 谁来写:agent 自己调工具?harness 自动抽取?另一个 agent?
- 什么时候写:对话中(hot path)?对话后(background)?
- 怎么读:常驻上下文?按需检索?两者结合?
- 历史与回滚:能不能看到记忆是怎么演变的?写坏了怎么办?
- 约束:格式、大小有没有强制?
- 多机与多 agent:怎么同步?怎么隔离?
C.2 四种思路
Letta 服务器(MemGPT 一脉)。 记忆分三层:常驻系统提示的记忆块(agent 用 core_memory_* 一类工具改写),可检索的归档记忆(archival,向量存储里的段落,agent 用工具插入和搜索),以及回忆记忆(recall,对话历史,可搜索)。写入主要由 agent 自己在对话中通过工具完成;Letta 后来也引入了在后台整理记忆的 sleep-time agent。letta-code 的 MemFS 可以看作这套设计的“文件化”:块变成文件、改写工具变成 shell + git,后台整理变成 worktree 里的反思子 agent。
抽取式记忆服务(以 Mem0 为代表)。 harness(而不是 agent)在每轮对话后用一个 LLM 从消息里抽取事实,与已有记忆做相似度比较,决定新增、更新、删除还是忽略;使用时按当前问题检索相关记忆注入上下文。记忆的单位是一条条短事实,存储在向量库(可选图结构)里。它的优势是对 agent 透明、接入简单;代价是没有“常驻层”的概念——没检索到就等于不存在——也没有人类可读、可编辑、可回滚的整体视图。
通用长期存储(以 LangGraph 的 Store 为代表)。 提供一个按命名空间组织的键值存储(值是 JSON 文档,可选语义检索),把“存什么、何时存”留给应用决定;配套的工具包把记忆形成分为对话中(agent 通过工具写)和后台(另一个流程在对话后整理)两种模式,并把“程序性记忆”实现为对提示词本身的优化。它是积木而不是成品:灵活,但每个应用都要自己回答本附录 C.1 的七个问题。
Claude Code 的项目记忆。 以文件为中心:项目里的 CLAUDE.md(以及用户级、本地级的同名文件)在会话开始时进入上下文,团队可以把它提交到代码仓库;用户或 agent 编辑这些文件就是在“改记忆”。较新的版本还有自动记忆:每个项目一个记忆目录,MEMORY.md 作为一行一条的索引常驻上下文,每条记忆一个带 name/description 等 frontmatter 的 Markdown 文件、按需读取——与 MemFS v2 的“根索引 + 带描述的文件 + 按需加载”几乎是同一个思路,只是没有 git 版本控制、hook 校验和后台反思这几层。(本书正是在这样一个会话里写成的,它的记忆目录就在用户主目录下的 .claude/projects/<项目>/memory/。)
C.3 对照表
| 维度 | letta-code MemFS | Letta 服务器 | 抽取式(Mem0 类) | 通用存储(LangGraph Store 类) | Claude Code 项目记忆 |
|---|---|---|---|---|---|
| 存储 | git 仓库里的 Markdown | 数据库(块)+ 向量库(归档) | 向量库(+ 可选图) | 命名空间 KV(+ 可选向量) | Markdown 文件 |
| 单位 | 文件(可大可小) | 块 / 段落 | 短事实 | 应用定义的文档 | 文件 / 一条记忆一个文件 |
| 谁写 | agent(shell+git)、反思子 agent、memory worker | agent(工具)、后台 agent | harness 抽取 | 应用决定 | 用户、agent |
| 何时写 | 对话中 + 对话后(后台) | 对话中 + 后台 | 每轮之后 | 应用决定 | 对话中 |
| 怎么读 | core 常驻;子目录与技能只放索引;recall 检索 | 块常驻;归档、recall 检索 | 检索注入 | 应用决定 | 常驻(CLAUDE.md、索引)+ 按需读文件 |
| 历史 / 回滚 | 完整 git 历史,可 diff、revert | 有限 | 记忆项有更新记录(视实现) | 视实现 | 依赖用户自己的版本控制 |
| 约束 | hook:frontmatter、索引链、预算、只读 | 块大小上限 | 抽取器决定 | 无 | 无强制 |
| 多机 | git 远端,回合后 push / rebase | 服务器即真相 | 服务即真相 | 存储即真相 | CLAUDE.md 随代码仓库同步 |
| 多 agent 隔离 | 路径守卫 + 内核沙箱 | 服务端权限 | 按用户 / agent id 分区 | 命名空间 | 按项目目录 |
C.4 MemFS 的独特取舍
把这些放在一起看,letta-code 的选择很鲜明:
- 把记忆当代码管。 版本控制、提交前检查、分支隔离、合并、远端同步——软件工程里管理“一群人并发修改的文本”的整套工具,原封不动地拿来管理“一群 agent 并发修改的记忆”。第 16、17 章的并发设计几乎全部建立在 git 的语义上。
- 让 agent 用通用工具写记忆。 这给了 agent 最大的表达力(可以重组目录、建技能、存图片),也把格式正确性的负担从工具 schema 转移到了 hook 和提示词上。第 6、9、15 章里小模型的种种失误,正是这个取舍的代价;更强的模型和后台反思是它的补偿。
- 常驻与按需由目录结构表达。 不需要额外的元数据或检索系统来决定“什么常驻”——
mv一下就能升级或降级一条记忆。 - 对人透明。 记忆就是一堆 Markdown 和一段 git 历史,人可以直接读、改、审。这对一个会“自己改自己”的系统来说,可能是最重要的安全属性。
没有哪种设计在所有维度上占优。如果你的 agent 只需要记住几十条用户偏好,抽取式服务可能最省事;如果你在构建一个会长期演化、需要人类审计、跨机器运行的 agent,MemFS 的这套“把记忆当代码”的思路值得认真考虑。