Agent论文和工业界实战总结(3)|Agent 记忆、上下文、Skills 与 Harness:经验如何被保存、调用和更新
最近在复盘 Agent 相关论文和工程落地时,我发现一个有意思的现象:大家聊 Agent 框架时,张口闭口都是规划(Planning)、工具调用(Tool Use)、多智能体协作(Multi-Agent),但真正决定 Agent 能不能在真实业务里长期跑下去的核心,反而被放在最后讨论——记忆(Memory)、上下文(Context)、技能(Skills)和 Harness 这四层东西。
为什么这么说?因为短期 Demo 谁都能做,但一个能连续跑几周、经历几十上百轮任务、还能越用越顺手的 Agent,靠的就是这四层。这篇文章是"Agent 论文和工业界实战总结"系列的第三篇,我把自己在论文里读到的东西和实际工程里踩过的坑混在一起聊聊:经验到底怎么被保存、怎么被调用、又是怎么被更新的。适合正在做 Agent 应用开发、或者想从"会调 API"进阶到"懂 Agent 架构"的读者。
先说结论:记忆不是简单的"历史消息拼接",上下文不是"窗口越大越好",Skills 不只是"给模型写提示词",Harness 更不是"一个调度器"那么简单。这四者是一个完整闭环,缺一环,Agent 就是个一次性玩具。
1. 先拆清楚:记忆、上下文、Skills、Harness 各管哪一段
1.1 四者的分工与边界
很多人在读 Agent 相关论文的时候,容易被各种术语绕晕。我自己的理解方式,是把 Agent 的运行过程类比成一个新员工入职后的工作状态:
- 上下文(Context):相当于员工当前办公桌上摆着的资料。只看得到眼前这一摊,做完一单就清理一单。对应到 Agent,就是当前这一轮任务里,喂给大模型的全部信息——系统提示词、历史对话、工具返回结果、中间推理过程。
- 记忆(Memory):相当于员工脑子里的长期积累。上个月处理过的类似问题、踩过的坑、老板交代过的偏好,都存在脑子里。对应到 Agent,就是跨任务、跨会话保存下来的经验信息,可能存成向量库、数据库或者结构化文件。
- Skills(技能):相当于员工沉淀下来的操作手册。比如"处理发票报销的流程是A-B-C",或者"遇到某类报错先检查哪里"。对应到 Agent,就是一段可复用的指令、脚本或工具封装,让模型不用每次从零推理一遍。
- Harness:相当于公司的管理制度和工位环境。员工怎么领任务、怎么交成果、出了错找谁汇报、手里有哪些系统可以用,这些不是员工自己决定的,而是公司给的框架。对应到 Agent,就是承接模型、调度工具、管理会话、注入上下文的那套外围骨架。
我会用这个比喻贯穿全文。先记住这四层,后面所有的论文结论和工程实践,其实都是在细化这四层各自该怎么做。
1.2 为什么这个阶段要重新审视"记忆"这件事
早期 Agent 论文(比如 ReAct、BabyAGI 那一批)对记忆的处理非常粗暴:把对话历史拼一起塞进上下文窗口,窗口不够就截断最老的消息。这在玩具场景够用,但一旦到了工业界,你会发现几个致命问题:
- 任务周期长,跨天的会话累积下来,历史消息轻松超过窗口上限;
- 用户的偏好和习惯是长期稳定的,不该每次从零学;
- 同一个 Agent 可能服务多用户多项目,记忆不做隔离就是灾难;
- 经验的价值在于"踩过的坑不再踩",而这需要记忆具备更新和遗忘机制。
所以这一轮 Agent 的论文和工程实践,大家都在认真回答一个问题:经验到底以什么形式存、按什么策略取、靠什么机制更新。这篇文章就从这三个动作展开。
2. 记忆:从"拼历史消息"到"多层记忆架构"
2.1 短期记忆、长期记忆与工作记忆的分层
认知科学里把人的记忆分成感觉记忆、工作记忆和长期记忆。Agent 的论文最近也开始借用这套分层,但叫法略有不同。综合我读到的论文和开源框架的落地实现,工业界比较收敛的分法是三层:
短期记忆(Short-term Memory):就是当前会话内的上下文,通常直接存模型输入里。这层的核心约束是窗口长度,后面专门讲。很多中文社区讨论里说的"上下文超长""1M 上下文",其实讨论的就是这层。
长期记忆(Long-term Memory):跨会话保存的事实性知识和经验,比如用户资料、项目背景、历史决策记录。存储介质一般是向量库(如 Chroma、Milvus、FAISS)+ 结构化数据库的组合。读取时通过检索把相关片段拉回短期记忆。
工作记忆(Working Memory):这个提法最近在 Agent 论文里出现频率很高。它指的是"当前任务正在处理中的中间状态"——比如已经完成了一半的多步任务、正在等待的工具结果。很多框架直接用消息列表 + 状态变量来模拟,但更严谨的做法是单独维护一个"任务状态追踪器"。
我在实际工程里的体会是:三层必须物理隔离。如果把长期记忆和历史消息放同一个存储,检索的噪声会大到你怀疑人生。我见过一个项目把用户闲聊和关键决策全塞一个向量库,结果 Agent 在回答技术问题时突然引用一句用户昨天的吐槽,场面非常尴尬。
2.2 记忆的写入:什么值得记,什么不值得记
"什么值得记"这个问题,论文里讨论得不多,但工业界踩坑踩得最多。我给大家一个比较实用的写入过滤清单:
- 值得记:用户明确的偏好声明("以后报告用中文")、任务的关键决策和结果("上周选择了方案B,因为成本原因")、错误及修正记录("这个接口超时,下次先重试一次")、跨会话复用的实体信息(客户名称、环境地址)。
- 不值得记:临时对话的寒暄、已经被修正过的中间错误(除非有学习价值)、随时间快速失效的信息(今天的临时验证码)、单次任务的中间过程(除非任务要跨会话续跑)。
写入这块还有一个容易忽略的点:记忆写入本身要有去重和版本控制。很多团队直接往向量库 insert,跑两周后查出几百条互相矛盾的记忆,比如"用户喜欢简短回答"和"用户喜欢详细回答"同时存在。我的做法是写入前先检索一遍同主题已有记忆,做一次合并或替换,这个逻辑放在记忆服务层,而不是让模型每次自己判断。
2.3 记忆的调用:检索策略决定记忆质量
调用的核心是检索。工业界主流方案是"混合检索 + 重排序":
- 第一层:向量相似度检索,把用户当前的问题(或者最近的对话摘要)向量化,召回 Top 50;
- 第二层:关键词/全文检索,把包含准确实体词(比如项目名、报错码)的记忆捞出来,因为向量检索对精确匹配不敏感;
- 第三层:重排序(Rerank),把前两步的结果合并,用一个小模型或 LLM 按相关性重新打分,取 Top 5~10 注入上下文。
为什么不能只靠向量检索?因为很多记忆是"当时有用但表述模糊"的文本,比如"那个接口偶尔会慢"。如果你后来遇到了"接口慢"的问题,向量检索能用相关词召回;但如果是"报错码 504",你就需要精确关键词命中。混合检索的召回率明显更高。
另外,调用记忆还有一个时效性问题:不同记忆的"新鲜度"权重应该不同。比如用户的地址信息,三个月前的记忆不如三天前可靠。所以检索打分时,我会给记忆加一个时间衰减因子,距离当前时间越近,分数加成越高。这个在论文里叫 recency bias,实测下来对用户体验的提升非常明显。
2.4 记忆的更新:冲突消解与遗忘机制
记忆更新是最容易被忽视、但也是最影响长期体验的一环。几种典型场景:
场景一:新旧记忆冲突。用户先说"我喜欢简短回答",后来又说"这个报告麻烦写详细点"。如果你不做冲突消解,两条记忆都会进库,检索时模型直接精神分裂。我的处理方式是在写入时增加一个"覆写"流程:检索到同主题已有记忆时,不是新增,而是将旧记忆标记为过期(或直接替换),并记录更新时间。
场景二:记忆过时。用户的服务器 IP 换了、项目的技术栈换了,旧记忆还在库里。单纯靠时间衰减还不够,我建议对记忆做"置信度 + 最后确认时间"双字段管理,定期(比如每周)让 Agent 在对话中自然确认一次关键信息,确认后更新置信度。
场景三:遗忘。之前热议的"创伤记忆"概念,其实是从心理学借来的——某些失败经验如果被记成"这个操作绝对不行",反而会过度限制 Agent。工业界的对应问题是:一条失败记忆是否应该永久保留?我的答案是:失败记忆要记录,但必须同时记录"失败条件"和"当时环境",否则 Agent 会因噎废食。比如"调用X接口失败"和"在流量高峰时段调用X接口失败"是完全不同的经验,后者才是有价值的记忆。
提示:记忆更新一定要有审计日志。生产环境里记忆库被写坏、被污染的情况我遇到过不止一次,没有审计日志你根本查不出是哪一轮对话把脏数据写进去的。
3. 上下文:窗口是资源,上下文是工程
3.1 上下文窗口用完了怎么办
这是中文社区里被问爆的问题:"大模型上下文窗口用完了怎么办?""1M 上下文是什么意思?"
先说 1M 上下文(比如 Gemini 系列和某些国产模型宣称的超长窗口)。它指的是模型输入侧能容纳的 token 总量,大约相当于一本《三体》三部曲的体量。看起来很大,但对 Agent 来说,1M 只是一个"更好用的客厅",不是"无限储物间"。原因有三:
- 计算成本随长度增加而上涨(哪怕注意力机制做了优化,长上下文的处理时间也明显更慢);
- 模型对超长上下文中"中段"信息的注意力覆盖能力仍然有限,学术上叫"lost in the middle"——你塞 50 万字进去,模型往往只记得开头和结尾;
- 每次请求都把全量历史带上,延迟和费用在工业界不可接受。
所以我的核心建议是:不要幻想扩大窗口解决问题,而是要把上下文当成一个需要持续清理和组织的"工作台"。
3.2 上下文工程的三个核心动作:裁剪、压缩、结构化
我自己在工程里把上下文管理拆成三个动作:
裁剪(Truncation):不是简单砍掉最老的对话,而是按消息类型分优先级。系统提示词 > 最近的用户指令 > 工具返回的关键结果 > 中间推理过程 > 最老的寒暄。砍的时候从低优先级开始砍。很多框架的默认实现是纯按时间砍,这是不对的——你可能把用户五分钟前说的重要需求砍了,却留着一小时前的闲聊。
压缩(Summarization):当历史消息超过阈值(比如总 token 的 60%),触发一次自动摘要,把旧消息压缩成要点,放进上下文的开头作为"记忆锚点"。这个摘要可以由便宜的小模型来做,关键是要把"决策、偏好、待办"这三类信息保出来。
结构化(Structuring):把上下文从"一串聊天记录"改造成"结构化区块"。我会把上下文组织成:任务目标区、用户画像区、历史决策区、工具说明区、当前对话区。模型对这种分块输入的利用率,明显高于糊成一团的纯文本流。
注意:上下文压缩不是"一次性摘要完就完事"。工业界更稳妥的做法是层级压缩——先按 10 轮对话一组做摘要,再对摘要做摘要,保留一个摘要树。需要回溯细节时,再按摘要树的索引去翻原始记录。
3.3 上下文数据流:从输入到工具结果的流转闭环
"上下文数据流图的分解"是最近搜索热度很高的词。说白了,就是要搞明白信息在 Agent 内部是怎么流转的。我这里给一个最简版本的流转图(文字描述):
用户提问 → 检索长期记忆 → 组装上下文(系统提示 + 记忆片段 + 历史摘要 + 当前问题) → 模型推理 → 决定调用工具 → 工具结果回填上下文 → 再次组装(此时上下文已增长) → 模型继续推理 → 循环直到产出最终回复 → 提取值得写入记忆的信息 → 写入/更新记忆库。
这个流转里最常见的 bug 是:工具的结果没有做"收敛"就塞回上下文。比如你让 Agent 查询一个数据库,返回了 200 行记录,直接全塞进上下文,下一轮模型根本找不着重点。正确做法是:让模型先对工具结果做一次"信息抽取",只把与当前任务相关的字段回填上下文。这一步能省一半 token。
另一个容易踩的坑是:上下文组装是每次推理前都要做的,不是只在任务开始时做一次。很多第一次写 Agent 的人,习惯把上下文当成一个固定字符串,结果工具调用三轮之后,模型已经忘了最初的任务目标。我建议每次进入模型前都重新组装一次上下文,确保任务目标始终在上下文的最高优先级位置。
4. Skills:把经验固化成可复用的"肌肉记忆"
4.1 Skills 的本质:从 Prompt 到技能包
搜索热词里频繁出现"Skills""agent skills""codex skills""前端开发 skills",说明这个概念的讨论已经从论文圈扩散到了开发者群体。那 Skills 到底是什么?
我的理解是:Skills 是一段结构化的、可复用的操作指引,它让模型不必从零推理一个已经成熟的流程。打个比方,没有 Skills 的 Agent 像一个每次都要重新读一遍说明书的新手;有 Skills 的 Agent 像一支形成肌肉记忆的球队——战术已经训练好了,场上只需要执行和微调。
一个标准的 Skills 文件通常包含:
- 触发条件:什么场景下应该使用这个技能;
- 执行步骤:分步的操作流程,每步需要什么输入、产出什么结果;
- 前置检查:开始前确认哪些前置条件(比如环境变量、权限);
- 常见问题:这个流程里最容易出错的地方和对应的处理方式;
- 工具依赖:需要调用哪些外部工具或脚本。
为什么 Skills 比"写死提示词"更好?因为提示词是给模型看的自然语言,而 Skills 是一种可以被组织、检索、版本化管理的资产。你可以为一个 Skills 写测试用例、做 A/B 验证、灰度上线,这些都是提示词做不到的。我在实践中的体会是:把业务里高频操作沉淀成 Skills,是 Agent 从"能跑"走向"好用"的关键一步。
4.2 如何设计一个高质量 Skills
前端开发 Skills 最近被反复讨论,我拿这个举例。假设你希望 Agent 能完成"前端组件开发"这件事,一个质量差的 Skills 可能是:
"当你需要开发前端组件时,请遵循公司规范。"
这种废话式 Skills 等于没有。一个好的 Skills 应该具体到:
- 先检查项目里是否有组件库(检查 package.json);
- 如果有,优先复用组件库已有的基础组件;
- 新建组件时必须包含:props 类型定义、样式文件、单元测试;
- 组件命名遵循 PascalCase,文件放在 src/components 下;
- 完成后运行 npm run test 确认测试通过;
- 如果遇到样式冲突,先检查全局样式变量而非硬编码。
看到区别了吗?高质量的 Skills 具备三个特征:可执行、可检查、可容错。"可容错"指的是包含异常处理路径(比如第 6 条),这恰恰是很多团队写 Skills 时漏掉的部分。
另外一个关键点:Skills 不要写得过长。模型对超长指令的遵循度会衰减,一个 Skills 最好控制在 600 字以内,能用表格和列表绝不用大段散文。如果流程实在复杂,拆成多个 Skills 并建立调用链,而不是写一个"史诗级"文档。
4.3 Skills 的调用机制与更新节奏
Skills 的调用一般有两种方式:
- 显式调用:模型通过工具调用的方式主动加载某个 Skills 文件(比如 Anthropic 的 Claude Skills 模式)。这种方式适合"低频但重要"的技能,比如"部署上线流程"。
- 隐式注入:把部分常用 Skills 直接放在系统提示词里,模型不需要"调用"就能遵循。适合"高频且简短"的技能,比如"代码风格规范"。
两种方式可以混用。我见过一个不错的配置:高频小技能放系统提示,中频技能做成工具,低频复杂技能走显式加载。
Skills 的更新节奏,论文里提得少,但工程上很关键。我的建议是:每个 Skills 都挂一个版本号和一个"失效检测器"。失效检测器的逻辑很简单:当这个 Skills 被执行后,产出结果连续多次被用户纠正或退回,就自动降低它的使用优先级,并标记给开发者人工审查。这个机制能防止 Skills 变成"过时的教条"。
5. Harness:把四层串起来的骨架
5.1 Harness 到底是什么,和 Agent 有什么区别
"harness"这个词在中文社区讨论度上升得很快,很多人在问"harness 和 agent 区别"。其实 Harness 不是一个独立的 Agent 类型,而是 Agent 的运行骨架——它负责把大模型、工具、记忆、Skills、上下文管理、安全策略全部串起来。可以这么理解:
- Agent是"大脑 + 手脚",负责思考和行动;
- Harness是"身体 + 神经系统",负责支撑大脑和手脚协同工作的所有外围机制。
一个 Harness 至少包含:模型调用层(兼容多模型接入)、工具注册与调度层、上下文管理模块、记忆读写模块、Skills 加载模块、安全与权限控制、日志追踪系统。市面上开源的 Agent 框架(LangChain、LlamaIndex、Dify、Coze 等)核心做的其实就是一个 Harness,只是各自侧重点不同。
为什么 Harness 值得单独讨论?因为当 Agent 从 Demo 走向生产,你会发现自己花在模型 prompt 上的时间越来越少,花在 Harness 上的时间越来越多:怎么管理会话状态、怎么做工具调用的超时重试、怎么隔离不同用户的数据、怎么审计 Agent 的每一步行为。这套东西做不好,模型再强也白搭。
5.2 Harness 的核心模块:一个工程化的最小实现
我自己在项目中搭 Harness 时,最忌讳把逻辑写死在主流程里。一个清爽的架构应该是分层解耦的,至少分四层:
接口层:接收用户请求,做鉴权和参数校验,然后交给编排层。
编排层:负责 Agent 的主循环。判断当前状态是"需要模型推理"还是"需要执行工具";调用模型后解析输出,如果是工具调用,就去工具层找对应工具;如果是最终回复,就走输出通道。这个循环是 Harness 的心脏。
能力层:把记忆、Skills、工具、上下文管理各做成独立模块,提供清晰的接口给编排层调用。比如记忆模块提供retrieve(query)、store(item)、update(item_id, new_content),编排层不关心底层是向量库还是 MySQL。
基础设施层:日志、监控、限流、安全审计。这部分我强烈建议从第一天就做,不要等出事了再补。Agent 的不可控性比常规后端服务高一个量级,没有日志,你连排查问题的入口都没有。
5.3 Harness 的安全与权限:不能忽略的一层
搜索热词里有 "agent安全",这个话题在 Harness 的语境下尤其重要。因为 Agent 能调用工具,意味着它能执行命令、改文件、发请求。一个没有权限控制的 Harness,等于把生产环境的钥匙交给了模型。
我的安全实践经验有三条:
- 工具级别的白名单:不是所有工具都对 Agent 开放,尤其是删除类、支付类、外发类操作,必须显式配置。有些工具允许 Agent 调用,但需人工审批后才能执行(human-in-the-loop)。
- 上下文注入隔离:如果同一个 Harness 服务多个用户/项目,记忆和上下文必须按命名空间隔离。我在之前的项目中就吃过亏:用户A的项目信息串到了用户B的会话里,排查半天发现是向量库检索没加过滤条件。
- 操作审计:Agent 的每一步工具调用(参数、结果、耗时)都要落日志。出问题时,审计日志是你和业务方沟通的唯一凭证。
6. 工程落地中的常见问题与排坑实录
6.1 记忆库污染与检索混乱
现象:Agent 回答问题时突然引用毫不相关的历史记忆,或者同一主题出现互相矛盾的记忆。
排查思路:先查写入链路——是不是有并发写入导致去重逻辑失效?再看检索链路——向量检索的 embedding 模型是否更换过(换了模型,向量空间就变了,新旧向量无法对齐)?最后看过滤条件——检索时是否带了用户/项目维度的过滤。
我的做法:给每条记忆加namespace字段,所有检索强制带过滤;写入前做"同主题冲突检查",遇到冲突先标记旧记忆 pending-review,而不是直接写入新记忆。这个机制上线后,记忆污染问题减少了八成。
6.2 上下文超长导致延迟飙升
现象:Dify 工作流里上下文超长、大模型上下文窗口用完了,任务执行越来越慢,费用也越来越高。
排查思路:先看是哪些消息占了大头。我遇到过最离谱的情况是,工具返回了一张完整的网页 HTML,5 万字符直接进上下文。这类问题光靠"摘要"救不回来,要从源头控制——工具结果必须在入上下文前做裁剪和抽取。
我的做法:建议给 Harness 加一个"token 预算"机制。比如设定单次任务上下文上限 30k token,其中系统提示 5k、记忆 8k、工具结果 10k、对话历史 7k。某类内容超预算时,先走压缩流程,压缩后仍超的,降级为"仅保留要点"。这套机制让我的项目 token 消耗下降了 40%。
6.3 Skills 失效,Agent 开始"绕路"
现象:明明配置了 Skills,Agent 在某些场景却不按 Skills 执行,而是走一步看一步。
排查思路:先确认 Skills 的触发条件写没写清楚——很多失效案例是"触发条件"跟实际场景匹配不上。再看注入位置——如果 Skills 内容被上下文压缩机制挤出去了,模型当然看不到。最后看模型类型——不同模型对复杂指令的遵循能力差异很大,同一个 Skills 在 A 模型上执行得很好,换 B 模型就崩了。
我的做法:Skills 上线前做一次"遵循度测试":准备 10 个典型场景,让 Agent 按 Skills 执行,统计执行成功率。通过标准是 90% 以上。另外运行时给 Skills 执行结果加追踪日志,每步都记录,"绕路"时能快速定位是哪个环节没生效。
6.4 记忆与上下文之间的"循环打架"
现象:Agent 把"当前对话里已经说过的事"又去记忆里检索了一遍,导致上下文里出现重复信息,有时还因为记忆版本更旧而纠正了用户刚说的新信息。
排查思路:这个问题的本质是记忆检索和上下文组装没有做"信息重叠检测"。检索回的长期记忆,跟当前对话已有的信息冲突时,应该以当前对话为准。
我的做法:在记忆注入前加一层"新鲜度裁决"——凡是当前对话中有明确表述且时间更近的信息,记忆检索结果直接丢弃对应片段;只有当前对话未覆盖的信息才允许注入。这个逻辑写成一个独立模块后,上下文里的矛盾信息几乎消失了。
7. 一些我个人沉淀下来的实操心得
回头看这轮 Agent 论文和实践总结,我最想分享的体会是:记忆、上下文、Skills、Harness 不是四个独立模块,而是一个互相咬合的闭环。记忆要靠 Harness 管理,检索结果要经上下文工程裁剪,Skills 要挂在 Harness 的调用链上,上下文状态又要反馈给记忆更新逻辑。任何一环偷懒,整体就会出问题。
最后分享两个小技巧,都是我在实际项目中验证过的:
技巧一:给记忆和 Skills 都加上"最后使用时间"字段。这个字段太有用了。可以定期找出"长期没被检索到的记忆"和"长期没被加载的 Skills",凑成一份"僵尸资产清单"给业务方人工确认——该删的删,该更新的更新。这比任何复杂的算法都能更直接地保持记忆库的卫生。
技巧二:上下文管理一定要做"可观测"。不要等到 Agent 回答质量下降才去猜上下文出了什么问题,而是做成一个可视化面板,实时展示当前上下文的组成(系统提示占比、记忆占比、工具结果占比、对话历史占比)。轮数多了之后你会发现,很多问题光看这个柱状图就能定位。我在项目里加了这套后,排查效率至少提高一倍。