Skip to content

附录 I|Tool 与 Toolset 和 Skill 的边界:Hermes 为什么要把能力拆成三层

先把三层能力边界切开

很多刚开始学 Agent 的人,第一次看一个复杂项目时,经常会把这三样东西混在一起:

  • tool
  • toolset
  • skill

看起来它们都像“让 Agent 更会做事的能力”,于是就很容易产生一种模糊理解:

  • 反正都是能力,放一起也差不多
  • 多加几个 tool 就等于多几个 skill
  • toolset 只是分组名字,没什么本质意义

但如果你带着这种理解去看 Hermes,很容易看晕。

因为 Hermes 明显不是把这三者混着设计的。

它做的是三层分工:

  • tool:真正执行动作的原子能力
  • toolset:控制当前会话暴露哪些动作能力
  • skill:把可复用的方法论、流程和经验打包成按需加载的策略单元

这篇附录就专门回答一个对初学者非常关键的问题:

Hermes 为什么不把“能力”只做成一层,而要拆成 tool、toolset、skill 三层?

这一篇主要结合这些源码和测试文件来看:

  • tools/registry.py
  • model_tools.py
  • toolsets.py
  • run_agent.py
  • agent/prompt_builder.py
  • tests/test_model_tools.py
  • tests/tools/test_delegate_toolset_scope.py
  • tests/agent/test_prompt_builder.py

1. Hermes 最重要的判断:动作能力、能力暴露、经验复用,不是同一个问题

如果你先不看具体实现,只看目录结构,其实 Hermes 已经把这三层分得很明显了:

  • tools/ 目录里是一堆具体工具实现
  • toolsets.py 单独定义工具集合与组合方式
  • skills 相关逻辑又单独落在 tools/skills_tool.pyagent/skill_commands.pyagent/prompt_builder.py

这说明 Hermes 从架构上就在避免一个常见错误:

把“Agent 会什么”理解成一个单层列表。

在真实系统里,这其实至少是三个完全不同的问题:

1.1 它到底能执行什么动作

比如:

  • web_search
  • terminal
  • read_file
  • browser_click

这是 action layer,也就是 tool 层。

1.2 当前这一轮到底允许它看到哪些动作

比如:

  • CLI 能不能开 terminal
  • 某个子 Agent 能不能继续 delegation
  • 某个平台是否要隐藏某些交互型工具

这是 capability surface,也就是 toolset 层。

1.3 当它遇到某类复杂任务时,应该遵循什么套路

比如:

  • 调试应该先做什么检查
  • 前端任务要遵循什么风格
  • 某类平台操作有哪些坑

这是 reusable workflow,也就是 skill 层。

Hermes 把这三类问题拆开,最大的好处就是:

每一层都只解决自己那一类复杂度。


2. Tool 在 Hermes 里首先是“可注册、可调度、可校验的动作原语”

先看 tools/registry.py

这个文件头已经把关系说得很直接:

  • 每个 tool 文件在模块加载时调用 registry.register()
  • 注册自己的 schema、handler、toolset membership、availability check
  • model_tools.py 再统一向外提供 schema 和 dispatch 能力

这说明 Hermes 对 tool 的定义非常清楚:

tool 不是“模型知道的一个概念”,而是一个正式注册过的动作单元。

2.1 一个 tool 至少有五个正式属性

ToolEntryregister(...) 能看出来,一个 tool 至少带着这些东西:

  • name
  • toolset
  • schema
  • handler
  • check_fn

再往后还有:

  • requires_env
  • description
  • emoji
  • max_result_size_chars

也就是说,Hermes 里的 tool 从一开始就不是 prompt 里的一句“你可以搜索网页”,
而是一个完整的运行时对象。

2.2 tool 的本质是动作,不是经验

这一点特别重要。

例如 web_search 的职责很单纯:

  • 接收参数
  • 执行搜索
  • 返回结果

它不负责告诉模型:

  • 遇到什么场景该先搜什么
  • 搜完以后怎么筛选
  • 哪类问题更适合换另一条路径

这些不是 tool 的工作,而是上层策略的问题。

所以如果你把 tool 理解成“Agent 的知识”,就会很容易误判。
tool 在 Hermes 里更接近:

可以被调度的执行接口。

2.3 tests 也在守这个边界

tests/test_model_tools.py 里对 handle_function_call(...) 的测试,本质上就在守一件事:

不管传进来的调用多乱,dispatch 这一层都要维持稳定、统一的接口语义。

比如:

  • 未知 tool 必须返回 JSON error
  • agent loop 接管的 tool 不能在普通 dispatch 里乱跑
  • get_toolset_for_tool(...) 要能查到每个 tool 的归属

这进一步说明 Hermes 把 tool 当成正式执行原语,而不是提示词里的散装能力描述。


3. Toolset 在 Hermes 里不是“目录分类”,而是运行时可见面的裁剪层

很多人第一次看到 toolsets.py,容易低估它,以为只是把工具做个分组。

但 Hermes 里 toolset 的作用远远不只是“分类好看”。

先看 toolsets.py 开头,它直接说了:

  • toolsets allow you to group tools together for specific scenarios
  • can be composed from individual tools or other toolsets
  • support dynamic toolset resolution

这说明 Hermes 把 toolset 设计成了:

控制当前会话到底暴露什么能力的中间层。

3.1 toolset 决定了“模型这轮能看到什么”

model_tools.py 里的 get_tool_definitions(...)

这个函数的核心逻辑很清楚:

  • 如果传了 enabled_toolsets
  • 就先 resolve_toolset(...)
  • 得到 tools_to_include
  • 再交给 registry 取 schema

也就是说,模型看到的并不是“所有已注册工具”,
而是“当前 toolset 解析后的可见工具”。

这点非常关键。

因为在 Agent 里,“系统里存在某工具”和“这一轮把它暴露给模型”是两回事。

Hermes 正是在用 toolset 解决这个问题。

3.2 toolset 还支持组合,不只是平铺

toolsets.py 里既有基础 toolset:

  • web
  • terminal
  • file
  • skills
  • memory

也有组合型和场景型 toolset:

  • debugging
  • safe
  • hermes-cli
  • hermes-gateway

resolve_toolset(...) 还支持递归展开 includes

这说明 Hermes 不是单纯在维护一个“标签系统”,
而是在维护一套可以组合、复用、平台化装配的能力面。

3.3 同一个 Hermes,在不同入口可以长得不一样

toolsets.py 里有一整组:

  • hermes-cli
  • hermes-telegram
  • hermes-discord
  • hermes-whatsapp
  • hermes-gateway

这些定义非常值得初学者注意。

因为它们说明 Hermes 从架构上接受一个现实:

同一个 Agent,不同运行入口,本来就应该暴露不同的能力组合。

这就是 toolset 层存在的根本价值:

它不是解释“工具是什么”,而是在回答:

当前这个运行面,应该把哪些工具交给模型。


4. model_tools.py 是这三层关系里的关键中间站:把 toolset 解析成真正的 tool surface

如果说 tools/registry.py 是工具总表,toolsets.py 是可见面配置,
model_tools.py 就是两者真正接起来的地方。

4.1 _discover_tools() 先把工具都发现出来

model_tools.py 一开始会 import 各种 tools.* 模块,触发它们注册到 registry。

这一步建立的是:

  • 系统里理论上有哪些 tool

4.2 get_tool_definitions(...) 再按 toolset 过滤

然后它再根据:

  • enabled_toolsets
  • disabled_toolsets

去解析出当前这轮真正该出现的工具。

而且这里还有一个特别值得学的工程细节:

Hermes 不只过滤“有没有这个工具”,
还会基于实际可用的 available_tool_names 动态修 schema。

例如:

  • execute_code 的 schema 会只列当前真的能在沙箱里用的工具
  • 如果 browser_navigate 出现在当前工具面里,但 web_search / web_extract 没有,它就会删掉描述里对 web tools 的交叉引用

这一步很有代表性。

因为它说明 Hermes 真正在意的不是“系统内部有多少能力”,
而是“模型此刻看到的能力说明,必须和 runtime 真实一致”。

这就是 toolset 层和 tool 层被严格接起来的方式。

4.3 get_toolset_for_tool(...) 让工具归属成为正式查询能力

无论在 registry 还是 model_tools.py,都能看到 get_toolset_for_tool(...)

这看起来像一个小函数,但它其实非常重要。

因为一旦 toolset 成为 runtime 的正式一层,
系统内部很多地方就需要知道:

  • 这个 tool 属于哪个能力面
  • 当前有哪些 toolset 实际已启用

后面你会看到,Hermes 连 skills 索引的裁剪都要依赖这个映射。


5. Skill 在 Hermes 里处理的,不是“能不能做”,而是“该怎么做更稳”

到了这一层,就能看出 skill 和 tool 的本质差异了。

tool 解决的是:

  • 能执行什么动作

skill 解决的是:

  • 面对某类问题,应该遵循什么策略、步骤和经验

这也是为什么 Hermes 要在 run_agent.py 的 system prompt 装配里,单独加上 skills 相关部分。

run_agent.py_build_system_prompt(),你会发现它会:

  • 按当前 valid_tool_names 决定是否注入 MEMORY_GUIDANCE
  • 决定是否注入 SESSION_SEARCH_GUIDANCE
  • 决定是否注入 SKILLS_GUIDANCE

然后如果当前工具面里有:

  • skills_list
  • skill_view
  • skill_manage

它才会继续计算 avail_toolsets,再调用 build_skills_system_prompt(...)

这一步非常关键。

因为它说明 skill 不是底层动作原语,而是建立在一组工具可见的前提之上的策略层。

换句话说:

  • 没有 skill_view,模型就没法按需加载 skill
  • 没有 skill_manage,模型也不该被鼓励去维护 skill
  • 当前 toolset 不同,skills index 的可见面也会跟着变化

这代表 Hermes 的 skill 从来不是脱离 runtime 独立存在的一段说明文案,
而是和当前工具面联动的上层能力。


6. Hermes 最有工程味的一点,是 skill 还会反过来依赖 toolset 做裁剪

这一点很容易被忽略,但其实特别能体现 Hermes 的成熟度。

run_agent.py 在构建 skills prompt 时,会先根据 valid_tool_names 反推出:

  • 当前有哪些 avail_toolsets

再把这些信息传给 build_skills_system_prompt(...)

agent/prompt_builder.py 里的 _skill_should_show(...) 会继续用这些信息决定:

  • 某个 fallback skill 要不要隐藏
  • 某个 requires skill 要不要显示

这就形成了一个非常漂亮的闭环:

  1. toolset 决定当前可见 tool
  2. 当前可见 tool 再反推出当前有效 toolset
  3. 当前有效 toolset 再决定哪些 skill 应该被模型看到

对学习智能体的人来说,这里特别值得记住。

因为它说明 Hermes 不是在做三层彼此独立的能力系统,
而是在做三层有边界、但相互约束的运行时结构。

tests/agent/test_prompt_builder.py 里关于 fallback / requires 的那批测试,本质上就在验证这个闭环没有断。


7. 子 Agent 的 toolset 约束,进一步说明 toolset 是权限边界,不是文档分组

如果你还觉得 toolset 只是“整理方便”,那 tests/tools/test_delegate_toolset_scope.py 非常值得看。

这些测试守的是一个很现实的问题:

如果父 Agent 只有某些 toolset,子 Agent 能不能自己申请更多?

答案是不行。

测试里明确验证了:

  • 子 Agent 请求的 toolsets 必须和父 Agent 已有 toolsets 取交集
  • delegationclarifymemory 这类 blocked toolsets 会被剥掉
  • 如果没有请求新的 toolsets,子 Agent 继承父级的可见面

这说明 toolset 在 Hermes 里已经不只是“能力组织单位”,
还是正式的权限边界。

这一点对做多 Agent 系统的人尤其重要。

因为如果没有这层边界,模型完全可能在 delegation 时借机扩权。

Hermes 的处理很成熟:

  • tool 是动作
  • toolset 是动作边界
  • delegation 时先继承边界,再做裁剪

这比“子 Agent 默认也能看到所有工具”安全得多。


8. 对学习智能体的人来说,这三层最值得记住的五个原则

看完 Hermes 这一套实现,你会发现它其实在反复强调几条很重要的设计原则。

8.1 不要把动作和策略混成一层

tool 负责做事,skill 负责指导怎么更稳地做事。
这两者混在一起,系统会越来越乱。

8.2 不要把“系统里有”误当成“这轮该暴露”

toolset 的存在,就是为了把注册能力和当前暴露能力分开。

8.3 能力说明必须和 runtime 真实可见面一致

Hermes 动态修 schema、动态裁 skills index,本质都在守这个原则。

8.4 复用经验应该建立在真实工具面之上

skill 不是空中楼阁。
它只有在相关 tools 真可用时,才值得被展示和调用。

8.5 多 Agent 系统一定要有能力边界

toolset 不只是开发便利,它还是 delegation 和权限收敛的关键抓手。


最后把三层分工收一下

如果把这一篇压缩成一句话,那就是:

Hermes 之所以把能力拆成三层,是因为“会做”“给不给做”“遇到这种事应该怎么做”本来就不是一回事。

在 Hermes 里:

  • tool 是动作原语
  • toolset 是运行时可见面和权限边界
  • skill 是按需加载的复用策略包

也正因为它把这三层拆清楚了,Hermes 才能同时做到:

  • 底层动作很多,但不会每轮都全暴露
  • 平台和子 Agent 的能力面可以被精确裁剪
  • 经验复用不会反过来污染整个 system prompt

对学习智能体的人来说,这一层非常值得抄。

因为它告诉我们:

真正成熟的 Agent,不是“能力名词越多越强”,
而是“能力分层越清楚,系统越稳、越好扩展”。