上下文窗口越做越大,1M token 已经不算新闻,但开发者反而越来越频繁地遇到一件事:模型"失忆"。会话一长,推理质量就开始漂移,成本跟着每个回合线性上涨;一旦 /clear 清空上下文,之前敲定的架构决策、用户约束、项目状态全部蒸发,下一轮对话得从头解释。

Engrim 这个项目想解决的就是这件事。作者在 README 开头写了一句很刺眼的话:"为什么每个回合要为 20 万 token 被遗忘的噪声付费?模型是一次性工具,你项目的决策不是。"

思路:记忆外置,检索代替全量塞进窗口

Engrim 的做法是把"记忆"从上下文里剥离出来,落进一个本地 SQLite 文件(~/.engrim/memory.db),按项目维度打标。会话启动时只注入一小包精选记忆,默认 4000 字符预算,而不是把整个历史塞进去。

它的定位是"跨模型、跨 agent 的情景记忆引擎"。同一个仓库,你可以在 Google Antigravity、Claude Code、Cursor、Windsurf 之间随意切换,记忆不丢,换模型不需要重新交代一遍背景。这背后的判断很朴素:上下文应该来自按需检索,不是全部塞进窗口。这也是它跟一堆"给 agent 加个向量库"的项目最本质的区别,它是把记忆当成一等公民来做,而不是往 prompt 里追加几个文件。

数据模型:四张表 + FTS5 + 来源追踪

核心是 memories 表,字段是 id / ts / project / type / summary / detail / status / tags / links / source / origin_agent

type 有六种:decision(决策)、fact(事实)、feedback(反馈)、state(状态)、user(用户偏好)、reference(参考)。status 三种:active / superseded / done。这里有个细节值得学:supersede 只是标记一条记录作废,不删除历史,所以你能追到"这个决定当时为什么被推翻"。

origin_agent 是来源追踪字段,取值 antigravity / claude-code / cursor / cli / user。当多个 agent 协作同一个代码库时,每条记忆都能查到是谁写的。老库用 ALTER TABLE memories ADD COLUMN origin_agent 做非破坏迁移,不会丢数据。

会话启动时注入的 boot pack 不是按时间排序,而是按一套优先级硬编码:

_PRIO = {"user": 0, "feedback": 1, "state": 2, "decision": 3, "fact": 4, "reference": 5}

用户偏好永远排第一,"怎么跟我协作"这类信息优先级最高,状态和决策次之,参考类垫底。这个顺序很有道理:一条"用户要求全部用 pytest"比十条历史决策更能避免模型在下一个会话里犯同样的错。注入时的样子大概是:

 engrim · memory restored — you don't have to re-explain
  18 of 54 curated records loaded (~3850 chars)

[DECISION]
- #942 (via Claude Code): Switched primary database from MongoDB to PostgreSQL
- #910 (via Cursor): Standardized on Pydantic v2 schemas across API boundaries

预算内装不下的记录一条 recall 就能拉回来,所以"只加载 18 条"不是信息丢失,是延迟加载。

全文检索用 FTS5 虚拟表,走的是外部内容表模式:

CREATE VIRTUAL TABLE memories_fts USING fts5(
  summary, detail, tags,
  content='memories', content_rowid='id',
  tokenize='porter unicode61'
);

content='memories' 让 FTS5 不重复存文本,行 id 直接关联 memories 表,bm25(memories_fts) 就是排序函数。分词器用 porter unicode61,英文词干化,查询 database 也能命中 databases

另外两张表:embedding(memory_id / model / dim / vec) 存向量,log(id / ts / project / session / role / content / raw / msg_uuid) 是"飞行记录仪",记录每轮对话和动作行,供 engrim review 扫描还有哪些决策没被捕获。

混合检索 Minder:bm25 + 静态向量 + RRF

检索引擎叫 Minder,两路混合:

一路是词法,用 FTS5 的 bm25 排名做关键词精确匹配。查询进来会先 tokenize 成裸词,这是防呆设计,不然一个 C++ 或者 useState() 这种带符号的字符串直接扔给 FTS5,就会撞语法错误。

一路是语义,用 model2vec 的静态嵌入算 cosine 相似度,命中同义表达。关键在"静态"两个字:model2vec 加载只要几十毫秒,纯 CPU 跑,不需要 GPU,也不做每次查询的神经网络前向。写入时 add 自动嵌入存进 embedding 表,读取时才算 cosine。

两路结果用 reciprocal-rank fusion(RRF)融合,常数 C=60

score[id] += 1.0 / (60 + rank_i)   # 对词法路和语义路各累加一次

经典 RRF 公式,好处是不用归一化两路分数,直接按排名融合,实现起来几行代码。语义路是全程带保护的:ENGRIM_EMBED=off 强制纯词法(零第三方依赖),model2vec 加载失败也优雅降级到纯词法,检索永远不会因为语义后端挂了而崩。

多环境接入:hooks + MCP stdio

跨模型的能力来自接入层。engrim setup 自动探测机器上装了哪些环境,一键全部配好:

  • Antigravity:写 ~/.gemini/config/hooks.jsonPreInvocationengrim hook --agent agy --event bootStop 跑 stop 事件。
  • Claude Code:写 ~/.claude/settings.jsonSessionStart / SessionEnd / Stop / UserPromptSubmit 四个 hook,再往 CLAUDE.md 追加记忆使用说明。
  • Cursor / Windsurf:往 ~/.cursor/mcp.json 注册 MCP server,跑 engrim serve --mcp

MCP 是零依赖的 JSON-RPC 2.0 stdio server,stdout 严格只发 JSON-RPC 消息,诊断日志全走 stderr,不然会污染协议流。暴露四个工具:engrim_recall(query, project, k, type)engrim_add(type, summary, detail, tags)engrim_context(project, budget=4000)engrim_review(project)

项目归属的判定是一套优先级:显式 -p > $ENGRIM_PROJECT > 当前目录的 git root > cwd。它会往上找 .git / .hg / .svn / .claude 标记,到 $HOME 就停。这个"停"很关键:~/.claude~/.git 几乎人人都有,要是继续往上走,所有非仓库项目会被坍缩成一个桶。Windows 上还要 normcase + normpath 归一化盘符大小写和分隔符,否则同一个目录从 os.getcwd() 和 Claude Code hook 的 payload 拼出来的 tag 不一样,记忆就裂成两个桶。

实证:105 会话,153k token 压到 1k 以内

README 里最硬的是这段案例:在一个跑真钱的 5 万行算法交易系统上,连续 105 个会话,186 个单元测试零回归,跨模型切换零失忆。

具体数字:153,000 token 的工作量(架构决策、参数调优、调试),被压缩成不到 1,000 token 的活动记忆包。也就是说,每次会话重启重新加载的上下文成本,砍掉了 99% 以上。在 Antigravity CLI、Claude Code、Cursor MCP 三个环境之间切换同一个仓库,没有模型漂移,没有架构回归。

我的做法

几个我认可的取舍,准备直接照搬:

第一,用 SQLite 而不是向量库。单文件、零运维,FTS5 内置全文检索,加一个 model2vec 静态向量表就够用,不用为"记忆"上一整套 RAG 基建。对个人开发者,这比向量数据库务实得多。

第二,检索替代塞满。记忆预算 4000 字符,逼着你只保留决策、约束、状态这类高价值信息,而不是把每轮对话都记下来。这个克制本身就是产品设计。

第三,resume-pointer 工作流。结束会话前加一条 resume-pointer 记录,标注下一步做什么,下个会话的 boot pack 顶部就钉着 [▶ RESUME HERE]。把"续写"从玄学变成机制,这个设计我特别喜欢。

我准备在自己项目里挂两个用法:一是把 engrim 接进 Claude Code 的 SessionStart hook;二是把项目里的约定(比如"这个 repo 用 pytest 加 xdist 跑测试")用 engrim add -t user 存进去,省得每个新会话都重新交代一遍。

一句话收尾:上下文窗口会继续变大,但"记忆"这个问题的答案,大概率不是更大的窗口,而是更聪明的检索。


Engrim 是一个 MIT 协议的 Python 项目,pip install engrim 即可安装,Python 3.10+,纯本地离线,无任何遥测。