本节摘要:本节是全书高潮的第一站——解剖外循环的物质载体:技能。Hermes 的技能不是私有格式,而是兼容 agentskills.io 开放标准的 SKILL.md 文件:YAML frontmatter(name/description/platforms/metadata)+ markdown 正文,配
references/、templates/、scripts/支撑目录构成"知识包"。仓库内置 82 个技能(14 大类),optional-skills/另有 117 个可选技能(21 大类)。技能进 prompt 走的是渐进披露:系统提示只注入一张"name+60 字符 description"的索引表,正文由模型用skill_view按需加载。本节走读agent/skill_utils.py的 frontmatter 解析、agent/prompt_builder.py的索引组装(双层缓存+快照),并说清技能与工具的本质区别:工具是代码能力(schema+handler),技能是知识程序(指令+资源文件)。
内容来源:原项目源码
agent/skill_utils.py(parse_frontmatter/extract_skill_description/iter_skill_index_files)、agent/prompt_builder.py(build_skills_system_prompt)、tools/skills_tool.py(skills_list/skill_view schema)、agent/learn_prompt.py(技能创作铁律)、skills/与optional-skills/目录、website/docs/user-guide/features/skills.md。
⚠️ 注意:系统提示里的技能索引会把 description 截断到 60 字符(超出部分换
...,实际保留 57 字符)。这不是 UI 装饰而是路由生死线——描述第 61 个字符之后的内容永远不会被模型看到。官方把它列为"被违反最多的规则且绝非装饰性问题"。写技能时 description 必须 ≤60 字符、一句话、以句号结尾。
阅读完本节,你应当能够:
parse_frontmatter 的 BOM 剥离与 YAML 降级解析。build_skills_system_prompt 的双层缓存(进程内 LRU+磁盘快照)与三级技能目录(项目级>本地>外部)。skill_view 首次返回 SKILL.md+linked_files,二次加载按 file_path 取参考文件,以及重复查看的去根机制。agentskills.io 规范的核心思想:一项技能 = 一个目录,目录根必须有 SKILL.md,格式为"YAML frontmatter + markdown 正文"。Hermes 完全兼容该标准(website/docs/user-guide/features/skills.md 原文:"compatible with the agentskills.io open standard")。看一个真实内置技能 skills/smart-home/openhue/SKILL.md:
--- name: openhue description: "Control Philips Hue lights, scenes, rooms via OpenHue CLI." version: 1.0.1 author: community license: MIT platforms: [linux, macos, windows] metadata: hermes: tags: [Smart-Home, Hue, Lights, IoT, Automation] homepage: https://www.openhue.io/cli prerequisites: commands: [openhue] --- # OpenHue CLI Control Philips Hue lights and scenes via a Hue Bridge from the terminal. ## Prerequisites ...(安装命令)... ## When to Use - "Turn on/off the lights" - "Dim the living room lights" ... ## Common Commands ### List Resources openhue get light # List all lights ...
frontmatter 关键字段:name(小写连字符,≤64 字符)、description(≤60 字符的路由钩子)、platforms(OS 门控,如 [macos])、metadata.hermes.tags(检索标签)。正文遵循 agent/learn_prompt.py 里内嵌的 HARDLINE 创作标准——八段式固定顺序:
# agent/learn_prompt.py(节选,_AUTHORING_STANDARDS) # Body section order (omit a section only if it genuinely has no content): # 1. "# <Human Title>" then a 2-3 sentence intro # 2. "## When to Use" — bullet list of concrete trigger phrases. # 3. "## Prerequisites" — exact env vars, install steps, credentials. # 4. "## How to Run" — the canonical invocation, framed through Hermes tools. # 5. "## Quick Reference" — a flat command/endpoint list, no narration. # 6. "## Procedure" — numbered steps with copy-paste-exact commands. # 7. "## Pitfalls" — known limits, rate limits, things that look broken but aren't. # 8. "## Verification" — a single command/check that proves the skill worked.
值得注意的是"Hermes 工具框定"条款:正文必须说"invoke through the terminal tool"、引用 read_file/search_files/patch 而不是 cat/grep/sed——这让技能成为操作程序而非 shell 文档。SKILL.md 之外,一个技能目录还可挂四个支撑子目录:references/(知识参考)、templates/(起步模板)、scripts/(可重跑脚本)、assets/(资源)。skills/research/arxiv/ 就带 scripts/;大块知识(整本书、成摞论文)采用"精瘦 SKILL.md 索引 + 按章 references/ 文件"的知识库形态,参考文件在被 skill_view(file_path=...) 读取之前零成本。
frontmatter 的解析在 agent/skill_utils.py:175:
175 def parse_frontmatter(content: str) -> Tuple[Dict[str, Any], str]: 182 # A single leading UTF-8 BOM (U+FEFF) is stripped before parsing. Windows 183 # GUI editors (Notepad, PowerShell ``>``) prepend one when saving a SKILL.md 184 # as UTF-8 ... Left in place, the BOM defeats the ``---`` fence 185 # check below and the whole frontmatter is silently discarded — name, 186 # description, ``platforms`` gating, env-var setup, and conditional 187 # activation all vanish. 189 if content.startswith("\ufeff"): 190 content = content[1:] 193 if not content.startswith("---"): 194 return frontmatter, body 196 end_match = re.search(r"\n---\s*\n", content[3:]) ... 207 try: 208 parsed = yaml_load(yaml_content) # CSafeLoader 全量 YAML 211 except Exception: 212 # Fallback: simple key:value parsing for malformed YAML
两个工程细节:先剥单个前导 BOM(Windows 记事本保存的文件会因此整块 frontmatter 失效);YAML 解析失败时降级为逐行 key:value 切分——宁可用残缺元数据也不让技能消失。
skills/ 是随安装播种的内置库,15 个目录中 index-cache/ 是索引缓存,真正的技能分类 14 个,共 82 个 SKILL.md:
| 分类 | 数量 | 代表技能 |
|---|---|---|
| productivity | 18 | 日程、任务、剪贴板工作流 |
| creative | 17 | manim-video、comfyui、excalidraw、p5js |
| software-development | 11 | 代码审查、测试、git 工作流 |
| research | 8 | arxiv、blogwatcher、grounded-citations |
| github | 8 | PR 审查、issue 分诊 |
| autonomous-ai-agents | 7 | hermes-agent(自配置技能) |
| apple | 5 | apple-notes、imessage、findmy |
| mlops | 5 | 微调、数据管线 |
| media | 4 | 音视频处理 |
| 3 | 邮件工作流 | |
| note-taking | 2 | 笔记同步 |
| smart-home | 2 | openhue 灯控 |
| social-media | 2 | 发帖工作流 |
| devops | 1 | 部署 |
optional-skills/ 是不随安装播种的可选库,21 个分类共 117 个技能,重的领域单独成类:mlops 31 个(各云平台的训练部署配方)、research 12、creative 15、finance 9(记账、税务、投资);还有区块链 3、游戏 2、健康 2、支付 3,乃至 yuanbao/(元宝平台接入)1 个、migration/(从其他 agent 迁移)1 个。两级库的差别只在播种策略:内置技能在新 profile 创建时复制进 ~/.hermes/skills/ 并被 curator 视为可归档候选(第 4-02 节),可选技能需显式安装;运行时两者都活在同一个 ~/.hermes/skills/ 主目录——"the primary directory and source of truth"。
82 个技能全文塞进系统提示是不可接受的,Hermes 的解法是渐进披露(progressive disclosure)两级:第一级,系统提示注入压缩索引(agent/prompt_builder.py:1895 的 build_skills_system_prompt);第二级,模型调 skill_view(name) 拉全文,再调 skill_view(name, file_path="references/api.md") 取支撑文件。索引渲染的核心(prompt_builder.py:2233 起,节选):
2233 index_lines = [] 2234 for category in sorted(skills_by_category.keys()): ... 2240 for name, desc in sorted(skills_by_category[category], ...): ... 2245 index_lines.append(f" - {name}: {desc}") 2247 result = ( 2248 "## Skills (mandatory)\n" 2249 "Before replying, scan the skills below. If a skill matches or is even " 2250 "partially relevant to your task, you MUST load it with skill_view(name) " 2251 "and follow its instructions. " ... 2271 "<available_skills>\n" 2272 + "\n".join(index_lines) + "\n" 2273 "</available_skills>\n"
desc 从哪来?skill_utils.py:1184:
1175 SKILL_PROMPT_DESC_LIMIT = 60 ... 1184 def extract_skill_description(frontmatter: Dict[str, Any]) -> str: 1186 desc = _normalize_skill_description(frontmatter) 1187 if not desc: 1188 return "" 1189 if len(desc) > SKILL_PROMPT_DESC_LIMIT: 1190 return desc[:SKILL_PROMPT_DESC_LIMIT - 3] + "..." 1191 return desc
build_skills_system_prompt 是全库最讲究性能与稳定性的函数之一:①两层缓存——进程内 LRU dict 按(技能目录/工具集/平台/禁用表)为键,外加磁盘快照 .skills_prompt_snapshot.json 用 mtime+size 清单校验,冷启动才全量扫描文件系统;②三级目录优先级——项目级(./.hermes/skills,仓库自带技能影子同名本地技能)>本地 ~/.hermes/skills/>外部目录(skills.external_dirs,只读);③可见性门控——platforms 不匹配当前 OS 隐藏,requires_tools/fallback_for_tools 条件激活(第 3 章的 registry 信号在这里接上),org 镜像目录 _org/ 做 token 门控;④绝不整体隐藏——编码姿态下无关分类只降级为"names only"一行,技能名永远可见,"agent-created skills are the model's project memory"。第二级的 skill_view 同样精细(tools/skills_tool.py:1999 的 schema 描述):首次返回 SKILL.md 全文加 linked_files 字典;重复查看未变更文件时返回短桩("unchanged since loaded earlier")省 token,上下文压缩后自动失效重发全文。
第 3 章的工具是 registry.register(name, schema, handler) 注册的代码能力:模型看到的只有 JSON schema,执行发生在宿主 Python 进程,模型无权修改。技能是 ~/.hermes/skills/ 下的数据文件:模型通过 skill_view 读、通过 skill_manage 写,内容是自然语言指令+资源文件。三点对照:
| 维度 | 工具(133 个) | 技能(82+117 个) |
|---|---|---|
| 本体 | Python 函数+schema | SKILL.md 目录包 |
| 进入 prompt | 工具 schema 列表 | name+60 字符索引 |
| 成本模型 | 每请求都带 schema | 命中才加载全文 |
| 谁能改 | 宿主开发者 | 模型(agent)与用户皆可 |
| 承载 | 动作能力(terminal/browser) | 程序性知识(怎么做事) |
关键在最后一行:工具的集合在运行期是冻结的,技能集合是活的——这正是外循环成立的前提。技能索引注入的那段 "## Skills (mandatory)" 还埋了三句外循环的钩子指令:"If a skill has issues, fix it with skill_manage(action='patch')"(使用中改进)、"After difficult/iterative tasks, offer to save as a skill"(从经验创建)、"If a skill you loaded was missing steps ... update it before finishing"(离场前修正)——下一节的 curator 与后台评审就是这三句话的制度化。
💡 循环要点:技能系统的全部设计围绕一个不等式:索引成本恒定(约每个技能一行),知识容量无界(references/ 任意深)。渐进披露让"多学一项技能"的边际 prompt 成本趋近于零,而 SKILL.md 作为普通文件可被 agent 自己读写——知识与知识的生产关系被统一在同一种介质上。这是外循环能在不膨胀上下文的前提下持续积累的物理基础。
references/ 知识、templates/ 模板、scripts/ 脚本、assets/ 资源;知识库形态=精瘦索引+按章参考。~/.hermes/skills/。parse_frontmatter 剥 BOM+YAML 降级解析;description 截 60 字符是路由生死线。skill_view 两级加载+未变更去重;技能是模型可读写的数据,工具是模型只可调用的代码。下一节进入外循环的中枢神经:curator 如何管理技能的创建→评审→启用→退役全生命周期,skill_ledger 如何为每次变更记账、usage 如何统计、provenance 如何溯源。