第 6 章 学习闭环:Agent 如何”知道自己进步了”
前两章分别讲了”Agent 记什么”(记忆)和”Agent 会做什么”(技能)。但一个问题还没回答:Agent 怎么知道自己做得好不好?没有这个信号,记什么、做什么都是盲目的。
这一章讲”学习闭环”:从成功和失败中产生信号,把信号转化成对记忆和技能的修改,监控这个修改的效果,必要时回滚。这个闭环听起来像 ML 里的”训练”,但它和 ML 训练有一个本质区别 —— 它不改模型权重,只改 Agent 外部的文件(memory、skills)。这个区别决定了整套机制的工程约束和可能性。
6.1 成功信号从哪来:三个正交的来源
Agent 做完一件事之后,怎么判断做得好?
ML 领域里这个问题有标准答案:labeled data + loss function。但 Agent 场景下没有 label,甚至没有明确的”对错”—— 你让 Agent 帮你写一封邮件,它写出来了,但写得好不好?没有客观指标。
Hermes 的做法是从三个正交的来源获取信号,每个来源单独不可靠,组合起来形成一个相对稳定的判断。
来源一:用户反馈(Explicit Feedback)。
最直接的信号。用户在每次对话后可以:
- 明确赞赏(“这次做得挺好”)
- 明确批评(“这不是我要的”)
- 修正(“应该是 X 不是 Y”)
- 沉默
前三种是显式的正负信号,沉默是一个有信息量但模糊的信号 —— 它可能意味着”满意”,也可能意味着”没心情给反馈”或”任务失败但懒得说”。Hermes 的做法是:显式反馈直接用,沉默信号不作为正信号,只作为”没有负信号”的弱指示。
用户反馈的问题是稀疏。大多数用户不会每次都给反馈。Hermes 会通过一些 UI 设计鼓励反馈(比如任务完成后提供快捷按钮),但指望用户给所有交互打分是不现实的。
来源二:任务完成度(Task Completion Signal)。
Agent 很多任务的成功标准是可被程序判断的:代码能不能跑、文件有没有生成、API 调用有没有返回 200。这些都可以在不需要用户反馈的情况下自动判断。
具体到 Hermes 的实现:每次 skill 执行会记录一个 completion_status,取值 success / partial_success / failure。判断逻辑在 agent/trajectory.py 里,大致规则是:
- 所有步骤都执行完,最后一步的返回是合法结果 → success
- 有步骤失败但任务主目标达成 → partial_success
- 任务主目标没达成 → failure
这个信号的覆盖范围比用户反馈广得多 —— 所有任务都能产生一个 completion_status,不依赖用户。代价是它不一定反映”质量”。一段代码能跑不代表它是好代码;一封邮件发出去了不代表用词得体。
来源三:自评估(Self-Evaluation)。
让 LLM 自己判断自己刚才做得好不好。这听起来可疑(LLM 不是会幻觉吗?),但在实践中效果比想象中好 —— 尤其是用另一个 LLM(不同的模型或同模型不同 prompt)做评估时。这个方法学术界叫 LLM-as-a-Judge(本书后文统一用这个全称,不用 “LLM judge” 简称)。
Hermes 的做法是:对每次重要任务完成后,用一个 evaluator prompt 让 LLM 从几个维度打分:
- 任务是否完成了用户真正想做的事(而不是字面意思)
- 执行过程是否高效(有没有走弯路)
- 结果质量如何(格式、完整性、准确性)
- 有没有引入副作用(改了不该改的东西)
这个 evaluator 的输出不是单一分数,而是一份结构化报告。报告本身会被存进 trajectory 里,作为下次学习的输入。
三个信号组合的原则
Hermes 不是简单地把三个信号加权平均。它有几条基于经验的规则:
- 用户的显式批评是最高优先级。哪怕 completion_status 是 success、self-evaluation 是高分,只要用户说”这不对”,系统就认定这次是失败,立即触发学习。
- completion_status = failure 是硬失败。哪怕用户没有明确批评,任务没完成就是没完成。
- 只有三个信号全部正向,才是”明确成功”。用户没反馈 + 完成度 success + 自评高分 → 归为”默认成功”,用于统计,但不触发激进的学习(不扩充技能、不改写记忆)。
- 信号冲突时要保守。比如用户说”挺好”但 completion_status 是 partial —— 这说明用户可能没仔细看结果,或者结果有隐藏问题,不应该基于这次触发技能创建。
这些规则不是通用最优解,它们反映的是 Hermes 的设计哲学:宁可错过一次学习机会,也不要基于错误的信号做出错误的学习。这个哲学在第 6.5 节会再被强调。
6.2 技能迭代的触发条件
有了信号之后,下一个问题是”什么时候要改 skill”。Hermes 的技能迭代有三种触发场景。
场景一:新技能的生成(已在第 4 章讲过)。重复模式 + 用户确认触发,走一遍生命周期的”提议 → 审查 → 发布”。
场景二:现有技能的修订(Refinement)。一个已经存在的 skill 在多次执行后积累了一些失败或低质量的 case,Hermes 会定期(默认每晚一次)扫描这些 case,对有问题的 skill 生成修订建议。
修订流程:
- 挑候选。从
trajectory里找 “执行过但最近有失败” 的 skill。 - 收集样本。为每个候选收集最近 N 次执行的日志(成功和失败都要,用来对比)。
- 让 LLM 分析。Prompt 大致是:“这是 skill A 的定义,这是它最近的 N 次执行(含失败),请分析失败原因并给出改进后的 SKILL.md”。
- 审查新版本。和新技能的审查流程一样,走质量闸门。
- A/B 或直接替换。如果 Hermes 运行在”保守模式”,新旧版本会 A/B 一段时间(新任务随机用新版或旧版,对比成功率);否则直接替换。
- 版本记录。SKILL.md 的 frontmatter 里有
version字段,每次修订递增;旧版本归档到skills/<name>/_history/。
场景三:技能的废弃(Retirement)。两种情况:
- 连续失败:连续 N 次执行都是 failure(可配置,默认 5 次),skill 被自动标记为
deprecated,后续不再参与 matching。 - 长期未用:超过 X 天(默认 90 天)没有被触发过,skill 被提议归档。归档的 skill 从
skills/移到skills/_archive/,不删除,以备用户将来手动恢复。
两个条件的决策规则要写清楚,否则读者实操时会纠结两者怎么组合:
规则:连续 5 次失败 → 立即标记
deprecated(这是硬信号,等不起);90 天未用 → 先标记archived并保留 30 天缓冲(这是软信号,用户可能只是最近没用),30 天后仍未被触发才从skills/移到skills/_archive/。两个条件独立触发,先命中谁走谁的路径。
这三个场景合起来构成了”技能库的新陈代谢”。没有这个新陈代谢,skills/ 目录会单调增长并慢慢变烂 —— 第 4.4 节讲的 skill drift 问题就会显现。
上面提到的”从
trajectory里找 ‘执行过但最近有失败’ 的 skill” —— trajectory 是 Agent 执行一次任务的完整账本,定义和存储位置见第 3 章的 trajectory 定义框。修订流程的数据全部来自 trajectory,所以只有你开启了 trajectory 持久化,学习闭环才有数据可学。
6.3 离线反思 vs 在线更新:延迟和稳定性的权衡
一个关键设计决定是:学习是在每次对话之后立即发生,还是批量延迟发生?
两种做法各有道理。
在线更新(online):每次对话结束就立即做学习。好处是时效性最强 —— 刚发生的失败立即反映到技能和记忆里,下次立即受益。坏处是:
- 对话结束后的延迟增加。反思和更新要花 LLM 调用,用户在界面上会等一段时间才看到”可以关闭”
- 单次事件过拟合。一次偶然的失败被立即写入,可能是噪声
- 冲突处理复杂。如果用户连续对话,前一次对话的”反思”还没结束,后一次对话就开始了
离线批处理(offline batch):把学习延迟到固定时间(每晚、每次会话结束若干小时后)。好处是:
- 不影响交互延迟,用户体验顺滑
- 可以在一个批次里综合多个事件,避免单次事件过拟合
- 可以做全局优化,比如跨 skill 的重构
坏处是:
- 时效性延后,刚发现的问题下次还会发生
- 批处理任务需要调度基础设施(cron、worker)
Hermes 的选择是混合模式:
- 即时更新:用户的显式批评会立即触发相关 skill 的降权(不用立即修改文件,只是在内存里标记”下次不要用这个 skill”)
- 延迟重写:skill 文件的实际修改(修订、废弃)在每晚 3:00 左右批处理
- 快速路径例外:对明显严重的问题(连续 5 次失败),即使是离线时间也立即触发,不等夜间批处理
这个混合模式的代码实现在 memory_manager.py 和 trajectory.py 里,通过一个内部事件总线(大致是一组队列)协调。设计上不复杂,但调参需要经验 —— 批处理频率、即时更新阈值、降权幅度等都是可配参数。
6.4 对比:Voyager 和 Generative Agents 的学习机制
Hermes 的学习循环不是凭空发明的。它吸收了过去几年里几个有代表性研究项目的经验,其中最值得对比的是 Voyager 和 Generative Agents。
Voyager(2023,NVIDIA) 是一个在 Minecraft 里自主生存和探索的 Agent。它的学习机制核心是:
- 技能库(Skill Library):Agent 把解决过的任务写成 JavaScript 函数存下来,下次遇到类似任务直接调用
- 课程学习(Curriculum):Agent 根据当前已掌握的技能,自动生成难度递增的探索任务
- 自我验证(Self-Verification):每个技能写完后,Agent 用 LLM 验证它是否能实际工作
Voyager 是第一个把”skill as code”和”自动课程”放在一起的有影响力项目。它证明了 LLM Agent 能够在一个相对封闭的环境里实现某种形式的”自学习”。
Hermes 从 Voyager 借鉴了三件事:
- 把技能沉淀成可复用的独立单元(Hermes 用 Markdown,Voyager 用 JavaScript)
- 技能入库前要做自验证(在第 4.5 节的质量闸门里)
- 失败不只是错误,也是学习信号(在第 6.1 节的信号来源里)
但 Hermes 没有借鉴 Voyager 的”自动课程” —— 原因是个人 Agent 的任务由用户驱动,不需要 Agent 自己生成任务。
Generative Agents(2023,Stanford) 是一个模拟小镇居民的 Agent 项目(著名的 “Smallville”),每个居民都是一个 LLM Agent。它的学习机制核心是:
- 记忆流(Memory Stream):按时间顺序存所有观察
- 反思(Reflection):定期让 LLM 对近期记忆生成更高层次的抽象
- 检索评分(Retrieval Scoring):检索时综合考虑 recency、importance、relevance 三个分数
Generative Agents 的贡献是把”反思”作为记忆系统的一等公民,而不是只做原始存储。Hermes 从中吸收的是:
- 分层抽象(第 3 章的三层记忆,最底层是原始会话,越往上越抽象)
- 检索的多维评分(Hermes 的检索融合了关键词、语义、时间衰减)
- 反思作为定期任务(Hermes 的离线批处理反思直接对应 Generative Agents 的 “nightly reflection”)
但 Generative Agents 的 reflection 太过激进 —— 它会让 Agent 对普通琐事都生成”人生感悟”式的抽象。Hermes 的 reflection 更保守:只提炼”可验证的事实”和”稳定的偏好”,不做情感抽象。这个区别反映了两者的使用场景不同(虚拟小镇 vs 实用助手)。
三者对比:
| 维度 | Voyager | Generative Agents | Hermes |
|---|---|---|---|
| 学习的主要单元 | 技能(JS 函数) | 反思(文字) | 技能(Markdown) + 反思(Markdown) |
| 任务来源 | 自动课程 | 模拟生活 | 用户驱动 |
| 成功信号 | 环境反馈(能不能生存) | 模拟互动 | 用户反馈 + 自评估 + completion |
| 是否可审计 | 代码可读,但无版本 | 几乎不可审计 | 文件 + git,完全可审计 |
| 生产就绪度 | 研究原型 | 研究原型 | 设计目标是生产 |
Voyager 和 Generative Agents 是”探索可能性”的研究项目。Hermes 是”把已经验证的技术整合成生产系统”的工程项目。这本书围绕 Hermes 展开,不是因为它”更先进”,而是因为它更能代表”当前可以落地的 Agent 学习机制”的上限。
6.5 学习循环的可观测性:没有监控的自学习就是技术债务
这一节是整章最重要的一节。
“自学习”听起来是个好词 —— 好像 Agent 会越用越聪明。但在工程上,没有可观测性的自学习是一个快速累积的技术债务。原因是:
- 你不知道它学到了什么:skills/ 和 memory/ 里多了东西,但你没读过
- 你不知道它学得对不对:新学的东西可能是错的,但没有指标告诉你
- 你不知道它在哪里卡住:某类任务一直失败,但没有报警
- 你不知道它要多少钱:自学习消耗 token,但没有成本追踪
解决这四个问题的基础是可观测性。第 10 章会讲完整的观测基础设施,这里只讲”学习循环相关的最小观测集”。
最小观测集应该包括:
指标一:每日学习增量。一天之内新增了多少 skill、修订了多少 skill、废弃了多少 skill、写了多少 memory 条目。这是最基本的”体检”指标,突然变化通常意味着出问题了。
指标二:学习的成功率。新创建的 skill,首次执行的成功率;修订后的 skill,对比修订前的成功率。如果”学习后反而变差”,说明修订机制本身有问题。
指标三:学习的成本。反思、自评估、修订这些动作消耗的 token 和时间。这个成本应该占总成本的一小部分(经验值:5–15%),过高说明过度学习,过低说明学习没跑。
指标四:学习的影响面。一次 memory 更新影响了多少后续对话,一次 skill 修订影响了多少后续任务。这帮助你判断学习是否”触达”真实使用。
指标五:异常模式。同一个 skill 短时间内被多次修订(可能进入”来回改”的抖动),同一条 memory 被反复覆写(说明记的事实不稳定),等等。这些需要报警。
这些指标都可以从 trajectory.py 里的数据提取。Hermes 没有开箱即用的仪表盘,但它暴露了足够的 API 让你自己做一个。第 10 章会给一个基于 OpenTelemetry 和本地 SQLite 的极简实现。
一个真实的反面教材。 我在社区里见过的一个翻车案例:
某用户运行 Hermes 两个月后,发现最近的 Agent 表现越来越差 —— 很多之前能做好的任务现在做不对了。他去看 skills/ 目录,发现有几个核心 skill 被 Hermes 连续修订了十几次,每次都稍微改了一点,但综合起来离初始版本已经面目全非。而且这些修订的”理由”在日志里都是合理的(“处理一个边界 case”)。单看每一次修订都没问题,但累积起来是一次缓慢的崩坏。
这个案例告诉我们:自学习的最大风险不是”一次大错”,而是”一千次小错的累积”。要防这种累积,必须有”长期趋势”的观测 —— 不只看每次修订是否合理,还要看一个 skill 的”累积修订历史”是不是在朝好的方向走。
没有这种观测,自学习会悄无声息地走偏。等你发现问题时,回滚的成本已经很高了。
6.6 学习循环的五个常见失败模式
最后梳理一下学习循环本身最容易出错的五个地方。
失败模式一:反思的 Prompt 漂移。Hermes 做反思用的 Prompt 是写死的模板,但模板里有一些”基于当前 memory 内容填充的动态部分”。随着 memory 变化,反思的”起点”也在变化,久而久之反思会产生和初始意图不一致的结果。对策:反思 Prompt 的静态部分要版本化,任何改动要有意识、有记录。
失败模式二:评估模型和生成模型是同一个。第 4 章讲过这个,这里再强调一次。让同一个模型同时”写 skill” 和 “判断 skill 好不好”,结果必然是”判断偏向自己的产出”。Hermes 允许你配置独立的 evaluator model,强烈建议使用。
失败模式三:正反馈循环(Positive Feedback Loop)。一个 skill 被创建,它成功执行了一次,于是 Hermes 更信任它,更倾向于下次用它,又一次成功……直到它在一个实际上不适合的场景里被强行使用并导致严重失败。对策:不要让”信任度”无限累积,给每个 skill 的”信任分”加一个上限。
失败模式四:学习数据的污染。如果你在测试环境里让 Hermes 故意做一些错的事(为了调试),而这些事被写进了真实的 memory 和 trajectory,后续的学习会被污染。对策:测试环境和生产环境要严格分离,每个环境有独立的 Hermes 工作目录。
失败模式五:用户 “沉默 = 满意” 的错误假设。默认情况下,如果用户不反馈,Hermes 会把那次交互当成”默认成功”。但现实里,用户的沉默经常意味着”任务失败了,但我懒得说”。长期下来这会让 Hermes 对自己的能力产生过度乐观的估计。对策:区分”无反馈”和”正反馈”,只有显式正反馈才计入成功,无反馈标记为”未知”。
这五个失败模式都是慢性病 —— 你一天两天看不出问题,一个月两个月之后就会显现。预防它们的核心是第 6.5 节讲的可观测性。没有观测,这些病你无法诊断。
6.7 实践:观察一次完整的学习循环
这一节给你一个完整的动手实验,让你亲眼看到学习循环的每个阶段。
准备:
- 确认你在第 2 章创建的 Hermes 工作目录是 git 管理的
- 准备一个任务:让 Hermes 帮你生成一份”本周工作总结”(这是一个可以反复做的任务)
步骤 1:第一次做任务(创建 skill 的机会)。
对 Hermes 说:“帮我生成本周的工作总结,读我 ~/work-log.md 里最近 7 天的内容,按项目分类,每个项目用两三句话概括。”
如果 work-log.md 不存在,先创建一个写点内容。让 Hermes 完成任务。
做完之后,再说一次类似的(“下周也按这个格式做一份”),触发 Hermes 提议创建 skill。同意创建。
步骤 2:观察 skill 文件。切到另一个终端:
cd ~/.hermes
git diff --stat # 看 Hermes 创建了什么
cat skills/weekly-summary/SKILL.md记录这个 skill 的初始版本。
步骤 3:触发一次失败。把 ~/work-log.md 改成一个错误的格式(比如删掉一些关键字段),让 Hermes 再生成一次周报。它大概率会生成一份残缺的报告,或者报错。
步骤 4:显式反馈。对 Hermes 说:“这次做得不对,work-log.md 格式换了,skill 没处理这种情况。请改进。”
步骤 5:等待学习(或强制触发)。默认是夜间批处理。如果你不想等,可以用 CLI 命令 hermes learning run-now(如果有这个命令,具体名称查阅附录 B)触发一次立即学习。
步骤 6:观察变化:
git diff skills/weekly-summary/你应该看到 skill 的内容被修订了 —— 可能增加了”格式检查”步骤,可能引入了”fallback 到手动解析”的路径。
步骤 7:复测。再让 Hermes 生成一次周报,看新版 skill 能不能处理”新格式”。
步骤 8:回滚(可选)。如果你觉得修订后的版本反而变糟了,可以 git 回滚:
cd ~/.hermes
git log skills/weekly-summary/SKILL.md
git checkout <previous-commit> -- skills/weekly-summary/SKILL.md这一步的能力是 Hermes 的 git-friendly 设计带给你的关键价值 —— 你永远可以回到历史上某个 “好” 的状态。没有 git,这件事会复杂很多。
做完这个实验,你已经完整观察了一次学习循环:信号 → 触发 → 修订 → 验证 → 回滚(可选)。把这个循环内化成肌肉记忆,后面所有和学习相关的章节都会更容易理解。
下一章讲执行引擎和模型路由 —— Hermes 是怎么在”思考、执行、反思”的循环里运转的,以及它怎么在 200 多个可选模型里动态挑合适的那一个。