附录 H|Skills 运行时专章:Hermes 为什么不把技能直接塞进 system prompt
先问为什么技能不直接塞进 system prompt
很多人第一次给 Agent 加“技能”时,最自然的做法通常是:
- 写一段方法说明
- 把它拼进 system prompt
- 让模型每轮都看到
如果技能数量很少,这种做法确实能跑。
但只要系统开始变大,你马上会遇到几个现实问题:
- skill 一多,system prompt 会越来越肥
- 很多 skill 其实当前轮根本用不上,却一直占上下文
- 不同平台、不同工具面下,某些 skill 根本不该出现
- skill 可能还有
references/、templates/、scripts/这类配套文件,不可能全塞进 prompt - skill 还会经历搜索、安装、禁用、升级、隔离、信任判断这些工程动作
这时候你就会发现:
skill 本质上不是一段“提示词附录”,而是一类运行时能力对象。
Hermes 在这件事上的做法非常值得学习智能体的人认真看。
因为它没有把 skills 当成一堆长期常驻的提示词块,而是拆成了三层:
- system prompt 里放的是 skills 索引
- 真正调用时才把具体 skill 装配成一条消息
- supporting files、安装来源、trust level、size limit 这些问题交给独立运行时设施处理
这一篇主要结合这些源码和测试文件来看:
agent/prompt_builder.pyagent/skill_commands.pyhermes_cli/skills_hub.pytests/agent/test_prompt_builder.pytests/tools/test_skills_tool.pytests/tools/test_skill_size_limits.pytests/tools/test_skills_hub.py
1. Hermes 最重要的判断:skills 不是常驻正文,而是“先索引,后加载”
先看 agent/prompt_builder.py 里的 build_skills_system_prompt(...)。
这个函数的注释写得很明确:
- 它要 build a compact skill index
- 它有两层 cache
- 它会扫描本地 skills 和 external dirs
注意这里的关键词不是 skill content,而是 skill index。
这件事非常关键。
因为它说明 Hermes 从一开始就在避免一个很常见的错误:
把所有 skill 正文长期塞进 system prompt。
Hermes 的做法不是:
- “模型先把所有技能全文都背下来”
而是:
- “模型先知道现在有哪些技能可用”
- “如果某个 skill 明显匹配任务,再用
skill_view(name)去加载它”
你看 build_skills_system_prompt(...) 最后生成的内容就会发现,它明确告诉模型:
- 先 scan the skills below
- 如果匹配,就
skill_view(name) - 如果 skill 有问题,再
skill_manage(action='patch') - 如果没有匹配项,就正常继续
也就是说,Hermes 给模型的不是一堆已经展开的技能正文,
而是一张可导航的技能地图。
对于初学 Agent 的人,这里最值得学的不是“怎么列目录”,而是这背后的工程判断:
真正稳定的 Agent,不应该让 system prompt 永远背着全部能力细节,
而应该让 system prompt 只承担“能力发现入口”的职责。
2. build_skills_system_prompt(...) 的核心价值,是把 skill 变成一份可筛选、可缓存、可治理的索引
继续看 agent/prompt_builder.py,你会发现这份 skills 索引不是随便扫一下目录就结束了。
它背后至少做了四层工程处理。
2.1 先做 cache,不让每次都重扫
Hermes 为 skills prompt 做了两层缓存:
- 进程内 LRU cache:
_SKILLS_PROMPT_CACHE - 磁盘 snapshot:
.skills_prompt_snapshot.json
而且 snapshot 不是盲信,它还会用 _build_skills_manifest(...) 校验:
SKILL.mdDESCRIPTION.md- mtime
- size
这说明 Hermes 已经把“技能索引”当成 system prompt 基础设施,而不是一个便宜的字符串拼接动作。
为什么这点重要?
因为一旦你的 Agent 进入真实运行,system prompt 的稳定性和装配成本都很敏感。
Hermes 在这里的判断很成熟:
- skill 索引需要稳定
- 但又不能一直读旧数据
- 所以需要 manifest + snapshot 这种折中机制
这已经不是“提示词写作”范畴,而是典型的 runtime engineering。
2.2 不是所有 skill 都该出现
build_skills_system_prompt(...) 还会做多种过滤:
- 平台过滤:
skill_matches_platform(...) - 用户禁用过滤:
get_disabled_skill_names() - 条件显示过滤:
_skill_should_show(...)
其中 _skill_should_show(...) 这一层特别值得看。
它支持的不是简单 on/off,而是:
fallback_for_toolsetsfallback_for_toolsrequires_toolsetsrequires_tools
这代表 Hermes 在认真回答一个高级但真实的问题:
某个 skill 是否应该在此刻对模型可见,不只取决于它是否存在,
还取决于当前 runtime 有没有更原生的工具可用。
举个最直观的例子:
- 如果
webtoolset 已经可用,那某个“DuckDuckGo 兜底 skill”就应该隐藏 - 如果某个 skill 明确要求
terminaltoolset,而当前运行面没有 terminal,它也不该出现在索引里
tests/agent/test_prompt_builder.py 里就专门把这些规则一条条钉死了:
- fallback skill 在主工具存在时必须隐藏
- requires skill 在工具缺失时必须隐藏
- 无条件 skill 始终可见
- 不传过滤参数时,为了兼容旧逻辑,仍然展示全部
这说明 skills index 不是静态目录,而是会跟着 runtime 条件变化的动态可见面。
2.3 skill 不是只有本地目录
build_skills_system_prompt(...) 还会扫描:
- 本地
skills_dir - external skill directories
而且本地 skill 优先级更高,外部目录同名时会被跳过。
这件事说明 Hermes 对 skill 的理解已经不是“项目里附带几篇说明文档”,而是:
一套允许本地、自定义、外部来源共同参与的能力层。
2.4 category 也是正式结构,不是随便分文件夹
Hermes 还会读取分类级别的 DESCRIPTION.md,把 category description 一起放进 skills index。
于是模型看到的不是一串平铺名字,而是:
- category
- category description
- skill name
- skill description
这会直接提升模型做“先判断有没有可复用 skill”这一步时的命中率。
所以这一层的重点不是美观,而是检索效率。
3. Hermes 真正激活 skill 的方式,不是扩写 system prompt,而是注入一条运行时消息
如果 prompt_builder.py 解决的是“先给模型看见 skill 地图”,
那 agent/skill_commands.py 解决的就是:
当用户或系统真的要用一个 skill 时,Hermes 怎么把它装到当前回合里。
这里最值得看的函数有两个:
_load_skill_payload(...)_build_skill_message(...)
3.1 _load_skill_payload(...) 说明 skill 首先是可加载对象
这个函数会做几件事:
- 接受 skill 名称或路径
- 归一化成 skill identifier
- 调
tools.skills_tool里的skill_view(...) - 返回
loaded_payload、skill_dir、display_name
这说明 Hermes 调用 skill 的第一步,不是把某段 prompt 模板直接拼进去,
而是先把 skill 当成一个可解析、可读取、可返回元数据的对象来处理。
也就是说,skill 在 Hermes 里先是“资源”,后才是“提示内容”。
3.2 _build_skill_message(...) 说明 skill 被注入成消息,而不是 system prompt 的永久部分
这个函数会把 skill 组装成一段完整的消息载荷,里面包括:
- activation note
- skill 正文内容
- 解析出来的 config 值
- setup note
- supporting files 提示
- 用户在这次 skill 调用里附带的 instruction
- runtime note
这一步非常重要。
因为它直接说明 Hermes 的技能注入策略是:
- skill 只在需要的时候进入上下文
- 进入上下文的形态是一条明确的运行时消息
- 它服务的是当前任务,不是永久污染 system prompt
这和附录 B 里那条 Prompt Builder 流水线刚好接上。
也就是说:
- system prompt 负责长期稳定的能力索引
- skill message 负责当前回合的按需展开
这个边界感特别值得学。
很多初学者做 Agent 时,容易把“全局设定”和“局部任务提示”揉成一层。
Hermes 则把它们拆得很清楚。
4. Supporting files 的设计,说明 Hermes 从一开始就没打算把 skill 全文一次性灌进上下文
继续看 _build_skill_message(...),你会发现一个很关键的细节:
如果 skill 目录里有这些内容:
references/templates/scripts/assets/
Hermes 不会把它们全文一股脑注进去。
它做的是:
- 把 supporting files 列出来
- 明确提示模型可以用
skill_view(name, file_path=...)按需查看
这一步其实就是在告诉我们:
Hermes 把 skill 视为一个小型知识包,而不是一段单文件 prompt。
为什么这很重要?
因为真实世界里的高质量 skill,往往不只有“怎么做”的描述,还会有:
- 参考文档
- 模板片段
- 辅助脚本
- 静态资源
如果你把这些都常驻塞进上下文:
- token 浪费会非常严重
- 大量无关材料会稀释当前任务
- 真正重要的信息反而不容易被模型抓到
Hermes 的做法明显更像一个成熟 runtime:
- 先把主技能正文加载进来
- 再把配套资源作为可延迟读取对象暴露出来
tests/tools/test_skills_tool.py 也在守这个行为:
skill_view("my-skill")能正常返回正文skill_view("my-skill", file_path="references/api.md")能读取指定引用文件linked_files会在查看 skill 时被显式返回- disabled skill 不允许被查看
这些测试说明 supporting files 不是“目录里碰巧有点别的文件”,
而是 skill runtime 的正式组成部分。
5. hermes_cli/skills_hub.py 说明 skills 还是一个供应链问题,而不只是提示词问题
如果只看 prompt_builder.py 和 skill_commands.py,你可能会觉得:
skills 主要是“运行时怎么发现、怎么加载”的问题。
但 hermes_cli/skills_hub.py 会把视角再往外推一层。
这个文件里能看到的动作包括:
searchbrowseinspectinstall
而且每个 skill 还会带上:
- source
- identifier
- trust level
- install metadata
- security audits
这说明 Hermes 已经明确把 skill 当成一个“能力分发与治理对象”来对待。
这一步非常像现代软件包管理,而不像“多存几段 prompt 文案”。
比如在 do_search(...)、do_browse(...) 这些函数里,Hermes 会展示:
- skill 名称
- 来源站点
- trust level
- identifier
在 _resolve_short_name(...) 里,如果同名 skill 有多个来源,它不会替你草率决定,
而是要求你用完整 identifier 指定。
这件事非常工程化。
因为一旦 skill 开始来自:
- official
- skills.sh
- GitHub
- community source
那问题就不再只是“有没有这篇 skill”,而会变成:
- 从哪里来
- 信不信它
- 装的是不是同一个
- 以后怎么升级或隔离
tests/tools/test_skills_hub.py 也在验证这些供应链能力:
- skills.sh 搜索结果会映射成带前缀的 identifier
- inspect/fetch 会代理到底层 source,再重新标记来源
- trust level 会区分 trusted 和 community
- 搜索结果需要去重和排序
所以从 Hermes 的实现看,skills 绝不是 prompt 的附属品,
而更像 Agent 世界里的“能力包管理系统”。
6. size limit、disabled、platform filter 这些约束,进一步说明 skills 是运行时资产
很多人做 Agent 时,对 skill 的理解还停留在“多一篇 Markdown 而已”。
但 Hermes 的测试已经说明,它根本不是这么看的。
6.1 平台过滤说明 skill 有环境适用性
tests/agent/test_prompt_builder.py 里专门测试了:
platforms: [macos]的 skill 在 Linux 上不该出现- 在 macOS 上则应该出现
这说明 Hermes 认为 skill 不是抽象知识,而是和运行环境强相关的能力说明。
6.2 disabled skill 说明能力面可以被治理
无论在 build_skills_system_prompt(...) 还是 skill_view(...),
Hermes 都会尊重 disabled skills 配置。
这代表 skills 不是“只要放进目录,模型就一定看得到”,
而是一个可以被用户主动裁剪的能力面。
6.3 size limit 说明 Hermes 区分“创作入口”和“运行时读取入口”
tests/tools/test_skill_size_limits.py 特别值得一看。
它验证了两件表面矛盾、实际上很成熟的事:
- agent 通过
skill_manage(create/edit/patch/write_file)写 skill 时,要受大小限制 - 但人工放进去或 hub 安装进来的超大 skill,
skill_view(...)仍然可以读取
这背后其实是 Hermes 很清楚地区分了两种路径:
- 生成路径要限流,防止 Agent 自己写出失控的大文件
- 运行时读取路径不能假定所有现有 skill 都很小
这是一种非常典型的工程判断。
如果不这样分,你就会在两个极端之间摇摆:
- 要么放任 Agent 不断生成巨型技能包
- 要么让 runtime 因为“大 skill 不合规范”而读不出来
Hermes 选择的是:
- 对写入做约束
- 对读取做兼容
这说明它把 skill 当成真实运行资产,而不是理想化模板。
7. 对学习智能体的人来说,这套设计最值得记住的四个原则
看完这一整套实现,你会发现 Hermes 在 skills 这件事上其实反复守着几个原则。
7.1 全局层放索引,局部层放正文
system prompt 适合放稳定、轻量、长期有效的能力地图。
具体 skill 正文应该在命中任务时按需展开。
7.2 能力暴露必须跟 runtime 条件对齐
某个 skill 是否出现,不只看它在不在磁盘里,
还要看平台、工具面、用户配置、禁用状态。
7.3 supporting files 应该按需读取,而不是默认常驻
高质量 skill 往往是一个包,不是一段纯文本。
所以读取策略必须分层,而不是一次性灌入上下文。
7.4 skill 终究是供应链对象
只要 skill 可以搜索、安装、共享、升级、禁用,它就不是 prompt 附件,
而是 Agent 生态里的能力包。
最后把技能运行时收住
如果把这一篇压缩成一句话,那就是:
Hermes 没把 skill 当成“system prompt 的补丁包”,
而是把它做成了“可发现、可筛选、可按需加载、可治理的运行时能力对象”。
所以在 Hermes 里,skills 这件事其实被拆成了三段:
agent/prompt_builder.py负责给模型一张稳定的能力地图agent/skill_commands.py负责把命中的 skill 装配成当前任务可用的消息载荷hermes_cli/skills_hub.py负责 skill 的搜索、安装、来源和信任治理
这也是为什么 Hermes 的 skills 体系看起来不像“多加了几篇 Markdown”,
而更像一个真正会生长、会治理、会适配运行环境的 Agent 能力层。
对于正在学习智能体的人,这一层非常值得抄。
因为它提醒我们:
当你的 Agent 开始变复杂时,skill 的本质就不再是提示词技巧,
而是运行时架构设计。