目录

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,避免全局空间污染。

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(内容表)的缓存一致性,触发器在 UPDATEDELETE 时采用了专有的 '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)

  1. 分词整理:使用正则 [\p{L}\p{N}_]+ 匹配多语言字符(包含中日韩字符),过滤特殊符号,并对每个 Term 增加双引号进行短语包装以规避 FTS5 语法崩溃。

  2. OR-Join:多词检索时,词条间使用 OR 连接。

    • 设计考量:AND 连接在代码场景下过于严苛。若用户搜 postgres database port 5433,而记忆中只有 postgres port 5433,AND 会导致检索结果为 0(Cache Miss)。OR 连接能保证最大召回率,将排序交给 BM25 算法。
  3. 相对分数过滤

    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” 架构?

  1. 精确字节匹配 vs. 模糊语义匹配:在 Coding 场景下,记忆中的 port: 5433ClassNamev1.2.3 必须是字面完全精准的。向量检索的语义相似度容易将不同的技术参数混淆(例如将不同的端口都视作相似的数据库配置),导致 Agent 写入错误代码。
  2. 免服务依赖的本地运行要求:MiMo-Code 作为轻量化本地 CLI 工具,其记忆必须做到开箱即用。SQLite FTS5 零外部依赖,毫秒级本地响应,比拉起本地向量库或调用在线 Embedding 接口更轻量高效。
  3. 高可观测性与直接干预性:Markdown 格式的 MEMORY.md 对人类开发者极其友好。如果 Agent 产生了幻觉或记错了规则,用户可以直接用编辑器打开删除或纠偏。这种人机协同的纠偏成本在向量数据库中是难以承受的。