Skip to Content
Hermes Agent 源码解读第 4 章 技能系统的机制:生命周期与质量闸门

第 4 章 技能系统的机制:生命周期与质量闸门

上一章讲了”Agent 记得什么”。这一章讲”Agent 会做什么”。

记忆和技能的区别在第 3 章结尾说过:记忆是关于世界的陈述知识(declarative),技能是关于如何做事的过程知识(procedural)。这个区分在概念层面清晰,但在工程上经常模糊 —— 一个”周报怎么写”的技能,本身就是一段知识,它能不能单独存成一个文件、能不能被 Agent 自己写出来、能不能被自动淘汰,这些问题才是”技能系统”这门学问真正要回答的。


记忆与技能的边界表(贯穿全书的判断依据)

后面 13 章里每次出现”这个东西该放 memory 还是 skill?”的问题,都可以回到这张表。

维度记忆(memory)技能(skill)
知识类型陈述性(declarative)—— “是什么”过程性(procedural)—— “怎么做”
典型内容用户画像、事实、偏好、项目状态工作流、步骤序列、脚本模板
存储位置memory/ 下的 Markdown 笔记 / SQLite 会话skills/<name>/SKILL.md + 可选脚本
写入触发反思驱动(会话结束后低频批处理)重复检测 + 用户确认(见 4.2 生命周期)
更新频率用户现实变化就更新(偏好、地点、项目)失败驱动的修订 + 上游 API 变化
生命周期可能永久存在(稳定事实)创建 → 使用 → 修订 → 废弃
例子”用户用 TypeScript,不喜欢 Python""生成本周 GitHub 活动简报的 7 步流程”

边界情形:技能调用时从记忆读参数

这是最容易让人困惑的地方。看案例:

  • “生成本周工作总结” 是技能(固定 7 步流程)
  • 但这个技能执行时,第 3 步会读 memory/preferences.md 里的 “用户希望总结按项目分组”(这是记忆)

技能是”流程骨架”,记忆是”流程执行时需要的活的参数”。技能引用记忆,不拥有记忆。第 14 章的案例二会再一次遇到这个边界(研究助手的 “当前理解” 是记忆,不是技能参数 —— 即便它被 skill 重度使用)。

这一章不讲具体的 skill 长什么样(那是第 5 章的事)。这一章讲技能系统的机制:Hermes 把技能定义成什么样的数据结构,技能的生命周期有几个阶段,技能怎么从”建议生成”走到”被实际使用”,以及一个”劣质技能”怎么被识别、拒绝或修复。

4.1 Skill as Markdown:为什么是文档而不是函数

主流的 Agent 框架把技能/工具定义为函数。LangChain 的 @tool 装饰器把一个 Python 函数变成工具;OpenAI Function Calling 用 JSON Schema 描述函数签名;MCP 协议规定 tool 的 schema。这些方案的共同点是:tool = 函数签名 + 执行代码

Hermes 做了一个不同的选择:skill = Markdown 文档 + 可选的辅助代码。一个 skill 就是一份人和 AI 都能直接读的文档,里面写了”这个 skill 是做什么的、什么时候触发、怎么执行、有什么坑”。

这个差别看起来只是格式不同,但它的深层含义不同。要理解这一点,先看两种方案的典型样子。

函数式 tool 的典型样子(LangChain):

from langchain.tools import tool @tool def list_markdown_inventory(directory: str) -> str: """列出目录下所有 Markdown 文件,按修改时间排序,返回 Markdown 表格。 Args: directory: 要扫描的目录路径 """ # ... 实际代码 ... return table_str

文档式 skill 的典型样子(Hermes):

--- name: markdown-inventory description: 列出目录下所有 Markdown 文件,按修改时间排序,输出 Markdown 表格 trigger_keywords: ["markdown 清单", "md 盘点", "列出 markdown", "整理 markdown 文件"] parameters: - name: directory description: 要扫描的目录路径 required: true default: "." cost_estimate: low risk_level: safe --- ## 场景 当用户需要快速盘点某个目录下的 Markdown 文档时使用。典型触发语如: "帮我看看这个目录下都有哪些 md 文件"、"整理一下我的笔记清单"。 ## 执行步骤 1. 确认目录参数。如果用户没说,默认当前目录。 2.`find` 命令查找所有 `.md` 文件。 3. 对每个文件获取修改时间和字数。 4. 按修改时间倒序排序。 5. 生成 Markdown 表格返回。 ## 参考实现 ```bash find "$DIR" -name "*.md" -type f -print0 | while IFS= read -r -d '' f; do mtime=$(stat -c "%y" "$f" 2>/dev/null || stat -f "%Sm" "$f") words=$(wc -w < "$f") echo "$mtime|$f|$words" done | sort -r ``` ## 注意事项 - 如果目录很大(超过 1000 个文件),应该分批处理并给用户进度反馈 - 符号链接默认跟随,如果用户不希望跟随要加 `-P` - 隐藏目录(`.git` 等)默认跳过 ## 失败模式 - 目录不存在 → 明确告知用户,不要默默返回空表 - 权限不足 → 告知用户并建议 `sudo` 或切换目录 ## 相关技能 - `markdown-word-count`: 单文件字数统计 - `folder-inventory`: 通用目录盘点(不限 md)

两个方案的本质区别在四点:

区别一:谁”执行”这个技能。 函数式 tool 是”LLM 决定调用哪个函数,执行在外部代码里完成”。文档式 skill 更灵活 —— 它可以是”LLM 按文档描述的步骤自己写代码并执行”,也可以是”LLM 直接调用文档里提供的参考实现”,甚至可以是”LLM 根据文档的思路重新组织一套步骤”。同一个 skill 在不同上下文里可能被”解读”成不同的执行路径。

区别二:谁能改这个技能。 函数式 tool 要改代码,需要开发者懂编程语言、懂框架、能提 PR。文档式 skill 改起来就像改一份笔记 —— 用户自己就能改。这对”个人 Agent”场景尤其关键,因为用户不是开发者。

区别三:Agent 能不能自己写新技能。 让 LLM 生成一个符合复杂 schema 的 Python 函数,它能做但容易出错(类型、导入、异常处理都要对)。让 LLM 写一份 Markdown 文档相对容易得多 —— 这本来就是 LLM 最擅长的事。Hermes 把”Agent 自生成技能”作为一等公民,这个设计决定直接依赖于技能格式选择 Markdown。

区别四:技能的”语义层次”。 函数式 tool 通常是原子的 —— 一个函数做一件事,上层的”工作流”要靠 Agent 在多个函数之间自己组合。文档式 skill 可以是原子的(一步搞定),也可以是复合的(步骤 1、2、3,其中每一步可能调用别的工具或 skill),粒度可以和任务的复杂度匹配。

这个选择的代价:

  • 解读开销。每次调用 skill 时,LLM 要先”读”这份 Markdown、理解步骤、再决定怎么做,这比直接调函数多一轮 token 消耗。
  • 非确定性。同样的 skill 文档,LLM 在不同时间可能”解读”出略不同的执行路径,不如函数那样精确可重现。
  • 难以做强类型参数校验。函数有明确的签名和类型,skill 的参数是自然语言描述,类型检查弱。

Hermes 的取舍是:接受这些代价,换取”用户可改”和”Agent 可自生成”两个更重要的能力。第 16 章的 mini-hermes 也会沿用这个设计,你可以自己对比两种方案的体感差异。

4.2 技能的生命周期:五个阶段

一个 Hermes skill 从诞生到消亡,大致经历五个阶段:

阶段一:提议(Proposal)。 Hermes 检测到”这件事值得沉淀成技能”。触发条件大致有几种:

  • 重复模式检测:同一类操作在最近 N 次对话中出现超过 M 次(例如连续两次都是”列 Markdown 生成表格”)
  • 用户显式要求:用户说”把这个做成一个模板”、“下次你自己知道怎么做”
  • 复杂度阈值:一个任务的步骤数超过某个阈值(例如超过 5 个工具调用),且最终成功,Hermes 会认为值得沉淀,避免下次重新”推理”
  • 模型主动建议:LLM 在完成任务后自己判断”这件事值得记下来”,输出一个 suggestion

提议阶段不会直接写文件。它只是产生一个”候选”(candidate),放进一个内部队列等待下一阶段处理。

阶段二:审查(Review)。 候选技能进入质量闸门。Hermes 会做几件事:

  1. 去重检查:查找 skills/ 目录下是否已有功能相近的 skill。如果有,要么放弃这次提议(复用已有),要么考虑合并(把已有的 skill 改进一下)。
  2. LLM 自评:让 LLM 以”技能审查员”的角色判断这个 candidate 是否合格,评分维度包括:描述是否清晰、步骤是否可执行、参数是否完整、是否有明显的边界 case 没考虑。
  3. 用户确认(可选):根据用户配置的”严格度”,审查结果可能需要用户拍板(默认是严格模式,需要确认)。
  4. 格式化:把 candidate 转成标准的 SKILL.md 格式,包括 frontmatter、各个 section。

只有通过所有检查的 candidate 才会进入下一阶段。

阶段三:发布(Publish)。 通过审查的 skill 被写入 skills/<skill-name>/SKILL.md。同时:

  • 更新 skills_index(SQLite 里的一张表),方便后续快速检索
  • 把 skill 加入当前 session 的可用工具列表(在后续对话中模型可以看到这个 skill)
  • 记录一条 audit log:谁、何时、基于哪次对话创建了这个 skill

阶段四:执行(Execution)。 skill 被”触发”并实际执行。触发方式有三种:

  • 用户描述触发:用户说了一句话,Hermes 的 skill matcher(后面 4.3 讲)匹配到这个 skill,决定调用它
  • Agent 主动选择:Agent 在规划任务时主动引用某个 skill 作为其中一步
  • 显式调用:用户直接说 “跑一下 markdown-inventory”(CLI 里支持斜杠命令)

执行的过程本质上是:Hermes 把 skill 的 Markdown 内容作为一段特殊的 context 注入 Prompt,然后让 LLM 按文档描述的步骤做事。如果 skill 里有参考代码,LLM 会优先尝试直接执行(而不是重新生成代码),这可以节省 token 并提高稳定性。

每次执行都会被记录:成功 / 失败、耗时、token 消耗、工具调用序列。这些数据是下一阶段(迭代)的输入。

阶段五:迭代或废弃(Iterate or Retire)。 一个 skill 的后续命运有几种可能:

  • 持续成功:什么都不变,继续用
  • 轻微失败:Hermes 会记录失败 case,积累到一定数量后触发 skill 的”修订”—— 让 LLM 基于失败 case 给出一个改进后的版本
  • 严重失败:连续 N 次完全失败(例如 skill 依赖的外部 API 变了,一用就挂),直接标记为 deprecated
  • 长期未用:超过 X 天没有被触发过,Hermes 可能建议归档或删除(避免 skill 目录膨胀)

这五个阶段是一个闭环,而不是一条直线。一个 skill 可能被创建、用了几次、失败了、修订、再用、再失败、废弃。整个过程都有文件痕迹,你随时可以 git log 看它的历史。

4.3 技能匹配:怎么从一句话找到”该调哪个 skill”

用户说了一句话,Hermes 怎么决定要不要调用某个 skill、调哪个?

这个问题看起来简单,做好很难。有三种常见思路:

思路一:关键词匹配。 每个 skill 在 frontmatter 里声明 trigger_keywords,用户话里出现关键词就触发。这个方案的优点是简单、可预测、零额外成本,缺点是覆盖弱 —— 用户不会每次都用你预设的关键词,同义词、不同语序、省略的情况都会漏。

思路二:向量相似度。 把每个 skill 的 description 转成 embedding,把用户的话也转成 embedding,算余弦相似度,超过阈值的触发。优点是覆盖广、能处理同义,缺点是假阳性高(语义相似不代表功能匹配)和向量模型依赖

思路三:LLM 判断。 把所有 skill 的简短描述塞进 Prompt,让 LLM 自己决定”这次该调哪个 skill,或者不调任何 skill”。优点是最灵活,缺点是每次都要一次额外的 LLM 调用,贵且慢。

Hermes 的实现是三者组合的混合策略:

用户输入 [关键词粗筛] → 命中明确关键词 → 直接调用(最快路径) ↓ 无命中 [向量初筛] → 取 top-10 候选 [LLM 精判] → 从候选里选 0 或 1 个 执行或不执行

这种分层的好处是大部分情况下不需要走到最贵的一层。用户说”帮我整理 md 清单”,关键词就能命中 markdown-inventory,不需要向量也不需要 LLM 判断。只有用户说得很模糊(“把我的笔记整理一下”)时才会退到向量 + LLM 判断。

这个分层的参数是可调的,比如向量的阈值、候选数、是否允许跳过 LLM 判断(激进模式)。第 7 章讲模型路由时会回到这个话题 —— 类似的”分层决策”在 Agent 系统里反复出现,它是降低成本的核心技术。

4.4 Skill Drift:技能污染的三种形态

前面讲了技能是怎么被创建、审查、执行的。但技能系统最棘手的问题不在创建,而在随着时间推移技能库会变脏。这个现象叫 skill drift(技能漂移),或者更通俗地叫 技能污染

注意:本书里”漂移”一词指两件不同的事。第 3.5 节讲过 memory drift(记忆漂移 —— 记录的事实和现实脱节,比如”用户在北京”三个月后不再为真)。这里讲的 skill drift技能库的退化(重复技能、过度具体、错误沉淀),两者成因、诊断方式、对策完全不同。读到”漂移”时,先看上下文确认是在说 memory 还是 skill。

技能污染有三种典型形态:

形态一:重复技能(Duplicate Skills)。 同一个功能被创建成多个 skill。原因通常是去重检查不够严格 —— 用户第一次让 Hermes 做”生成周报”,skill 名叫 weekly-report;几周后用户说”帮我总结一下本周的工作”,Hermes 没识别到这是同一件事,创建了一个 work-summary skill。两个 skill 做的事几乎一样,但 matching 时 LLM 要在它们之间纠结,浪费 token 还可能选错。

对策:严格的去重检查 + 定期的”技能合并”任务。Hermes 有一个内置的 skill-consolidation 周期任务,它会扫描所有 skill 找出语义相近的,提议合并。

形态二:过度具体(Over-Specialization)。 skill 写得太针对某一次具体情境。比如第一次用户说”帮我整理一下 ~/Documents/研究生课程 下的 Markdown 文件”,Hermes 可能会把”~/Documents/研究生课程”这个路径写死在 skill 里。下次用户想整理别的目录时,这个 skill 就派不上用场。

对策:生成阶段的 Prompt 里明确要求”不要把具体的参数值写死,要用占位符”,而审查阶段要检查这一点。但这个问题总会有漏网之鱼,所以 Hermes 允许用户在调用 skill 时覆盖 skill 里的默认参数。

形态三:错误沉淀(Wrong Knowledge Codified)。 最严重的一种。skill 里写的做法本身就是错的,但因为 Hermes 没有做充分的验证就发布了,后续每次调用都在犯同一个错误。

典型 case:Hermes 帮用户写了一段 Python 脚本,里面用了一个已经废弃的 API。用户没注意到,Hermes 把这段代码沉淀成一个 skill。下次用户让 Hermes 执行类似任务时,Hermes 直接复用了这个 skill —— 结果继续用废弃的 API,脚本报警告甚至报错。

对策:skill 的”发布门槛”和”调用监控”都要做。发布门槛在审查阶段尽量通过 LLM 自评和代码静态检查拦住明显的错误;调用监控是在每次执行后记录成功/失败,连续失败要触发重审或废弃。

4.5 质量闸门:一个 skill 值得被写入的判断标准

上面的”技能污染”问题最终都指向同一个核心问题:什么样的 skill 算”足够好”可以被写入?

这个判断标准不能简单用一句话总结,它是一个多维评分卡。Hermes 的质量闸门大致考虑以下几个维度:

维度 A:描述清晰度(Description Clarity)。

  • skill 的 name 和 description 能否准确概括它做什么?
  • 有没有明确的触发场景描述?
  • 读完 description,一个不懂这个 skill 的人能不能判断”我这个问题该不该用它”?

维度 B:步骤可执行性(Step Executability)。

  • 步骤是否具体到”可以照着做”?
  • 有没有依赖没说清楚的前置条件?(例如 “先检查文件是否存在” 里的”如何检查”必须有具体方法)
  • 参考代码(如果有)能不能直接跑?

维度 C:参数完整性(Parameter Completeness)。

  • 所有需要的参数都列出来了吗?
  • 每个参数有没有类型、描述、默认值?
  • 必选参数和可选参数的区分是否明确?

维度 D:边界处理(Edge Cases)。

  • 常见的失败情况有没有被考虑?(空输入、权限不足、网络失败、超时、等等)
  • 对这些失败情况的处理是”返回错误”还是”静默失败”?
  • 有没有说明”什么情况下不应该用这个 skill”?

维度 E:成本和风险标注(Cost & Risk Labels)。

  • cost_estimate:low / medium / high。用来在资源紧张时做选择
  • risk_level:safe / caution / dangerous。dangerous 级别的 skill 每次执行都需要用户确认

维度 F:和现有 skill 的关系(Relationship)。

  • 是否和现有 skill 重复?
  • 是否应该是某个现有 skill 的”参数变体”而不是新 skill?
  • 是否应该引用其他 skill(例如 markdown-inventory 可以引用 folder-word-count)?

质量闸门的实现是一个 LLM 打分流程。把 candidate skill 和一份包含上述维度的 rubric 一起丢给 LLM,让它逐维度打分,最终生成一个总分。总分超过阈值的进入发布,低于阈值的进入”需要人工审核”队列,极低的直接拒绝。

这个流程有两个注意事项:

注意事项 1:打分的 LLM 不应该和生成的 LLM 是同一个(至少不应该是同一次调用)。原因是 LLM 有偏向于”认可自己写的东西”的倾向。理想情况下打分用另一个(甚至另一家)模型做独立判断。Hermes 支持配置”审查模型”独立于”生成模型”。

注意事项 2:质量闸门不是一次性的。一个通过了审查的 skill,在执行一段时间后可能因为环境变化变得不再合格。所以 Hermes 会定期对”执行中”的 skill 做一次重审,把 degraded 的 skill 移回”待修订”状态。

4.6 源码导读:skill 相关的三个文件

Hermes 在 agent/ 目录下和技能机制相关的核心文件是:

  • skill_commands.py — 技能的”命令式接口”。当 Agent 要触发一个 skill 时,最终都会走到这个文件里的某个函数。它负责定位 skill 文件、解析 frontmatter、准备执行环境、调度实际执行、收集结果。
  • skill_utils.py — 工具函数集合。包括 skill 的匹配、去重、相似度计算、frontmatter 解析、skill 文件的读写辅助。这是一个”瑞士军刀”式的文件,其他文件都会 import 它。
  • prompt_builder.py — 虽然名字不是 skill 专属,但它里面有一段逻辑专门负责”把相关的 skill 注入 Prompt”。理解 skill 是怎么被 LLM”看到”的,要读这里。

建议的阅读顺序:

  1. 先读 skill_utils.py,熟悉基础的数据结构和 helper 函数
  2. 然后读 skill_commands.py,理解执行路径
  3. 最后在 prompt_builder.py 里找 inject_skills 或类似的函数,理解 skill 是怎么进到 Prompt 里的

读的时候,对照第 4.2 节的五阶段生命周期,在代码里标注每一阶段对应的入口函数。你会发现”生命周期”这个抽象在代码里有非常直接的映射。

4.7 实践:诱导 Hermes 生成一个劣质 skill,然后修复它

这个练习的目的是让你亲眼看到”质量闸门”在不严格时会漏掉什么。

步骤 1:诱导生成。对 Hermes 说:

帮我在当前目录创建一个叫 report.txt 的文件,内容写”2026 Q1 报告”,然后把它的权限改成 644,然后打开它。

这是一个非常特定的任务(特定的文件名、特定的内容、特定的权限、特定的最后步骤)。做完之后,让 Hermes 把这个过程沉淀成一个 skill:

把刚才的操作做成一个 skill,下次我说”创建报告”的时候你就这么做。

观察 Hermes 生成的 skill。如果它的质量闸门不严,你会看到这样的问题:

  • 文件名可能被写死成 report.txt(应该是参数)
  • 内容可能被写死成 2026 Q1 报告(应该是参数)
  • 权限 644 可能被当成通用规则(对 .txt 文件来说 644 没问题,但对 .sh 就不对了)
  • “打开它”的具体含义可能被忽略(哪个 editor?是在哪里打开?)

步骤 2:手动审查。打开生成的 skills/create-report/SKILL.md(或类似名字),按照 4.5 节的六个维度逐一检查:

  • 描述清晰吗?
  • 步骤可执行吗?
  • 参数完整吗(应该有文件名、内容、权限作为参数)?
  • 边界处理呢(文件已存在怎么办?权限设置失败怎么办?)?
  • 成本和风险标注合理吗?
  • 有没有和现有 skill 重复?

你大概率会发现至少三四个问题。

步骤 3:修复。把这些问题手动修复 —— 参数化、补充边界处理、加上正确的风险标签。然后让 Hermes 重新尝试”创建一份 2026 Q2 报告”的任务,看它能不能正确调用修复后的 skill。

步骤 4:反思。思考一下:如果质量闸门做得更严格(比如强制要求每个 skill 至少声明 N 个参数、至少处理 M 个边界),会不会阻止劣质 skill 的产生?但更严格也意味着更多任务没法被沉淀。这个权衡在 Hermes 里是通过配置项暴露给用户的 —— 你可以设置”宽松模式”(尽可能多地生成 skill)、“平衡模式”(默认)或”严格模式”(只沉淀高质量 skill)。

没有”最优”的设置。你的选择应该匹配你的使用场景:如果你是一个技术熟练的用户,能自己审查和修复 skill,宽松模式给你最大的自主性;如果你是一个普通用户,希望 Agent 尽量不产生垃圾,严格模式更合适。

4.8 技能系统的七个陷阱

陷阱一:把”有用”和”通用”混为一谈。 一个 skill 只在特定项目里有用不代表它不是好 skill,但不能假装它是通用的。好的 frontmatter 应该明确写出 skill 的适用范围。

陷阱二:技能之间的”隐式依赖”。 skill A 调用 skill B,但 A 没明确声明这个依赖。当 B 被删除或修改时,A 就坏了。对策:SKILL.md 里有一个 related_skillsdependencies 字段,任何引用都要显式声明。

陷阱三:技能文件过大。 一个 skill 文件超过 1000 行时,LLM 读它的 token 消耗已经和”直接让 LLM 重新推理”差不多了。好的 skill 文件应该控制在 200–500 行以内,超出的部分应该拆分成多个关联 skill。

陷阱四:把”技能”和”知识”混同。 “怎么做某件事”是技能,“关于某个领域的事实”是知识。后者应该进 memory/,不应该进 skills/。我看到过有人把”北京各区地铁线路”写成一个 skill,这是错的 —— 这是知识,不是技能。

陷阱五:不给 skill 加版本。 skill 改动后没有版本标记,就没法追溯”用户上次调用的是哪个版本”。Hermes 的 SKILL.md frontmatter 里有一个 version 字段,每次修订要递增。

陷阱六:忘记给 skill 加退出条件。 复合 skill 有时会陷入”这一步失败了,重试还是放弃”的抉择。好的 skill 会明确标注”最多重试 N 次""遇到 XX 错误直接终止”。

陷阱七:跨用户共享 skill 时不做清洗。 如果你把自己的 skill 分享给别人,要先检查里面有没有写死了你的私人路径、邮箱、API key。Hermes 有一个 skill-export 命令会自动做这个清洗,但不要 100% 依赖它 —— 最后一道门永远是人。

下一章,我们把这套机制转换成具体的肌肉记忆。我挑了 Hermes 官方 skills 目录下六个代表性的 skill,每一个都按”问题→设计→代码→调用场景→可迁移模式”的结构拆开讲。读完第 5 章,你会知道一个”好 skill”长什么样 —— 不是抽象的标准,而是具体的文本。

Last updated on