# Memoirs：教会智能体记忆（而不必在云端失去理智）


## 千 Token 的失忆症

我用过的每一个对话式智能体——Claude、Cursor、各种 CLI 循环——都有着同样一个悄无声息的缺陷。它们醒来时对昨天毫无记忆。上下文窗口已经变得巨大，但它们在每个新会话中仍然从一张白纸开始。如果你试图通过保持一个持续的会话线程来绕过这一点，最终会得到更糟糕的东西：一个在每一轮都把整个聊天历史倾倒进上下文窗口的架构怪物。你付出了 40K token 的推理成本，而智能体仍然把最重要的事实埋在中间某处——看不见，被忽略。这就是 *Lost in the Middle*（迷失在中间）效应。它既昂贵，又根本不起作用。

针对这个问题有企业级方案。沉重的方案。云托管的向量数据库，在好的日子里网络往返时间以秒计。但我可不打算把我的架构笔记、我半遮半掩的凭据和我个人的约定发送给某个我无法掌控的外部 API。我想要一个*本地优先*的东西。从构造上就是私密的。一个真正理解我的环境变量、我的框架选择、我的风格——并把这些知识保留在身边的东西。

这就是后来的 [memoirs](https://github.com/misaelzapata/memoirs)。不是一个 RAG 封装层。那会太容易，也太无聊。我想要一个真正的记忆系统——一个能有机地遗忘并巩固真正重要内容的系统。

## 底层解剖：SQLite 之上的 6 层

我把持久化层构建在大多数人低估的东西之上：SQLite。不只是一个扁平的 `.db` 文件。而是一个被强力加持的 SQLite 实例，搭配 `sqlite-vec` 用于稠密向量搜索，以及原生的 `FTS5` 用于带 BM25 评分的倒排索引词法搜索。

架构最终落定为六层，因为记忆系统不是一个存储盒——它是一座水处理厂：

1. **原始日志**：进水管。接收一切——完整的对话历史、嘈杂的 diff、原始输出。
2. **提取**：一个在本地运行的小型策展 LLM（目前是 Qwen 2.5 3B——快到察觉不出来），从噪声中过滤出信号：启发式规则、凭据、偏好、风格标记。
3. **图谱**：连接组织。记忆使用 Zettelkasten 风格的原则相互链接，在概念和会话之间寻找共享的语义节点。
4. **双重索引**：对 sqlite-vec 和 FTS5 进行原子化的并行写入。两个索引保持同步。
5. **记忆引擎**：策展层。记忆随时间获得和失去分数，具备完整的双时态支持——你可以查询系统在过去任意时刻所相信的内容。
6. **表层**：暴露层。HTTP + REST，更重要的是一个 22 端点的 MCP 服务器，它让任何智能体都能干净地查询系统，而无需了解其内部任何细节。

## RRF 与 BM25：向量并不能解决一切

早期，我撞上了一堵墙。`sentence-transformers` 的嵌入以一种非常特定的方式失败：精确标识符召回。让 `memoirs` 检索一个特定的工具名或一个带版本号的依赖字符串，它返回的却是语义相邻的噪声。向量空间在意义上很出色。它们知道"汽车"接近"车辆"。但当一条记忆包含 *"旧系统使用 psql-driver-v9"* 时，嵌入并不会给这个精确字符串任何特殊权重。对于一个试图重建某个决策的智能体来说，这是一个致命的疏漏。

解决方案是把一个新东西与一个非常古老的东西结合起来：Reciprocal Rank Fusion（倒数排名融合，RRF）。我们并行运行两种搜索——一个通过 sqlite-vec 的稠密语义查询，以及一个通过带 BM25 评分的 FTS5 的精确匹配倒排索引查询。如果 BM25 精确找到了字符串，而语义搜索理解了周围的上下文，RRF 就会稳定分数，把正确的结果稳稳地推到第一位。结果是：p50 检索延迟降到了约 3.9 毫秒。在一个本地 SQLite 数据库上。这远远胜过大多数云向量存储。

## 艾宾浩斯、遗忘曲线与异步睡眠

如果一个系统不能遗忘，它就没有长期记忆。智能体有一种淹没在陈旧知识中的倾向。"昨天我在和 Docker API 较劲"紧接着就是"今天我们彻底放弃了 Docker，改用裸 OCI 调用。"如果记忆引擎以同等置信度呈现这两个事实，智能体就会做出错误的决定。每一次都是。

我翻回一本旧笔记本，实现了基于赫尔曼·艾宾浩斯遗忘曲线的衰减函数：$R(t) = e^{-\Delta t_h / (S \cdot 24)}$。新记忆随时间渐近地失去权重。但它们的强度分数——它们原始的信号质量——会在系统每次检索并积极确认那条记忆时被乘大。核心架构决策和深层偏好的衰减，比一次深夜会话中的调试战记要慢得多。

这所需的计算——巩固、语义冲突消解、图谱压缩——太昂贵了，无法实时运行。所以我求助于一个人类时刻在用、而软件几乎从不使用的机制：*睡眠*。

我写了一个守护进程（`sleep_consolidation.py`），它监视 CPU 的低谷。它只在开发者和智能体都离开时运行——也就是锁空闲且系统空闲已超过 N 分钟的那些空闲窗口。在那个盲目的异步窗口里，本地的 Qwen 或 Phi 模型醒来，审查最近存储的画像，搜寻记忆版本之间的矛盾，并巩固或归档那些陈旧的部分。图谱会在下一个会话开始之前，在后台悄悄地被压缩。

在基准测试周期结束时，在像 LoCoMo 这样的记忆检索基准上取得强劲的 MRR 分数——而 RAM 开销仅为 231 MB，相比之下 LlamaIndex 或 Mem0 所消耗的要多得多——验证了这个假设。最好的智能体功能不是靠把数百万 token 发送给远程提供商而构建的。它们是靠深入理解基础索引、并在你自己的机器上强加无情的资源纪律而构建的。

