agent记忆系统

0 阅读12分钟

agent运行时喂给LLM的内容可以分为两类:

  1. 常驻提示词里的:系统提示、工具说明、对话历史、AGENTS.md等,每次请求时加载或续用,相当于计算机的内存。
  2. 需要时再去检索的:其他对话的历史记录、团队知识库、代码库、网络上的新闻等,它们默认不在上下文里,这一层相当于计算机的外部存储。 它们可能是向量数据库、SQL表,也可能是飞书文档这类在线知识库,统称记忆系统

记忆系统的问题和常驻提示词里的是不一样的。记忆会腐烂、过期、互相矛盾、被错误检索。

下面按记忆的引入、写入、作废、共享、监测这几个环节来讲。

一、该不该引入记忆系统?

“agent又忘了”是开发时最常见的问题。但大部分时候会发现这些“失忆”根本轮不到记忆系统(外部存储)出场。

先看两个例子。

  1. agent调用工具失败了,报错信息只有一句“调用失败”,agent不知道怎么修正,只能靠猜。这是工具契约的问题,上记忆系统也救不了。
  2. agent每次回答都要在上下文里翻找项目规范,而规范本应写进AGENTS.md自动加载。如果你用的是claudecode,但把规范写在AGENTS.md里,是找不到的,claudecode只看CLAUDE.md。所以这是配置摆放的问题,记忆系统同样救不了。

还有一种比较隐蔽的问题:上下文过多。复旦大学的论文在SOP-Bench上做过对比:给模型注入288 tokens的“原始流程加背景描述”,任务成功率66.48%;注入165 tokens的“只保留行动规则”,成功率同样是66.48%。多出来的描述没有带来任何行为价值。

所以在考虑外部存储之前,先做一次上下文审计:哪些内容是真正影响决策的,哪些只是在“说一些正确的废话”。

为什么要先做这些而不是直接上记忆系统?因为记忆系统有成本:要更新,要对抗过期和污染。

真正值得放进记忆系统(外部存储)的信息有这些特点:动态的、跨会话的、需要被更新或作废。

二、什么时候写入?

确定需要记忆系统之后,下一个问题是:一条原始对话,应该在什么时候被提炼成“记忆”?这个选择会影响记忆库的质量,因为一旦信息被压缩成结论,原始细节就丢了。

先看业界三个框架的做法。

  • mem0是在写入时整合。每来一段新对话,先从中提取出候选事实,再拿这些候选去检索库里已有的相似记忆,判断这条信息是新增、修订还是无关。然后让LLM决定add(库里没有,作为新记忆写入)、update(库里已有相似的,去修订)、delete(与库里某条记忆矛盾,把旧的那条作废)还是noop(不值得记,什么都不做)。整条链路的目的,是把“提取”和“与旧记忆对账”合成一步,避免重复和矛盾同时进入库里。
  • zep的做法是只打时间戳不删除。每条事实带两个时间戳:valid_at 表示这条事实从何时开始成立,expired_at 表示从何时起系统认为它作废。出现矛盾事实时,旧条目标记作废,但数据本身保留。
  • letta把整合搬到后台,用户空闲时由一个专门的agent做记忆整理,避免在关键对话路径上增加额外操作。

相比之下,研究性系统的选择更保守:voyager中的agent先执行任务,等任务完成并通过评估,才把学到的技能写进长期记忆;reflexion反过来,任务失败之后才把反思写进去;generative agents会给每次经历打一个重要性分,累计到150才做一次总结。

这些系统没有一个会在对话进行中即时提炼记忆,因为对话刚结束时无法预知未来会问什么,此刻的提炼标准只能来自当前上下文,而不是未来的查询。

原始文本存下来很便宜,提前“理解”并提炼成结论,反而可能把关键证据丢掉。

另外,voyager和reflexion都等一个“任务结束”的信号才动手,背后的逻辑是:只有执行过的信息才有资格被沉淀下来,否则记忆库里塞满的只是未经验证的猜测。

但这也不是绝对的。有一种例外情况:当agent要处理的对话内容非常固定时。比如一个只负责报销单的agent,它每次接到的都是同一套字段:金额、日期、类别。这种情况下,系统是提前知道了未来要查哪些信息的,对话结束后立刻提炼记忆就是合理的,因为提炼标准是确定的,不会漏掉关键内容。反过来,如果agent面对的是开放式的对话,没法预测下一次用户会问什么,那就别急着在对话中总结。原始记录保留下来更可靠,等到真正需要的时候再提炼,反而能保住细节。

三个可落地的要点:

  • 区分要入库的信息是固定的还是开放的,固定的可提前提炼信息入库,否则按原始内容记录;
  • 触发写入选在四个高信号时刻:检索没找到答案时、用户纠正了agent时、任务成功收尾时、系统空闲时;
  • 新信息入库前设一道门禁:先通过一次实际执行验证真实性,验证通过才允许写入长期记忆。

三、怎么让记忆保鲜?

先区分四种操作:删除、过期、降权、撤回。它们的检索行为完全不同。

操作检索时的行为谁在用
删除不可达,审计线索一起没掉mem0的delete
过期对“现在”不可达,对“当时”可达zep的invalid_at
降权依然可达,只是排名靠后mem0的memory decay
撤回必须不可达目前没有完整实现

看一下降权在真实系统里是什么样的。mem0的memory decay会把长期没访问过的记忆压到0.3倍权重,频繁访问的记忆获得最高1.5倍加成。这套机制适合处理“优先级”:不那么相关的记忆,排名往后退。但在实际场景里,也有人用它处理“正确性”:把已经过时或被推翻的记忆调低权重,期望它们不要被检索出来。降权适合处理优先级,不适合处理正确性

一条已经被新事实推翻的旧记忆,哪怕权重被压到0.1倍,在top-K截断或者查询措辞变化时依然可能被重新检索。它被召回时带着的信号是“不太相关”,而不是“已被推翻”。模型会把一条已经作废的事实,当成一条可信度稍低但仍然成立的当前事实。

想让错误记忆真正作废,需要让它在检索阶段就不可见,而不是到排序阶段才降低权重。具体办法是给每条记忆标上有效时间区间,查询时强制过滤掉当前时间落在区间外的条目。zep的数据模型已经为每条事实建了四个时间戳,分别是:事实成立区间的起点valid_from与终点valid_to,以及系统记录它失效的时间invalidated_at、首次被检索到的时间retrieved_at。前两个描述“这条事实在世界上何时为真”,后两个描述“系统何时观察到它”。

其实这套带时间区间的设计在数据库领域已经存在多年。SQL标准里的“系统版本应用时间表”就是同一套思想,agent memory要做的不是发明新机制,而是把现成的数据库能力接进LLM的语义记忆管理里。

此外还有一个更麻烦的问题:级联作废。假设agent的记忆库里有两条:“服务器是ubuntu”和“所以应该用apt”。现在发现第一条是错的要作废。那么由它推导出的“应该用apt”也需要作废,但很难追踪这条依赖链。

落地做法是维护一条provenance链路:每条记忆除了内容本身,再存一个来源字段,记录它是直接从对话提炼的,还是从另一条记忆推导出来的。源记忆作废时,遍历所有引用了同一个来源的派生记忆,递归标记为存疑。如果框架不支持这个字段,可以在提炼时让每条新记忆带上原始对话的UUID引用,作废时回查所有引用同一段对话的记忆条目,至少能做到追溯。

自动检测冲突的准确率也有限。曾经看到过有篇论文测过,LLM识别记忆冲突的准确率大约60%,但要说清具体是哪两条冲突,准确率差不多是40%。也就是说,系统能察觉到“有矛盾”,但指不准矛盾双方。 这个问题的做法只能折中了:不要追求全自动闭环,改成半自动。当检测到可能存在矛盾时,把候选对推进一个待审核队列,人工做批量点击确认。

记忆保鲜的做法:

  • 用时间戳过滤而不是降权,来作废错误记忆,查询时强制过滤区间外的条目;
  • 追踪记忆之间的依赖链,源记忆作废时递归标记所有派生记忆为存疑;
  • 冲突检测别追求全自动,改成半自动:LLM报候选,人工批量确认。

四、共享时怎么不互相污染?

多agent共享记忆,错误信息的传播会被成倍放大。目前公开资料里,几个代表性系统的选择有明显差别。

  • 同一任务内的多agent,cognition的建议是共享完整执行轨迹,至少要比摘要完整,因为只有看到完整轨迹,并行agent才能避免基于不完整的上下文做判断。
  • 子agent向主agent回报时,anthropic的建议是传“产物”加schema,也就是最终结果和对应的格式说明,避免来回转述造成的失真。
  • 跨团队、跨组织协作时,google a2a协议干脆规定:内部记忆、工具、业务逻辑一概不共享,agent之间只交换任务描述和有格式定义的产出物。这样做的好处是,跨组织协作时对方只需要能直接使用的结果和双方约定的格式,不需要内部推理过程,因为暴露内部记忆反而会放大权限和污染风险。

这些做法的共同点,是共享有边界的显式信息,而不是共享一个所有人都能往里写、往里取的公共池子。如果把所有agent的记忆倒进同一个向量库,靠语义相似度去检索,这样既没有轨迹的因果结构,也没有产物的格式边界,一条错误记忆进去后,所有agent都会学到它,一个老鼠屎打烂一锅汤。

几个可以落地的约束:

  • 子agent把产出物写进外部系统(文件、数据库表),只回传路径引用;
  • 跨团队协作交换标准化artifact,不交换内部prompt和记忆草稿;
  • 如果必须共享记忆,每条记忆都标上来源和权限域,写入审计保持开启。

五、怎么发现它已经坏了?

可以通过三个方式来应对记忆系统的逐渐腐化。

  1. 看冷落程度。mem0已经在跟踪每条记忆的访问频率,长期没被用到的会逐渐降权。可以定期筛出一段时间内零访问的记忆来处理,它们大多数已经是噪声了。
  2. 看上下文利用率。AWS Well-Architected框架的AGENTREL08-BP04要求按组件分开统计token消耗:系统提示词、检索上下文、对话历史、工具结果。之所以要分开统计,是为了定位到底是哪一环在膨胀。如果只记总量,超了阈值也无从下手。上下文窗口利用率超过80%就应该告警并触发裁剪。渐进式的膨胀不易察觉,也可以主动定期观察增长率,不必等阈值触发。
  3. 做错误注入实验:故意往记忆库里塞一条错误事实,然后观察它会污染多少份后续决策、需要多久才能被发现、能不能通过历史快照回滚到污染之前。“根据错误记忆执行了破坏性操作”远比“答错一道题”更严重。

结语

agent的记忆问题和数据库治理很像,难处不在写入,而在写入之后:信息会过时,会被推翻,会在共享中扩散,也会不知不觉变成噪声。

如果agent最近越来越不靠谱,排查顺序是:

  • 先确认是不是必须上记忆系统,也许改工具错误信息或补配置文件就能解决;
  • 再确认写入时机,有没有在信息还没验证的时候就把猜测固化了下来;
  • 然后检查作废机制,看看“撤回”是真的不可达还是只是降权;
  • 接着检查共享边界,agent之间交换的是结构化产出物,还是同一个公共向量池;
  • 最后定期做健康指标检测,让衰败过程可见,而不是等出事故再去查日志。