news 2026/8/30 11:19:22

长程智能体为何总翻车?双记忆机制实现原理与落地实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
长程智能体为何总翻车?双记忆机制实现原理与落地实践

长程智能体(long-horizon agent)一直是 Agent 应用里最容易翻车的类别。单轮问答、单次工具调用表现不错,可一旦任务变成“先调研、再写方案、再执行、再校验”的多步流程,很多模型会在第 5 到第 10 步之间忘记最初的目标约束,或者被中间结果带偏。Recuris 这类双记忆机制就是冲着这个问题来的:它把 Agent 的记忆拆成工作记忆和长期记忆两套系统,让当前执行状态和跨任务经验各归各的,从而提升长程任务的最终成功率。

这篇文章不打算复读论文里的抽象定义,而是从落地角度拆一遍:长程 Agent 到底为什么失败,双记忆机制解决的痛点是什么,你在自己的 Agent 代码里怎么改,以及成功率怎么测才不是自我感动。适合正在做 Agent 框架、智能体工作流、或者被多步任务稳定性折磨的开发者看。最值得关注的点是:双记忆不是“多存一点历史”,而是把记忆的写入、读取、更新当成和推理同等重要的模块来设计。

1. 长程智能体真正卡住的地方:不是单步能力,而是状态管理

1.1 长程任务失败的典型场景

我自己在跑多步 Agent 的时候,失败基本集中在三类场景,跟模型单步推理能力关系不大。

第一类是早期约束丢失。用户第 1 步明确说了“所有输出用中文,代码块里不要英文注释”,到第 8 步模型完全忘了这回事,输出开始混入英文注释。这不是模型不会写中文,而是它没记住约束。

第二类是中间结果覆盖计划。工具返回了一大段内容,模型把这一段当成了新目标,顺着工具输出往下走,完全偏离了原来的任务计划。很多长程任务跑偏就是从这一步开始的。

第三类是嵌套子任务回不来。主流程里拆了一个子任务,子任务花了很多轮才完成,回到主流程时上下文里已经没有主流程的步骤清单了。模型不知道接下来该干嘛,只能随便生成一个下一步。

这类问题本质上是状态管理问题。任务越长,中间状态越多,上下文里堆积的信息越杂,模型越难判断“什么东西现在最重要”。

1.2 为什么“把历史全部塞进上下文”治标不治本

有人会说:上下文窗口不是很大吗,把历史全传过去不就行了?这个思路在早期能缓解问题,但长期看有三个明显代价。

第一个代价是成本。每一步都携带全部历史,token 消耗按任务步数线性增长,长任务跑到十几步以后,单步调用成本可能翻了好几倍。批量跑的时候这个开销非常明显。

第二个代价是注意力稀释。长上下文不等于有效信息密度高。工具返回内容里可能只有一句话有用,其余全是噪音。模型需要在大量不相关内容里找重点,找到的概率往往会下降,而不是上升。

第三个代价是没有更新机制。历史是只增不改的。如果第 3 步做了一个假设,第 6 步发现假设是错的,仅仅追加新结论并不能让模型放弃旧假设。旧内容还在上下文里,随时可能被再次引用。

于是就有了一个关键判断:长程 Agent 需要的不是“记住所有东西”,而是“在正确时间读到正确的东西”。这正是双记忆机制要解决的问题。

2. 双记忆机制怎么理解:工作记忆管执行,长期记忆管沉淀

2.1 两层记忆的分工

双记忆机制的核心是给记忆分两个存储空间,各自采用不同的写入和读取策略。

工作记忆负责当前任务正在执行的状态。它的生命周期和当前任务绑定,任务结束就清空。里面应该包含:当前目标、子任务清单、已完成步骤、当前步骤、最近一次工具结果、待决策事项。它的特点是更新频繁,每一步都要同步,并且一致性要求很高。如果工作记忆里记的目标已经过时,后面的每一步都会跟着错。

长期记忆负责跨任务保留的内容。它不随单个任务结束而清空,包括用户偏好、领域规则、之前踩过的坑、可复用的方案模板。它的特点是写入低频、读取按需。不会全量塞进每个 Prompt,而是根据当前任务检索出最相关的几条注入。

我一般习惯用一句话区分:工作记忆回答“我现在在做什么”,长期记忆回答“这类事情我以前是怎么做对的”。

2.2 为什么两套记忆比一张总列表更有效

如果只做一个“总记忆列表”,会遇到三个没法解决的问题。

第一个是优先级不明确。长期经验和当前临时状态混在一起,检索时不知道该按什么排序。当前任务里的步骤信息往往比三个月前的一个经验更重要,但总列表里它们地位相同。

第二个是沉淀通道缺失。任务结束时,临时状态和经验总结没有区分,要么都保留,要么都丢弃。都保留会让长期记忆越来越脏,都丢弃会让 Agent 永远学不到经验,第二个类似任务照样踩第一个任务的坑。

第三个是更新规则混乱。新信息进来时,是覆盖旧信息还是追加?旧结论被推翻时,要不要标记无效?在单列表结构里,这些规则很难表达清楚。

双记忆机制把更新规则拆开了:工作记忆高频覆盖,任务结束清空;长期记忆低频写入,由“这次有没有沉淀价值”决定,写入之前要先过滤。这个拆分本身就能减少很多脏数据。

2.3 Recuris 这个名字里的关键点:循环写回

Recuris 这个名字里带 Recur,和 recurrence 同源。从机制设计思路来看,它的一个重要特征不是“建好两套记忆就完事”,而是让记忆在每个执行步骤里循环更新。

具体说,每个步骤会经历一个闭环:读取当前工作记忆 → 调用模型或工具执行 → 把结果写回工作记忆 → 判断是否有值得沉淀的经验 → 有则压缩写入长期记忆 → 进入下一步。这个循环保证了记忆不是建好就不动的静态存储,而是和任务执行同步演进的动态状态。

这也是我建议落地时最该先学的动作。不要先纠结用什么向量库、用什么 embedding 模型,先把“每步结束都回写状态”这个循环跑通,双记忆机制就有一半的骨架了。

3. 在自己项目里复现双记忆改造:从最小版本开始

我没有拿到 Recuris 的完整工程源码,下面这套是我按照双记忆思路,在一个普通 Python Agent 项目里整理出来的通用路径。你不用照抄,重点看改造顺序。

3.1 第一步:把 Agent 的状态从“文本”变成“结构”

大多数 Agent 项目最麻烦的问题不是没记忆,而是所有状态都藏在自然语言上下文里,没有显式的数据结构。第一步就是把状态抽出来。

我建议先定义一个工作记忆对象:

@dataclass class WorkingMemory: goal: str # 当前任务总目标 constraints: list[str] # 用户明确约束,例如“必须中文输出” step_list: list[str] # 子任务步骤清单 completed_steps: list[str] current_step: str latest_result: str pending_decisions: list[str] warnings: list[str] # 需要持续提醒自己的事项

这个结构不一定完备,但足够起步。它把目标、约束、步骤、结果拆成了独立字段。模型在每一步都能直接读到这些字段,不需要从大段历史里重新推算。

为什么要先做这一步?因为双记忆的所有更新逻辑都需要一个可修改、可校验、可序列化的载体。纯文本追加没法判断“哪部分被覆盖了”,结构化字段可以。

3.2 第二步:工作记忆的写入和更新

工作记忆的更新发生在每个步骤结束后。通用流程是:调用工具 → 解析结果 → 更新工作记忆 → 再进入下一步。

这里最容易错的是更新时机。如果你在工具结果还没解析完就更新状态,写进去的是旧的或者残缺的数据。先解析,再更新,顺序不能反。

伪代码长这样:

def update_working_memory(mem: WorkingMemory, step_output: dict): if step_output.get("completed_step"): mem.completed_steps.append(step_output["completed_step"]) if step_output.get("next_step"): mem.current_step = step_output["next_step"] if step_output.get("new_constraint"): mem.constraints.append(step_output["new_constraint"]) if step_output.get("result"): mem.latest_result = step_output["result"]

关键点是每次更新都是显式的字段修改,而不是往记忆里追加一句话。这样你随时能回答“当前目标是什么”“已完成到哪一步”。

3.3 第三步:长期记忆的沉淀和检索

长期记忆的存储可以用向量库,也可以用 SQLite 加关键词。我个人建议先用最简单的形式:每条记忆是一条带文本、标签、创建时间、来源任务 ID 的记录。等技术跑通了,再考虑换成向量检索。

写入长期记忆要有门槛。不是每一步的结果都值得存。值得存的有几类:任务成功的总结、任务失败的教训、用户反复强调的偏好、可复用的方案模板。

def commit_to_long_term_memory(item: dict): # item 必须至少包含 text, task_id, success_flag # 示例:只有 success_flag 为 True 或用户明确反馈时才写入 if not item.get("success_flag") and not item.get("user_confirmed"): return long_term_store.insert(item)

这个门槛很重要。如果任务失败的中间结果也写进长期记忆,下一次任务很可能会把错误结论当作经验引用,造成记忆污染。

检索时按当前查询做相似度排序,取前几条注入 Prompt:

def retrieve_long_term(query: str, top_k: int = 5): candidates = long_term_store.search(query, limit=top_k * 3) # 按相似度排序,再按 recency 微调 return candidates[:top_k]

这里的 top_k 不要设太大。我一般建议 3 到 5 条。长期记忆注入太多,效果和全量历史一样,同样会稀释注意力。

3.4 何时压缩、何时覆盖

工作记忆里的步骤清单不能无限膨胀。任务执行到第 10 步时,前面 8 步的细枝末节往往已经不重要了。这时候应该做一次压缩:把旧步骤合并成一个“已完成阶段摘要”,保留目标字段和当前步骤。

覆盖策略则更关键。当新信息推翻旧结论时,不要只追加一条新结论,而要同时把旧结论标记为 invalid。否则旧结论还在工作记忆或长期记忆里,下一次检索可能又把它捞出来。

def invalidate_old_conclusion(mem: WorkingMemory, old_id: str, new_conclusion: str): # 将旧结论标记为已推翻,并写入新结论 mem.conclusions[old_id]["status"] = "invalid" mem.conclusions[old_id]["superseded_by"] = new_conclusion

这一步是很多人忽略的。多数 Agent 的失败不是不知道新信息,而是新信息覆盖不了旧信息,导致模型在两个互相矛盾的结论之间摇摆。

4. 成功率怎么测:别让“看着能跑”骗了你

双记忆机制到底有没有提升,不能靠一两条 Demo 判断。得有一个能重复执行的评测流程。

4.1 先定义什么叫“任务成功”

这里最容易犯的错是把“最后有输出”当作成功。对于一个长程任务,输出内容只是最低标准。真正判断成功要看四个维度:

维度判断标准
目标达成最终结果是否满足任务明确提出的交付目标
约束保留用户早期的约束(语言、格式、禁用项)是否全程未被破坏
中间步骤合法工具调用是否按计划执行,是否跳过关键校验
输出可执行结果是代码时能否运行,是文档时是否结构完整

我建议每条测试任务都写清楚“通过条件”和“部分通过条件”。比如调研类任务,通过条件是“结论覆盖全部 5 个必答问题且引用来源正确”,部分通过是“覆盖 3 个以上但不超过 4 个”。有了这个口径,统计才有意义。

4.2 测试集怎么搭

测试集不需要很大,但要有区分度。我一般用 30 到 50 条任务,覆盖三个难度段:3 到 5 步的短任务,6 到 10 步的中等任务,10 到 15 步的长任务。每类至少 10 条。

任务类型也要混合。纯多工具调用、检索加报告生成、代码生成加自测校验、表格处理加多轮修正,这几类正好对应长程 Agent 最常见的落地场景。

关键的一点:每条任务要有明确的输入和必达目标,还要写禁止行为。比如“不得删除原有字段”“必须用中文输出”“最终答案不得超过 800 字”。这些禁止行为专门用来测试早期约束是否被保留。

4.3 对比口径要单一变量

测 Recuris 双记忆机制的效果时,对照组和实验组之间只能有一个变量:记忆模块开还是关。其他东西必须保持一致。

  • 同一个基础模型
  • 同一套 Prompt 模板
  • 同样的工具集和重试规则
  • 同样的任务顺序和随机种子

如果同时换了模型或者改了 Prompt,最后数据不好看时你根本不知道是谁的问题。

统计口径我建议这样:每条任务跑 3 次,最终成功率 = 跑通的任务数 / 总任务数。同时记录每步的平均耗时、最大 token 数、任务平均步数。后两个指标能反映记忆机制带来的成本变化,不能只看成功率。

4.4 失败建模:失败本身也要分类

评测的目的不只是给一个成功率,还要解释成功率为什么变化。我一般把失败归成几类:

  • 约束遗忘:早期约束在后期被破坏
  • 规划漂移:中间结果把计划带偏
  • 工具解析失败:工具返回格式无法处理
  • 记忆污染:长期记忆提供了错误结论

对照实验做完以后,如果记忆污染类失败反而增加了,说明长期记忆的写入门槛太低;如果约束遗忘减少了,说明工作记忆里的 constraints 字段起了作用。通过这种归因,你能知道下一步该调什么,而不是盲目加参数。

5. 落地双记忆机制时常见的坑和排查链路

5.1 记忆污染:长期记忆里存了错误结论

这是双记忆机制最容易踩的坑。失败任务的中间结果被写入长期记忆,下一次检索时模型把错误结论当成经验,然后重复同一个错误。

排查顺序:先看长期记忆里有没有“来源任务失败”的记录,再看写入代码里有没有 success_flag 过滤,最后看用户手动确认机制是否启用。我建议初始阶段只允许成功任务和用户明确反馈写入长期记忆,失败任务最多存一条失败原因摘要,不存具体结论。

5.2 工作记忆膨胀:什么都记等于没记

如果你把每一步的完整输出都塞进工作记忆,工作记忆很快就会变成第二个完整历史。模型要读的内容没有变少,反而多了一层结构。

排查方法:打印工作记忆的 token 数,看看任务中期是不是还在线性增长。如果增长太快,就要把老步骤压缩成摘要,只保留目标、当前步骤、关键约束和最近一步结果。工作记忆的内容要少到模型在一屏之内能扫完。

5.3 检索命中质量差:相关记忆没有进入 Prompt

现象是模型明明有长期记忆,但回答时完全没用到。这时不用急着怀疑模型,先查检索。

排查链路:

  1. 直接把当前查询拿去检索,看召回的内容是否相关
  2. 如果召回不相关,检查 embedding 模型和任务语言的匹配度
  3. 如果召回相关但模型没用,检查注入 Prompt 的位置和格式
  4. 如果注入位置没问题,检查是否被 token 截断
  5. 最后看 top_k 和相似度阈值,太小会漏,太大会淹

还有一个容易被忽略的点:检索查询本身应该来自工作记忆里的当前步骤,而不是把整个任务历史作为查询。当前步骤信息越具体,检索质量越高。

5.4 更新顺序错误导致脏状态

如果你发现 Agent 有时候会返回旧的结果,或者明明上一步已经完成下一步却还在重做,大概率是更新顺序错了。

正确的顺序是:执行工具调用 → 解析工具结果 → 更新工作记忆 → 按需写入长期记忆 → 组装下一次 Prompt。任何把写回放在解析之前的做法,都会让状态落后一步。我排查时会直接在更新函数里加日志,打印“解析前状态”和“解析后状态”,对比一眼就能看出是不是顺序问题。

5.5 边界:什么时候双记忆机制不是最优解

双记忆不是银弹,有些场景上它并不划算。

1 到 3 步的短任务,上下文本来就小,增加记忆模块只会提高复杂度和延迟,成功率提升有限。单轮工具调用也不需要用双记忆,直接把返回结果传给模型就行。如果你的任务只有 5 步以内,先别急着上双记忆,把基础 Prompt 和工具结果处理做好更实际。

另外,双记忆机制对模型的指令遵循能力有一定要求。模型必须能稳定输出结构化状态更新,如果在状态更新这一步就频繁出错,记忆模块反而会成为新故障源。遇到这种情况,先解决模型输出的结构化问题,再谈记忆架构。

我个人更建议分阶段落地:先跑通单任务,再测 10 条长任务,确认成功率有提升、成本在可接受范围后,再扩展批量场景。双记忆机制真正值钱的地方不是一次 Demo 跑得多漂亮,而是它在连续多轮、多任务、多约束的条件下,还能把该记住的记住,该忘掉的忘掉。要做到这一点,靠的不是一次性设计,而是持续观察记忆的写入质量、检索命中率和失败归因数据。把这些数据盯住了,双记忆改造才算真正落地。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/30 11:17:28

京东实习生笔试真题解析:技术岗核心考点与备考策略

先说结论:这份“京东2016实习生招聘笔试真题-技术岗位选择题B”,放在今天看,依然是一份非常值得反复琢磨的复习提纲。题目年份虽然早,但大厂实习生笔试的考察逻辑并没有发生本质变化:数据结构与算法是绝对核心&#xf…

作者头像 李华
网站建设 2026/8/30 11:15:55

基于DEAP数据集的情绪识别:从数据获取到模型构建全流程实战

简介:本资源是面向人工智能与情感计算方向研究者、高校师生及情绪识别初学者的DEAP数据集情绪分类实践项目,聚焦生理信号驱动的情绪识别建模与实验复现。压缩包共38个文件,含28个Java源码(实现特征提取、SVM/随机森林分类器、交叉…

作者头像 李华
网站建设 2026/8/30 11:12:49

Tolaria 表格公式教程:跨笔记单元格引用 [[note]].B5 完全指南

Tolaria 表格公式教程:跨笔记单元格引用 [[note]].B5 完全指南 【免费下载链接】tolaria Desktop app to manage markdown knowledge bases 项目地址: https://gitcode.com/GitHub_Trending/to/tolaria Tolaria 是一款把 Markdown 知识库管理得井井有条的桌面…

作者头像 李华
网站建设 2026/8/30 11:12:22

DevOps面试指南:核心概念、工具链与CI/CD实战解析

准备 DevOps 相关岗位面试时,我经历过一段低效的阶段:每天刷面试题、背命令,可面试官换一个业务场景来问,答案就变得支离破碎。原因其实不复杂,DevOps 知识体系太宽,工具链横跨代码管理、构建、部署、容器、…

作者头像 李华
网站建设 2026/8/30 11:11:57

在Julia中实现Better Gaussian Splatting:原理、优化与实践

先用一句话说结论:Gaussian Splatting 这类三维场景表达方法,最近在重建、渲染、虚拟拍摄领域都非常活跃,而 Julia 刚好适合把它的数据结构和迭代流程做到既清晰又高效。这篇博客就围绕 Better Gaussian Splatting in Julia 这个方向&#xf…

作者头像 李华