
最近跑 Agent 多步任务时我一直被同一个问题卡住模型能力没问题工具也能正常调用但任务跑着跑着前面已经确认过的信息会突然“丢失”。先是反复追问用户后来开始自己编一个看起来合理的答案整个流程就偏掉了。顺着这个问题去查 Agent 记忆相关的方案时看到了上海AI Lab团队推出的 MemHarness。这个项目名字里的 harness 很关键它不是一个简单的外挂存储插件而是把 Agent 的记忆当成一套完整运行机制来重构。这篇文章就围绕这类记忆框架要解决的问题、背后的设计思路、怎么在本地项目中复现类似机制以及实测时容易踩的坑展开尽量写得能当落地参考用。先说结论如果你只做单轮问答或者短流程工具调用默认的上下文拼接已经够用不需要专门引入记忆层。但只要你开始做多步骤任务、跨会话任务、需要持续跟踪用户偏好的 Agent记忆就变成瓶颈。MemHarness 这类方案值得重点关注的方向不是“多存一点历史消息”而是把记忆分层、分生命周期、可检索、可更新、可淘汰。下面按实际落地顺序拆一遍。1. 先理解 Agent 记忆问题为什么同一个模型会“聊着聊着就忘了”1.1 Agent 不是模型本身而是模型加状态加工具的组合很多新手容易把“Agent 没有记忆”理解成“模型不行”。真实情况是模型本身只负责推理它拿到什么输入就基于什么输入生成输出。你问一个模型“我刚才让你记住的订单号是多少”如果这句话不在当前输入里模型不可能知道也不会因为同一个会话ID就自动恢复。Agent 之所以比普通对话复杂是因为它多了几个东西外部工具、执行循环、状态管理。所谓“记忆”本质上是 Agent 在执行过程中维护的一组跨步骤状态。这一步得到的结果下一步要能继续使用用户第一次说的偏好后面每次决策都要能取回来某个分支任务失败了主任务的目标不能因此丢失。所以给 Agent 加记忆不是在模型层做改造而是在 Agent 的运行框架里加一层状态管理机制。1.2 对话窗口是内存不是记忆很多人把“把历史对话全部塞进上下文窗口”当成记忆方案。窗口够大的时候这个办法确实能解决一部分问题比如长文本摘要、连续问答。但它的本质是内存不是记忆。内存的特点是随任务结束被清空随上下文长度增加而变贵随新旧信息混在一起而变乱。一个长任务跑下来中间步骤的记录如果全塞进上下文很快会超过窗口就算没有超过早期的关键信息也会被大量中间日志稀释。另一个问题是成本。每轮调用都要把全部历史重新发送给模型Token 消耗会随步数线性增长。跑得越久单次调用越贵响应也越来越慢。所以记忆系统要解决的问题不是能不能装下而是怎么在装不下的情况下仍然让关键信息在需要的时候出现在合适的位置。1.3 记忆缺失会引发幻觉、重复、漂移记忆缺失最直接的表现就是幻觉。模型在缺失上下文时不会主动说“我不记得了”而是会用概率补全一个看起来合理的答案。这就是 Agent 场景里“一本正经地胡说八道”的一个重要来源。你以为模型在推理其实它只是在做语言概率填充。除了幻觉还有两类问题也很常见一类是重复模型在同一个任务里反复问同一个问题或者反复执行同一个工具调用因为状态没有保留下来另一类是主题漂移原本要完成 A 目标执行几步之后模型自己把目标微调成了 B或者把用户之前明确否定的方案又重新提出来。这三类问题表面上看是模型能力问题实际上是记忆机制缺失造成的工程问题。所以我建议在排查 Agent 效果时遇到幻觉、重复、漂移先不要急着换模型或者调提示词先检查记忆链路。2. MemHarness 的核心思路把记忆从“拼接上下文”变成“分层状态”2.1 记忆重构的本质是分层从公开资料和项目命名来看MemHarness 这类框架最值得借鉴的思路是把 Agent 记忆当成一个分层系统而不是一个“大袋子”。人类记忆大致可以分成工作记忆、长期记忆、情景记忆。Agent 侧可以对应成三层短期运行状态、关键事实、任务场景记录。短期运行状态当前任务目标、当前已经执行的步骤、下一步计划、最近一次工具返回结果。这层对应的是“现在正在做什么”。关键事实用户偏好、项目约束、业务规则、已经确认过的结论。这层对应的是“这件事长期不变的部分”。任务场景记录过去某个任务是怎么完成的、遇到过什么异常、最终怎么处理的。这层对应的是“经验”。这三层信息的使用频率和更新频率完全不同。短期运行状态每步都在变关键事实只在确认或修改时更新任务场景记录在任务结束后写入平时按需检索。把这三层混在一起存就是大部分 Agent 记忆混乱的根源。2.2 记忆的生命周期写入、读取、更新、淘汰MemHarness 这类框架真正花心思的地方不是存储而是记忆的生命周期管理。写入阶段要回答一个问题这句话值不值得记住。不是所有对话内容都要写进记忆。用户随口说的“今天天气不错”和“我只用中国移动号码”价值完全不同。前者是临时寒暄后者是长期约束。写入时应该有筛选逻辑或者至少给记忆打上重要度标签。读取阶段要回答这次决策需要什么信息。每轮任务开始前Agent 应该去记忆库检索与当前任务相关的记录而不是把所有记录一股脑注入上下文。更新阶段要回答旧记忆和新事实冲突怎么办。当用户说“我改主意了之前选的方案不要了”系统要把旧结论标记为过期而不是两条记忆同时存在让模型自己猜。淘汰阶段要回答哪些记忆已经没用了。用户的项目结束后这个项目的细节就不应该继续占据检索结果。如果只做“写入读取”那只是一个带搜索的聊天记录库。加上“更新淘汰”才是记忆系统。2.3 结构化记忆与检索策略另一个关键设计是记忆尽量结构化不要整段整段存对话流水。一条好的 Agent 记忆通常包含这些字段记忆主体、内容、类型、重要度、创建时间、最后更新时间、来源、版本、是否有效。比如“用户偏好”类型的记忆主体是用户内容是“支付方式使用企业账户”重要度是高频来源是第一轮对话。结构化的好处是检索和更新都方便。你可以按类型过滤比如当前任务只需要“业务规则”类记忆也可以按版本覆盖新确认的偏好直接替换旧记录还可以在排障时按来源追踪这条记忆是哪一轮写入的哪次修改过。检索策略上不要只看相关度。同一场景下关键任务状态应该优先于历史经验用户明确表达过的偏好应该优先于模型推断出来的画像最近更新过的记忆通常比很久没修改的记录更可靠。把这些优先级接进检索排序比单纯依赖向量相似度稳定得多。3. 在自己的 Agent 项目里复现类似记忆重构3.1 先给 Agent 加一个“记忆层”我不太建议一上来就引入复杂框架。先把记忆层拆成一个最小的管理器搞清楚数据流再逐步扩展。一个最小记忆管理器至少需要四块存储、写入、读取、更新。下面是一个简化版的思路用 Python 伪代码表示重点是数据组织方式不是具体实现。dataclass class MemoryRecord: memory_id: str agent_id: str memory_type: str # task_state / user_preference / scene_log content: str importance: int # 1-55 表示最高 source_turn: str # 来源轮次 created_at: str updated_at: str version: int # 每次更新自增 active: bool # 是否有效False 表示已淘汰或过期 class MemoryStore: def __init__(self): self.records {} self.index_by_type {} def write(self, record: MemoryRecord): self.records[record.memory_id] record self.index_by_type.setdefault(record.memory_type, []).append(record.memory_id) def update(self, memory_id: str, new_content: str): record self.records[memory_id] record.content new_content record.version 1 record.updated_at now() def deactivate(self, memory_id: str): self.records[memory_id].active False实际落地时存储这层可以用 SQLite、PostgreSQL 或者向量数据库但数据组织方式建议照这个思路来。先有记忆 ID再有内容再有类型和版本这比把大段文本直接塞进向量库更容易维护。3.2 设计记忆写入和更新的触发时机记忆系统最容易出问题的地方是不知道什么时候该写。写少了Agent 还是失忆写多了记忆库充满垃圾信息检索结果被噪声淹没。我的建议是先从四类触发时机入手。第一用户明确给出的关键事实。比如“我叫张三”“预算不超过十万”“部署环境是内网”。这类信息在对话中只要出现就应该写入或更新。第二工具返回的关键结果。比如查询订单接口返回了订单状态翻译任务返回了最终结果文件路径。工具调用结果如果不能沉淀到记忆里下一步 Agent 就还得重新调一次。第三任务状态的阶段判定。当一个子目标完成时要把状态从“进行中”改成“已完成”并记录完成结果。这是防止重复执行的关键。第四模型推理中出现的自洽结论。比如模型经过对比后说“方案 A 比方案 B 更适合当前场景”如果这不是临时推理而是会影响下一步决策的结论也应该保存。写入触发不能贪多。先实现第一类和第二类验证数据流稳定后再逐步加第三类和第四类。3.3 检索与注入怎么让记忆真正影响下一步行为保存下来的记忆要能影响模型行为才有效。这里的关键是注入方式。我一般不会把检索到的记忆直接混在对话历史的最前面或者最后面而是放在一个独立的“记忆块”里比如在 system prompt 中明确分成“系统指令”和“当前可用记忆”两部分。这样做的好处是模型知道这段内容是对当前决策有约束力的记忆而不是又一轮普通对话。【系统指令】 你是任务执行助手基于当前记忆和用户输入完成操作。 当记忆与用户当前输入冲突时以用户当前输入为准。 【当前可用记忆】 - 任务为电商客户生成月度运营报告 - 已完成完成数据提取共获得 12 个门店的 6 月销售数据 - 用户偏好报告正文使用中文图表标题使用中文 - 业务规则环比增长低于 5% 的门店需要单独标注 【用户当前输入】 继续生成报告重点关注环比下降的门店。这种方式有几个好处记忆块位置固定模型容易识别内容经过筛选不会把无关历史全部堆进去出现冲突时有明确规则用户当前输入优先级更高。3.4 不同方案的取舍向量库、结构化存储、混合记忆实现记忆检索时技术选型经常让人纠结。我给一个比较实际的对照你可以根据自己项目情况选。方案优点缺点适用场景纯对话历史拼接实现简单无额外依赖Token 成本高长任务会溢出检索能力为零单轮问答、短流程演示向量库语义检索能处理模糊查询支持自然语言召回需要 embedding相关度不等于有用度容易召回相似但不相关的记忆场景日志检索、知识库问答结构化记忆加规则查询精确、可控、易更新、易排查需要提前设计字段和筛选逻辑灵活度较低用户偏好、业务规则、任务状态混合记忆兼顾精确和灵活最接近人类记忆实现复杂度高需要维护两套存储和同步逻辑生产级 Agent、跨会话任务我个人的建议是先从结构化记忆开始。把任务状态、用户偏好、业务规则这三类用结构化方式存好跑通之后如果确实需要模糊召回历史经验再引入向量库。一开始就上向量库看起来高级但排障时你会很痛苦因为你不知道模型到底召回了哪条记忆。4. 评估记忆系统好不好不要只看“想起来”4.1 准确率、召回率、一致性评估 Agent 记忆比评估模型推理更工程化。不能只看“他好像想起来了”要看三类指标。第一是准确率。记忆内容本身是否准确。用户说的是“每周一上午开会”系统存成“每周一开会”就是准确率问题。这种错误会在写入阶段产生越到后面越难查。第二是召回率。该用的记忆有没有用上。任务要求用中文生成报告但 Agent 最后交了一版英文报告说明记忆没有被召回或者召回了但没有注入到决策链路里。第三是一致性。同一事实在多次检索中是否保持一致。用户说“把南京作为仓库”第一次执行时记得第二次执行时忘了第三次又记起来了这种不稳定比彻底遗忘更影响可靠性。建议设计一套测试用例故意构造 10 个需要长期记忆的多轮任务跑完后检查“关键事实是否在正确步骤被使用”“有没有被错误改写”“有没有和当前输入冲突”。不要只看最终输出对不对。4.2 记忆污染和遗忘曲线记忆系统运行一段时间后会遇到一个新问题记忆污染。污染来源有很多。模型在推理中产生的错误中间结论被写入记忆用户的一次口误被当成事实保存旧版本业务规则没有及时淘汰新规则和旧规则同时存在检索时把 A 项目的经验召回给 B 项目。污染一旦产生模型会在多个后续步骤里被带偏而且排查成本极高。要给“遗忘”设计一个机制。最简单的做法是每条记忆都有生命周期。定期任务扫描时重要度低、长时间未使用、已标记过期的记忆从活跃区移到归档区归档或直接删除。不是所有记忆都值得长期保存。框架在这里的设计思路值得参考的地方就是“记忆也要有版本和过期时间”而不是只增不减。4.3 安全性记忆里不能放什么Agent 记忆系统安全性的核心是权限和边界。记忆越全Agent 越智能但风险也越高。有几类信息我建议在架构上直接排除明文密码、密钥、Token、身份证号、银行卡号等敏感信息除非业务确实需要且做了脱敏和加密处理否则不要写进记忆。即使用户在对话中主动提了也应该在写入前做脱敏或拦截。另一个容易被忽略的点是多 Agent 之间的记忆隔离。同一个系统里如果有多个 Agent 或服务多个用户记忆必须按 agent_id 和 user_id 分域存储和检索。不然就会出现一个用户的任务状态被另一个用户看到或者 A 项目的规则被 B 项目继承。这类问题一旦发生往往是最严重的隐私事故。还有一层是提示注入的风险。当记忆内容来自外部输入比如网页内容、邮件内容、用户粘贴的文本这些内容可能包含恶意的 prompt 指令。如果原样写进记忆再在下一次任务中原样注入给模型相当于给了攻击者一个操控 Agent 的通道。建议在写入前做过滤和标记对来自不可信来源的内容保留“待确认”状态不直接作为高优先级记忆使用。5. 实测中的常见问题和排查顺序5.1 记忆没生效先查触发链路现象用户明确说了“之后都叫李经理”但下一轮 Agent 又开始问“您贵姓”。很多人第一反应是提示词写得不够好然后去调 system prompt。我建议先查触发链路按顺序排查用户这句话有没有触发写入逻辑。如果写入逻辑只在工具调用后触发用户主动提供的信息可能根本没被保存。写入时有没有正确设置 memory_type 和重要度。如果被当成临时信息存进场景日志后续任务按类型过滤时就检索不到。检索时有没有覆盖这个类型。很多实现里读取逻辑只查特定记忆类型写入和读取类型对不上就会出现“明明存了但拿不到”。这个问题的核心是数据流不是模型能力。可以先在记忆管理器的写入和读取接口处打日志看用户输入有没有经过写入看每次任务启动时读取了哪些记忆。5.2 输出还是瞎编先看检索结果现象记忆系统显示“已经存过订单金额”但模型最终输出里的金额和记忆里对不上。这种问题往往出在检索和注入环节。先打印出当前轮的检索结果看模型是否真的拿到了那条记忆。如果拿不到问题在召回策略或过滤条件如果拿到了但输出不对问题在注入方式比如模型把记忆看成普通对话建议而不是强约束条件。我见过最多的场景是召回数量限制导致的信息丢失。为了提高效率检索时只取 topK 条如果任务状态和用户偏好混在一起早期写入的关键规则很容易被后面的日志挤掉。解决方法是按记忆类型分开设置 topK并给高重要度记忆更高的优先级权重而不是所有记忆统一排序。5.3 记忆越跑越乱要查写入策略现象刚开始效果不错跑了几十条任务之后Agent 开始出现前后矛盾甚至把之前已经否定的方案重新提出来。这是记忆污染的前兆要重点查写入策略。打开记忆库看一遍大概率会发现三类问题重复写入同一事实存了多个版本冲突写入用户后来改主意了但旧记录没有失效噪声写入大量推理过程被当成了长期记忆。处理方式不复杂但需要坚持写入前加一步“查重和冲突检测”如果有同类型、同主体的记忆先更新已有记录而不是新建模型产生的推断类内容单独存放在“推断”区域不要和用户明确表达的事实混在一起定期清理低重要度记忆。宁可从源头少写一点也不要让污染产生后再清理。5.4 成本飙升要控制检索数量和上下文长度现象功能正常了但调用 Token 数量暴增跑一个长任务成本翻了好几倍。记忆系统的引入必然带来额外成本但可以控制。检查三个地方一是每轮任务检索了多少条记忆如果全部注入成本肯定高控制在 5 到 10 条高价值记忆通常足够二是每条记忆的长度如果存的是整段对话建议改成摘要或结构化字段三是记忆块的位置和重复度同一个 session 内不变的高优先级记忆可以只在任务状态变化时更新不需要每轮重新注入一遍。还有一点容易被忽略工具返回结果不要原样写进记忆。工具原始输出往往包含了大量无关字段写入前先做一个 extract只保留对后续决策有用的结果。这既能控成本也能降低记忆噪声。6. 落地建议适合学习也适合做原型但生产化要补的东西不少6.1 从单 Agent 到多 Agent 记忆隔离学习阶段可以只做一个单 Agent 记忆 demo比如给客服机器人加一个“记住用户偏好”的能力。但生产环境里记忆系统远比这个复杂。多 Agent 场景下每个子 Agent 应该有独立的工作记忆同时共享一个经过授权的共享记忆区域。比如任务规划 Agent 的工作记忆是当前任务计划执行 Agent 的工作记忆是当前步骤状态用户偏好则放在共享区。不能让两个 Agent 直接读写同一个记忆库否则执行 Agent 把中间状态覆盖到任务规划里整个流程就乱了。记忆隔离要从架构上做而不是靠模型自律。6.2 日志和审计记忆系统越复杂越要有日志。日志至少要覆盖几个关键节点什么内容被写入、为什么被写入、谁写的、什么内容被检索、检索结果给了哪个 Agent、哪条记忆被更新或淘汰。有了日志才能回答“模型为什么突然这样”。没有日志记忆系统出了问题只能靠猜。建议把记忆操作日志单独保存和普通运行日志分开方便在出问题时快速回溯。每次修改记忆时保留修改前的内容和修改后的内容。这样即使模型把记忆改坏也能回滚。6.3 版本迁移和重置策略记忆系统上线运行一段时间后必然会遇到升级和迁移问题。比如你换了向量库或者改了记忆字段结构旧数据怎么处理或者某个用户明确要求删除自己的历史记忆系统要能按用户维度做清空。建议在设计初期就支持记忆的导入导出和按 agent_id、user_id 删除。不要等到数据量大了再做到时候迁移成本会非常高。另外测试环境和生产环境的记忆要完全隔离。不要用测试数据污染生产记忆库否则会出现测试任务的状态被生产任务检索到的情况。踩过几次这类问题后我最大的感受是Agent 记忆不是一个“加一个数据库”的功能而是一整套和 Agent 循环深度绑定的状态管理机制。MemHarness 这类框架带来的更多是思路上的参考具体怎么落地还是要看你的使用场景、任务类型和数据规模。如果只是学习建议先做一个最小的结构化记忆 demo跑通“写入 — 检索 — 注入 — 更新”这条链路再往里面加语义检索和复杂场景。先把单任务跑稳再考虑批量和多 Agent这个顺序不会错。