Ch07: 记忆系统的设计约束——存什么、怎么存、何时忘
Part 2: 记忆基础概念 前置章节:Ch06: Memory vs RAG——经验和知识不是一回事 后续章节:Ch08: DB-Agent-Memory——四层金字塔与零依赖哲学
1. 从”图书馆管理员”说起
想象你是一座图书馆的管理员。你的工作不只是”把书放到架子上”那么简单——你要回答五个问题。
第一,什么时候收书? 每天有几十个人送书来,有些是刚出版的新书,有些是别人捐赠的旧书,有些是读者读完还回来的。你不能每来一本书就跑一次上架——那样你一天什么都不用干了,光上架就够忙的。但你也不能攒一个月才上架——读者想看的新书可能早就等不及了。你需要一个”收书节奏”。
第二,书怎么分类放? 你可以按作者姓氏排,可以按主题分类排,可以按出版年份排,也可以按读者的借阅频率排。每种排列方式都有好处——按主题排的人找书方便,按频率排的人知道哪些是热门。但没有一种排列方式能满足所有人的所有需求。
第三,读者要书时怎么找? 如果图书馆只有一百本书,你闭着眼睛都能找到。但如果有十万本书,你需要目录、索引、搜索系统。最简单的做法是把所有书的目录背下来(全量注入),但那不可能。更实际的做法是按需求搜索——但搜索本身也需要时间和成本。
第四,旧书怎么处理? 科技类书籍三年就过时了,法律条文年年改,但是经典文学不会过时。有些书虽然内容过时了,但还有历史参考价值。你不能简单地把所有超过五年的书扔掉——但也不能让过时的科学教材继续误导读者。
第五,不同读者怎么隔离? 张教授的借阅记录和李同学的借阅记录不能混在一起。但有些书是共享的——参考工具书谁都能查。而且张教授的研究组可能需要共享一组书,不能只对张教授一个人开放。
这五个问题——写入时机、存储模型、检索策略、遗忘机制、隔离与共享——就是记忆系统的五个设计约束。每一个约束都有多种选择,每种选择都有权衡。十个系统在五个约束上做出了不同的选择,这些选择构成了它们各自的设计指纹。
这一章逐一展开五个约束,最后用一个五维矩阵表格做横向对比。
2. 约束一:写入路径——什么时候存、怎么提取
写入路径回答两个问题:什么时候把信息存进记忆系统(写入时机),以及用什么方式从原始对话中提取出值得存储的结构化信息(提取方式)。
2.1 三种写入时机哲学
写入时机是最能体现一个系统设计哲学的选择。三种哲学对应三种对”信息丢失”和”写入成本”的权衡。
哲学一:持续学习——每轮对话后立即写入
代表系统:Claude Code。
Claude Code 在每一轮用户-Agent 交互后,启动一个后台 agent(extractMemories),从当前对话中提取值得记住的事实,写入 .claude/memory/ 目录下的 markdown 文件。这是一个 fire-and-forget 操作——后台 agent 在运行时不阻塞主对话,用户不需要等待。
持续学习的核心权衡:
- 好处:信息几乎不丢失。你在这轮对话中说的任何重要信息,下一轮开始前就已经被存储了。即使对话突然中断、浏览器崩溃、网络断开,已经写入的记忆不会丢失。
- 代价:每轮对话后多一次 LLM 调用。如果一轮对话包含大量信息,提取可能需要较长时间。虽然不阻塞主对话,但增加了系统总体的计算消耗。
Fire-and-forget 是持续学习的关键实现技巧。”Fire”是发起后台提取任务,”forget”是主对话不等提取完成就继续。这保证了用户体验不受影响——你不会感觉到”Agent 在花时间记笔记”。但 fire-and-forget 意味着写入可能失败——后台 agent 可能崩溃、LLM 调用可能超时——而你不会知道。这是一个”用可靠性换即时性”的权衡。
哲学二:回顾学习——会话结束后批量写入
代表系统:Codex Phase1。
Codex 的 Phase1 在整个会话(rollout)结束后,才并行启动提取任务。它从历史 rollout 中提取结构化 JSON,每个 rollout 独立提取,并行度(concurrency)为 8。
回顾学习的核心权衡:
- 好处:写入成本被摊薄。一个会话可能持续几十轮对话,但只在结束时做一次提取。相比持续学习的”每轮一次”,总 LLM 调用次数少得多。而且会话结束后提取,可以”纵观全局”——看到整个对话的全貌,可能发现跨轮次的模式和关联,这是单轮提取做不到的。
- 代价:信息可能丢失。如果会话在提取前中断(用户关掉页面、网络断开、服务重启),整个会话的信息就丢了。回顾学习把”存”这个动作推迟到了会话结束,这意味着在会话进行中,新的记忆还没有被存储。
哲学三:抢救保存——在上下文压缩前紧急写入
代表系统:OpenClaw Pre-Compaction Flush。
OpenClaw 发现了一个独特的问题:当 Agent 的上下文窗口快要满时,系统会执行”上下文压缩”(compaction)——把旧的对话内容摘要化,腾出空间。但压缩过程中,大量细节信息会被丢弃——而这些信息中可能包含值得记住的内容。
OpenClaw 的解决方案是在压缩前主动 flush——运行一个嵌入式 agent,扫描即将被压缩的上下文,把重要信息写入日记文件(daily/YYYY-MM-DD.md)。这就像你在会议快结束、大家准备散会前,赶紧把白板上的要点拍个照——不是最优雅的方式,但保证信息不丢。
抢救保存的核心权衡:
- 好处:在信息最危急的时刻(即将被压缩丢弃)挽救重要内容。这是一种”最后一道防线”的思路——其他写入时机可能会遗漏,但压缩前的 flush 是最后一次机会。
- 代价:flush 操作本身需要时间和上下文空间。在上下文快要满的情况下再插入一个 agent 来执行 flush,可能导致上下文溢出或延迟增加。而且 flush 的质量受限于当时的上下文状态——如果上下文已经被压缩过,flush agent 看到的就是不完整的信息。
2.2 三种提取方式
确定了”什么时候存”,接下来是”怎么提取”——从原始对话中提取出结构化记忆信息的方式。
方式一:LLM 提取
代表系统:mem0、Claude Code。
核心思路是把对话内容交给 LLM,让 LLM 判断哪些信息值得记住,然后以结构化格式输出。
mem0 的实现:每次对话结束后,将完整对话发送给 LLM,prompt 要求提取独立的事实条目(如 “用户喜欢 Python”、”用户对花生过敏”)。LLM 输出 JSON 格式的事实列表,然后存入向量数据库。
Claude Code 的实现类似——extractMemories agent 读取当前对话,决定哪些信息值得写入 .claude/memory/ 下的文件。
LLM 提取的权衡:
- 好处:提取质量高。LLM 能理解语义,能判断信息的重要性,能从复杂的对话中提炼出简洁的事实。
- 代价:每次提取都需要一次 LLM 调用,成本不可忽略。而且提取质量受 LLM 能力限制——如果 LLM 没有识别出某条关键信息,它就丢了。
方式二:Agent 自操作
代表系统:Letta。
Letta 不由系统自动提取——而是让 Agent 自己通过 tool call 来操作记忆。Agent 有 core_memory_append、core_memory_replace、archival_memory_insert 等 tool,由 Agent 自己决定什么时候存、存什么。
这是一个本质性的设计差异。LLM 提取是”系统替 Agent 记”——Agent 不知道自己被记住了什么。Agent 自操作是”Agent 自己记”——Agent 明确知道自己的记忆状态,可以主动更新和修正。
Agent 自操作的权衡:
- 好处:Agent 有完整的记忆控制权。它可以主动更新过时信息(core_memory_replace),可以删除不再需要的内容,可以决定什么值得存入长期记忆。
- 代价:依赖 Agent 的判断力。如果 Agent 判断失误(把不重要的事存了,或把重要的事漏了),记忆质量就差。而且 Agent 的每次 tool call 都消耗 token,频繁操作成本高。
方式三:管线化处理
代表系统:cognee ECL(Extract-Cognify-Load)。
cognee 不在对话进行中提取记忆——它把提取过程设计为一个三阶段管线:
- Extract:从原始数据(对话、文档)中抽取节点和关系
- Cognify:根据 Ontology(本体论)定义的知识结构,将抽取的节点映射到类型化的概念和关系上
- Load:将处理后的知识图写入图数据库
管线化处理的权衡:
- 好处:处理深度最高。ECL 管线不是简单地提取”事实”,而是把原始信息映射到精确定义的知识结构上。这意味着记忆不是扁平的事实列表,而是有语义关联的知识图谱。
- 代价:管线复杂度最高。你需要预先定义 Ontology(为每个领域定义概念类型和关系类型),这本身就是一个高成本的工作。ECL 管线的运行也需要多个 LLM 调用和图数据库操作,延迟和成本都高。
2.3 写入路径的权衡总结
三种写入时机和三种提取方式可以交叉组合——但实际系统中,它们有自然的搭配倾向:
- 持续学习 + LLM 提取:Claude Code 的选择。追求实时性,接受每轮额外的 LLM 调用成本。
- 回顾学习 + LLM 提取:Codex 的选择。追求全局视角,接受会话中断时的信息丢失风险。
- 抢救保存 + Agent 自操作:OpenClaw 的选择。在信息最危急时用最直接的方式保存。
- 回顾学习 + 管线化处理:cognee 的选择。深度优先,延迟和成本都可以接受。
核心权衡一句话:实时写入成本高但不丢信息,批量写入成本低但可能遗漏。
3. 约束二:存储模型——用什么结构存
确定了写入路径,接下来是存储模型——用什么数据结构组织记忆。存储模型直接决定了记忆可以被怎样检索、怎样更新、怎样被用户理解。
3.1 五种存储模型
模型一:扁平列表
代表系统:mem0。
| mem0 把每条记忆存为一个独立的事实条目,格式通常是 “user_id | fact_text | embedding_vector”。所有记忆在同一个平坦的表里,没有层级、没有分类、没有关联。 |
扁平列表就像一个便利贴盒子——每条记忆是一张便利贴,写好了塞进盒子里。找的时候你只能一张一张翻(或者用向量搜索做语义匹配),没有”先找大分类再找小条目”的结构化路径。
扁平列表的好处是简单——写入只需一次 insert,不需要判断”这条记忆属于哪个分类”“和已有记忆有什么关系”。代价是检索依赖向量搜索——如果你存了一千条记忆,找相关的必须做语义匹配,而语义匹配的精度受嵌入模型能力限制。
模型二:分层金字塔
代表系统:DB-Agent-Memory。
DB-Agent-Memory 的四层金字塔从下到上是:Observation(原始观测)-> Fact(独立事实)-> Summary(主题摘要)-> Profile(用户画像)。信息从底层向上逐层压缩——底层的多条 Observation 被提炼为一条 Fact,多条 Fact 被聚合为一条 Summary,多条 Summary 被综合为一个 Profile。
分层金字塔就像一个从”原始笔记”到”总结报告”到”核心结论”的提炼管线。底层的 Observation 保留了完整细节但不方便检索;顶层的 Profile 丢失了细节但能快速定位方向。
分层金字塔的好处是提供了”先粗后细”的检索路径——你可以先查 Profile 知道”这是一个后端工程师”,再查 Summary 知道”他的数据库配置偏好”,再查 Fact 知道”他用 PostgreSQL 9.6,主从架构”。代价是分层提取本身需要 LLM 调用,而且每一层的提炼都可能引入信息损失或错误。
模型三:文件系统
代表系统:Claude Code、Codex、OpenClaw。
三个生产级 Coding Agent 都选择了文件系统作为记忆存储——记忆是 markdown 文件,存在项目目录下(.claude/memory/、MEMORY.md、daily/)。
文件系统作为存储模型有几个独特优势:
- 用户可编辑:用户可以直接用文本编辑器打开 MEMORY.md,手动修改或删除记忆。这是其他存储模型做不到的——你不能直接编辑向量数据库中的条目。
- 可 git 管理:记忆文件可以纳入版本控制,变更历史可追溯。这在团队协作中特别有价值——谁改了什么记忆、什么时候改的,一目了然。
- 透明:用户能看到 Agent 记住了什么,能理解 Agent 的行为为什么是这样。相比数据库中不可见的向量,文件系统的透明性大大增加了可信任度。
代价是:检索能力弱。你不能在文件系统上做向量搜索——你需要额外的索引层(Claude Code 的 MEMORY.md 就是索引文件)或者全量注入(Codex 把 MEMORY.md 的全部内容塞进 system prompt)。
模型四:知识图谱
代表系统:Graphiti、cognee。
知识图谱把记忆存为”实体-关系-实体”的三元组(如 “用户” -[使用]-> “PostgreSQL”),所有实体和关系构成一个图。图数据库(Neo4j)提供图遍历和子图匹配的查询能力。
知识图谱的独特优势是关联推理。如果你知道”用户使用 PostgreSQL”和”PostgreSQL 支持 JSONB”,你可以通过图遍历推断出”用户可以使用 JSONB 查询”——即使没有人直接说过这句话。这是扁平列表做不到的——在扁平列表中,”用户使用 PostgreSQL”和”PostgreSQL 支持 JSONB”是两条独立的事实,没有关联。
代价是:构建和维护图谱的成本高。每条新增记忆都需要做实体识别和关系抽取(通常用 LLM),而且需要处理实体消歧(”PG”和”PostgreSQL”是同一个东西吗?)和关系冲突(”用户使用 PostgreSQL”和”用户使用 MySQL”矛盾吗?)。图数据库的运维成本也高于简单的文件系统或向量数据库。
模型五:认知容器
代表系统:MemOS MemCube。
MemOS 引入了 MemCube 的概念——一个”记忆容器”,内部可以包含四种认知科学类型的记忆:Episodic(情景记忆)、Semantic(语义记忆)、Procedural(过程记忆)、Profile(画像记忆)。
认知容器和分层金字塔的区别在于:分层金字塔是”提炼深度”的分层(从原始到精炼),认知容器是”记忆类型”的分类(从经验到技能到知识到画像)。一个 MemCube 可以同时包含同一主题的情景记忆(”上周三遇到一次连接池耗尽”)、语义记忆(”pgbouncer 可以解决连接池耗尽”)、过程记忆(”配置 pgbouncer 的步骤是…“)和画像记忆(”用户偏好用连接池中间件而非直连”)。
认知容器的抽象层次在五种模型中最高,也最接近认知科学的理论框架。但代价是实现复杂度高——你需要为每种记忆类型设计不同的存储和检索策略,而且类型之间的转换(比如从情景记忆提炼语义记忆)需要额外的机制。
3.2 两极分化现象
观察十个系统的存储模型选择,会发现一个明显的两极分化:
- 生产系统用文件系统:Claude Code、Codex、OpenClaw 三个生产级系统全部选择文件系统。
- 开源库用数据库:mem0(向量数据库)、Letta(PostgreSQL + 向量数据库)、Graphiti(Neo4j)、cognee(图数据库)都用数据库。
这不是偶然的。生产系统的用户是开发者——他们需要看到、理解、编辑、版本控制自己的记忆。文件系统天然满足这些需求。开源库的用户是其他开发者——他们需要将记忆库集成到自己的应用中,需要复杂的查询能力(向量搜索、图遍历),数据库天然满足这些需求。
一个有趣的例外是 DB-Agent-Memory——它选择了分层金字塔模型,但在实现上用 SQLite 作为底层存储。这意味着它的”逻辑模型”是分层的,但”物理存储”是数据库。它试图在两种路线之间找到平衡点——既提供结构化的分层逻辑,又利用数据库的查询能力。
3.3 存储模型的权衡总结
核心权衡一句话:文件系统透明但检索弱,数据库强大但不透明。
| 模型 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|
| 扁平列表 | 简单、写入快 | 检索依赖向量搜索、无结构 | 轻量级应用、快速集成 |
| 分层金字塔 | 检索有层级路径、信息有压缩 | 提取成本高、层间信息损失 | 需要深度个性化的场景 |
| 文件系统 | 透明、可编辑、可 git | 检索能力弱、需要额外索引 | Coding Agent、开发者工具 |
| 知识图谱 | 关联推理、结构化知识 | 构建成本高、运维复杂 | 需要推理的知识密集型应用 |
| 认知容器 | 类型化记忆、科学理论基础 | 实现复杂度最高 | 学术研究、记忆操作系统 |
4. 约束三:读取路径——怎么找到相关记忆
存好了记忆,接下来是”怎么找到相关的”。检索策略直接影响 Agent 的回答质量——找到正确的记忆,回答就准确;找到不相关的记忆,回答可能被干扰;找不到记忆,Agent 就”失忆”。
4.1 四种检索策略
策略一:全量注入
代表系统:Codex。
Codex 把 MEMORY.md 的全部内容注入 Agent 的 system prompt。如果 MEMORY.md 有 2000 token,Agent 在每次对话开始时就”看得到”全部 2000 token 的记忆。
全量注入就像把所有便利贴摊在桌面上——你一眼就能看到所有信息。但如果便利贴太多(记忆太多),桌面就放不下了——上下文窗口有容量限制。
全量注入的核心权衡:
- 好处:检索成本为零——不需要搜索,所有记忆都在上下文中。而且”相关性”是百分之百——不存在”搜到了不相关的”或”漏掉了相关的”问题,因为全部都在。
- 代价:上下文窗口消耗。如果记忆总量超过几千 token,全量注入就不可行了。而且 LLM 存在”注意力稀释”问题——上下文太长时,模型对中间位置信息的关注度下降(”lost in the middle”现象)。这意味着全量注入的记忆越多,模型对每条记忆的”理解深度”越浅。
策略二:LLM 语义检索
代表系统:Claude Code。
Claude Code 用一个两步检索流程:第一步,将 MEMORY.md(索引文件)的全部内容注入 system prompt——但 MEMORY.md 只包含每条记忆的标题和一句话摘要,不是完整内容。第二步,由 LLM(Sonnet 模型)根据当前对话内容,从不超过 200 个记忆文件中选出最相关的 5 个,将这 5 个的完整内容注入上下文。
LLM 语义检索就像一个图书管理员——你告诉管理员你在找什么,管理员根据她的理解从书架上挑出最相关的几本书给你。管理员的理解力(LLM 的能力)直接决定了检索精度。
LLM 语义检索的核心权衡:
- 好处:检索精度高——LLM 能理解复杂的语义关联,比向量搜索更精准。比如用户问”上次那个数据库的问题”,LLM 能理解”数据库的问题”可能指的是”连接池耗尽”,而向量搜索可能只匹配字面上的”数据库”。
- 代价:成本高——每次检索都需要一次 LLM 调用(Sonnet),从 200 个文件中选 5 个。这比向量搜索贵一个数量级。而且延迟更高——LLM 调用的延迟在秒级,向量搜索的延迟在毫秒级。
策略三:向量搜索
代表系统:mem0、supermemory。
向量搜索是最”正统”的检索策略:把每条记忆的文本用嵌入模型转化为向量,存入向量数据库。检索时把查询也转化为向量,在数据库中做近似最近邻搜索(ANN),返回最相似的 k 条记忆。
向量搜索就像在一个巨大的图书馆里用”找和这个主题相似的书”的方式检索——你不需要知道书的精确标题或作者,只需要描述”我在找什么”,系统返回最相关的前 k 条。
向量搜索的核心权衡:
- 好处:成本和精度的中间路线。比全量注入精准(只返回相关的),比 LLM 语义检索便宜(嵌入模型的调用成本远低于 LLM)。延迟在毫秒级,适合生产环境。
- 代价:精度受嵌入模型限制。嵌入模型将文本压缩为几百维的向量,这个过程不可避免地丢失信息。两个语义相似但实际不相关的记忆(”用户喜欢 Python”和”Python 是一种蛇”)可能在向量空间中很近,导致误检索。嵌入模型也无法处理复杂的推理关联——它只能匹配”表面语义相似度”。
策略四:图遍历
代表系统:Graphiti、cognee。
图遍历利用知识图谱的结构进行检索:从一个已知的实体出发,沿着关系边遍历到相邻的实体,形成一条推理链。比如从”用户”出发,沿”使用”边到”PostgreSQL”,再沿”支持”边到”JSONB”,你就得到了”用户可以使用 JSONB”这个推论。
图遍历就像沿着关联线索一步一步调查——你先知道”用户用 PostgreSQL”,然后查”PostgreSQL 有什么特性”,然后发现”支持 JSONB”。每一步都是沿着结构化的关系前进的,不是”模糊搜索”。
图遍历的核心权衡:
- 好处:结构化推理能力强。图遍历可以发现向量搜索找不到的间接关联——”用户用 PostgreSQL”和”JSONB 支持部分索引”在向量空间中可能很远,但在图上可能只隔两条边。
- 代价:依赖图谱质量——如果实体识别有错、关系抽取有遗漏、图不连通,遍历就会走歪或走不通。而且图遍历的计算成本高于向量搜索——每一步遍历都是一次数据库查询,多跳遍历的延迟可能很高。
4.2 检索策略的三角权衡
四种检索策略可以放在一个”精度-延迟-成本”的三维空间中理解:
- 全量注入:精度中等(取决于 LLM 对长上下文的处理能力),延迟零(无需检索),成本零(无需额外调用)
- LLM 语义检索:精度高,延迟高(秒级),成本高(每次 LLM 调用)
- 向量搜索:精度中等,延迟低(毫秒级),成本中等(嵌入模型调用)
- 图遍历:精度取决于图谱质量和查询深度,延迟中等到高,成本中等
在实际系统中,很多采用了混合策略。supermemory 用三条流(BM25 + 向量 + 图遍历)做检索,然后融合排序。Graphiti 用两阶段架构(向量粗筛 + Cross-Encoder 精排)。这些混合策略试图在三角权衡中找到更好的平衡点——但每次”加一路召回”都意味着更高的系统复杂度和维护成本。
核心权衡一句话:精度、延迟、成本——三者不可兼得,你需要根据场景选择取舍。
5. 约束四:遗忘机制——什么时候删、怎么删
遗忘是最容易被忽略但影响最深远的约束。一个只增不减的记忆系统就像一个从不扔东西的储藏室——刚开始很有用,但东西越来越多,找东西越来越难,过时的东西还会误导你。
5.1 两类过时:显式 vs 隐式
显式过时:用户明确表示某条记忆不再适用。比如用户说”我不再用 React 了”或”我换了工作”。这类过时相对容易处理——检测到矛盾信息时更新或删除旧记忆。
隐式失效:记忆没有显式地被否定,但已经不再适用。比如用户半年没提 React,可能已经不用了但没明说。或者用户从”Python 后端”转到了”Rust 后端”,但他从来没说”我不再用 Python”——只是不再在对话中提到 Python 相关的话题。
隐式失效远比显式过时难处理。原因有三:
- 你不确定这条记忆是否真的”失效”了——用户可能只是最近没提,不代表不用了
- 删除的代价可能很高——如果删错了(用户还在用 Python 但你忘了),后果比”多存了一条”严重
- 没有明确的信号告诉你”这条该忘了”——不像显式过时有”用户亲口说”的触发点
5.2 现有方案
十个系统中,只有三个有值得一提的遗忘机制。大多数系统选择了”只增不减”——只管往里存,不管删。这不是因为遗忘不重要,而是因为遗忘太难设计了。
方案一:Graphiti 的 invalid_at 双时间轴
Graphiti 给知识图谱的每条边加了两个时间戳:valid_at(事实生效时间)和 invalid_at(事实失效时间)。当检测到矛盾信息时(比如”用户住北京”和”用户搬到了上海”),旧边的 invalid_at 被设置为当前时间,新边的 valid_at 被设置为当前时间。
双时间轴的好处是:你可以做时间维度的查询——”在 2024 年 6 月,用户住哪里?”答案是”北京”;”在 2025 年 1 月,用户住哪里?”答案是”上海”。信息没有被物理删除,只是被标记为”在某时间点之后不再有效”。
代价是:需要矛盾检测机制——系统需要判断新信息是否与旧信息矛盾。Graphiti 用 LLM 来做这个判断,但这不是百分之百可靠的。如果 LLM 没有检测到矛盾(比如”用户搬到上海”和”用户住北京”在措辞上不够直接),旧记忆就不会被标记为失效。
方案二:Codex 的 citation 正反馈循环
Codex 用一个间接的方式来处理遗忘:不是显式地”删”旧记忆,而是通过 citation(引用)机制让有用的记忆”浮现”,让没用的记忆”下沉”。
机制是这样的:每次 consolidation agent(整合 agent)决定保留一条记忆时,它会记录”这条记忆是从哪些来源中提炼的”。下次整合时,如果一条记忆的 usage_count(被引用次数)高,说明它经常被用到,更可能被保留。如果一条记忆的 usage_count 低,说明它很久没被引用了,整合 agent 可能选择不保留它。
这是一个”隐式淘汰”机制——没有显式地删,但低引用的记忆会在下次整合时被自然淘汰。好处是温和——不会因为一次没用到就删掉可能还有用的信息。代价是迟缓——一条过时的记忆可能需要经过多次整合周期才会被淘汰。
方案三:Claude Code 的 freshness caveat
Claude Code 在记忆文件中加了一个 freshness(新鲜度)标注。如果一条记忆超过 24 小时且经过 5 次以上会话,autoDream 机制会触发整合——把多条细粒度的记忆合并为更精炼的摘要,并在摘要中添加 caveat(警告),比如”此信息来自 5 天前的对话,可能已过时”。
Freshness caveat 不是真正的遗忘——它不删,只是”提醒你这条可能过时了”。这就像食品包装上的”最佳食用日期”——过了这个日期不代表食物一定坏了,但你需要更谨慎地判断。
5.3 “只增不减”的现实
为什么大多数系统选择只增不减?三个原因。
原因一:删除的代价不对称。删错一条有用记忆的代价远高于多保留一条无用记忆的代价。多保留一条无用记忆,最坏情况是检索时多了一条干扰项;删错一条有用记忆,Agent 就”忘了”重要信息,可能导致错误的行为。在代价不对称的情况下,保守策略(只增不减)是理性的。
原因二:隐式失效判断极难。前面说过,你很难确定一条记忆是否真的”失效”了。在没有明确信号的情况下做删除,风险太高。
原因三:成本收益不对等。设计一个好的遗忘机制需要大量工程投入(矛盾检测、时间衰减、引用计数、用户确认流程等),但收益是”检索时少看几条不相关的”——这通常可以通过更好的检索策略来解决,而不需要删掉记忆本身。
所以大多数系统选择了”让检索来过滤,而不是让遗忘来删除”——存就存了,检索时只返回相关的。这不是最优解,但在当前的工程约束下是务实的选择。
核心权衡一句话:遗忘比记忆更难设计,大多数系统选择只增不减,用检索过滤替代物理删除。
6. 约束五:隔离与共享——不同用户、不同项目、不同 Agent 之间
最后一个约束回答的是:谁能看到谁的记忆。
6.1 三种隔离粒度
粒度一:per-user
每条记忆绑定一个 user_id,不同用户的记忆完全隔离。这是最常见的粒度——mem0、Letta、DB-Agent-Memory 默认都按 user_id 隔离。
per-user 隔离的好处是隐私和安全——用户 A 的偏好不会泄露给用户 B。代价是信息孤岛——同一个项目、同一个团队的人无法共享记忆。如果用户 A 和用户 B 在做同一个项目,他们分别告诉各自的 Agent “项目用 PostgreSQL”,这个信息被存了两遍,而且无法交叉验证。
粒度二:per-project
记忆按项目分组,同一个项目内的所有用户共享记忆。这是 Claude Code 的默认策略——记忆文件存在项目目录下(.claude/memory/),任何在这个项目目录下工作的用户都能看到这些记忆。
per-project 隔离的好处是协作——同一个项目的开发者共享项目级的知识(技术栈、代码规范、架构决策),Agent 不需要每个开发者重新”教”一遍。代价是隐私——如果你在这个项目目录下告诉 Agent 你的个人偏好(”我喜欢深色主题”),其他开发者也能看到。
粒度三:global
所有用户、所有项目共享同一份记忆。Codex 是最接近 global 的——它的 MEMORY.md 是全局的,不按用户或项目隔离。所有使用同一个 Codex 实例的人看到相同的记忆。
global 的好处是简洁——不需要管理隔离逻辑。但代价是混乱——不同用户、不同项目的记忆混在一起,可能互相干扰。你用 PostgreSQL 的记忆可能和另一个人用 MySQL 的记忆冲突。
6.2 三个系统的隔离选择对比
| 系统 | 隔离粒度 | 实现方式 | 好处 | 代价 |
|---|---|---|---|---|
| Codex | Global | 单一 MEMORY.md | 简洁,无隔离逻辑 | 不同用户/项目记忆混合 |
| Claude Code | Per-project | 记忆文件在项目目录下 | 同项目协作,跨项目隔离 | 项目内无用户隔离 |
| mem0 | Per-user | user_id 字段过滤 | 隐私安全,个性化 | 项目内信息不共享 |
6.3 多 Agent 共享记忆服务的冲突问题
当多个 Agent 实例共享同一个记忆服务时,会出现更复杂的隔离问题。
场景:你有一个 Coding Agent 和一个写作 Agent,它们都连接同一个 mem0 实例、用同一个 user_id。Coding Agent 记住了”用户偏好 Python”,写作 Agent 记住了”用户偏好中文散文”。两条记忆绑定同一个 user_id,但属于不同的 Agent 上下文——当 Coding Agent 检索记忆时,它可能看到”用户偏好中文散文”这条不相关的记忆,造成干扰。
更严重的情况是矛盾:Coding Agent 记住了”项目使用 Python”,而一个数据库管理 Agent 记住了”项目使用 Go”(因为它看到的是数据库工具链部分的代码是 Go 写的)。两条记忆绑定同一个 user_id 但互相矛盾——Agent 无法判断哪个正确。
现有方案对这类冲突的处理都很初步:
- mem0 的去重机制只能处理完全相同的事实,不能处理矛盾
- Letta 的 Agent 自管理可以让 Agent 自己判断,但依赖 Agent 的推理能力
- Graphiti 的双时间轴可以记录矛盾,但需要在实体级别做消歧
这是一个开放问题——目前没有成熟的工业级解决方案。
7. 五维矩阵:十个系统在五个约束上的选择
把前面五个约束的讨论整合成一张表,可以清晰看到每个系统的设计指纹。
| 系统 | 写入路径 | 存储模型 | 读取路径 | 遗忘机制 | 隔离粒度 |
|---|---|---|---|---|---|
| DB-Agent-Memory | LLM 提取(按层) | 分层金字塔(SQLite) | 按层检索(Profile 优先) | 无显式遗忘 | per-user |
| mem0 | LLM 提取(每轮) | 扁平列表(向量数据库) | 向量搜索 | LLM 去重更新 | per-user |
| Letta | Agent 自操作(tool call) | Core Memory(prompt)+ Archival(数据库) | Core Memory 全量注入 + Archival 向量搜索 | Agent 自管理(core_memory_replace) | per-user |
| Graphiti | LLM 提取 + 图构建 | 知识图谱(Neo4j) | 向量粗筛 + 图遍历 | invalid_at 双时间轴 | per-user |
| cognee | ECL 管线 | 知识图谱(图数据库) | 图遍历 | 无显式遗忘 | per-user(可配置) |
| supermemory | LLM 提取 + 多源捕获 | 扁平列表 + 图 | BM25 + 向量 + 图三流融合 | 无显式遗忘 | per-user |
| MemOS | Dream Pipeline(后台巩固) | 认知容器(MemCube) | 调度器选择类型化检索 | Dream Pipeline 隐式淘汰 | per-user |
| Claude Code | 持续学习 + LLM 提取(fire-and-forget) | 文件系统(markdown) | 索引注入 + LLM 语义检索 | freshness caveat(超 24h + 5 会话加警告) | per-project |
| Codex | 回顾学习 + LLM 提取(两阶段 pipeline) | 文件系统(markdown + JSON) | 全量注入 MEMORY.md | citation 正反馈循环(隐式淘汰) | global |
| OpenClaw | 抢救保存(Pre-Compaction Flush) | 文件系统(三层文件布局) | MEMORY.md 全量注入 + 日记检索 | 无显式遗忘 | per-project |
7.1 从矩阵中能看到的模式
模式一:写入路径两极分化。三个生产系统各自代表一种写入哲学——持续学习、回顾学习、抢救保存。七个开源项目则偏向 LLM 提取或管线化处理。生产系统更关注”不丢信息”,开源项目更关注”提取质量”。
模式二:存储模型两极分化。生产系统统一选文件系统,开源项目统一选数据库。这不是偶然的——两种路线分别优化了”用户可理解性”和”系统查询能力”。
模式三:读取路径各有侧重。全量注入(Codex)在记忆量小时最优,LLM 语义检索(Claude Code)在记忆量中等时最优,向量搜索(mem0)在大规模时最优,图遍历(Graphiti/cognee)在需要推理时最优。没有一种策略在所有场景下都最优。
模式四:遗忘几乎空白。十个系统中只有 Graphiti 有显式的遗忘机制(invalid_at),Codex 有隐式的淘汰(citation 循环),Claude Code 有轻量的提示(freshness caveat)。其余七个系统选择只增不减。遗忘是目前记忆系统最大的设计空白。
模式五:隔离粒度不够灵活。大多数系统只支持一种隔离粒度(per-user 或 per-project),不支持”同项目不同用户”或”同用户不同项目”的精细隔离。多 Agent 共享记忆时的冲突问题几乎没有系统处理。
8. 本章小结
回顾本章的核心要点:
- 记忆系统有五个设计约束:写入路径、存储模型、读取路径、遗忘机制、隔离与共享
- 写入路径:三种写入时机(持续学习/回顾学习/抢救保存)和三种提取方式(LLM 提取/Agent 自操作/管线化),核心权衡是实时写入成本高但不丢信息、批量写入成本低但可能遗漏
- 存储模型:五种模型(扁平列表/分层金字塔/文件系统/知识图谱/认知容器),两极分化——生产系统用文件系统(透明但检索弱),开源库用数据库(强大但不透明)
- 读取路径:四种检索策略(全量注入/LLM 语义检索/向量搜索/图遍历),核心权衡是精度-延迟-成本的三角——三者不可兼得
- 遗忘机制:显式过时相对容易,隐式失效极难处理;现有方案包括 Graphiti 的双时间轴、Codex 的 citation 循环、Claude Code 的 freshness caveat;大多数系统选择只增不减
- 隔离与共享:三种粒度(per-user/per-project/global),各有取舍;多 Agent 共享记忆的冲突问题目前没有成熟方案
- 五维矩阵揭示了五个模式:写入两极分化、存储两极分化、读取各有侧重、遗忘几乎空白、隔离不够灵活
这五个约束不是独立的——它们互相影响。写入路径决定了存储模型能包含什么信息,存储模型决定了读取路径能用什么策略,读取路径的精度影响遗忘机制的紧迫性,隔离粒度影响写入和读取的复杂性。设计记忆系统时需要同时考虑五个约束,而不是逐个优化。
下一章进入 Part 3,我们开始精读七个开源项目——从 DB-Agent-Memory 的四层金字塔开始。
导读目录:README.md 上一章:Ch06: Memory vs RAG——经验和知识不是一回事 下一章:Ch08: DB-Agent-Memory——四层金字塔与零依赖哲学