Skip to Content
Hermes Agent 源码解读第 5 章 Hermes 明星 Skill 解剖

第 5 章 Hermes 明星 Skill 解剖

上一章讲了 skill 的机制:生命周期、质量闸门、匹配策略。这一章要做一件完全不同的事 —— 不再讲机制,讲具体的 skill 长什么样

Hermes 的 skills/ 目录下有 26 个分类,每个分类里又有多个具体 skill。我不会全部讲一遍 —— 那样做既没意义(很多 skill 功能重复)也没空间(全书也装不下)。这一章挑六个 skill 作为”标本”。挑选的原则不是”最热门”,而是”每一个代表一种值得学习的设计模式”。读完这六个,你应该能把它们背后的模式迁移到自己写的 skill 里。

六个 skill 分别是:

  1. github — 代表外部 API 型 skill 的标准做法
  2. feeds — 代表长期运行型 skill(带状态、增量、断点续跑)
  3. research — 代表多步编排型 skill(一个 skill 内部调度多个子任务)
  4. note-taking — 代表记忆耦合型 skill(skill 如何和 memory 系统协作)
  5. mcp — 代表桥接型 skill(skill 如何包装外部协议)
  6. red-teaming/godmode — 代表反面标本(哪些 skill 应该被限制)

每个 skill 的讲解结构统一:它要解决什么问题 → 它的 frontmatter 怎么设计 → 它的内部步骤是什么 → 执行时的注意点 → 可以迁移到你自己 skill 的设计模式

注意:Hermes 的上游仓库会继续迭代,下面这些 skill 的具体内容可能在你读到本章时已经和最新版本有所不同。我会保留设计层面的分析不变,对具体代码细节加上”当前稳定版”的时间戳。你可以在附录 G 勘误页看到已知的差异。

配套完整 SKILL.md:本章讲的六个 skill 都有真实可用的完整版本在 book/assets/skills-showcase/ 目录下。你可以一边读本章一边对照完整文件,或者直接拷贝到你的 Hermes / mini-hermes workdir 使用。

5.1 本章怎么读

如果你只有时间读一个 skill,读第 5.5 节 note-taking。它是全章的枢纽,把第 3 章的记忆系统和第 4 章的技能机制缝在一起 —— 读完你会对”Agent 系统”产生一种”哦原来是这么运作的”的直觉。

如果你有时间读三个,读 github(最广泛的外部 API 模式)、note-taking(枢纽)、red-teaming/godmode(反面教材)。这三个覆盖了 Hermes skill 设计的正面、中间、反面三个维度。


5.2 github skill:外部 API 型的标准做法

它要解决什么问题

GitHub 是开发者 Agent 最常见的外部系统之一。典型需求包括:看 issues、review PR、分诊 bug、生成 release note、跟踪仓库动态。这些需求如果每次都让 Agent 从零写代码调 GitHub API,token 成本和出错概率都会很高。github skill 的目的是把这些高频操作沉淀成可复用的 skill,让 Agent 直接调用。

Hermes 的 skills/github/ 目录下实际上不是一个 skill,而是一组 skill:

skills/github/ ├── DESCRIPTION.md # 分类总说明 ├── github-auth/ # 鉴权 ├── github-repo-management/ # 仓库级操作 ├── github-issues/ # issue 相关 ├── github-pr-workflow/ # PR 相关 ├── github-code-review/ # 代码审查 └── codebase-inspection/ # 代码库探索

这是一个很重要的组织方式 —— 把相关的 skill 放在同一个父目录下,用 DESCRIPTION.md 做顶层导航。这样 Agent 在匹配 skill 时,可以先”路由”到父目录,再从父目录里挑具体的子 skill。我们挑 github-issues 作为主要解剖对象,因为它是最常被触发的。

frontmatter 设计

(以下内容是对 Hermes 当前稳定版的还原和解读,不是逐字节的原文。)

--- name: github-issues description: 在 GitHub 仓库中查询、创建、评论、关闭 issue,支持按标签/状态/作者筛选 category: github trigger_keywords: - github issue - "issue 分诊" - "看看 bug" - "创建 issue" - "回复这个 issue" depends_on: - github-auth parameters: - name: repo type: string description: "仓库全名,例如 owner/repo" required: true - name: action type: enum values: [list, get, create, comment, close, reopen] required: true # ... 其他参数根据 action 而定 cost_estimate: low risk_level: caution # 写操作(create/comment/close)属 caution 级 requires_confirmation: - create - close ---

这个 frontmatter 里有几个值得学的设计点:

设计点 1:声明对 github-auth 的依赖。鉴权是一个共享关注点,每个操作 skill 都依赖它 —— 把它独立成一个 skill 而不是在每个 operation skill 里重复鉴权代码,是典型的”DRY”思路。

设计点 2:action 是枚举,不是自由文本。这降低了 LLM 的幻觉空间。你不会看到 Hermes 突然决定 “action: delete”(不在枚举里)然后调 API 挂掉 —— LLM 在写参数时会被约束在合法值范围内。

设计点 3:requires_confirmation 按 action 分粒度。list/get 是读操作,不需要确认;create/close 是写操作,需要用户确认。这比”整个 skill 都要确认”或”整个 skill 都不确认”更精细,也更符合实际使用体验。

设计点 4:cost_estimate: low。这个标签告诉 Hermes 的调度器”这个 skill 不贵,可以放心调”。相比之下,research skill 会标成 high,因为它会触发大量 LLM 调用。

内部步骤

一个典型的 github-issues skill 文档正文大概是:

## 前置条件 - 已通过 `github-auth` skill 完成 GitHub token 配置 - 目标仓库是用户有访问权限的 ## 执行流程 ### action: list 1. 读取 `gh auth status` 确认 token 可用 2. 构造 query:`gh issue list --repo <repo> [筛选参数]` 3. 解析输出,格式化成表格返回 参考命令: ```bash gh issue list \ --repo "$REPO" \ --state "${STATE:-open}" \ --limit "${LIMIT:-30}" \ --json number,title,author,labels,updatedAt ``` ### action: create 1. 确认用户已经提供 title 和 body 2. **向用户展示一次将要创建的内容摘要,等待确认** 3. 确认后执行 `gh issue create` 4. 返回创建的 issue URL ### action: comment 1. 读取目标 issue 的当前状态 2. 如果 issue 已 close,警告用户并询问是否仍要 comment 3. 执行 `gh issue comment` ## 失败模式 - Token 过期 → 提示用户重新运行 `github-auth` - 速率限制(rate limit)→ 从 error message 里解析 reset 时间,告知用户 - 仓库不存在或无权限 → 明确区分这两种错误,不要混为一谈 - 网络问题 → 最多重试 3 次,每次间隔 exponential backoff ## 相关技能 - `github-pr-workflow`: 如果 issue 实际是个 PR,应该路由到这个 skill - `github-repo-management`: 如果用户的需求是仓库级别的(比如 release notes),应该路由到这个 skill

可迁移的设计模式

从这个 skill 可以提炼出三个外部 API 型 skill 的通用模式:

模式 1:鉴权分离。把”怎么拿 token”和”怎么用 token”分成两个 skill。好处是鉴权逻辑可以被多个业务 skill 共享;坏处是 Agent 在调用业务 skill 时要先确认鉴权已完成,多一次判断。但这个代价值得。

模式 2:优先用 CLI 工具而不是 HTTP。Hermes 的 github skill 大量使用 gh CLI 而不是直接调 GitHub REST API。原因是 CLI 工具把很多边界情况(分页、速率限制、认证、输出格式)已经处理好了,让 skill 文档变短变稳。当你的目标服务有成熟的 CLI 时,优先用 CLI;没有 CLI 时再考虑直接调 HTTP。

模式 3:错误的”区分度”。失败模式里明确区分了”token 过期” “speed limit” “仓库不存在” “网络问题”,而不是笼统的”失败”。这是 Hermes skill 质量的标志 —— 不同错误需要不同的恢复策略,区分度就是恢复能力的上限。


5.3 feeds skill:长期运行型的典型范例

它要解决什么问题

信息订阅(RSS、邮件列表、GitHub release、arxiv 新论文等)是个人 Agent 里最有价值的场景之一 —— 你希望 Agent 主动盯着这些信息源,发现值得关注的新内容时告诉你,而不是让你自己一条条刷。

这类 skill 的技术难点不在”拉取数据”,拉数据几行代码就完事。难点在:

  • 增量:不能每次都从头拉,要记住”上次拉到哪条了”
  • 去重:同一条信息可能从多个源出现,不能重复通知
  • 相关性过滤:信息很多,但用户只关心一小部分,要根据用户画像做筛选
  • 断点续跑:上次 Agent 挂了,下次启动要能从断点恢复
  • 频率控制:拉太频繁浪费资源,拉太少时效性差

frontmatter 设计

--- name: rss-watcher category: feeds description: 监听 RSS/Atom 订阅源,筛选与用户兴趣相关的新条目并推送 trigger_keywords: - "订阅" - "新文章" - "rss" - "关注一下" parameters: - name: feed_urls type: list[string] description: RSS 源的 URL 列表 required: true - name: interest_keywords type: list[string] description: 用户感兴趣的关键词,用来做初筛 required: false - name: relevance_prompt type: string description: 自然语言描述的相关性判断标准 required: false state_file: memory/feeds/rss-watcher-state.json cost_estimate: medium # 会触发 LLM 做相关性判断 risk_level: safe schedule: "0 */3 * * *" # 默认每 3 小时跑一次 ---

这里新出现的字段是 state_fileschedule

state_file 告诉 Hermes:这个 skill 有持久状态,状态文件的位置在 memory/feeds/rss-watcher-state.json。每次 skill 执行时,它会先读这个文件、更新它、再写回。这是”增量”和”去重”的基础。

schedule 告诉 Hermes:这个 skill 不需要用户主动触发,它应该被 cron 调度。语法是标准 cron 表达式。Hermes 的 cron/ 模块会按这个时间表自动触发 skill 执行。

内部步骤

## 状态格式 state_file 的 JSON schema: ```json { "last_run": "2026-04-05T12:00:00Z", "feeds": { "<feed_url>": { "last_seen_id": "...", "last_seen_published": "..." } }, "seen_items": [ // 最近 1000 条的 item id,用来去重 "hash1", "hash2", ... ] } ``` ## 执行流程 1. **加载状态**。读 `state_file`,如果不存在则初始化空状态。 2. **抓取**。对每个 feed_url 并发拉取。使用 conditional GET(带 `If-Modified-Since` 头)减少不必要的流量。 3. **增量过滤**。对拉回来的条目: - 如果 item id 在 `seen_items` 里,跳过 - 如果 item 的发布时间早于 `last_seen_published`,跳过 - 其余为"候选新条目" 4. **初筛**(便宜的一步)。用 `interest_keywords` 做简单的关键词匹配, 筛掉明显无关的条目。这一步不花 LLM 调用。 5. **精判**(贵的一步)。对通过初筛的条目,用 LLM 根据 `relevance_prompt` 判断是否真的相关。**这里用便宜模型**(在第 7 章会讲原因)。 6. **推送**。对判断为相关的条目,通过 gateway 发给用户(Telegram / Slack / 飞书 / 邮件)。 推送格式:标题 + URL + 一句话摘要 + 相关性评分。 7. **更新状态**。把这次处理过的 item id 加入 `seen_items`, 更新 `last_seen_*` 字段。`seen_items` 超过 1000 条时,裁剪到最近的 1000 条。 8. **持久化**。写回 state_file。 ## 失败模式 - **单个 feed 失败**:不应该影响其他 feed,记录错误但继续 - **LLM 调用失败**:当前批次的精判跳过,等下一次调度再试;不影响状态推进 - **推送失败**:推送内容暂存到 `memory/feeds/pending-notifications/`,下次重试 - **状态文件损坏**:备份到 `.backup`,重建一份,会导致一次重复推送但不会丢数据 ## 可调参数 - 初筛阈值:默认匹配至少 1 个关键词才进精判 - 精判阈值:默认相关性分 >= 0.7 才推送 - seen_items 容量:默认 1000,可以调高(占更多磁盘)或调低(增加重复风险)

可迁移的设计模式

模式 1:状态和逻辑分离。skill 的”逻辑”在 SKILL.md 里,“状态”在独立的 state_file 里。这意味着你可以随时修改 skill 的逻辑而不丢状态,也可以随时检查 state 文件而不重跑 skill。很多新手写定时任务时把状态写死在代码里,这是不专业的。

模式 2:两级过滤(便宜 + 贵)。初筛用关键词(零 LLM 成本),精判用 LLM(有成本但准确)。这种两级结构在 Agent 系统里反复出现,本质是”用便宜的方式尽量剔除明显的负样本,把 LLM 的昂贵判断留给真正需要的少数样本”。第 7 章还会再讲这个模式。

模式 3:失败隔离。单个 feed 失败不影响其他 feed。这在写复合型 skill 时特别重要 —— 没有隔离的 skill 会出现”10 个子任务里 1 个失败全部滚回”的情况,用户体验很差。

模式 4:状态的向后兼容。state_file 的 schema 变化时,要能优雅地迁移(或至少不崩溃)。好的做法是在 state 里带一个 schema_version 字段,读的时候先看版本。


5.4 research skill:多步编排型

它要解决什么问题

“研究”是 Agent 最具想象空间、也最容易翻车的任务类型。典型需求:

  • “帮我调研一下今年 GPU 虚拟化的最新进展”
  • “写一份关于 RAG 优化的综述”
  • “比较一下 Pulsar 和 Kafka,找出各自的优缺点”

这类任务共同的特点是:没有单一工具能解决,需要多步。至少包括:分解问题、多源搜集、交叉验证、综合总结、结构化输出。

Hermes 的 research skill 是一个复合 skill —— 它本身不直接做很多事,而是编排其他更底层的 skill 和工具

frontmatter 设计

--- name: deep-research category: research description: 对一个主题做多步调研,输出结构化研究报告 trigger_keywords: - "调研" - "研究一下" - "综述" - "深入了解" parameters: - name: topic type: string description: 研究主题(自然语言描述) required: true - name: depth type: enum values: [quick, standard, deep] default: standard description: 研究深度影响调研耗时和成本 - name: output_format type: enum values: [markdown_report, bullet_summary, flashcards] default: markdown_report uses_skills: - web-search - url-fetch - pdf-reader - note-taking cost_estimate: high # LLM 调用密集 risk_level: safe max_duration_seconds: 1800 # 最多 30 分钟 checkpoint_interval: 120 # 每 2 分钟保存一次进度 ---

新字段:

  • uses_skills:显式声明依赖哪些底层 skill
  • max_duration_seconds:给长任务加一个最大运行时限,防止跑飞
  • checkpoint_interval:每 N 秒保存一次进度,用于断点续跑

内部步骤(简化版)

## 执行流程概览 ```mermaid flowchart TB A[分解问题] --> B[生成子查询] B --> C[并行检索] C --> D[来源质量评估] D --> E[冲突检测] E --> F{需要更深入?} F -->|是| B F -->|否| G[综合总结] G --> H[格式化输出] G --> I[写入 memory] ``` ## 阶段 1:分解问题 根据 `depth` 参数决定分解粒度: - `quick`:3–5 个子问题 - `standard`:5–10 个子问题 - `deep`:10–20 个子问题,且允许递归细化 分解由 LLM 完成,使用**强模型**(例如 claude-3-5-sonnet), 因为分解质量直接决定后续所有步骤的上限。 ## 阶段 2:生成子查询 为每个子问题生成 1–3 个搜索查询字符串。此步用便宜模型即可。 ## 阶段 3:并行检索 调用 `web-search` skill 对所有子查询并发执行。 注意:并发数有限制(默认 5),避免被目标服务限流。 ## 阶段 4:内容抓取 对搜索结果的 top URL 调用 `url-fetch` / `pdf-reader` 拉取正文。 长文档做分段,超过 10K token 的部分做摘要。 ## 阶段 5:来源质量评估 对每个来源打分: - 权威性(机构、作者、出版物) - 时效性(发布时间距今多远) - 相关性(和子问题的匹配度) 低质量来源降权,不直接丢弃(因为它们可能包含反方观点,有参考价值)。 ## 阶段 6:冲突检测 把不同来源对同一子问题的陈述摆在一起,用 LLM 识别"共识"和"分歧"。 分歧部分要被单独记录,写入最终报告时要明确标注。 ## 阶段 7:需要更深入? 根据 depth 参数和当前已用预算决定是否触发新一轮查询。 如果 quick 模式,直接进入总结;如果 deep 模式,可能递归最多 3 层。 ## 阶段 8:综合总结 `output_format` 组织最终输出: - markdown_report:完整报告,有引言、分节、结论、参考文献 - bullet_summary:要点列表 - flashcards:问答对,用于后续复习 ## 阶段 9:持久化 把研究结果的摘要写入 `memory/research/<topic-slug>.md`, 下次用户问相关问题时,可以快速召回。 ## 断点续跑 每个阶段结束时保存一次 checkpoint 到 `state/research-<id>.json`, 包括:当前阶段、已完成的子问题、已抓取的 URL、已用的 token 数。 中断后重启时从最近的 checkpoint 恢复。 ## 失败模式 - **单个 search 失败**:记录并继续,不中断整体 - **LLM 限流**:等待退避后重试 - **达到 max_duration**:强制终止,用当前已有的数据生成部分报告 - **达到预算上限**:同上,并向用户说明"数据不完整"

可迁移的设计模式

模式 1:编排型 skill 不直接做事,只调度research 自己不会写 HTTP 请求、不会解析 HTML,它把这些事甩给 web-search / url-fetch 这些底层 skill。这种分层让每个 skill 都保持”单一职责”,便于单独测试和改进。

模式 2:长任务必须有断点续跑。任何运行超过 1 分钟的 skill 都应该有 checkpoint 机制。没有 checkpoint 的长任务是在给用户埋雷 —— 一旦中间崩一次,之前的工作全部丢掉,用户体验极差。

模式 3:预算意识(budget awareness)。skill 要知道自己大概能花多少 token、跑多久,并在接近预算时主动收敛。max_duration_secondscost_estimate 都是为这个服务的。

模式 4:分歧比共识更重要。LLM 天然倾向于给出”一致的答案”,哪怕原始资料里存在分歧。好的 research skill 会主动识别并保留分歧,而不是假装所有人都同意一件事。这对后续用户做决策的价值很大。


5.5 note-taking skill:记忆耦合型(本章枢纽)

它要解决什么问题

这是本章最重要的一个 skill,也是我花最多篇幅讲的一个。原因是它把第 3 章的记忆系统和第 4 章的技能机制缝在一起

note-taking 要解决的问题表面看很普通:用户想记笔记,Agent 帮忙记。但深入看,它是在问一个本质问题:Agent 的”用户笔记”和 Agent 自己的”记忆”是什么关系?

有两种极端做法:

  • 完全分离:用户的笔记是一个独立的存储,Agent 看不到(比如让 Agent 把笔记写到 Notion),Agent 自己的记忆另外一套。
  • 完全合并:用户的笔记直接写进 Agent 的 memory/ 目录,没有边界。

Hermes 的选择是一种中间状态:用户的笔记放在 memory/notes/ 目录下,但有明确的目录结构和 frontmatter 标注,Agent 知道哪些是”用户主动写给自己看的”(要高度保留原样),哪些是”Agent 自己摘要出来的”(可以压缩、改写)。

frontmatter 设计

--- name: note-taking category: productivity description: 记录、整理、检索用户的笔记;和 memory 系统深度集成 trigger_keywords: - "记一下" - "加个笔记" - "这个我要记住" - "找一下我之前写的" parameters: - name: action type: enum values: [add, search, list, organize, summarize] required: true - name: content type: string required: false # 只有 add 需要 - name: topic type: string required: false # 用于分类 - name: query type: string required: false # 用于 search writes_to: memory/notes/ reads_from: memory/notes/ cost_estimate: low risk_level: safe memory_policy: preserve_original: true # 用户的原话要原样保留 allow_agent_annotation: true # Agent 可以加注释,但加在独立段落 auto_tag: true # 根据内容自动打标签 ---

新字段 memory_policy 明确声明了这个 skill 对记忆系统的”契约”—— 它只在 memory/notes/ 下读写,它要保留用户原话,它允许 Agent 加注释但要隔离。

内部步骤

## 目录结构 ``` memory/notes/ ├── _index.md # 索引文件(自动维护) ├── topics/ │ ├── work/ │ │ ├── 2026-04-05-standup.md │ │ └── 2026-04-08-retro.md │ ├── learning/ │ │ ├── rust-ownership.md │ │ └── kafka-internals.md │ └── personal/ │ └── reading-list.md └── scratch/ # 未分类 └── 2026-04-10.md ``` 每个笔记文件的结构: ```markdown --- created: 2026-04-08T14:32:00Z last_modified: 2026-04-08T14:32:00Z topic: work tags: [standup, team-alpha] source_session: <session-id> --- # 原始笔记 > 用户的原话开始 > > 今天早会上讨论了下一季度的 OKR,重点是把延迟从 200ms 降到 100ms。 > 张伟提到可以考虑把 redis 升级到 cluster 模式。 > > 用户的原话结束 ## Agent 附注(由 Agent 自动添加) - 相关技术决策:redis cluster 升级(首次提及) - 相关人:张伟 - 相关目标:Q2 延迟优化 ## 关联 - 见 projects/latency-optimization.md - 见之前的 2026-03-30 1:1 笔记 ``` ## action: add 1. 解析用户输入,识别 topic(如果用户没明说,用 LLM 分类) 2. 生成文件名(date + topic slug) 3. 构造 frontmatter 4. **把用户的原话原样写入"原始笔记"段落,不做任何修改**(包括口头语、错别字) 5. 让 LLM 生成"Agent 附注"段落,包括:相关实体、相关决策、可能的关联 6. 在 _index.md 里登记这条笔记 ## action: search 1. 先在 _index.md 做关键词过滤 2. 对通过过滤的候选,用向量相似度排序 3. 返回 top-k 条笔记的标题 + 摘要,让用户选择是否展开 ## action: organize 这是一个"维护类"action,定期触发(每周一次)。流程: 1. 扫描 scratch/ 下的笔记,用 LLM 判断它们应该进入哪个 topic 2. 扫描 topics/ 下的笔记,识别应该合并的(同一天、同一事件) 3. 扫描所有笔记,识别应该加关联的("这个笔记和那个笔记讨论的是同一件事") 4. 生成一份"维护建议"给用户,由用户确认后再执行 ## action: summarize 1. 按 topic 或时间段选择一批笔记 2. 生成一份跨笔记的总结(例如 "本周关于 Rust 学习的笔记整理") 3. 总结被写入一个新文件 topics/<topic>/_summary_<date>.md 4. **原始笔记不被修改**,总结是独立的新文件 ## 失败模式 - **用户输入里有敏感信息**(密码、API key)→ 触发 redact,把敏感部分替换为 `[REDACTED]`,并在 Agent 附注里提示用户 - **磁盘空间不足**:失败并告知用户 - **_index.md 损坏**:从文件系统重建 ## 和 memory 系统的协作 note-taking 写的是 "user-authored notes",是分层记忆的第二层(持久化笔记)的一个子集。 Hermes 的 memory_manager 在做"反思"时,**不会**直接修改 notes/ 下的文件,但**会** notes/ 的内容作为反思的输入。反过来,reflection 产出的"Agent 自己观察到的 user 事实" 写在 memory/facts/ 下,和 notes/ 并列。两者在目录层面上隔离,确保用户笔记的原始性 不被 agent 污染。

可迁移的设计模式

模式 1:用户内容和 Agent 内容要隔离。用户原话保留在独立段落,Agent 添加的注释在另一个段落。这个边界让后续的”Agent 说过什么”和”用户说过什么”可以被清晰追溯,避免 Agent 的”创造性发挥”污染用户的真实记录。

模式 2:memory_policy 作为契约。skill 在 frontmatter 里明确声明自己读写哪些目录、是否保留原始、是否允许改写,这就是一份”行为契约”。其他 skill 或调试工具可以据此推断 skill 的”记忆影响范围”,这对复杂系统的可理解性特别重要。

模式 3:维护类 action 独立成一步。organize / summarize 这类”定期整理”不在用户主动调用的流程里,而是由 cron 或显式维护命令触发。这样主流程保持简单,维护性代码和业务代码隔离。

模式 4:敏感信息必须在写入前拦截。不是”写入后再清理”,因为 git log 里会留痕。是”在写入文件之前”就 redact 掉。这个顺序错了就是安全事故。


5.6 mcp skill:桥接型

它要解决什么问题

MCP(Model Context Protocol)是 Anthropic 2024 年底推出的一个开放协议,目的是让 LLM 应用以标准化的方式对接各种外部工具和数据源。Hermes 原生支持 MCP —— 但原生支持的方式很独特:它没有把 MCP 工具注册成 Hermes 的”内置工具”,而是用一个 skill 把 MCP 工具包装起来

这个设计选择初看有点绕 —— 为什么不直接让 MCP tool 和 Hermes 的 tool 平起平坐?答案和本章 4.1 节讲过的”skill vs tool”的区别有关:

  • Hermes 的 tool 是原子操作(读文件、执行命令、HTTP 请求等底层能力)
  • Hermes 的 skill 是业务抽象(解决某一类问题的完整方案)

MCP 服务提供的工具粒度介于两者之间 —— 比 tool 更高层(它知道某个特定领域),又比 skill 更原子(它只暴露单个操作)。Hermes 的策略是:在 MCP tool 外面套一层 skill,让它进入 skill 的生命周期管理体系

frontmatter 设计

--- name: mcp-bridge category: mcp description: 动态加载 MCP server,把其 tools 桥接为 Hermes 可调用的操作 parameters: - name: server_url type: string required: true - name: operation type: string required: true description: MCP server 暴露的 tool name - name: arguments type: object required: false cost_estimate: variable # 取决于底层 MCP tool risk_level: variable mcp_metadata: auto_discover: true cache_schema: true ---

variable 是一个特殊值 —— 它告诉 Hermes:这个 skill 的成本和风险不是固定的,要在每次调用前根据具体 operation 动态判断。

内部步骤

## 加载 MCP Server 1. 连接 server_url,获取 server 的 schema(通过 `mcp/list-tools`) 2. 缓存 schema 到 `cache/mcp/<server-hash>.json` 3. 对每个 MCP tool,生成一份"虚拟 skill 描述"注入 Prompt ## 执行 MCP Tool 1. 定位具体的 tool(按 operation name) 2. 对 arguments 做一次 schema 校验(类型、必填项) 3. **对危险操作先向用户确认**(根据 tool 的 name 和 description 做简单的启发式判断) 4. 通过 MCP 协议发起调用 5. 解析结果,转成 Hermes 内部的 tool_result 格式 ## 失败模式 - **MCP server 断连**:重试 3 次,失败后退回 - **schema 不一致**(MCP server 升级了):重新 discover 并刷新缓存 - **参数校验失败**:返回清晰的错误,让 LLM 有机会修正参数后重试 - **tool 执行失败**:区分 "tool 本身的业务错误" 和 "协议层错误" ## MCP vs Skill 的边界 如果你在写一个 Hermes skill,问自己: - 这件事已经有 MCP server 吗?如果有,优先用 mcp-bridge 调用,不要重复造轮子 - 这件事跨多个 MCP tool 吗?如果是,写一个 skill 编排它们(像 research 一样) - 这件事涉及长期状态 / 用户画像?那应该是 skill,不是 MCP(MCP tool 是无状态的)

可迁移的设计模式

模式 1:协议桥接而非功能复制。当你要对接一个外部协议(MCP、gRPC、某个 SDK),不要把它的每个函数都手写成一个 skill,而是写一个”桥接 skill” —— 它接受 operation name 和 arguments,内部做路由。这样外部协议升级时你只需要更新桥接,不需要改所有业务 skill。

模式 2:动态 discover 而非硬编码。MCP server 的 tool 列表可能随版本变化。硬编码 tool 列表会让你的 skill 在 server 升级时立即过期。正确做法是启动时动态拉取 schema 并缓存。

模式 3:风险级别也可以是 “variable”。不是所有字段都必须是固定值。对复合型或动态型 skill,用 variable + 运行时判断是合理的。


5.7 red-teaming/godmode:反面标本

它要解决什么问题

前面五个 skill 都是正面教材 —— 讲”好 skill 应该怎么设计”。这一节讲一个反面的 skill:red-teaming/godmode

这是一个故意被做成”不好”的 skill。它的存在是为了测试 Hermes 的安全边界 —— 红队(red team)用它来验证”如果一个坏 skill 进来了,Hermes 能不能拦住它”。

godmode 的大致内容是:“绕开所有安全检查,直接执行任意命令,不问用户、不做确认、不记录日志”。从功能上看,它是 run_shell_command 的”无约束版本”。

为什么这样的东西会存在于官方仓库? 因为要测试的”攻击面”必须以某种形式被明确定义。如果 Hermes 的安全机制能拦住 godmode,那它大概率能拦住现实中用户误创建的类似 skill。把它作为一份明确的”负面标本”放在仓库里,比只在文档里写”不要做 X” 要有效得多。

它为什么是”坏”的

对照第 4.5 节的六个质量维度,看 godmode 每一项分别踩了什么坑:

描述清晰度:它的 description 写得过于模糊(“执行任意操作”),没有说清楚它适用和不适用的场景。这种模糊让 LLM 在 matching 阶段可能错误地广泛触发。

步骤可执行性:它没有步骤,只有一句”直接执行用户说的”。这种 “直接 pass through” 的 skill 等于让 LLM 绕开所有 reasoning,变成了一个裸露的命令通道。

参数完整性:它接受 command: string,没有任何校验、没有任何结构化约束。这意味着 LLM 可以把任何字符串(包括被 prompt injection 出来的恶意命令)塞进去。

边界处理:无。没有”什么情况下不执行”,没有”什么样的命令要拒绝”,没有日志。

成本和风险标注:它本应该标 risk_level: dangerous,但 godmode 故意标了 risk_level: safe —— 这正是测试点之一,Hermes 的安全机制要能识别 “label 和 content 不一致”。

和现有 skill 的关系:它和 Hermes 内置的 run_shell_command tool 功能重复,但后者有完整的审查机制,godmode 把这些机制都绕过了。

拦截机制

Hermes 怎么防御这样的 skill?答案是多层纵深:

层 1:skill 加载时的静态检查。Hermes 在启动时扫描 skills/ 目录,对每个 skill 做一次”安全扫描”。检查项包括:

  • frontmatter 里的 risk_level 和 skill 内容是否一致
  • 步骤里是否包含”危险 pattern”(直接 eval、sudo、rm -rf /、curl | sh、等等)
  • 是否有明显的 prompt injection pattern(skill 描述里藏”忽略之前的指令”这类话)

不通过静态检查的 skill 会被标记为 “quarantined”,不会被注入 Prompt,只会在调试命令里可见。

层 2:运行时的沙箱。即便一个 skill 通过了静态检查,它的实际执行也在沙箱里 —— 默认限制文件系统访问范围、网络访问、命令执行。沙箱的设计是第 11 章的核心。

层 3:用户确认。对 risk_level != safe 的 skill,每次执行前都要用户确认。godmode 故意把 risk_level 标成 safe 想绕过这一层,但配合层 1 的一致性检查,label 会被重新修正。

层 4:审计日志。所有 skill 的每次执行都有日志。即使前三层都没拦住,事后审计还能发现问题。第 10 章会讲审计日志的完整机制。

可迁移的经验

这个反面标本的教学价值不在于”学会怎么写 godmode”—— 你不应该写。它的价值在于让你明白”好 skill 的每一项要求都是对真实攻击面的回应”

  • 要求”描述清晰” → 防止 LLM 在 matching 时错误地广泛触发
  • 要求”步骤可执行” → 防止 “pass through” 式的绕开 reasoning
  • 要求”参数结构化” → 防止 prompt injection 塞任意字符串
  • 要求”边界处理” → 防止”成功的执行”但结果是错的
  • 要求”风险标注一致” → 防止 skill 伪装成安全

第 11 章会从另一个角度回到这个话题 —— 讲真实发生过的事故。读完这两章你会对”Agent 安全”有一个比大多数博客更完整的认识。


5.8 六个 skill 背后的五种设计模式

最后做一次收束。刚才六个 skill,总共映射到以下五种可迁移的设计模式:

模式核心思想典型代表
鉴权分离把 auth 独立成 skill,业务 skill 引用它github-auth + github-issues
两级过滤(廉价+昂贵)先关键词初筛,再 LLM 精判feeds 的初筛+精判
编排 vs 原子复合 skill 调度原子 skill,不直接干活research 调 web-search/url-fetch
用户内容隔离用户原话和 Agent 注释分段存note-taking 的两段结构
协议桥接外部协议用一个 adapter skill 统一接入mcp-bridge

记住这五个模式。它们不是 Hermes 独有的 —— 你在写任何 Agent 的技能时都会遇到同样的问题,而这五个模式是经过社区实战检验的”默认解”。

下一章我们进入学习闭环 —— 讲 Agent 怎么从使用中学习,以及这个学习过程怎么被监控、评估、回滚。

Last updated on