MIMO-Code的记忆系统设计
简单分析了一下 MiMo-Code 的记忆系统设计,感觉非常有意思,特地写了这篇文档来总结和分析一下。
[!NOTE] 理论基石:局部性原理 (Principle of Locality) 能够使用存储金字塔(Memory Hierarchy)架构去建模和实现记忆系统,本质上是因为 Coding 场景下的 Agent 行为和数据访问具有极强的局部性。如果数据访问是完全随机且无序的,多级缓存不仅无法提升性能,反而会因为缓存失效(Cache Miss)和同步开销导致性能大幅退化。
1. 记忆系统的“存储金字塔”模型
LLM 的 Context Window(上下文窗口)就像是 CPU 的寄存器/高速缓存,容量极其昂贵;而磁盘上的 Markdown 文件则是外存。系统依此构建了以下存储金字塔架构:
| 存储层级 | 对应物理媒介 | 读写策略 & 特点 | 在 MiMo-Code 中的角色与数据 |
|---|---|---|---|
| L0 (Registers) | LLM Context Window (最内层) | 极速读写,单价极高。 仅保留物理时间当前这一轮的交互。 |
当前 Turn 的用户 Prompt、正在编辑的局部代码段、上一步工具的输出。 |
| L1 (L1 Cache) | System Prompt Injected Context | 随每次 Prompt 自动注入,常驻内存。 通过 Active Recall 协议防止重复读取。 |
结构化会话状态 checkpoint.md,以及项目 MEMORY.md 核心规则。 |
| L2/L3 (Cache) | notes.md |
实时追加,无结构化开销。 采用 Write-Back (写回) 策略。 |
Agent 的临时记录、报错、想法。在 Checkpoint 触发时被清空并写回下层。 |
| L4 (Memory) | tasks/<tid>/progress.md |
页面调度 (Paging)。 按空间隔离,按需加载。 |
物理隔离的子任务详细日志。仅在 Agent 处理对应任务时被载入 Context。 |
| L5 (Storage) | MEMORY.md & SQLite FTS5 |
持久外存,支持 Git 版本化。 Lazy Reconcile 本地索引。 |
跨会话的项目上下文、硬约束规则、架构决策。 |
| L6 (Archive) | mimocode.db |
冷数据存储,不直接参与运行时上下文。 | SQLite 中的全量历史对话消息与工具调用日志。 |
2. 局部性原理的体现与应用
A. 时间局部性 (Temporal Locality)
定义:如果一个数据项被访问,那么在不久的将来它很可能再次被访问。
在 Coding 过程中,Agent 遇到的编译报错、刚做完的修改,在接下来的 5-10 轮对话中大概率会被高频使用。
- 设计应用:Agent 随手记入
notes.md的报错与微观察,会在 Checkpoint 事件中被并入checkpoint.md(如## §8 Errors and fixes)。由于checkpoint.md常驻 L1 缓存,Agent 能立即在其物理上下文中命中这些局部经验,无需再通过工具向外检索,极大地规避了“重复掉进同一个坑”的现象。
B. 空间局部性 (Spatial Locality)
定义:如果一个存储位置被引用,那么临近的存储位置在不久的将来也很可能被引用。
在复杂项目中,Agent 编写模块 A 时,绝大多数背景知识和上下文都集中在模块 A 及其依赖项中,与其他模块(如模块 B、C)无关。
- 设计应用:
- 按 Task ID 隔离:子任务的详细日志独立存放在
tasks/<tid>/progress.md。只有当 Agent(或子 Agent)切换到对应的 Task 空间时,才会按需“调度”该页面的数据,实现了空间的物理隔离与去噪。 - Section Spillovers (分区溢出分流):如果 L1 Cache 中的某章节(如设计决策)超出了 Token 预算,系统会自动将其“分段”剥离为
checkpoint-<topic>.md。LLM 只有在访问该特定主题时才会去 Read,避免全局空间污染。
- 按 Task ID 隔离:子任务的详细日志独立存放在
C. LLM 独特的“有损写回 (Lossy Write-Back)”机制
在传统计算机中,缓存的写回(Write-Back)必须是无损(Lossless)的,确保字节一致性。但在 LLM 窗口受限的场景下,无损写回会导致 L1 空间迅速暴涨。
- 有损语义整理:当 L2/L3 的
notes.md积累到 Token 阈值时,checkpoint-writer子 Agent 会扮演“缓存整理器”。它对冗长、反复试错的对话和调试日志进行有损语义压缩,只提炼出最高密度的核心结论(例如:将 20 轮试错整理为“Webpack 编译因 A 报错,通过修改 B 修复”),然后将结论写回到持久层的MEMORY.md(L5)或会话状态的checkpoint.md(L1),并清空当前的 L2 缓存。
3. SQLite FTS5 索引与检索
为了保证外存层(L5)数据在需要时能被秒级载入,MiMo-Code 设计了基于 SQLite 內建 FTS5 的本地检索系统。
Table Schema 与缓存一致性触发器
CREATE TABLE `memory_fts` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`path` text NOT NULL UNIQUE,
`scope` text NOT NULL,
`scope_id` text DEFAULT '' NOT NULL,
`type` text NOT NULL,
`body` text NOT NULL,
`fingerprint` text NOT NULL,
`last_indexed_at` integer NOT NULL
);FTS5 外部内容表触发器 (Preventing Index Pollution)
为了保持 memory_fts_idx(FTS5 虚拟表)与 memory_fts(内容表)的缓存一致性,触发器在 UPDATE 和 DELETE 时采用了专有的 'delete' 语法,以正确清理已失效的 token,防止索引累积损坏:
CREATE TRIGGER `memory_fts_ad` AFTER DELETE ON `memory_fts` BEGIN
INSERT INTO `memory_fts_idx`(`memory_fts_idx`, rowid, body) VALUES('delete', OLD.id, OLD.body);
END;
CREATE TRIGGER `memory_fts_au` AFTER UPDATE ON `memory_fts` BEGIN
INSERT INTO `memory_fts_idx`(`memory_fts_idx`, rowid, body) VALUES('delete', OLD.id, OLD.body);
INSERT INTO `memory_fts_idx`(rowid, body) VALUES (NEW.id, NEW.body);
END;检索算法:OR-Join + 相对分数过滤 (Relative Score Floor)
-
分词整理:使用正则
[\p{L}\p{N}_]+匹配多语言字符(包含中日韩字符),过滤特殊符号,并对每个 Term 增加双引号进行短语包装以规避 FTS5 语法崩溃。 -
OR-Join:多词检索时,词条间使用
OR连接。- 设计考量:AND 连接在代码场景下过于严苛。若用户搜
postgres database port 5433,而记忆中只有postgres port 5433,AND 会导致检索结果为 0(Cache Miss)。OR 连接能保证最大召回率,将排序交给 BM25 算法。
- 设计考量:AND 连接在代码场景下过于严苛。若用户搜
-
相对分数过滤:
const cutoff = floorRatio > 0 ? topScore * floorRatio : -Infinity; return results.filter((r, i) => i === 0 || r.score >= cutoff);- 设计考量:在记忆库很小的项目初期,BM25 算出的绝对得分通常极低。若采用绝对值过滤(如 score > 5),所有相关结果都会被误杀。采用相对得分(保留前 15% 分数的条目)能自适应不同的语料规模,保证检索召回的稳定性。
4. 记忆系统的定期演化:Dream & Distill
记忆不仅需要读取与写入,更需要演化和优化。MiMo-Code 设计了两个手动指令来维持金字塔下层的高信噪比:
/dream (内存整理与知识沉淀)
- 对应 GC/数据提炼:定期审查最近 7 天的原始交互轨迹(L6)和会话草稿(L2/L3),将验证有效的知识、架构决策沉淀到项目
MEMORY.md(L5)。 - 信噪比控制:限制
MEMORY.md在 200 行 / 10KB 以内。合并重复项,清理已过时的临时决策。
/distill (工具化提炼)
- 对应指令集扩展:审查最近 30 天的频繁操作。如果发现某些流程(如部署测试、特定 Bug 修复)被高频重复使用,它会将该流程打包为更上层的指令集资产(如自定义 Skill、子 Agent 角色或自动化脚本),完成从“记忆”到“能力”的质变。
5. 设计权衡 (Trade-offs)
为什么没有采用业界流行的 “向量数据库 (Vector DB) + Embedding + RAG” 架构?
- 精确字节匹配 vs. 模糊语义匹配:在 Coding 场景下,记忆中的
port: 5433、ClassName、v1.2.3必须是字面完全精准的。向量检索的语义相似度容易将不同的技术参数混淆(例如将不同的端口都视作相似的数据库配置),导致 Agent 写入错误代码。 - 免服务依赖的本地运行要求:MiMo-Code 作为轻量化本地 CLI 工具,其记忆必须做到开箱即用。SQLite FTS5 零外部依赖,毫秒级本地响应,比拉起本地向量库或调用在线 Embedding 接口更轻量高效。
- 高可观测性与直接干预性:Markdown 格式的
MEMORY.md对人类开发者极其友好。如果 Agent 产生了幻觉或记错了规则,用户可以直接用编辑器打开删除或纠偏。这种人机协同的纠偏成本在向量数据库中是难以承受的。