MemForest:当 Agent 的"记忆"被当成数据库问题来做,写吞吐量直接 6 倍

一个困扰长上下文 Agent 半年的问题:你以为是检索质量不够,其实是写入路径根本卡死了。

写在前面

最近在调一个长程多轮对话的 Agent,碰到一个很扎心的现象——上线第一周还挺欢实,跑到第三周整个系统就开始变"重"。每问一个问题要等 8 秒、10 秒,看日志才发现,时间根本不花在生成回答上,全花在"更新记忆"上了。

我之前以为这是召回的问题,疯狂调 embedding 模型、调 chunk 切分、加 re-rank。结果发现都没用——因为瓶颈压根不在读,而在写。

每来一段新对话,Agent 要把它"消化"进现有记忆里。现在主流的 Mem0、MemoryOS、EverMemOS 这一类系统,都是用 LLM 边读边改:把相关记忆全捞出来 → 让 LLM 决定哪些要更新、哪些要合并 → 再写回去。听起来挺聪明,但有一个致命问题:LLM 卡在写入路径上,每次写都要让 LLM 跑一遍,记忆越多越慢,慢到根本没法用。

这篇 MemForest 是新加坡国立 + Zero Gravity Labs 在 2026 年 5 月挂到 arXiv 的 paper(投 VLDB),它做的事其实只有一句话:别再用 LLM 串行地维护全局摘要了,把记忆当成一个写优化的时序数据库来管理。

我读完之后第一反应是:终于有人从数据库的角度认真看 Agent 记忆这件事了。前置工作里那些"记忆 OS""三级缓存"的命名其实都在装数据库,但没有一个真把 B+树/LSM 这些写优化结构的本质拿过来用。MemForest 把这层窗户纸捅破了。

效果也确实给力:在 LongMemEval-S 上 pass@1 拿到 79.8%(30B 模型下最强有状态基线),写吞吐量比 EverMemOS 快约 6 倍,写入路径关键路径从 O(M) 直接降到 O(log N) 量级

下面我把这篇论文掰开揉碎讲一下——它的痛点诊断是怎么做的、为什么它的方案能 work、哪些数据值得信、哪些地方我觉得还有问题。


论文信息

  • 标题:MemForest: An Efficient Agent Memory System with Hierarchical Temporal Indexing
  • 作者:Han Chen, Zining Zhang, Wenqi Pei, Bingsheng He(新加坡国立大学),Ming Wu, Jason Zeng, Michael Heinrich, Wei Wu, Hongbao Zhang(Zero Gravity Labs)
  • arXiv2605.23986 (2026-05-16, cs.DB)
  • 代码github.com/Concyclics/MemForest
  • 会议目标:VLDB(数据库顶会)

一、问题:现有 Agent 记忆系统到底卡在哪?

一张图看懂痛点

图1:长上下文记忆效率(LongMemEval-S,Qwen3-30B)。(a) 现有系统的延迟里,写密集型的"抽取+维护"占了大头;(b) MemForest 把整条更新吞吐量–准确率前沿往右上方推。

图1:左边是各家系统单次写入的延迟分解,写入耗时被维护操作(深色那段)严重拖累;右边是吞吐量-准确率帕累托前沿,MemForest 在两个指标上同时拿下最优。

这图我盯着看了一会儿。左边最让我意外的是 EverMemOS 那条柱子——大部分系统时间不是花在生成答案,也不是花在召回,而是花在"维护"上。说白了就是:用户问一句,系统忙着整理它的笔记,整理完了才有空回答你

作者把现有系统的瓶颈拆成两个,我觉得拆得挺准:

瓶颈 1:串行 LLM-in-the-loop 抽取

当前主流系统(Mem0、MemoryOS、EverMemOS、LightMem)的写入流程几乎都长成这样:

新对话 → LLM 抽取事实 → 检索相关旧记忆 → LLM 决定怎么合并/更新 → 写回
                ↑                              ↑
            串行 LLM 调用                    又一次串行 LLM 调用

每来一段对话,至少触发 2 次 LLM 调用,而且这两次都在写入的关键路径上。LLM 调用 = 几百毫秒到几秒,这就是延迟的源头。

瓶颈 2:粗粒度全状态重写

更要命的是,很多系统采用"全局摘要"或者"扁平合并"的策略——新来一段对话,要把它跟整个历史摘要重新揉一遍。这意味着维护成本随累积记忆 M 增长,而不是随新增证据 N 增长。记得越多,写得越慢。这是一个根本性的可扩展性灾难。

我之前在做类似系统时也踩过这个坑——当时图省事用了一个全局 markdown 文件存"长期记忆",每次更新让 LLM 重写整个文件。前两周还行,第三周就开始 OOM 了。当时没想明白,现在看其实就是这个问题:写入路径关键路径是 O(M)。

一张表说清楚关键路径

论文 Table 1 把各家系统的写入关键路径做了对比,我把它精简了一下:

系统 写路径关键路径 后果
Mem0 O(M) 检索证据上的串行 LLM 更新
MemoryOS O(M+N) 有序提升 + 热状态重写
EverMemOS O(M) 单元级合并到维护内存
LightMem O(M+N) 缓冲写入 + 全局合并
MemPalace O(M) 快速追加,但不维护时序
MemForest O(log N) 量级 局部 MemTree 更新 + 并行刷新

注:M = 累积记忆量,N = 单棵树内事实数(远小于 M)。

这就是关键。从 O(M) 降到 O(log N),差不多就是从顺序扫数组降到 B+树查找的那种量级差距。如果你做过数据库索引,看到这一行就该对它的设计思路有数了。


二、核心思路:把 Agent 记忆当成"写优化的时序数据库"

我觉得 MemForest 最值钱的洞察其实就一句话:

Agent 的记忆问题,本质上是一个"高频写入 + 时序敏感 + 多视角检索"的数据管理问题。

数据库领域早就发明过应对这种问题的工具——LSM-Tree、B+树、分层索引、并发控制。但之前的 Memory OS 只是在用语义压缩在做"摘要",用 vector store 在做"召回",并没有把数据库的写优化思想拿过来。

MemForest 把这件事给做了。它的设计哲学有三条:

  1. 解耦 LLM 与写入关键路径:让 LLM 在写入时只负责"局部摘要",不再做"全局合并决策"
  2. 局部更新替代全状态重写:用一个分层时序索引(MemTree),让每次写入只影响 O(log N) 条路径
  3. 并行抽取 + 并行刷新:榨取硬件并发,让独立的写入操作真正并发执行

通用工作流:先看大家在做什么

图2:智能体记忆系统的通用工作流。新对话经过抽取(extraction),更新维护内存状态(maintenance),未来查询触发检索(retrieval)。

图2:所有 Agent 记忆系统都跑在这个三段式上——抽取(写入路径起点)、维护(写入路径终点)、检索(读路径)。MemForest 的所有创新都集中在"抽取+维护"这一段。

任何 Agent 记忆系统都跳不出这三个动作。MemForest 没改变这个框架,但把"抽取"和"维护"的内部实现彻底重构了。

MemForest 整体架构

图3:MemForest 架构。会话被抽取为规范化事实(canonical facts),路由到作用域 MemTree,并通过派生构件的选择性刷新进行维护。检索时从森林级召回,再从区间摘要浏览到叶子证据。Planner 是可选组件。

图3:左边是写路径(Session Ingestion + Lifecycle Maintenance),右边是读路径(Forest-level Recall → 树内 Browse)。中间这片"森林"由三类树构成:Session Tree(会话树)、Entity Tree(实体树)、Scene Tree(场景树)。

这张图信息量挺大,我们一块块拆。

共享内存基底(Shared Memory Substrate)

整个系统的"持久层"分两类:

  • 持久状态(Persistent State):真理来源——规范化事实、作用域分配、MemTree 结构、源会话引用
  • 派生访问构件(Derived Access Artifacts):可重新生成的——区间摘要、节点嵌入、根索引行

这种"持久 vs 派生"的分离,跟数据库的"基础表 vs 物化视图"完全是同一种思想。当你需要重做 ranking 或者改 embedding 模型时,只重生成派生构件就行,不用动持久状态。这是工程上非常友好的设计。

三种 MemTree:从不同视角组织同一份事实

树类型 组织方式 适用场景
Session Tree(会话树) 按单个会话的时序 保留原始交互上下文,回退用
Entity Tree(实体树) 围绕循环主题(人/项目/偏好) 跨会话查"用户对 X 的看法"
Scene Tree(场景树) 跨多实体的语义连贯情境 查"上次出差讨论了什么"

同一条事实可以同时挂在多棵树上——这就是"森林"的来源。读的时候从不同视角召回,写的时候每棵树独立维护。

我蛮喜欢这个三视图设计。之前看过的一些方案要么只按时间组织(Session-only),要么只按实体组织(Entity-only),结果就是某些查询模式下召回率特别差。MemForest 在消融里也证明了这点(后面会看到),entity+scene 的组合已经很能打。


三、MemTree:把"数据库索引"搬到 Agent 记忆里

这是整篇论文最核心的技术。

图4:MemTree 把单个时序作用域物化为时间排序的层次结构。叶子保留局部证据,内部节点汇总区间,根支持粗略召回。同一结构支持局部插入、脏路径刷新和层次检索。

图4:每棵 MemTree 是一棵按时间排序的平衡树。新事实从叶子追加进去,只有从该叶子到根的路径上的节点会被标记为 dirty 需要重摘要。

一句话讲清结构

  • 叶子:按时间顺序存储局部证据(具体的 canonical facts)
  • 内部节点:是其子节点区间的"摘要"(由 LLM 生成,但只摘要受影响的路径)
  • :粗粒度的全局表示,用于森林级召回
  • 分支因子 k ≈ 16(消融实验显示这是甜蜜点)
  • 树高 h = ⌈log_k N⌉

写入时,新叶子一插入,只有它到根这条路径(O(log N) 个节点)需要重新摘要——其他分支完全不动。

写路径算法(Algorithm 1 简化版)

输入:内存基底 M,路由记录 R_t
输出:更新后的持久状态和派生构件

1. 按目标树分组路由记录:B ← GroupByTree(R_t)
2. 对每个 (T_σ, R_σ) ∈ B:
   - 按时间排序 R_σ
   - 创建叶子节点 ℓ
   - 附加到 T_σ 并重平衡
   - 标记从叶子到根的祖先为 dirty
3. 收集所有 dirty 节点,按 level 分组
4. 自底向上、并行刷新(同 level 节点之间、不同树之间均可并发):
   - 叶子(entity/scene):直接 passthrough
   - 叶子(其他):摘要 unit text
   - 内部节点:摘要子节点
5. 并行更新 NodeIndex 和 RootIndex

这里的关键设计有两点:

第一,延迟刷新(Lazy Refresh)。dirty 标记不是立刻处理,而是攒一批一起处理。这让多个写入可以共享 dirty 路径(比如连续两条事实落到同一棵子树上,只摘要一次就够了)。

第二,按 level 并行。同一层的 dirty 节点之间没有依赖(B+树性质),不同树之间更没依赖,全部可以并发跑 LLM 摘要。这就把"串行 LLM 调用"变成了"并行 LLM 批处理"。

并行块抽取

写入的另一半是"抽取"——从原始对话里提取 canonical facts。MemForest 的做法是:

\[\mathcal{C}(S_t) = \{c_{t,j}\}_{j=1}^{\lceil n_t/b \rceil}\]

把会话切成块(默认 b=2 轮对话),每个块独立抽取,互不依赖、并发执行。这跟 MapReduce 的 Map 阶段完全是一个思路。

这里我觉得有个值得讨论的点:默认 b=2 是不是太激进了?2 轮对话能不能形成足够的上下文让 LLM 抽取出有意义的事实?论文没在这上面做太多消融,我猜实战里这个值得再调。


四、实验:数字硬不硬?

4.1 写入吞吐量:6× 加速名副其实

Table 2,LongMemEval 上的写入路径效率:

方法 30B 时间(s) 30B 加速 4B 时间(s) 4B 加速
MemForest 178.0 13.7× 136.9 12.4×
Mem0 353.2 6.9× 314.9 5.4×
EverMemOS 1048.8 2.3× 790.9 2.1×
MemoryOS 2439.9 1.0× 1695.1 1.0×

看到这个数我愣了一下——MemForest 比最慢的 MemoryOS 快 13.7 倍,比 SOTA 的 EverMemOS 快约 6 倍。

但这里有个坑:MemForest 的 token 消耗是 1.143M,而 Mem0 只有 294K。也就是说MemForest 用更多的 token 换了更短的时间——它没有省 LLM 推理量,省的是关键路径上的串行等待。这个 trade-off 我觉得是合理的,因为 token 可以并发跑、可以用便宜模型,但关键路径延迟省不下来。

4.2 查询时延:纯 embedding 模式有惊喜

Table 3 的查询时延数据:

方法 30B Total (s) 4B Total (s)
MemForest (LLM browse) 4.60 4.30
MemForest(emb 浏览) 2.19 秒 2.42 秒
Mem0 0.22 0.29
MemoryOS 2.29 1.10
EverMemOS 8.49 10.97

Mem0 在查询时延上是最快的——但你看回 Table 4 的准确率,它只有 32.8%(4B),基本没法用。

MemForest 的纯 embedding 浏览模式(emb)2.19 秒已经相当能打,跟 MemoryOS 持平但准确率高出一截。如果开 LLM browse 那 4.6 秒就有点慢了,但准确率最高。

4.3 准确率:LongMemEval-S 上拿了"最强有状态基线"

Table 4,30B 模型的 pass@1 准确率:

方法 Overall Single-User Pref K-Update Multi-Sess Temp
MemForest 79.8 97.1 76.7 75.6 73.7 79.7
MemForest (emb) 78.4 94.3 70.0 76.9 69.9 81.2
EverMemOS 66.2 87.9 50.0 86.7 62.1 52.5
LightMem 67.0 94.3 76.7 73.1 64.7 65.4
MemoryOS 50.0 71.4 43.3 55.1 34.6 36.8
Mem0 40.2 75.7 50.0 44.9 41.4 27.8

整体 79.8%,比 EverMemOS 的 66.2 高了 13.6 个点。Multi-Session(多会话推理)和 Temporal(时序推理)这两个 MemForest 特别强:73.7 vs 62.1,79.7 vs 52.5。

这个结果其实是合理的——MemTree 本身就是按时间组织的,对时序问题天然有优势。但 K-Update(知识更新)这一项 EverMemOS 是 86.7,比 MemForest 的 75.6 还高。我猜是因为 EverMemOS 的"全局合并"在"覆盖式更新"场景下确实更彻底,MemForest 的局部更新可能会留一些过时的旧叶子节点没清掉。

4.4 LoCoMo:被 EverMemOS 反超

Table 5,LoCoMo 上的准确率(30B):

方法 Overall Single-Hop Multi-Hop Open-End Temporal Adversarial
MemForest 68.4 78.0 67.3 65.6 83.1 35.9
EverMemOS 69.6 76.2 86.6 62.5 88.1 19.7

这里 MemForest 输了 1.2 个点,主要输在 Multi-Hop 上(67.3 vs 86.6)。

我得说这个数挺值得玩味的。LoCoMo 是更注重多跳推理的 benchmark,需要把分散在多处的事实串起来。EverMemOS 那种"全局合并"的策略在多跳场景下有优势,因为它把相关的事实"揉"在一起了,而 MemTree 的局部组织反而会让多跳信息分散。

但反过来,MemForest 在 Adversarial(对抗性问题)上甩了 EverMemOS 16 个点(35.9 vs 19.7),说明它在区分真假信息上更稳定。

4.5 写路径可扩展性诊断

图6:MemTree 写路径可扩展性诊断。(a) 批量 mark-dirty 刷新相对于即时摘要重生减少 LLM 摘要调用;(b) MemTree 构建时间随每树事实数适度增长;(c) 层级并行刷新在更大树上产生更大加速;(d) 每次调用的摘要容量在中等分支因子前保持稳定;(e) 端到端根召回在中等分支因子时达到峰值,证明默认 k 设置合理。

图6 是支撑这套设计选择的诊断图。重点看 (e):分支因子 k 在 16 左右是甜蜜点,太小(k=2 二叉树)或太大(k=64 接近扁平)都会让根召回率下降。

这里 (c) 和 (e) 给了我比较强的信心——层级并行不是空话,在更大的树上加速比反而更明显(说明它是真正可扩展的)。k=16 的甜蜜点也跟数据库 B+树的工程经验一致。

4.6 消融:哪些组件是真的必要?

Table 6,树家族组合(30B pass@8):

变体 pass@8
entity+scene+session 86.7
entity+scene 86.7
entity+session 85.0
session only 85.0
scene+session 83.3
scene only 81.7
entity only 63.3

这个消融有点意思——完整三棵树和 entity+scene 两棵树效果一样。也就是说在 LongMemEval 上,session tree 是冗余的。

但作者还是保留了 session tree,理由是"原始上下文回退"。我能理解,但严格来说这一项是在赌某些特殊查询会需要原始上下文。如果做产品落地,可以考虑把 session tree 做成可选项,按需开启。

Table 7,检索/浏览策略:

变体 pass@8
flat-10 78.3
root-only 73.3
emb 85.0
emb+planner 83.3
llm 88.3
llm+planner 90.0

LLM browse + planner 拉满到 90%。但注意 emb+planner 反而比单纯 emb 差(83.3 vs 85.0)——planner 对嵌入浏览没有帮助,只对 LLM 浏览有帮助。这个发现挺反直觉的,作者也没深入解释。我猜是因为 planner 输出的"查询计划"对 embedding 检索可能引入了噪声,但对 LLM 来说能更好地利用结构化信息。


五、批判性看一眼:哪些地方值得打个问号?

读完整篇,我有几个想吐槽的点:

1. Token 消耗其实涨了,论文淡化了这点。 Mem0 写一次 294K token,MemForest 1.143M token——多了快 4 倍。MemForest 用并行抢回了延迟,但总算力消耗是涨的。如果你是按 token 计费的(用云端 API),账单会更难看。这点论文里没怎么强调。

2. b=2 默认块大小没充分论证。 切得太碎可能让单块抽取出的事实质量下降。我个人会想做一组从 b=2 到 b=8 的实验看看。

3. LoCoMo 输给 EverMemOS 这件事被一笔带过。 论文用"具有竞争力(competitive)"包装了一下,但 multi-hop 上输了快 20 个点其实挺严重的。如果你的 Agent 主要做多跳推理(比如复杂客服、研究助手),MemForest 不一定是最优解。

4. 写路径里仍然有 LLM 摘要。 论文卖点是"打破 LLM-in-the-loop",但严格说它只是把 LLM 从串行关键路径上挪到了批量并行路径上——LLM 还在写路径里,只是被批处理 + 并行化了。如果后续有人做出"完全无 LLM 写路径"(比如纯 embedding 维护),MemForest 这套就有点尴尬了。

5. Lifecycle Maintenance(合并、删除、迁移)被讲得很简略。 真实生产环境里,过时记忆怎么删、矛盾事实怎么处理、用户主动要求遗忘怎么办——这些才是工程上最头疼的。论文里这一块基本是 future work。

6. 没和 MemGPT、A-MEM 直接比。 Related Work 里提了,但实验里没有。这俩在长程记忆领域也是经常被对标的,缺席让 baseline 选择看起来有点"挑软柿子"的嫌疑。

不过这些吐槽不影响我对整篇 paper 的整体好感——它是这个方向上目前我看到最系统、最像数据库工程师在认真思考问题的方案。


六、对工程的启发:你能从这篇 paper 里抄什么?

如果你也在做长程对话 Agent / 长上下文记忆系统,下面几个思路可以直接借鉴:

  1. 写入关键路径上能不放 LLM 就别放 LLM。LLM 是关键路径的死亡之吻——一旦放上去,延迟天花板就锁死了。哪怕是把 LLM 调用挪到异步批处理也好。

  2. 维护成本要随 N(新增)增长,不能随 M(累积)增长。如果你的系统在做"全局摘要重写"或者"全量索引重建",赶紧改。这是 O(M) → O(log N) 的本质区别。

  3. 多视角组织同一份数据。entity / scene / session 三视图在 LongMemEval 上的效果让我意识到,单一组织维度的记忆系统是在自废武功。哪怕只多一个视图,召回效果都能上一个台阶。

  4. embedding 检索 + 偶尔 LLM browse 是个 sweet spot。MemForest (emb) 模式 2.19 秒、78.4% 准确率,已经是个非常实用的产品级配置。LLM browse 慢一倍换 1.4 个点不一定划算。

  5. dirty 标记 + 延迟批量刷新是个万能技巧。这个思路在数据库、文件系统里用了几十年,搬到 Agent 记忆里同样好使。任何"边读边算"的场景都可以这么改造。


收尾

我蛮喜欢这篇 paper 的姿态——它没有去比谁的 prompt 更花哨、谁的"智能体认知架构"概念更宏大,而是老老实实把问题降维成了一个"写优化的时序数据管理问题",然后用数据库领域的成熟思想把它做掉了。

Agent 记忆这个方向到现在还在概念漫天飞的阶段,能看到一篇愿意把脏活做扎实的工作,还是挺珍贵的。如果你也在被长程 Agent 的"用着用着就慢死"折磨,这篇值得花一个下午认真读一下。

至于 MemForest 会不会成为下一代 Agent 记忆的事实标准——我觉得它的"森林 + 分层时序索引"框架大概率会被吸收,但具体实现细节(k=16、b=2、三视图)每家肯定会按自己场景再调一遍。这就跟 LSM-Tree 的故事一样,思想是普适的,参数是要调的。


觉得有启发的话,欢迎点赞、在看、转发。跟进最新 AI 前沿,关注我