上篇我们跟着一条消息走完了 Agent Loop 的完整旅程。但有一个环节被刻意略过了——循环里”执行工具”那一步。
模型说”我要调搜索工具”,然后呢?谁去执行?怎么找到它?如果 agent 有 50 多个工具,每加一个都要改核心代码?
这篇拆开这个黑盒。但这次不聊代码,聊决策——面对同样的问题,你会怎么设计?Hermes Agent 又是怎么选的?
你要解决的第一个问题:50+ 工具怎么管
想象你是 agent 的产品经理。循环跑起来了,模型能思考了。现在你要给它装上”手”——搜索、读文件、跑命令、浏览网页……50 多种能力。
最直觉的做法是什么?在循环里写一串判断:如果是搜索就调搜索,如果是读文件就调读文件……
这个方案在 5 个工具的时候还行。50 个的时候就崩了——每加一个工具,你都要改这段判断逻辑。改着改着就漏了、错了。更麻烦的是,这段代码变成了所有人的”瓶颈”:任何人都不能独立加一个工具,必须改这个中心文件。
你会怎么解决?
换个思路。如果每个工具都能”自己登记”呢?就像新员工入职,不是 HR 手动录入每个人,而是每个人自己填入职表。代码和登记在同一个文件里——写了工具就自动注册了,不可能忘。
Hermes Agent 就是这么做的。每个工具文件末尾都有一行”自我登记”的声明。工具存在,登记就在。
这个设计看起来简单,但它消灭了一个很大的风险:配置和实现分离。传统做法是维护一个中心配置文件,每加一个工具去里面加一行。问题是改了一处忘了另一处。自注册把这个风险从根上消除了。
第二个问题:怎么让新工具”自动上线”
工具自己登记了。但还有一个问题:谁来触发这个登记?
换个问法:你写好了一个新工具文件,放在项目目录里。系统怎么知道它存在?
你的选择有哪些?
选择一:手动配置。每次加新工具,去某个地方”告诉”系统一声。这是最直觉的,但也是前面刚否掉的。
选择二:自动扫描。系统启动时扫描整个目录,发现新文件就注册。听起来不错,但扫描顺序不可控,依赖关系理不清,出了问题很难排查。
Hermes 选了第三条路:一条单向的”汇报链”。
它把工具系统分成了四层。最底层是注册表——一个纯粹的空壳,只知道”怎么登记”和”怎么查找”,不知道有哪些工具。往上一层是所有工具文件,每个都导入注册表,把自己登记进去。再往上一层是编排层,负责把所有工具文件导入一遍——导入的动作本身就会触发登记。最顶层是循环,它只跟注册表打交道。

这条链的铁律是:依赖只能从上往下。工具可以找注册表,但注册表不知道工具的存在。加东西只改上层,不动底层。
这就像公司的组织架构。基层员工(工具)自己填入职表(注册表)。中层管理者(编排层)负责把所有员工的花名册汇总一遍。高层(循环)只看花名册,不直接管理每个人。加一个新人,只需要让他填表、加入花名册。组织架构本身不用改。
加一个新工具只需要做一件事:把它加进编排层的”花名册”里。一行字符串,完事。
到这里,”加一个工具只加一个文件”的承诺已经兑现了。循环代码从头到尾没动过,注册表也没改过。
第三个问题:循环怎么找到正确的工具
注册完成。现在回到循环——模型说”我要调搜索工具”,循环怎么执行到?
这个问题其实比想象中简单。注册表里已经存了所有工具的信息,每个工具都有名字。循环只需要按名字查表,找到对应的执行函数,调用它。找不到就返回一个”工具不存在”的提示,不崩。
这就像电话总机。 你拨分机号,总机帮你接通。总机不需要知道每个分机在哪间办公室、是座机还是手机。循环也只管拨号(给工具名),不管工具在哪、怎么实现。
这个设计的精妙之处在于:循环和工具之间完全解耦。循环不知道有哪些工具,工具也不知道循环怎么工作。它们之间只有一个契约——名字。你给我名字,我给你结果。
内置工具和外部工具(通过 MCP 协议接入的第三方工具)共存于同一张注册表,用同一套接口分发。对循环来说,它们没有区别。
第四个问题:异步工具怎么塞进同步循环
到这里,工具系统已经能工作了。但还有一个现实问题。
50 多个工具里,大多数是”同步”的——读文件、写文件、跑命令,做完就返回结果。但有少数工具是”异步”的——网络请求、浏览器操作,发出去之后要等一段时间才有结果。
矛盾来了:循环本身是同步的(一步一步往下走),但异步工具需要”等”。怎么办?
你的选择有哪些?
选择一:把整个循环改成异步。所有工具都用”等待”的方式处理。代价是——八成同步工具都要白包一层,本来一步搞定的事现在要绕个弯。报错堆栈变深,调试变难。
选择二:为异步工具单独建一条通道。循环保持同步不变,遇到异步工具时,把任务交给一个”常驻翻译官”去处理。
Hermes 选了选择二。
这个”常驻翻译官”是什么? 它是一个启动时就创建好、一直不关闭的事件循环。同步循环遇到异步工具时,把任务交给它,等它处理完拿回结果,继续往下走。
为什么翻译官必须”常驻”?因为有些异步资源——比如浏览器登录态、长连接——是跨多轮对话存活的。如果每次用完就拆掉,下一轮得重新登录、重新建连接,效率极低。

这个取舍在系统设计里反复出现:找到那个”八成”和”两成”,为两成建一座桥,不为它重建整个城市。 循环是八成,异步工具是两成。桥的存在,让主体不用改变。
这个设计在 S01 里埋过伏笔。当时讲”循环主体用同步写,异步工具走桥”,说是三个工程取舍之一。现在看到了具体实现——桥就是那个常驻翻译官。
第五个问题:没配 key 的工具怎么处理
最后一个问题。搜索工具需要一个 API 密钥。如果用户没配这个密钥,这个工具该怎么办?
三种处理方式:
一,让它出现在工具列表里,等模型调用时报错。——用户体验极差。
二,启动时检查,没配 key 就整个系统报错不让跑。——太粗暴,一个工具的问题不该影响全局。
三,安静地不可用。没配 key 的工具不出现在模型能看到的列表里。模型根本不知道它存在,不会调用,不会报错。
Hermes 选了第三种。每个工具注册时可以附带一个”可用性检查”——一个返回是/否的判断。系统在组装给模型的工具清单时,只列出检查通过的工具。
这是一种优雅降级:功能缺失时不崩溃,而是安静地收缩。对用户来说,没配密钥的工具就像不存在一样。
做产品的人都熟悉这个思路——功能开关、灰度发布、AB 测试,本质都是同一件事:让系统在不改变结构的前提下,动态调整哪些能力可用。
复盘:五个决策,一张表
回头看,工具系统的设计就是五个决策的串联:
| 问题 | 你的直觉方案 | Hermes 的选择 | 为什么 |
|---|---|---|---|
| 50+ 工具怎么管 | 中心配置文件 | 自注册:工具自己登记 | 代码和注册在一起,不可能忘 |
| 新工具怎么上线 | 手动配置或自动扫描 | 单向导入链:编排层汇总 | 加东西只改上层,不动底层 |
| 循环怎么找到工具 | if/elif 判断链 | 注册表按名字查表 | 循环和工具完全解耦 |
| 异步工具怎么办 | 全改异步 | 同步主体 + 常驻翻译官 | 不为两成改变八成 |
| 没配 key 的工具 | 报错或全局阻断 | 安静地不可用 | 优雅降级,不影响全局 |

和 S01 对比,变化集中在工具层,核心循环纹丝不动:
| 能力 | S01 | S02 |
|---|---|---|
| 工具调用 | 黑盒 | 注册表 + 分发 |
| 添加新工具 | 改循环 | 加一个文件 |
| 异步工具 | 不支持 | 常驻翻译官桥接 |
| 工具筛选 | 无 | 可用性检查 |
| 外部工具 | 无 | 同一张注册表 |
| 核心循环 | 不变 | 不变 |
最后,三个能带走的判断。
一,自注册优于中心配置。 代码和注册在一起,不可能忘。这个模式不只适用于工具——插件系统、路由注册、事件处理器,凡是需要”加东西不改老代码”的场景,都可以用。
二,依赖方向决定扩展性。 注册表在最底层,不依赖任何上层。加东西只改上层,不动底层。这就是开闭原则的工程实现——对扩展开放,对修改关闭。做产品也一样:好的平台架构,加功能不改底层,改底层不影响上层。
三,同步主体加异步桥。 不为少数派改变主体。找到那个”八成”和”两成”,为两成建一座桥,不为它重建整个城市。这个取舍在系统设计里反复出现——做产品时也一样,核心流程保持稳定,边缘场景用适配器解决。
这是「Hermes Agent 源码精读」系列的第二篇。上一篇讲了 Agent Loop,下一篇讲会话持久化——对话怎么跨重启存活。