技能系统 Skills
职责
Skill 是比 Tool 更高阶的能力单元:一段可复用的、带 frontmatter 的流程(类似 agentskills.io 规范),按领域归类在 skills/<category>/ 下。与工具的「静态函数」不同,技能可被学习闭环创建、改写、精炼,是 agent「越用越聪明」的载体。技能通过 skills_tool 暴露给主循环调用。
设计动机
为什么 skill 要按 category 分目录而不是平铺?技能数量会随使用增长(用户加的、hub 同步的、学习循环生成的都在 ~/.hermes/skills/),平铺后 skills_list 返回的列表很快变成噪音。category 让 progressive disclosure 第一层只暴露「这有一组 research 技能」而不是 50 条具体技能名。每个 SKILL.md 是单文件即定义(带 frontmatter),配合 skills/<category>/<skill>/references/ 的子文件做按需加载,token 不会一次性爆。
bundle 是另一层封装:把多个技能用一个 slash 命令加载,因为「后端开发」这种场景要同时挂上 code review、TDD、PR 三个技能,逐个 /skill 太碎。
关键文件
skills/ 目录— 按领域分组(apple、computer-use、research、software-development、yuanbao…),每个技能长这样:
skills/
├── my-skill/
│ ├── SKILL.md # 主指令(必需)
│ ├── references/ # 支撑文档
│ │ ├── api.md
│ │ └── examples.md
│ ├── templates/ # 输出模板
│ └── assets/ # 附件(agentskills.io 标准)
└── category/
└── another-skill/
└── SKILL.mdSKILL.md 顶部是 YAML frontmatter(name/description/version/platforms/prerequisites…),下面是正文指令。name 限 64 字符、description 限 1024,这两个字段是 progressive disclosure 第一层 skills_list 唯一返回的内容,保 token。
skills_tool— 把技能作为工具暴露给主循环。它注册了两个工具:
registry.register(
name="skills_list",
toolset="skills",
schema=SKILLS_LIST_SCHEMA,
handler=lambda args, **kw: skills_list(
category=args.get("category"), task_id=kw.get("task_id")
),
check_fn=check_skills_requirements,
emoji="📚",
)
registry.register(
name="skill_view",
toolset="skills",
schema=SKILL_VIEW_SCHEMA,
handler=_skill_view_with_bump,
check_fn=check_skills_requirements,
emoji="📚",
)skills_list 只返回 name + description + category(第一层 progressive disclosure);skill_view 加载完整 SKILL.md(第二层);reference/template 等支撑文件按需 skill_view(name, file_path="references/api.md") 拉(第三层)。_skill_view_with_bump 在成功后调 bump_view / bump_use 更新使用计数,curator 的 stale 计时器据此清理没人用的技能。
skill_manager_tool— 技能管理(列表/查看/启用)skills_hub— 技能中心同步(agentskills.io 兼容)skills_sync— 本地与远端技能同步skill_bundles— 技能束打包,注入到提示。bundle 是~/.hermes/skill-bundles/*.yaml里的小文件:
name: backend-dev
description: Backend feature work — code review, testing, PR workflow.
skills:
- github-code-review
- test-driven-development
- github-pr-workflow
instruction: |
Optional extra guidance to inject above the skill bodies./<bundle-name> 调用 build_bundle_invocation_message 把所有成员技能的 SKILL.md 拼进一条 user message。bundle 与 skill 同名时 bundle 优先——用户显式起了同名就是想覆盖那条碰巧撞名的技能。缺失的成员技能不报错,只在结果里附「哪些被跳过」的提示。
skill_preprocessing— 技能内容预处理。负责替换${HERMES_SKILL_DIR}/${HERMES_SESSION_ID}模板变量,以及执行!date +%Y-%m-%d`` 这种内联 shell snippet。内联 shell 失败返回[inline-shell error: ...]标记而不是抛异常,一个坏 snippet 不会拖垮整条技能消息。技能节点与边:125-193—build_skill_nodes/build_edges,把技能组织成学习图
数据流
- 启动期
skill_preprocessing(agent/skill_preprocessing.py)扫描skills/<category>/,读 frontmatter。 skill_bundles(agent/skill_bundles.py)把相关技能打包,经build_turn_context:268注入本轮提示。- provider 决定调用某技能 → tool_call
skills→tools/skills_tool.py加载并执行该技能的流程。 - 执行结果回填主循环。
- 学习闭环在后台审视本轮,可能:
- 创建新技能(
learning_mutations) - 把新技能登记进学习图
agent/learning_graph.py:125 - 通过
tools/skills_sync.py同步到技能中心
- 创建新技能(
边界与失败
- 路径穿越:skill name 会被拼接到
~/.hermes/skills/上找文件,_skill_lookup_path_error拒绝..段、绝对路径、Windows 盘符。否则name="../outside"能读到 skills 目录之外的文件。 - prompt 注入:SKILL.md 内容里有「ignore previous instructions」「system prompt:」「]]>」等模式会被
_INJECTION_PATTERNS标记。第三方技能(从 hub 同步的)是不可信内容,主循环加载时要 sanitize。 - bundle 缺成员:
build_bundle_invocation_message对缺失/禁用的成员技能只记到missing/disabled列表里,不报错——bundle 是宽容加载,少几个不影响其余技能。 - 缓存陈旧:扫描
skills/是 O(#dirs) stat,签名包含目录 mtime + disabled 集合 + 平台。但单文件SKILL.md的就地编辑只 bump 自身 mtime,目录签名看不到,所以额外加了 30s TTL 兜底。
小结
技能 = 可演化的高阶流程,frontmatter 描述、文件即定义、学习闭环可改写。它与工具的关系:工具是技能流程里调用的原子,技能是「怎么组合工具完成一类任务」的知识。技能的演化机制见学习闭环。