Featured image of post Agent 记忆系统设计:从上下文窗口到跨会话持久化,一套能落地的工程方案

Agent 记忆系统设计:从上下文窗口到跨会话持久化,一套能落地的工程方案

一个跑了三个月的客服 Agent,用户问它:我们上季度定的那个迁移方案,最后为什么没上 MySQL?它答不出来。更糟的情况是它答了,而且答得很自信:你们还在用 Postgres。

如果你在搭 Agent,这个场景迟早会来。最常见的两种应对,都不太够用。

第一种是把历史全塞进上下文。装不下,而且每一轮都要为这堆东西付一次钱。就算装得下,注意力也会被稀释。关于这一点,Chroma 那篇讲「上下文腐烂」(context rot)的研究说得很直白:上下文里的 token 越多,模型准确捞回其中信息的能力反而越差。Anthropic 在讲上下文工程时把这件事定性成资源问题:上下文是有限的,边际收益递减,你要做的是找到那组「信号最密、数量最少」的 token。

第二种是加一个向量库。召回率上去了,但它还是会把三个月前的旧事实当成现在的事实。因为向量库回答的是「哪段话跟你的问题像」,不是「哪句话还算数」。

这篇讲的是第三种:把记忆当成一套分层系统来搭。先说清楚它和站内两篇的分工,免得你以为又撞题了。《AI Agent 记忆层的「第三极」》 讲的是记忆的表示方式(明文文件树和向量、图怎么选)以及信任能力(撤销墓碑、双时态这些)。《Agent 状态管理与持久化》 讲的是进程别丢:checkpoint、崩溃恢复、序列化格式。这一篇夹在中间:讲跨会话记住这件事,工程上怎么搭、怎么选、哪里最容易翻车。

一、先把「记忆」拆成四层

「给 Agent 加记忆」这句话之所以经常做错,是因为「记忆」根本不是一层东西。它至少是四层,载体的时间尺度差了好几个数量级。

层 时间尺度 载体 写入触发 典型失败
工作记忆 秒到分钟 当前上下文窗口 每轮对话自动累积 塞太满,注意力被稀释
短期 小时到天 会话状态(内存/Redis) 每轮或每步 进程一崩就没了
长期 月到年 持久化存储(库/图/文件) 显式或抽取 记的是过期的
程序性 长期 指令、技能、偏好 人工或反思 从不更新,越来越不准

把这几层混在一起,系统会两头不讨好。把长期记忆塞进上下文,是拿最贵的资源干最便宜的活;把工作记忆写进数据库,是拿最慢的路径干最急的活。

这四层里,前两层好办:工作记忆靠上下文管理和压缩,短期靠状态持久化(就是前面那篇工程文讲的)。真正难、也真正决定一个 Agent「记不记得住」的,是后两层:长期记忆和程序性记忆。所以下面三节,分别讲它们的写入、检索、巩固。

二、写入路径:什么时候写、写什么

写入的第一个问题是触发时机,大致三种。显式写入是 Agent 主动调一个 memory 工具,像记笔记;隐式写入是每一轮都跑一次抽取,把对话里的事实拎出来;批处理写入是攒到会话结束或者空闲时再统一处理。

第二个问题更重要:写进去的,是抽取后的原子事实,还是原始片段?原子事实省 token、好检索,但丢上下文;原始片段保真,但贵。

这里有个 2026 年 4 月的改动值得单独说,因为它把写入路径的设计哲学换了个方向。Mem0 原来的做法是两趟:第一趟从输入里挑候选事实,第二趟拿这些候选去跟已有记忆对账,用 ADD、UPDATE、DELETE 三种操作调和。新的做法把对账这趟砍了,只保留单趟、且只做 ADD。

砍掉对账的理由,Mem0 自己写得很清楚:对账那一步正是上下文被毁掉的地方。 overwrite 有时会抹掉原始事实里的关键信息,delete 有时会删掉以后可能还有用的东西。改成只加之后,每一条抽取出来的事实都成为一条独立记录;信息变了,新事实跟旧事实并存,两条都活着。它的说法是,这样一来抽取延迟大致减半,而且记忆质量更好,因为模型把算力花在理解输入上,而不是花在跟已有状态做 diff 上。

同一版还有两个细节。一是 Agent 生成的事实变成了一等公民:以前 Agent 说「我已经帮你订好 3 月 3 日的机票」,系统经常直接忽略,只记用户明确说过的;现在这类「确认了一个动作」「给了一个建议」也算记忆。二是实体链接:把同一实体的多条记录挂到一起,这也是它能往上叠「分层记忆」的前提。

我自己看这段改动,比数字更值得留意的是它透露的判断:历史比当前状态值钱。 一个只会告诉你「现在是什么」的记忆,没法回答「它从哪变到哪」;而后者往往才是用户真正想知道的。

三、检索路径:召回,然后是注入

检索分两步,召回和注入,很多人只优化了第一步。

召回这一层,几家的路子不一样。最基础的是向量相似度;Mem0 的新算法往上叠了实体级匹配和分层记忆。检索时把各层并行打分再融合,问「Alice 怎么看远程办公」偏实体匹配,问「我上周开了哪些会」偏时间理解,问「这个项目用户的态度怎么变的」要靠跨多条记忆的高阶推理。Zep 走的是另一条路,核心是一个叫 Graphiti 的时序知识图引擎,它把非结构化的对话和结构化的业务数据合成到一张带历史关系的图里。数字上,Zep 在 MemGPT 团队自己立的 Deep Memory Retrieval 基准上是 94.8% 对 93.4%,在更难的 LongMemEval 上准确率最高提升 18.5%,同时把响应延迟压低了 90%。

但召回只是把候选捞出来。注入才是决定成本的那一步。捞到的东西不是全塞进去就好。

基准 旧算法 新算法 每次检索平均 token
LoCoMo 71.4 92.5 6,956
LongMemEval 67.8 94.4 6,787
BEAM(1M) — 64.1 6,719
BEAM(10M) — 48.6 6,914

这张表是 Mem0 2026 年 4 月那版的成绩,重点不在分数,在最后一列:它每次检索大约用掉 7,000 个 token,而 full-context 那类做法在同一批基准上经常一次吃掉 25,000 个以上。差出来的这一截,就是注入策略的价值。

我对这一层的判断是:检索是可测的,所以它最先被优化,也最容易买到。 召回差了,你能看见(答非所问),也能花钱解决(换更强的 embedding、换更好的 reranker、换一个现成的记忆服务)。它不是一个需要你自己想清楚的问题。

四、巩固:唯一换模型救不了的那一层

到这一步,多数人以为记忆系统做完了。其实最贵的部分才刚开始。

先看它为什么必须存在。三个力量在推:实体漂移,用户会变,一月份用 Postgres、三月份迁到 MySQL,两条事实要是权重相同,检索器这一轮谁分高就返回谁;索引精度,索引越大,近重复越多,互相抢位置;上下文经济,前面说过,塞满就是烧钱加稀释注意力。一个上线半年的客服助手,记着每个用户的每一句话,最后会自信地告诉一个早就迁走的客户「你还在用 Postgres」。召回没问题,记忆不对。

这就是巩固问题。它的政策层面,有人总结成四个杠杆:重要性(什么够格成为一条记忆)、合并(同一个实体的多条事实怎么统一)、衰减(旧事实的置信度怎么随时间下降)、淘汰(一条记忆什么时候彻底离开系统)。这份四杠杆的框架来自 Hindsight 今年五月的一篇博客,而 Hindsight 本身是做记忆产品的公司,所以你读它的时候要打个折:它列的各家「做了/没做」是它自己的盘点,方向可信,具体到某一家是否真的落地,最好自己回源码核一遍。

巩固和「信任」是两件事,这也是它跟那篇《第三极》的分工所在。那篇讲的撤销墓碑、双时态有效期、负向检索断言,回答的是「这条记忆能不能信」;这里讲的四杠杆,回答的是「用什么规则决定记什么、合什么、忘什么」。两件合起来,才是一个记忆系统敢不敢用的全部。

我的判断在这里:检索能靠换模型救,巩固不能。 召回差了,你换一个更强的模型、换一套更好的 embedding 就补回来了。但没有任何模型会自己知道「新地址应该压过旧地址」。那是一个策略决定,得由你在工程里写死。生产里的记忆事故,大多数是召回成功了、只是记的是过期的那条。你很难从一次「看起来正常」的回答里发现它。

五、落地:官方 MCP memory,和平台侧的三件套

讲完原理,说落地。现在起步最省事的一条路是官方的 MCP 记忆服务器。

@modelcontextprotocol/server-memory 是一个用本地知识图做持久记忆的实现,核心就三个概念:实体(图的节点,有名字、类型、一串 observation)、关系(实体之间的有向连接,用主动语态,比如 John works_at Anthropic)、observation(挂在实体上的原子事实,一条一个事实,能单独加、单独删)。它上周刚发到 v2.0.0(2026-10-19),npm 上周下载量大约 13.8 万,是这类工具里用得最广的一个。

它的好处是标准、可移植、轻量:没有外部数据库,一个本地图文件;换 Agent、换客户端都能接。但它有个必须自己补的短板:它把「记忆」降级成了「Agent 自己记得调工具」。默认情况下,Agent 不会主动去写;你不在 prompt 和工程里做兜底,它记下来的东西会少得可怜。这跟上一节说的一样:写入路径是个工程决策,不是模型能力。

平台侧这几年的做法,是把上下文管理拆成三件互补的工具,Anthropic 在 Claude Sonnet 4.5 那波把三个都做成了一等 API:压缩(compaction,把上下文窗口里的内容蒸馏成一份高保真摘要,让对话能继续)、工具结果清理(context editing 的一部分,把可重新获取的旧工具结果丢掉,但保留「这个调用发生过」的记录)、记忆工具(一个客户端文件系统,Agent 能读写一个专门的记忆目录,存储后端由开发者自己管)。Claude Code 在生产里就是压缩加两套记忆一起用。

还有一个我觉得位置最合理的做法,来自 Letta。他们把巩固从「用户等着的那几秒」挪到了「Agent 空闲的时间」,叫睡眠时间计算(sleep-time compute):利用空闲算力,把原始上下文整理成「学过的上下文」,等用户再来的时候,Agent 不用从头想。它的研究论文里给的结论是这能带来帕累托改进:把计算从高延迟的交互时段,挪到系统本来就在空转的时段,质量不掉。巩固这件事天生就不该发生在用户面前,放在空闲时段是对的。

六、选型矩阵

把上面几家摆到一张表上,选择就清楚了。

方案 表示 部署 检索 巩固 适合谁
Mem0 事实,可叠图 云 / 自托管 分层并行打分 + 实体匹配 ADD-only 抽取 要跨会话事实、想少自己搭
Zep / Graphiti 时序知识图 云 / 自托管 图 + 时序遍历 双时态 企业场景、要时间推理
Letta 记忆块 自托管 块内 + 工具 睡眠时间计算 想让 Agent 主动整理
官方 MCP memory 知识图(实体/关系/observation) 本地 图遍历 基本没有 要标准、可移植、轻量起步
明文文件树(IHMT 类) 本地纯文本树 本地 走树 + BM25 弱 一个人、几百到几千条、要可审计
纯向量 RAG 向量 自建 ANN 无 最基础的底子

这张表里,如果你只能选一个起点:想要省事、要跨会话事实,从 Mem0 起步;想要可移植、不引入外部服务,从官方 MCP memory 起步,再按需往上补巩固策略;如果你的场景是「记忆里躺着客户名、诊断记录,我要能自己审、自己删」,那就去看那篇《第三极》里讲的明文文件树。

七、一套最小可用的落地顺序

如果让我按顺序做,大概是这七步。

1. 先分层。长期记忆不要塞进上下文窗口,工作记忆不要写进数据库。
2. 写入用单趟、只加(ADD-only),旧事实保留,别用 UPDATE/DELETE 覆盖。
3. 给检索设 token 预算。目标:单次检索注入 < 7,000 token。
4. 巩固先做两件事:合并(同一实体统一)+ 衰减(旧事实降权)。
5. 淘汰最后做,而且要有依据。没把握就先不删,只降权。
6. 用官方 MCP memory 起步;需要时间推理再换 Zep 这类时序图。
7. 把「写记忆」写进 prompt 和工程,别指望 Agent 自己想起来写。

第 4 步的伪代码长这样,核心是让新旧事实并存,但让新的排前面:

def consolidate(new_fact, existing):
    # 合并:同一实体、同一属性,新的压过旧的,但不删旧的
    for old in existing.same_entity(new_fact.entity):
        if old.attribute == new_fact.attribute:
            old.status = "superseded"      # 不删除,只标记
            old.superseded_by = new_fact.id
    # 衰减:越旧的事实,检索时的权重越低
    for m in existing.all():
        m.weight = m.base_weight * decay(now - m.updated_at)
    return existing

注意这里没有一个 delete。这也是我写这篇时越来越确定的一点:记忆系统里「加」是简单的,「有依据地减」才是难的。

写在最后

把 Agent 记忆当成「加一个向量库」,是把它当成了检索问题。但检索是可测的、能买的,真正决定这套系统好不好用的,是写入和巩固这两条路径:写什么进去,以及什么时候让旧的东西退场。

这也不是一个能靠换模型解决的问题。模型再强,也不会自己知道「三月份迁走的那个客户,旧地址不该再排前面」。那得由你在工程里写死。所以如果只让我给一条建议:先把巩固策略想清楚,再去挑记忆服务。顺序反了的话,你会有一个召回率很高、但经常在答过时信息的系统。

相关阅读:

By AI博士 万戈