犀牛鸟 2026 研究笔记 estelledc.github.io

Ch18: 八大记忆设计模式——从 Flush 到 Rules-as-Memory

Part 5: 横切主题与前沿 前置章节:Ch17: OpenClaw 记忆——Pre-Compaction Flush 与 Dreaming 后续章节:Ch19: 十九个技术挑战——记忆系统的未解难题


1. 引言——为什么需要设计模式

1.1 从十个系统到八种模式

前十七章我们逐一精读了十个记忆系统——七个开源项目(DB-Agent-Memory / mem0 / Letta / Graphiti / cognee / supermemory / MemOS)和三个生产级系统(Claude Code / Codex / OpenClaw)。每个系统有自己的设计哲学、架构选择和实现路径,看起来各不相同。

但如果你退后一步,把十个系统放在一起看,会发现一个有趣的现象:不同系统在解决相似问题时,常常不约而同地采用了相似的方案。就像不同城市的交通系统虽然各有特色,但都会遇到”高峰期拥堵”这个共同问题,而解决方案不外乎限流、分流、扩容这几种思路。

软件工程中有一个经典概念来描述这种现象——设计模式。设计模式的定义是:在特定上下文中,针对反复出现的问题的、经过验证的经典解法。

这个概念来自建筑学家 Christopher Alexander 的观察:好的建筑不是凭空设计的,而是在特定文化中经过长期演化形成的模式。软件工程先驱们(Gang of Four)把这个思想搬到了编程领域——他们发现,不同的软件系统在解决相似的设计问题时,常常采用相似的结构。

记忆系统领域正在经历同样的过程。十个系统各自摸索,但解决的核心问题可以归类为有限几类,每类问题有几种经典解法。我们从前十章的源码精读和横向对比中,提炼出八种反复出现的记忆设计模式。

1.2 设计模式不是万能药

在深入每种模式之前,有三点需要明确。

第一,设计模式是”起步方案”不是”终极答案”。每种模式解决了它所针对的问题,但都带来了新的局限。选型时需要理解模式的 trade-off,而不是盲目套用。

第二,实际系统常常组合多种模式。Claude Code 同时用了 Index+Retrieval(模式二)和 Flush 变体(模式一),DB-Agent-Memory 同时用了 Semantic Pyramid(模式四)和 MCP Service(模式七)。模式是积木,系统是搭积木的结果。

第三,模式会演化。今天看到的八种模式是基于 2024-2025 年的技术现状提炼的。随着新的系统出现(比如未来的多 Agent 协作记忆),可能产生新的模式。设计模式是活的分类,不是死的教条。

1.3 八种模式总览

在逐一深入之前,先用一张表建立整体印象:

# 模式名 解决的问题 核心思想 代表系统
1 Pre-Compaction Flush 上下文压缩时记忆丢失 压缩前先抢救 OpenClaw
2 Index + Retrieval 记忆太多不能全量注入 索引目录 + 按需检索 Claude Code
3 Two-Phase Pipeline 实时提取和整合太耗资源 轻量提取 + 重量整合分开 Codex
4 Semantic Pyramid 所有记忆同等重要导致噪声 按抽象层次分层 DB-Agent-Memory
5 Virtual Memory 上下文窗口有限但记忆需无限 借鉴 OS 虚拟内存 Letta/MemGPT、MemOS
6 Temporal Graph 记忆有时间维度 知识图谱加双时间轴 Graphiti
7 MCP Memory Service 不同工具记忆不互通 记忆封装为 MCP Server supermemory、DB-Agent-Memory
8 Rules-as-Memory-Bank Agent 没有内建记忆 用 Profile 文件模拟记忆 Cline Memory Bank

这八种模式不是互相排斥的,而是可以组合的。后面会专门讨论组合策略。


2. 模式一:Pre-Compaction Flush(上下文压缩前抢救)

2.1 问题域——压缩那一刻的记忆丢失

想象你正在写一篇长篇论文,在一张 A4 纸上用极小的字密密麻麻地写。纸快写满了,你需要换一张新纸继续——但换纸的时候,旧纸上有些内容没有来得及誊抄就丢了。

LLM 的上下文窗口就是那张 A4 纸。对话越来越长,token 越来越多,当接近上下文窗口上限时,系统会触发 context compaction(上下文压缩)——把当前对话的内容压缩或截断,腾出空间给后续对话。

压缩的过程中,有些信息不可避免地会丢失。普通的压缩策略是”保留开头和结尾、压缩中间”——因为开头通常是 system prompt(不能丢),结尾是最近的对话(用户正在关注),中间的历史对话被压缩得最狠。

问题在于:中间的历史对话可能包含重要信息。用户可能在第 17 轮对话中说了一个重要偏好,但到了第 50 轮时,第 17 轮的内容被压缩掉了——这个偏好就永远丢失了,除非它已经被存储到了某个持久化的记忆系统中。

这就是 Pre-Compaction Flush 要解决的问题:在压缩发生之前,把重要信息”抢救”出来。

2.2 核心机制——嵌入式抢救 Agent

核心思路是在主对话流中嵌入一个专门的”记忆抢救 Agent”。这个 Agent 一直在后台监控对话,当检测到即将触发 compaction 时,它会立即激活,从当前上下文中提取重要信息,并写入持久化存储。

用流程图表示:

正常对话流:
  用户消息 → Agent 回复 → 用户消息 → Agent 回复 → ...
                                                    ↓
                                          token 接近阈值
                                                    ↓
Flush Agent 激活:
  扫描当前上下文 → 提取关键信息 → 写入持久存储(日记/数据库)
                                                    ↓
Compaction 安全执行:
  压缩上下文 → 腾出空间 → 对话继续

关键的时序约束是:Flush Agent 必须在 compaction 之前完成。如果 Flush 还没写完,compaction 就已经把上下文清掉了,那 Flush Agent 本身的信息来源也丢失了。

2.3 代表系统——OpenClaw

OpenClaw 是腾讯 tRPC-Agent-Go 的 Coding Agent 模式,它是最早显式将记忆保存前置于上下文压缩的系统。

OpenClaw 的实现细节:

嵌入式 Agent 声明:Flush Agent 通过 system prompt 中的工具声明让主 Agent 知道自己的存在。主 Agent 在对话过程中可以主动调用 memory_flush 工具,将当前上下文中的重要信息写入日记文件。

三层文件布局

Flush 的触发:不是等到 compaction 发生时才被动触发,而是在对话过程中由 Agent 主动判断”这段对话包含了值得保存的信息”时主动触发。这比被动触发更可靠——因为被动触发时可能已经来不及了。

Dreaming 异步整合:Flush 之后,日记文件中的信息需要异步整合到长期记忆中。OpenClaw 的 Dreaming 机制在后台运行,扫描日记文件,去重、合并、更新 MEMORY.md。

2.4 适用场景

长对话场景:Coding Agent 的编码 session 常常超过数万 token,compaction 几乎必然发生。没有 Flush 机制的 Agent 在长对话中一定会丢失重要信息。

用户不主动保存的场景:大多数用户不会在对话中说”请记住这个”。Flush Agent 自动识别重要信息并保存,降低了对用户记忆管理意识的要求。

信息密度不均匀的对话:有些对话的 90% 是琐碎交互(”嗯”“好的”“继续”),但 10% 包含关键决策。Flush Agent 可以在关键决策出现时主动保存,而不用等待整个对话结束。

2.5 局限性

增加压缩延迟:Flush Agent 本身需要 LLM 调用来提取信息,这会增加 compaction 前的延迟。在临界状态下(token 已非常接近上限),Flush Agent 的调用可能反而加速 compaction 的触发——因为它消耗的 token 也在上下文窗口内。

Flush Agent 质量依赖模型能力:提取什么、怎么提取,完全取决于 Flush Agent 的 LLM 能力。弱模型可能提取出噪声而遗漏关键信息,强模型可能过度提取导致存储膨胀。这个问题的本质和”信号 vs 噪声”是同一个问题,只是发生在了 compaction 前的时间窗口内。

时序竞争条件:如果对话产生 token 的速度超过了 Flush Agent 保存的速度,compaction 可能在 Flush 完成之前就触发了。OpenClaw 通过让 Agent 主动调用(而非被动等待 compaction 信号)来缓解这个问题,但不能完全消除。

只能抢救当前上下文内的信息:如果信息在之前的 compaction 中已经丢失了,Flush Agent 无法”复活”它。Flush 只能防止未来的丢失,不能修复过去的丢失。


3. 模式二:Index + Retrieval(索引 + 检索)

3.1 问题域——记忆太多,不能全量注入

想象你有一个藏书十万册的私人图书馆。每次你想找一本书的内容时,不可能把十万本书全翻一遍——你需要一个目录。目录告诉你每本书的书名、作者、内容简介和书架位置。你先查目录,找到可能相关的几本书,再去书架上取出来看。

记忆系统面临完全相同的问题。当记忆条目从几十条增长到数百条甚至数千条时,把所有记忆全量注入到 LLM 的上下文窗口中是不现实的——不仅超出 token 预算,还会导致”lost in the middle”问题(LLM 对上下文中部信息的关注度最低)。

问题是:如何在大量记忆中快速找到最相关的几条?

3.2 核心机制——索引文件 + 语义检索

Index + Retrieval 模式的核心思路是两层架构:

第一层:索引文件——一个精炼的”目录”文件,包含所有记忆条目的摘要。索引文件本身足够小,可以全量注入上下文窗口。

第二层:按需检索——根据当前对话的上下文,从索引文件中选出最相关的 N 个记忆文件,然后加载这些文件的完整内容注入上下文。

用流程图表示:

会话启动:
  加载索引文件(全量注入上下文)
        ↓
用户发送消息
        ↓
LLM 根据用户消息 + 索引文件
  → 选出最相关的 ≤N 个记忆文件
        ↓
加载选中文件的完整内容
  → 注入上下文
        ↓
LLM 基于完整上下文生成回复

关键的设计决策是:索引文件多大?N 取多少?索引文件的摘要多精炼?这些参数直接决定了系统的效果。

2.3 代表系统——Claude Code

Claude Code 是这个模式的最佳实践者。它的记忆系统叫 memdir,核心结构如下:

MEMORY.md:索引文件,硬顶限制 200 行 / 25KB。包含所有记忆文件的摘要和路径。每次会话启动时全量注入。

记忆文件:存储在 ~/.claude/projects/<project-hash>/memories/ 目录下,按项目和用户分层。每个文件是一个独立的 Markdown 文档,包含一类记忆(如”用户偏好”“项目架构”“踩坑记录”)。

sideQuery 检索:每一轮对话中,独立的 Sonnet 调用从索引文件中选出最相关的 5 个记忆文件(MAX_MEMORY_FILES_TO_INCLUDE = 5),加载完整内容注入上下文。

extractMemories 写入:会话结束后,后台 Agent 从对话中提取新记忆,更新或创建记忆文件,并同步更新 MEMORY.md 索引。

Claude Code 的巧妙之处在于:用 LLM 做检索决策,而不是传统的向量搜索。LLM 可以理解”用户在问数据库迁移”和”记忆文件中有一条’上周做了 PostgreSQL 迁移’“之间的语义关联——这种关联纯靠向量相似度可能抓不到。

3.4 适用场景

记忆量大(数十到数百个文件):当记忆文件数量超过可以全量注入的阈值时,Index + Retrieval 是最自然的解决方案。

人机协作编辑的场景:用户可以直接编辑索引文件和记忆文件,像整理笔记一样管理记忆。这种透明性是纯数据库方案做不到的。

记忆有自然分层:当记忆可以按项目、按主题、按时间自然分组为独立文件时,索引 + 文件的组织方式最清晰。

3.5 局限性

LLM 筛选成本高:每一轮对话都需要一次独立的 LLM 调用来做检索决策。如果用 Sonnet 级别的模型,每次调用约 0.01-0.03 美元。在高频对话中,这个成本会累积。

索引维护需要额外机制:每次新增或修改记忆文件时,索引文件需要同步更新。如果索引更新不及时,LLM 可能选不到最新的记忆文件。Claude Code 用 extractMemories 后台 Agent 来处理这个同步,但更新有延迟。

索引文件有硬顶限制:200 行 / 25KB 意味着当记忆文件数量增长到数百个时,每个文件在索引中的摘要必须越来越精简——精简到可能丢失关键信息,导致 LLM 无法准确判断文件的相关性。

不支持结构化查询:索引文件是自然语言文本,LLM 只能做语义理解来匹配。如果你需要”找出所有包含’PostgreSQL’的记忆文件”,LLM 可能不如全文索引精准。


4. 模式三:Two-Phase Pipeline(两阶段管线)

4.1 问题域——实时提取和整合的资源矛盾

想象一个新闻编辑部。记者们在前线源源不断地发回报道——有些是几句话的快讯,有些是几千字的深度稿。如果每来一篇稿件就立刻排版、校对、刊发,编辑部会被淹没——因为排版和校对是重量级操作,需要全量审视所有已有稿件来决定新稿件放在哪个版面、和哪些已有报道关联。

更合理的做法是:记者的稿件先存入一个”稿件池”(轻量操作),然后由资深编辑定期审阅稿件池,做全局性的排版和整合(重量操作)。

记忆系统的 Two-Phase Pipeline 就是这个思路。Phase1 是记者——轻量快速地从对话中提取候选记忆条目,存入暂存区。Phase2 是资深编辑——定期对暂存区做全局整合,去重、合并、更新记忆文件。

4.2 核心机制——并行提取 + 隔离整合

Phase1(实时提取):每次对话(或任务)结束后,立刻从对话中提取候选记忆条目。提取是并行的——多个对话的提取可以同时进行。提取结果是结构化的 JSON 条目,存入 staging 区。

Phase2(离线整合):在满足冷却期条件后,独立整合 Agent 读取所有 staging 条目 + 现有记忆文件,做全局性的合并、去重、更新。整合是串行的——同一时间只有一个 Phase2 在运行(需要全局锁)。

用流程图表示:

Session 1: [对话] → Phase1 并行提取 → staging 条目
Session 2: [对话] → Phase1 并行提取 → staging 条目
Session 3: [对话] → Phase1 并行提取 → staging 条目
                                          ↓
                                   冷却期到达(6小时)
                                          ↓
                            Phase2 整合 Agent:
                            读取 staging + 现有记忆
                            → 全局去重合并
                            → 增量更新记忆文件
                            → 清理 staging

4.3 代表系统——Codex

OpenAI 的 Codex(ChatGPT 的代码模式)是 Two-Phase Pipeline 的典型实现。

Phase1 的细节

Phase2 的细节

冷却期的设计意图:6 小时看起来很长,但有其道理。Codex 的用户通常一天内会有多个 session——如果每个 session 结束后都跑一次整合,不仅浪费资源,还会产生碎片化的记忆(每次只看到最近一个 session 的信息,缺乏全局视角)。6 小时冷却期确保整合 Agent 能看到足够多的新信息,做出更好的全局决策。

4.4 适用场景

高并发场景:多个 session 同时运行时,Phase1 的并行提取确保不阻塞主流程,Phase2 的全局锁确保整合的一致性。

资源敏感场景:整合是重量操作(需要读取所有记忆 + LLM 推理),放在后台异步执行不影响用户交互的延迟。

需要高质量整合的场景:Phase2 的全局视角能发现跨 session 的模式和矛盾,这是单 session 提取做不到的。比如用户在三个不同 session 中分别说了”在学 Rust”“在写 Rust 项目”“Rust 的性能比 Go 好”,Phase2 可以把这三条整合为”用户是 Rust 开发者,偏好 Rust 的性能优势”。

4.5 局限性

Phase2 冷却期的新鲜度延迟:6 小时冷却期意味着最新产生的记忆要等至少 6 小时才能被整合进主记忆文件。在这 6 小时内,新 session 看到的仍是旧版本的记忆。

整合质量不透明:Phase2 的整合 Agent 自动决定保留哪些记忆、删除哪些、合并哪些——但用户无法预知这些决策。如果整合 Agent 做了错误的合并,原始信息可能永久丢失(虽然 staging 条目还在,但用户通常不会去看)。

staging 表膨胀:如果冷却期设置过长或 Phase2 因故障未运行,staging 表会持续增长。staging 条目没有自动清理机制——只有 Phase2 成功运行后才会清理。

全局锁的单点瓶颈:Phase2 的全局锁确保了一致性,但也意味着整合是串行的——如果整合本身耗时很长(比如记忆量巨大时),后续的 Phase2 请求会排队等待。


5. 模式四:Semantic Pyramid(语义金字塔)

5.1 问题域——所有记忆同等重要导致检索噪声大

想象你有一个文件柜,里面塞满了各种文件——从出生证明到昨天的超市小票,从护照到外卖订单的确认短信。如果你需要找护照,你得翻过几百张超市小票才能找到——因为所有文件都被同等对待,没有分类,没有优先级。

记忆系统如果不对记忆做分层处理,就会变成这个文件柜。当你有 1000 条记忆时,检索结果中可能混杂着”用户三年前说了一句喜欢晴天”和”用户昨天决定切换技术栈”——前者早已过时,后者至关重要,但系统不区分它们。

语义金字塔的解决思路是:按抽象层次对记忆分层,越往上越精炼、越稳定、越通用。

5.2 核心机制——四层逐层蒸馏

语义金字塔将记忆组织为四个层次,从底到顶逐层蒸馏:

L0(观测层)——原始对话内容,最详细、最冗余。相当于快递分拣中心的原始包裹。

L1(事实层)——从观测中提取的独立原子事实。相当于分拣后的归类标签。

L2(摘要层)——对事实的阶段性聚合。相当于装箱汇总单。

L3(画像层)——长期稳定的用户/项目画像。相当于客户画像。

每一层的写入是自底向上的——L0 提取为 L1,L1 聚合为 L2,L2 提炼为 L3。每一层的检索是自顶向下的——先查 L3 有没有直接相关的画像,再查 L2 有没有更具体的摘要,最后才去 L1 找细节。

写入方向(从底到顶):
  L0 Observation → L1 Fact → L2 Summary → L3 Profile
  (原始对话)       (原子事实)  (场景聚合)   (用户画像)

检索方向(从顶到底):
  L3 Profile → L2 Summary → L1 Fact → L0 Observation
  (最精炼)    (较精炼)     (有细节)   (最详细)

5.3 代表系统——DB-Agent-Memory

DB-Agent-Memory 是语义金字塔模式的最佳实践者。

每层的存储和特征

晋升触发条件

MMD 注入优化:将记忆分为稳定区(L3 画像 + L2 摘要,放在 prompt 前部,KV Cache 友好)和动态区(L1 事实 + L0 观测,放在 prompt 后部,每次变化),最大化 Cache 命中率。

5.4 适用场景

需要不同粒度记忆的场景:有些查询只需要粗粒度信息(”用户是什么角色”),有些需要细粒度信息(”用户上周三讨论了什么方案”)。金字塔结构让系统可以根据查询需求选择合适的层级检索。

重视 Prompt Cache 友好性:稳定区/动态区分离让 KV Cache 命中率最大化——在按 token 计费的模型中,这是实实在在的成本优势。

零依赖部署环境:不需要向量数据库、图数据库,SQLite + sqlite-vec 就够了。适合嵌入式部署和资源受限环境。

5.5 局限性

层间晋升逻辑复杂:L1→L2→L3 的晋升不是简单的”够 N 条就触发”——需要判断哪些事实可以聚合、聚合后的摘要是否准确、哪些模式足够稳定可以晋升为画像。这些判断目前都依赖 LLM,质量不稳定。

自动晋升容易丢信息:每次蒸馏都是一次信息压缩——压缩必然伴随损失。L0 中的细节在 L1 中被提炼为事实,事实的上下文可能丢失;L1 中的多条事实在 L2 中被聚合,个别异常值可能被忽略。越往上,信息损失越严重。

提炼管道的 LLM 成本线性增长:每一层晋升都需要 LLM 调用。对话越频繁、记忆越多,LLM 成本越高。在成本敏感的场景下,这可能成为瓶颈。

底层噪声向上层渗透:如果 L0→L1 提取时 LLM 把噪声误判为信号(比如把”用户随口说了句 Python 不错”提取为”用户偏好 Python”),这个错误会逐层向上放大——L2 的摘要基于错误的事实,L3 的画像基于错误的摘要。金字塔结构天然有”错误放大”效应。


6. 模式五:Virtual Memory(虚拟内存)

6.1 问题域——上下文窗口容量有限但记忆需要”无限”

想象一台电脑只有 8GB 内存,但你要运行需要 100GB 数据的程序。现代操作系统是怎么解决这个问题的?虚拟内存——把不常用的数据换到硬盘上,只把当前需要的数据放在内存里。程序以为它有无限的内存,实际上是在内存和硬盘之间不断换入换出。

Agent 面临完全相同的问题:上下文窗口是有限的”内存”(当前 GPT-4 约 128K token),但用户可能在数月甚至数年间和 Agent 积累了远超上下文容量的交互历史。Agent 需要”记住”所有这些历史,但又不可能全塞进上下文窗口。

6.2 核心机制——Core Memory + Archival Memory

Virtual Memory 模式将记忆分为两个区域,直接类比操作系统的 RAM 和硬盘:

Core Memory(核心记忆/RAM):始终在上下文窗口中,读取零延迟,但容量有限(通常约 2000 token)。存储 Agent 的自我定义(persona)和用户的核心信息。

Archival Memory(归档记忆/硬盘):存储在数据库中,容量无限,但需要搜索才能访问。存储所有历史交互和提取的事实。

Recall Memory(回忆记忆/缓存):最近对话的搜索缓存,可以按关键词或语义搜索。

Agent 通过工具调用(tool call)管理记忆的换入换出:

Core Memory 工具:
  core_memory_append(key, value)     ← 向核心记忆追加信息
  core_memory_replace(old, new)      ← 替换核心记忆中的信息

Archival Memory 工具:
  archival_memory_insert(content)    ← 向归档记忆插入信息
  archival_memory_search(query)      ← 搜索归档记忆

Recall Memory 工具:
  conversation_search(query)         ← 搜索对话历史

Agent 自主决定什么时候把信息从 Archival 换入 Core(page in),什么时候把 Core 中不常用的信息换出到 Archival(page out)。当 Core Memory 满了,Agent 必须决定删什么来腾出空间——这就像内存满了必须换页。

6.3 代表系统——Letta/MemGPT、MemOS

Letta/MemGPT 是虚拟内存模式的开创者。源自 2023 年的 MemGPT 论文,核心创新是把操作系统的虚拟内存概念显式引入 Agent 记忆管理。Agent 有 7 个记忆管理工具,完全自主决定何时读写。

MemOS 是 Letta 虚拟内存思想的学术延伸。Letta 借用的是虚拟内存(空间管理),MemOS 借用的是整个操作系统——不仅做空间管理(MemCube 容器化),还做调度(MemScheduler)、做后台服务(Dream Pipeline)、做跨模态存储。MemOS 可以被看作 Virtual Memory 模式的”升维版”。

两者在虚拟内存设计上的关键区别:

维度 Letta MemOS
内存模型 Core + Archival + Recall MemCube(四种类型容器)
管理方式 Agent 自主(tool call) 系统调度 + Agent 可干预
换入换出 Agent 手动 page in/out MemScheduler 自动调度
容量管理 Core Memory 字符限制 MemCube 优先级 + 衰减

6.4 适用场景

需要大量历史记忆但上下文预算有限:当用户和 Agent 有长期的交互历史,不可能全量注入上下文时,Virtual Memory 提供了”按需加载”的方案。

Agent 有强元认知能力:这个模式的核心前提是 Agent 能自主判断”什么时候该查记忆”“查什么”“记什么”。在 GPT-4 级别的模型上这个前提基本成立,但在更弱的模型上,Agent 经常忘记保存或检索。

研究/实验性场景:Virtual Memory 模式提供了最灵活的记忆管理方式,适合探索 Agent 自主记忆管理的能力边界。

6.5 局限性

Agent 必须有强元认知能力:这是 Virtual Memory 模式最大的局限。Agent 需要”意识到”自己不知道某件事,然后主动搜索归档记忆——这种元认知能力在弱模型上经常缺失。实践中,弱模型的 Agent 经常出现三种失败模式:

  1. 忘记保存:Agent 和用户讨论了重要偏好但没有调用 archival_memory_insert
  2. 忘记检索:用户问了一个之前讨论过的问题,Agent 没有搜索归档记忆就凭”记忆”回答——可能回答错误
  3. Page fault 风暴:Agent 频繁在 Core 和 Archival 之间换入换出,每次都是完整的工具调用周期(2-5 秒),用户体验极差

每次记忆操作都是工具调用:读记忆、写记忆、搜索记忆——每个操作都是一次完整的工具调用周期(LLM 生成工具调用 → 系统执行 → LLM 处理结果)。这比系统自动管理记忆慢得多,也贵得多。

Core Memory 容量太小:约 2000 token 的限制意味着 Core Memory 只能存最核心的信息。一旦信息量超过容量,Agent 必须做艰难的选择——删什么?这种选择本身就需要推理能力,弱模型经常做错。


7. 模式六:Temporal Graph(时序图)

7.1 问题域——记忆有时间维度

“去年住北京”和”现在住上海”是两条不同的事实,但它们描述的是同一个属性(住址)在不同时间的值。如果你只存”住北京”和”住上海”两条记忆,检索”用户现在住哪”时可能两条都返回——你分不清哪个是当前状态。

再比如”用户喜欢 React”和”用户不再用 React 了,改用 Vue”——这两条记忆表达了完全相反的状态,但如果没有时间维度,系统无法区分”当前偏好”和”历史偏好”。

很多事实是有时间生命周期的:住址会变、工作会变、偏好会变、项目状态会变。简单的”添加事实/删除事实”模型无法表达这种时间性的变化。

7.2 核心机制——知识图谱 + 双时间轴

Temporal Graph 模式在知识图谱的每条边上添加两个时间戳:

当新事实和旧事实冲突时,不删除旧事实——只是把旧事实的 invalid_at 设为当前时间,然后创建一条新事实(valid_at = 当前时间,invalid_at = NULL)。两条事实都保留在图谱中,通过时间戳区分。

[2024-01] 用户 --住在--> 北京  (valid_at=2024-01, invalid_at=2025-03)
[2025-03] 用户 --住在--> 上海  (valid_at=2025-03, invalid_at=NULL)

查询 "用户现在住哪":
  → invalid_at IS NULL → 上海

查询 "用户2024年住哪":
  → valid_at ≤ 2024-01 AND invalid_at > 2024-12 → 北京

这个设计最优雅的地方在于:它不是”解决”冲突,而是”记录”冲突的完整历史。每条事实的来龙去脉都保留在图谱中,任何时刻的状态都可以通过时间过滤精确还原。

7.3 代表系统——Graphiti

Graphiti 是 Zep 团队出品的时序知识图谱引擎,是 Temporal Graph 模式的典型实现。

存储:Neo4j 图数据库。节点表示实体(人、地点、技术等),边表示关系(住在、使用、偏好等),每条边带双时间轴。

检索:两阶段架构:

  1. 向量粗筛:用 embedding 相似度找到候选节点和边
  2. Cross-Encoder 精排:用 Cross-Encoder 模型对候选结果做精排,确保返回最相关的结果

实体消解:当对话中出现”小明”和”明哥”时,Graphiti 需要 LLM 判断它们是否指同一个人。如果判断为同一人,两条边指向同一个节点——图谱更精简。如果判断错误,不同人的信息被错误合并。

图谱膨胀问题:每次事实变更都创建新边而非修改旧边——图谱只增不减。1000 轮对话后可能有数万个节点和边,查询性能逐渐下降。这是 Temporal Graph 模式最严峻的工程挑战。

7.4 适用场景

传记类信息:用户的住址、工作、关系等随时间变化的个人信息——这类信息天然适合用时序图表达。

状态变化频繁的场景:项目状态(规划中→开发中→已上线)、技术栈偏好(Python→Rust)、合作关系(同事→前同事)——这些频繁变化的事实需要精确的时间追踪。

需要历史追溯的场景:回答”用户什么时候开始用 Rust?”“用户之前用的是什么?”这类问题时,时序图可以直接给出答案,而不需要翻阅所有历史记忆。

7.5 局限性

图数据库依赖重:Neo4j 需要 JVM + 独立服务,推荐 4GB+ RAM。部署复杂度远超 SQLite 或文件系统方案。对于简单的记忆场景,这是典型的杀鸡用牛刀。

小规模记忆用图是杀鸡用牛刀:如果你只有几十到几百条记忆,关系也不复杂,用图数据库来存储和检索的成本远大于收益。一个简单的键值对或文本列表就够了。

Cross-Encoder 精排增加延迟:Graphiti 的两阶段检索(向量粗筛 + Cross-Encoder 精排)虽然精度高,但 Cross-Encoder 推理本身需要几百毫秒。加上图遍历的网络往返,总延迟可能超过 1 秒。

实体消解错误不可逆:如果 LLM 错误地把”小明”和”明哥”判断为同一个人,两个不同人的信息被合并到同一个节点——后续查询会返回混合了两个人的错误结果。这种错误的修复非常困难——需要在图谱中”拆分”节点,恢复所有关联的边。


8. 模式七:MCP Memory Service(MCP 记忆服务)

8.1 问题域——不同 AI 工具的记忆不互通

想象你有三个手机 App——日历、备忘录和邮件。日历记住了你的会议时间,备忘记录了你的待办事项,邮件里有你所有的商务通信。但这三个 App 之间完全不互通——日历不知道你的邮件里有明天的航班信息,备忘录不知道你的会议改期了。

你的 AI 工具也是这样。Claude Code 有自己的记忆(MEMORY.md),Codex 有自己的记忆(memories.md),Cursor 有自己的记忆(.cursor/rules/)。你在 Claude Code 里说”我偏好 TypeScript”,Cursor 不知道;你在 Codex 里讨论了项目架构决策,Claude Code 看不到。

MCP(Model Context Protocol)是 Anthropic 在 2024 年底推出的标准协议,目的是让 AI 工具能通过统一的接口访问外部服务。MCP Memory Service 模式就是利用 MCP 协议,将记忆引擎封装为一个标准化的外部服务——任何支持 MCP 的 AI 工具都可以连接。

8.2 核心机制——记忆引擎封装为 MCP Server

MCP Memory Service 的核心思路是将记忆操作封装为 MCP Server 暴露的工具:

MCP Server 暴露的工具:
  memory_add(content, metadata)     ← 添加记忆
  memory_search(query, top_k)       ← 搜索记忆
  memory_list()                     ← 列出所有记忆
  memory_delete(id)                 ← 删除记忆
  memory_profile()                  ← 获取用户画像

任何支持 MCP 的 AI 工具(Claude Desktop、Cursor、Cline 等)都可以作为 MCP Client 连接到记忆 Server,通过标准化的工具调用来读写记忆。

三种部署拓扑:

拓扑一:Host-Adapter 模式(DB-Agent-Memory)

Agent → MCP Adapter → DB-Agent-Memory 核心库

Adapter 很薄,核心逻辑在 DB-Agent-Memory 库中。支持嵌入使用(不通过 MCP 也可以直接调用)。

拓扑二:Pure Server 模式(supermemory)

Agent → MCP Server (supermemory)

所有逻辑在 Server 侧。多 Agent 共享同一套记忆,适合跨工具场景。

拓扑三:混合模式(mem0)

Agent → MCP Adapter → mem0 SDK → 后端服务

三层间接:Agent 通过 MCP 调用 Adapter,Adapter 调用 mem0 SDK,SDK 连接后端向量数据库。灵活但复杂。

8.3 代表系统——supermemory MCP、DB-Agent-Memory MCP Adapter

supermemory MCP Server 是 Pure Server 模式的代表。supermemory 的三流检索(BM25 + 向量 + 图遍历)封装为 MCP 工具,Agent 通过 search_memory 工具搜索,结果在 sub-300ms 内返回。支持多个 Agent 共享同一个 supermemory 实例。

DB-Agent-Memory MCP Adapter 是 Host-Adapter 模式的代表。将 DB-Agent-Memory 的记忆能力(四层金字塔 + MMD 注入)包装为 MCP 工具,同时保留 Adapter 嵌入使用的能力。核心优势是”双模式”——既可以嵌入 Agent 内部(零延迟),也可以通过 MCP 对外服务(跨 Agent 共享)。

8.4 适用场景

需要跨工具共享记忆:开发者同时使用多个 AI 工具(Claude Code + Cursor + Codex),希望记忆在工具之间互通。MCP 提供了标准化的接入通道。

Agent 没有内建记忆能力:Cline、Continue 等开源 Agent 没有自己的记忆系统,通过 MCP 连接外部记忆服务是最简单的获得记忆能力的方式——不需要修改 Agent 源码。

已有记忆基础设施需要接入新 Agent:团队已经有 mem0 或 DB-Agent-Memory 作为记忆后端,新加入的 Agent 只需要支持 MCP 就能接入——不需要为每个 Agent 单独做集成。

8.5 局限性

每次读写都是网络往返:MCP 通信通常是 stdio/SSE 方式。纯本地 MCP 通信延迟约 50-300ms,远程 MCP(跨网络)延迟约 200-500ms。相比之下,嵌入式的记忆操作延迟接近零。

MCP 不定义记忆数据模型:MCP 只定义了工具调用的接口格式,不定义记忆条目的数据结构、关系语义、冲突解决策略。不同 MCP 记忆服务返回的 JSON 结构可能完全不同——Agent 需要适配每个服务的特定格式。

Tool Description 质量影响 Agent 使用效果:MCP 工具的效果很大程度上取决于工具描述(tool description)的质量。如果 memory_search 的描述写得太模糊,Agent 可能不知道什么时候该调用它;如果描述太复杂,Agent 可能误用。这类似于 API 文档的质量直接影响开发者体验。

事务语义缺失:当多个 Agent 同时通过 MCP 读写同一条记忆时,写入冲突和一致性问题没有标准协议解决。MCP 不定义锁机制、版本号或冲突解决策略——这些都需要记忆后端自行实现。


9. 模式八:Rules-as-Memory-Bank(规则即记忆库)

9.1 问题域——社区 Agent 没有内建 User Memory 能力

想象你刚买了一台新电脑,它没有硬盘,只有内存——每次关机后所有数据都会丢失。但你发现了一个 workaround:你可以把重要信息写在便签纸上,贴在屏幕边框上。每次开机时你先把便签纸上的内容重新输入电脑,关机前把新信息抄到便签纸上。

这个 workaround 听起来很原始,但这就是 Rules-as-Memory-Bank 模式的本质——当 Agent 没有内建的记忆系统时,用现有的 Profile/Rules 机制来”模拟”记忆功能。

很多社区 Agent(Cline、Cursor 的社区方案等)没有内建 User Memory 能力——它们不能自动从对话中提取和存储用户偏好。但它们有一个 Profile 机制——可以在 .cursorrules.clinerulesmemory-bank/ 目录中放置 Markdown 文件,Agent 在每次会话开始时会自动加载这些文件。

9.2 核心机制——用 Profile 文件模拟 User Memory

核心思路是将记忆组织为一组固定结构的 Markdown 文件,利用 Agent 的 Profile 加载机制来实现”记忆持久化”。

典型的 Memory Bank 文件结构:

memory-bank/
  projectbrief.md     ← 项目基础信息(类似 L3 画像)
  techContext.md      ← 技术栈和工具链上下文
  systemPatterns.md   ← 架构模式和设计决策
  activeContext.md    ← 当前工作焦点(类似 L1 当前事实)
  progress.md         ← 进展记录和待办事项

运作方式:

  1. 会话开始:Agent 读取所有 Memory Bank 文件,全量注入 system prompt
  2. 会话中:Agent 根据加载的记忆内容调整行为
  3. 会话结束:Agent 更新 activeContext.md 和 progress.md

Custom Instructions(.clinerules.cursorrules)驱动这个读写循环:

.clinerules 中的指令:
  "每次会话开始时读取 memory-bank/ 下所有文件"
  "每次会话结束时更新 activeContext.md 和 progress.md"
  "遇到重要决策时更新 systemPatterns.md"

9.3 代表系统——Cline Memory Bank、社区 Cursor 方案

Cline Memory Bank 是社区中最流行的 Rules-as-Memory-Bank 实现。它通过 .clinerules 自定义指令驱动 Cline Agent 在会话边界读写 Memory Bank 文件。在 Cline 的 Discord 社区中有大量用户分享自己的 Memory Bank 模板和使用经验。

社区 Cursor 方案:Cursor 用户在 .cursor/rules/ 目录下创建带 alwaysApply 模式的规则文件,内容是项目上下文和用户偏好。Cursor 会在每次会话开始时加载这些规则——本质上和 Memory Bank 是同一个思路。

9.4 适用场景

记忆能力匮乏的 Agent 的权宜之计:当 Agent 没有内建 User Memory 也不支持 MCP 时,Rules-as-Memory-Bank 是唯一的选择——用现有的 Profile 机制”凑合”出记忆功能。

小型项目:当记忆量有限(几十条以内),全量注入不会超过 token 预算时,这种模式足够用。

高度人机协作的场景:用户可以直接编辑 Memory Bank 文件,精确控制 Agent 知道什么——这种透明性在某些场景下是优势。

9.5 局限性——三个根本缺陷

Rules-as-Memory-Bank 有三个根本性缺陷,不是优化能解决的:

缺陷一:无语义检索。所有 Memory Bank 文件全量注入上下文——没有”只加载相关文件”的能力。当文件数量增长到几十个时,全量注入的 token 成本线性增长,而且 LLM 要在大量文本中找到相关信息,检索效率急剧下降。相比 Claude Code 的 Index + Retrieval(按需加载 5 个文件)或 mem0 的向量搜索(只返回 top-K 相关条目),全量注入是最低效的检索方式。

缺陷二:无冲突合并。当多个会话同时更新同一个 Memory Bank 文件时,没有自动合并机制——只能靠用户手动解决冲突。如果 Agent A 把 activeContext.md 改成了”正在开发认证模块”,Agent B 同时把它改成了”正在修复数据库 Bug”,最后一个写入的覆盖前一个——信息丢失。相比之下,mem0 的 LLM 去重和 Graphiti 的时序边都能自动处理冲突。

缺陷三:人工触发。记忆的读写依赖于 Agent “记住”在会话开始时读文件、在会话结束时更新文件——但 Agent 并不总是遵守这些指令。特别是当对话很长或很复杂时,Agent 经常”忘记”在会话结束时更新文件。这是 Profile 机制的固有局限——它靠指令驱动而非系统保障。

这三个缺陷决定了 Rules-as-Memory-Bank 只能是过渡方案,不能是终极方案。当 Agent 支持 MCP 时,应该迁移到 MCP Memory Service(模式七)——后者有语义检索、有冲突处理、有系统保障。


10. 选型决策树——根据场景选择模式组合

10.1 按 Agent 能力选型

选择记忆模式首先要考虑你的 Agent 有什么能力:

你的 Agent 有内建记忆系统吗?
├── 有 → 使用内建模式 + 考虑 MCP 对外共享
│        ├── Claude Code → Index + Retrieval (模式二)
│        ├── Codex → Two-Phase Pipeline (模式三)
│        └── OpenClaw → Pre-Compaction Flush (模式一)
│
├── 没有,但支持 MCP → MCP Memory Service (模式七)
│                        ├── 需要跨 Agent 共享 → supermemory (Pure Server)
│                        └── 需要嵌入使用 → DB-Agent-Memory (Host-Adapter)
│
└── 没有,也不支持 MCP → Rules-as-Memory-Bank (模式八)
                          └── 这是权宜之计,尽快升级到 MCP

10.2 按记忆特征选型

记忆的内容特征也影响模式选择:

记忆特征 推荐模式 理由
事实频繁变化 Temporal Graph (模式六) 时序边天然追踪变化
需要不同粒度 Semantic Pyramid (模式四) 四层金字塔满足多粒度查询
记忆量大(数百条+) Index + Retrieval (模式二) 按需检索避免全量注入
长对话为主 Pre-Compaction Flush (模式一) 防止压缩时丢失
多 Agent 共享 MCP Memory Service (模式七) 标准化接入

10.3 按约束条件选型

工程约束往往是最实际的选型因素:

约束条件 推荐模式 理由
零外部依赖 Semantic Pyramid (模式四) SQLite 就够
低延迟(< 100ms) Semantic Pyramid (模式四) 本地存储 + MMD 注入
高精度检索 Temporal Graph (模式六) 图遍历 + 精排
最小改动接入 MCP Memory Service (模式七) 不改 Agent 源码
模型能力弱 避免 Virtual Memory (模式五) Agent 自管理不可靠

10.4 模式组合建议

实践中,单一模式很少能覆盖所有需求。常见的组合策略:

组合一:Pyramid + MCP(DB-Agent-Memory 的方向)

组合二:Flush + Pyramid(OpenClaw 的方向)

组合三:Index + Pipeline(Claude Code + Codex 的融合方向)

组合四:Bank → MCP(Cline 的升级路径)

选型的核心原则是:从最简单的满足需求的模式开始,按需增加复杂度。不要一开始就上 Temporal Graph + Two-Phase Pipeline + MCP——组合越多,系统越复杂,调试越困难。先用一个模式解决 80% 的问题,再考虑组合解决剩余 20%。


11. 演进趋势——从单一模式到模式组合

11.1 短期趋势(2026 下半年):MCP 成为事实标准

MCP Memory Service(模式七)正在快速成为记忆系统的标准接入方式。原因有三个:

第一,MCP 是 Anthropic 推动的开放协议,得到了主流 AI 工具的支持(Claude Desktop、Cursor、Cline 等都已经支持 MCP)。这意味着选择 MCP 作为接入方式不需要说服任何人——生态已经形成。

第二,MCP 解决了最紧迫的互操作性问题——让没有内建记忆的 Agent 也能获得记忆能力。DB-Agent-Memory 的 Host-Adapter 模式在这个趋势中位置有利——既可以嵌入使用,又可以通过 MCP 暴露。

第三,MCP 的工具调用模式比 Agent 自主管理(Virtual Memory 模式)更实用——Agent 只需要在需要时调用工具,不需要管理记忆的换入换出。这降低了对 Agent 元认知能力的要求。

11.2 中期趋势(2027):模式融合加速

生产级 Agent 正在从单一模式向模式组合发展:

Claude Code = Index + Retrieval + Flush 变体。Claude Code 的 extractMemories 机制本质上是一个”延迟的 Flush”——不是在 compaction 前抢救,而是在会话结束后提取。加上 autoDream 整合(24 小时 + 5 次会话触发),Claude Code 实际上组合了模式二(Index)和模式三(Pipeline)的部分特征。

Codex = Two-Phase Pipeline + Citation 正反馈。Codex 的核心是模式三,但 citation 正反馈循环(usage_count)引入了类似 Semantic Pyramid 的分层效果——被频繁引用的记忆相当于金字塔的上层(更精炼、更稳定),低引用的相当于下层(可被淘汰)。

OpenClaw = Flush + Dreaming。OpenClaw 组合了模式一(Flush)和类似 MemOS Dream Pipeline 的后台整合。Flush 保证不丢失,Dreaming 保证不膨胀。

DB-Agent-Memory = Pyramid + MCP(演进方向)。当前 DB-Agent-Memory 主要是模式四(Pyramid),但 MCP Adapter 正在开发中——完成后将组合模式四和模式七。

这些组合不是随意的——每种组合都解决了一对互补的问题:

11.3 长期趋势:从自动管理到人机协同

当前的记忆系统要么是全自动的(DB-Agent-Memory 的管道),要么是全自主的(Letta 的 Agent 自管理)。但最理想的模式可能是”系统自动 + 用户/Agent 微调”的混合:

Claude Code 的 MEMORY.md 已经隐含了这个方向——用户可以直接编辑索引文件,系统自动维护记忆文件,两者共存。但这种共存目前还没有正式的协议来协调——用户编辑和系统更新的冲突需要更优雅的解决机制。

长期来看,记忆系统可能演变为”个性化知识库”——既有从交互中学到的经验(Memory),也有从文档中检索到的知识(RAG),统一由一个分层存储 + 多模态检索系统管理。Memory 和 RAG 的边界将进一步模糊——正如 Ch06 所讨论的,经验和知识不是一回事,但管理它们的基础设施可以是同一套。


12. 本章小结

回顾本章的核心要点:

下一章我们将从”设计模式”转向”未解难题”——看记忆系统还有哪些技术挑战等待解决。


导读目录:README.md 上一章:Ch17: OpenClaw 记忆——Pre-Compaction Flush 与 Dreaming 下一章:Ch19: 十九个技术挑战——记忆系统的未解难题