Featured image of post AI Agent 记忆层的「第三极」:明文文件树用 200–900 个 token 记住一切,但真正的护城河是「怎么忘」

AI Agent 记忆层的「第三极」:明文文件树用 200–900 个 token 记住一切,但真正的护城河是「怎么忘」

一个模型在两次对话之间,什么都记不住。

每个新会话都是白纸。于是你一遍遍重讲你的技术栈、你的决策、你为什么最后没上 Postgres。最常见的解法是把一切塞进上下文——笔记、代码、历史,全贴进去。两个问题:装不下,以及每一轮都要为这堆东西付钱。

过去两年,这个问题只有一个标准答案:向量库 RAG。把记忆切块、embedding、丢进向量库,用的时候按相似度捞。后来又长出第二个答案:图 / 符号记忆,把知识存成知识图谱、带时间边,按关系走。

然后我注意到,今年秋天冒出了第三个——明文文件树。本地、纯文本、没有向量库、没有数据库、没有服务,就是一个文件夹里的一棵树,你能用任意编辑器打开它。它最清楚的例子叫 IHMT(Infinite Hierarchical Memory Tree,无限层级记忆树),一个 stdlib 写就的 MCP 记忆服务器,九月三十号发布,上了 Show HN。

我把它连同一个叫 Agent Memory Atlas 的横评库一起扒了一遍。扒完的结论有点反直觉:这棵树最值得写的不是它多能记,是它——以及几乎所有人——都不太会「忘」。 记忆层真正的护城河,正在从「存得下、找得到」挪到「敢不敢信、能不能纠正」。

先说「第三极」到底新在哪

把三种记忆按「怎么表示」摆在一起,差别就清楚了:

表示 检索方式 代表
第一极 向量 相似度 / ANN,扫全库 mem0、各类 RAG 记忆
第二极 图 / 符号 关系遍历、时间边 知识图谱类记忆
第三极 明文文件树 走一棵摘要树(词法匹配) IHMT 这类本地文件树

前两极管你要么买一个向量库、要么养一个图库,都得把记忆变成你打不开的东西——一堆向量、一堆边。第三极反过来:记忆就是你能读、能拷、能手改、能进版本控制的纯文本。

IHMT 的做法,一句话能说清:别把所有东西摊成一堆、每次全搜一遍;把它组织成一棵「摘要的树」,顺着枝子往下走,走到你要的那一片。

这就是在图书馆找书的办法。你不会去读四万个书脊——你看楼层(科学)、看通道(生物)、看书架(遗传),然后拿走一本。四个路标,而不是四万个书脊。

它的存储布局直白到有点可爱:

ihmt_memory/
  root.json                 # 树干:有几个域
  layer_0/<domain>/*.txt    # 叶子:原文 + 一段 JSON 头
  layers/1/*.json           # 枝:叶子的摘要
  layers/2..N/*.json        # 枝:摘要的摘要
  state/catalog.json        # 索引:id → 路径
  state/facts.json          # 事实时间线

叶子是一段真文本,头里带着日期、域、标签、父节点。枝节点里嵌着每个子节点的标题、摘录、关键词——所以一个枝子不用打开它的孩子就能被排序。这就是它检索便宜的根。

200–900 个 token,是怎么做到的

关键不是「省了百分之多少」,是这个:

200 条笔记  → 3 层 → 打开 9 个枝文件
1,000 条    → 4 层 → 打开 10 个
5,000 条    → 5 层 → 打开 14 个

语料涨了 25 倍,搜索成本从 9 个文件走到 14 个。 检索成本不随你记的东西增长——树深是对数增长的,多下一层就是多开几个文件。它的说法是:一次 search_memory 大约返回 200–900 个 token,不管你存了 50 条还是 5 万条;一次查询约 13 毫秒。

它自己给了一张「诚实数字」表(生成语料、真查询):

语料 全贴进上下文 IHMT 返回 打开文件
examples/ 6 个文件 4,501 638 48 里开 5
200 条笔记 13,898 211 230 里开 12
1,000 条 69,658 211 1,145 里开 13
5,000 条 231,637 138 5,717 里开 17

检索是走树,不是扫库。代价是:一个在树干上被剪掉的枝,不会再被回头看。所以它的兜底是「线索循环」——这点后面讲。

底层匹配是 BM25 词法,不是语义。accent 折叠、CamelCase 拆词,字段权重标题/关键词 3、标签 2、摘录 1,文档频率在当前层的兄弟节点之间算——正好是下降需要的区分度,不额外花 I/O。代价写在它自己的设计说明里:它匹配不了同义词。 想上 embedding 重排,把 BM25Ranker 换掉即可,树结构不用动。

「本地纯文本」这个选择,比它看起来更重

把它和向量库、托管记忆摆在一起,真正的分野其实不在检索,在所有权。

明文文件树不联网、不建库、不建账号,默认后端下数据一步不出本机(记忆作为工具输出到达模型,是另一回事)。拷贝这个文件夹就是迁移,丢进 git 就是版本控制,打开文本编辑器就能手改——「记忆是你的」这句话在这里是字面意思。

这对两类人特别关键。一类是合规敏感的人:记忆里可能躺着过期凭据、客户名、诊断记录,本地纯文本意味着你能自己审、自己删、自己证明它没出过门。另一类是不想被锁定的人:没有厂商格式、没有 API 依赖,文件夹在哪,记忆就在哪。

代价它也写在明处:词法匹配不懂同义词;本地意味着跨设备得你自己搬;单写者假设意味着「多个 agent 同时写」不是它的场景。它自己给的适用边界很实在——一个人、几百到几千条、用你自己当初存进去的词去搜。

我的判断是:这个取舍正在变成第三条路的真正卖点。当托管记忆服务要求你把最敏感的东西交给别人的服务器时,「我自己的一个文件夹」本身就成了功能——不是因为它检索更准,而是因为它可审计、可推翻、可带走。

这套设计里,我最欣赏的三个细节

一、拒答,而不是猜。 置信度是两个独立信号的加权:

confidence = 0.6 × coverage + 0.4 × margin

coverage 问「这片叶子真含着你问的东西吗」,margin 问「它跟对手分得开吗」。一个常见的人名会在第一项拿高分、第二项接近零——这正是不该猜的时候。于是它返回 AMBIGUOUS,列出候选,反过来问你要一条线索:

$ python main.py ask "Luis"
"Luis" is ambiguous (3 memories match this query equally well, confidence 0.62)…
Give me a clue to narrow it down (e.g. a place, a date, a project):
> vacaciones en Benidorm
query: "Luis + vacaciones en Benidorm" · confidence 0.74 · 5 node reads, 3 leaf reads, depth 2

「哪个 Luis」变成一个问题,而不是一段编造。这个设计我几乎想给它鼓掌。

二、代码块永不切断。 分块器有一条不变量:一个逻辑块绝不被切开。 Java/JS/C++ 等用字符级 BraceScanner 追大括号深度,跳过注释、字符串、模板字面量;一个类太大就按成员拆,类头放进叶子的 extra.context 而不是硬塞进正文。测试里断言:把一个文件的所有叶子拼回去,逐字节还原原文件。

三、叶子自描述。 每个叶子的 id 是 (source, position, content) 的哈希——同一段文本存两次,重写同一片叶子,不重复。头里带着一切,所以就算索引丢了,也能从文件重新 rebuild 出来。工程上这叫「让索引可抛弃」。

但真正的问题在「怎么忘」

到这里,它看起来是个漂亮的作品。它确实是个漂亮的作品——直到我读了那个横评库。

Agent Memory Atlas 读的是代码,不是 README。它给每个系统打七个「信任能力」的标记,规则很硬:只有在钉住的 commit 里真的找到机制才算,不算路线图、不算设计文档、不算「模块名听起来像」。

这七个是:撤销墓碑(tombstone)、显式信任状态、双时态有效期、作用域强制、追加式变更审计、人工复核面、负向检索断言。

它为什么选这七个,理由比清单本身更值钱:筛选标准是「它的缺失,会不会造成一个系统自己察觉不到的失败」。

检索质量不在名单上——尽管那才是绝大多数对比在量的东西。因为一个烂排序器会给出明显更差的答案,总有人会发现。而一个缺失的墓碑,会产出一个建立在用户早就否决过的值之上的、自信的答案,系统里没有任何东西知道。

这是整件事的题眼。烂召回是可见的失败;烂信任是静默的失败。

几个数字,值得摆出来(Atlas 语料是 732 个系统,作者说这是「顺手收集」而非抽样,所以是「读到了什么」,不是「全行业比例」):

  • 值级撤销墓碑:732 个里 66 个。 这是它找到的最大缺口。注意「值级」——记录级的删除标记不算,因为重新抽取会产生一条新记录,直接绕过它。
  • 负向检索断言(committed 测试里断言「某材料不得被检索到」):732 个里 443 个。
  • IHMT:0 / 7。

拆开看:IHMT 的 0/7 具体缺在哪

这部分我觉得最有用,因为它把「一篇好 README」和「一套能信的记忆」之间的缝,拆到了行号级。

纠正停在提示,没进排序。 它有事实时间线——location、employer、stack、diagnosis、treatment、medication 六个属性,13 条正则从英/西语句子里抽值,最新的 ACTIVE、旧的 HISTORICAL 带 superseded_by,检索命中旧值时附一条 OUTDATED 提示。听起来很完整。但 _rank 和 _adjust_for_code 里没有一行读这条时间线——新旧两条陈述只按各自的词打分。结果,作者自己的路线图记着:问「我住哪」,会在新旧地址之间返回 AMBIGUOUS。

纠正只是给旧材料贴了张便签,没有改变谁排在前面。 这不是「双时态」,顶多算「双时态的样子」。

剪枝会把「没搜到」说成「没存过」。 每层只开置信度最高的 3 个枝(beam_width=3),选枝靠子节点嵌的那点描述(标题、320 字摘录、10 个关键词、8 个标签)。一片叶子的词如果不在祖先的描述里,它所在的枝会在某一层落选、永远不被打分——下降于是走到头,返回「还没有存过相关的——要不要 save_memory」。一次召回失败,读起来像一次不存在,还会诱导你再存一遍。

宣传共享,实际单写。 README 说多个 agent(Claude Code、Codex、opencode)可以共用一个记忆;设计说明里却写着 store 假设同一时刻一个写者。catalog.json 和 facts.json 每次改动整文件重写,唯一的锁只在 rebuild_catalog 里拿。两个 agent 共用一个 IHMT_HOME 就是两个 server 进程——两次重叠的保存,可能丢掉一条 catalog 或时间线记录,而它的叶子文件还留在盘上。

来源标签只有一个。 每个 MCP 保存都记成 mcp://claude-code,不管你用的是 Claude Code、Codex 还是 opencode。一个被三个 agent 共享的记忆里,没有任何东西记录这条是谁写的。

没有墓碑。 没有任何动词能删除或拒绝一个值;把旧值用更晚的日期再存一遍,它又会变回 ACTIVE。模板叮嘱 agent「不要存密钥」——代码里没有任何检查。而且,agent 从工具结果里存下来的文本,跟其他记忆一视同仁,如果命中了正则,它能凭一个更晚的日期覆盖掉用户自己说过的话。

顺带说一句,这不是文件树独有的病。Atlas 读到向量库那一层:在 Chroma、Qdrant、LanceDB 上,被删除的向量本身会一直活到一次 vacuum / optimize / prune——而这次清理,记忆系统并不负责调度;LanceDB 默认还会把含被删数据的那一版留七天。pgvector 是唯一真把向量清零的。

我的判断:记忆层的仗,正在换战场

把这篇文章和我前几天那两篇放在一起读,你会发现是同一个形状。

那篇《软件没死!这轮 AI 淘金热,新范式是卖「矿机」》讲的是:AI 把「生成」打便宜了,价值就往「验收、判定、信任」那一层搬。更早那篇《AI 写的 SQL 没报错,58% 的答案却是错的》讲的是同一个病:看起来对,不等于真的对。

记忆层是同一场戏。「记住」已经被打得很便宜了——一棵文件树,stdlib 就能写,200–900 个 token 一次查询,还便宜得吓人。而**「可信地忘掉、可信地纠正」还很贵,而且几乎没人真做**。Atlas 把这件事拆成三条承诺,我觉得说得比任何 PR 都清楚:

  • 先证据后相信(审计、双时态);
  • 先划界后排序(作用域强制);
  • 先纠正后规模(墓碑、信任状态、人工复核、负向断言)。

它为什么强调「信任状态」不能是「置信度分数」?因为两者是不同的东西:分数回答「我多确定」,状态回答「这个能不能被当成真的用」。 把两者混成一个 float 的系统,表达不了「我有这条记录,但我不信它」。它还引了一篇论文(Safety Does Not Compose, arXiv:2608.27141)来证明这不是程度的差别、是种类的差别:一个用几何折扣衰减的风险分,等一轮的攻击者被挡住、等两轮的成功率 97.0%;而一个锁存的状态,在任意延迟下都是 0.0%——干净的完成率还一模一样。衰减的分数不是「更严格的过滤器」,是另一种东西。

所以,如果你在选一个记忆层,或者自己在写一个,我会把问题从「召回率多少」换成三个更硬的问题:

  1. 我说「忘掉那个」,它下次后台整理时会不会又冒出来?(墓碑)
  2. 它能不能表达「这条在案但不可信」?(信任状态)
  3. 有人能不能在记忆进入 prompt 之前,把它拦下来复核?(人工复核面——注意「能看见」不算「能复核」,agent 自己工具里的一个 approve 也不算,那等于让生产者自己清自己的队列)

还有一个 Atlas 单独拎出来的空白:prospective memory(前瞻记忆)——「记住我将来要做某件事」。它说这是「几乎没人建模」的一类。想想也合理:几乎所有记忆系统都在回答「过去发生了什么」,没几个在回答「接下来该提醒什么」。

写在最后

我不是要唱衰 IHMT。恰恰相反——用 stdlib 写出一棵能自描述、能逐字节还原、敢在并列时反问而不是猜的树,这个克制的工程量,一个下午就能审计完,本身就是件漂亮事。它诚实地在自己的文档里列出了「哪里不省」。

但「漂亮的开源项目」和「能信的 Agent 记忆」之间,隔着的正是那七个标记。IHMT 拿 0/7,不是因为它差,是因为整个领域都还没把这七个当回事——732 个系统里,值级墓碑只有 66 个。我们花了两年时间,把「怎么记住」卷到了极致,却没怎么管「怎么忘、怎么纠正、怎么在它说谎之前拦住它」。

记住正在变成水电一样的公共品。而记忆层真正的护城河,不会长在「我一次能捞回多少」上——它会长在「我敢不敢信它捞回来的东西,以及我能不能把它推翻」上。

Agent 记忆的下半场,比的不是谁记得多,是谁忘得干净、纠得清楚、拦得住。

By AI博士 万戈