ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Hermes Agent执行路径全解析:子系统划分与请求处理流程

Hermes Agent执行路径全解析:子系统划分与请求处理流程

1. 从一张“执行路径地图”说起:Hermes Agent 到底在解决什么问题

第一次接触 Hermes Agent 的人,十有八九会被它那一堆子系统名字绕晕。Agent 核心、工具执行器、记忆模块、规划器、消息总线、沙箱运行时……每个名字单拎出来都能讲半小时,但真正让人头疼的不是单个子系统怎么实现,而是它们之间到底怎么串起来、一次请求从进入到返回中间到底走了哪条路。这就是“执行路径地图”的价值所在——它不关心某个模块内部用了什么算法,它关心的是控制流和数据流在子系统之间的走向。

我最初看 Hermes Agent 的代码时,习惯性地从入口函数往下追,结果追到第三个文件就迷路了,因为它的调用链不是一条直线,而是会在几个关键节点上分叉:有的请求走同步路径直接返回,有的请求被丢进队列异步处理,有的请求会触发子 Agent 递归调用。如果你脑子里没有一张全局地图,读代码的效率会低得可怕。所以这篇内容我打算把 Hermes Agent 的子系统划分和执行路径完整地拆一遍,重点讲清楚每个子系统在路径中扮演什么角色、路径在哪些地方分叉、分叉的判定条件是什么。

这篇内容适合三类人:一是正在读 Hermes Agent 源码但被调用链绕晕的开发者;二是准备基于 Hermes Agent 做二次开发、需要知道该在哪个子系统下手的工程师;三是想理解一个成熟 Agent 框架如何组织控制流的架构爱好者。我会尽量用“一次真实请求的完整旅程”作为主线,把子系统一个个串进去,而不是干巴巴地列模块清单。

在正式开始之前先说明一点:Hermes Agent 的版本迭代比较快,不同版本之间子系统命名和路径细节会有差异。我下面讲的结构以当前主流版本的划分为准,如果你手上的版本对不上,重点看路径分叉的逻辑而不是具体的函数名,逻辑是相对稳定的。

2. 子系统划分:不是按代码目录分,而是按“在路径中的职责”分

很多人理解子系统划分时,习惯直接看代码目录结构,哪个文件夹就是一个子系统。这个思路在 Hermes Agent 上会踩坑,因为它的目录划分和运行时职责划分并不完全对应。比如工具执行相关的代码散落在三个目录里,而记忆模块的读写又和消息总线耦合在一起。所以我更推荐按运行时职责来划分子系统,这样每个子系统在执行路径上都有明确的“进入点”和“退出点”。

2.1 核心调度层:Agent Core 与 Planner 的分工边界

Agent Core 是整个执行路径的起点和终点。所有外部请求——不管是来自 CLI、WebUI 还是 API——最终都会汇聚到 Agent Core 的入口。但 Agent Core 本身不做复杂决策,它更像一个“交通枢纽”:接收请求、做初步的上下文组装、然后把请求交给 Planner 去规划。

Planner 的职责是把用户意图拆解成可执行的步骤序列。这里有个容易误解的点:很多人以为 Planner 会直接调用工具,其实不会。Planner 只负责产出“计划”,计划是一组结构化的步骤描述,每个步骤声明自己需要哪个工具、需要什么参数。真正执行工具的是下游的 Executor。这种“规划与执行分离”的设计好处很明显:计划可以被缓存、被审查、被修改,甚至在执行前被人工干预。

Agent Core 和 Planner 之间的边界判定条件通常是请求复杂度。简单请求(比如单轮问答、单工具调用)会走一条捷径,Agent Core 直接组装上下文后调用一次模型就返回,根本不经过 Planner。只有多步骤、需要工具编排的请求才会触发 Planner。这个分叉点是执行路径上的第一个关键分叉,后面我会详细讲。

2.2 工具执行层:Executor、Tool Registry 与沙箱运行时的三角关系

工具执行层是执行路径上最“重”的一层,涉及三个子系统:Executor、Tool Registry、Sandbox Runtime。

Executor 是执行计划的驱动者,它按顺序取出计划中的每个步骤,解析出需要的工具名和参数,然后向 Tool Registry 查询这个工具。Tool Registry 是一个注册中心,所有可用工具在启动时注册进来,每个工具声明自己的名称、参数 schema、执行入口、以及是否需要沙箱。

这里就是第二个关键分叉点:工具是否需要沙箱执行。纯计算类工具(比如文本处理、格式转换)直接在进程内执行,路径短、开销小。而涉及文件系统操作、网络请求、代码执行的工具,会被路由到 Sandbox Runtime,在一个隔离环境里执行。沙箱运行时负责资源限制、超时控制、输出捕获,执行完再把结果回传给 Executor。

我实测下来,这个三角关系里最容易出问题的是 Tool Registry 的参数校验。如果工具的 schema 定义和实际调用参数对不上,Executor 会在调用前就抛错,但这个错误信息有时候不够明确,会让你误以为是沙箱的问题。排查时先确认参数校验是否通过,再看沙箱日志,能省不少时间。

2.3 记忆与上下文层:短期记忆、长期记忆与消息总线的数据流向

记忆层经常被低估,但它对执行路径的影响很大。Hermes Agent 的记忆分两类:短期记忆(当前会话的上下文窗口)和长期记忆(跨会话的持久化知识)。

短期记忆的读写发生在 Agent Core 组装上下文的时候,它把当前会话的历史消息、系统提示、工具返回结果拼成一个完整的 prompt。长期记忆的读写则发生在两个时机:一是 Planner 规划前,会检索长期记忆里是否有相关经验;二是执行完成后,会把本次执行的关键结论写回长期记忆。

消息总线是记忆层和调度层之间的通道。所有子系统之间的异步通信都走消息总线,包括工具执行结果的回传、子 Agent 的状态更新、记忆写入的确认。理解消息总线的存在很重要,因为它意味着执行路径不总是同步的,有些步骤是发消息后等回调,而不是直接函数返回。这也是为什么单纯追函数调用栈会迷路的原因。

2.4 扩展层:子 Agent 与插件系统的接入位置

子 Agent 是 Hermes Agent 比较有特色的设计。当一个任务复杂到单个 Agent 难以处理时,主 Agent 可以派生一个子 Agent,给它独立的上下文和工具集,让它专注处理某个子任务。子 Agent 的执行路径是主路径上的一条“支线”,它有自己的 Agent Core 和 Planner 实例,但共享底层的 Tool Registry 和沙箱资源。

插件系统的接入点主要在 Tool Registry 和消息总线两处。插件可以注册新工具,也可以订阅消息总线上的事件做副作用处理。插件不直接修改执行路径,而是通过扩展工具集和监听事件来影响路径的走向。

3. 一次请求的完整执行路径:从入口到返回的六个阶段

光讲子系统还是太抽象,我把一次典型的多步骤请求拆成六个阶段,每个阶段标注经过哪些子系统、在哪些地方分叉。你可以把这条路径当成读代码时的导航。

3.1 阶段一:请求接入与上下文组装

请求从入口进来后,第一站是 Agent Core 的接入层。接入层做三件事:解析请求格式、识别会话 ID、加载对应的短期记忆。会话 ID 决定了加载哪段历史上下文,这一步如果会话 ID 缺失或错误,会导致上下文组装出问题,表现为 Agent “失忆”。

上下文组装是把系统提示、历史消息、当前用户输入按特定顺序拼接。这里有个细节:Hermes Agent 会在拼接时做一次token 预算检查,如果历史上下文太长,会触发截断或摘要策略。截断策略的选择会影响后续路径——如果触发了摘要,会额外调用一次模型来压缩历史,这条路径就多了一个模型调用。

组装完成后,Agent Core 会做第一次复杂度判定,决定是走简单路径还是复杂路径。判定依据通常是:请求是否包含明确的工具调用意图、是否需要多步骤、历史上下文里是否有未完成的任务。

3.2 阶段二:复杂度判定与路径分叉

复杂度判定是执行路径上最重要的分叉点,我单独拿出来讲。判定逻辑大致是这样的:

  • 如果请求是纯问答、无工具调用意图,走简单路径:直接调用模型生成回复,路径结束。
  • 如果请求包含单个明确的工具调用,走单工具路径:跳过 Planner,直接由 Executor 执行该工具,然后把结果回传给模型生成最终回复。
  • 如果请求需要多步骤编排、或意图不明确需要规划,走复杂路径:进入 Planner 规划阶段。

这个判定的准确性直接决定了系统的效率。判定过严,简单请求被当成复杂请求处理,浪费一次规划调用;判定过松,复杂请求被当成简单请求,导致任务执行不完整。我在实际使用中发现,涉及“先查再改再验证”这类模式的请求,判定器有时会误判为单工具路径,结果只执行了查询就返回了。遇到这种情况,可以在请求里显式声明需要多步骤处理,强制走复杂路径。

3.3 阶段三:Planner 规划与计划生成

进入复杂路径后,Planner 开始工作。它的输入是组装好的上下文,输出是一个结构化的计划。计划生成的过程通常是:先让模型理解任务目标,然后让模型列出达成目标需要的步骤,最后把每个步骤映射到具体工具。

计划的结构一般包含:步骤列表、每步的工具名、每步的参数、步骤之间的依赖关系。依赖关系很重要,它决定了 Executor 能否并行执行某些步骤。比如“查询天气”和“查询日程”两个步骤没有依赖,可以并行;“根据天气决定穿什么”依赖前两步的结果,必须串行。

Planner 生成计划后,Agent Core 会做一次计划校验,检查计划里的工具是否都已注册、参数是否完整、依赖关系是否有环。校验不通过会触发重新规划,这条重规划路径最多重试若干次,超过次数就降级为简单路径或直接报错。

3.4 阶段四:Executor 逐步执行与沙箱调度

计划校验通过后,进入 Executor 执行阶段。Executor 按依赖关系拓扑排序,把无依赖的步骤并行提交,有依赖的步骤等前置完成后再提交。

每个步骤的执行流程是:从 Tool Registry 查询工具定义、校验参数、判定是否需要沙箱、执行、捕获结果。需要沙箱的步骤会被路由到 Sandbox Runtime,沙箱启动、执行、回收都有开销,所以 Executor 会尽量复用沙箱实例,而不是每步都新建。

这里有个实测经验:沙箱的启动开销在冷启动时比较明显,如果你的任务里有大量短小的沙箱步骤,整体耗时会比预期长。优化思路是把多个小步骤合并成一个沙箱会话,在沙箱内连续执行,减少启停次数。

3.5 阶段五:结果聚合与记忆写回

所有步骤执行完后,Executor 把各步结果聚合,回传给 Agent Core。Agent Core 会把结果和原始任务一起交给模型,生成最终的自然语言回复。

同时,记忆写回发生在这个阶段。系统会判断本次执行是否产生了值得长期保留的知识,如果有,就写入长期记忆。写回的内容通常包括:任务类型、使用的工具序列、关键参数、执行结果。下次遇到类似任务时,Planner 检索长期记忆就能复用之前的计划,这也是 Hermes Agent 越用越顺手的原因。

3.6 阶段六:响应返回与路径收尾

最后一步是把回复返回给调用方,并做路径收尾:释放沙箱资源、清理临时上下文、更新会话状态。收尾阶段如果出错,通常不影响本次响应,但会影响下次请求的上下文加载,所以日志里要留意收尾阶段的告警。

4. 路径分叉的判定逻辑:三个关键决策点

上面六个阶段里,真正决定路径走向的是三个决策点。把这三个点吃透,执行路径地图就基本清晰了。

4.1 决策点一:简单路径 vs 复杂路径的判定依据

这个判定主要看两个信号:工具调用意图的明确程度和任务的多步骤特征。工具调用意图明确,指的是用户请求里直接提到了工具名或明确的动作(比如“读取这个文件”)。多步骤特征,指的是任务描述里包含“先……再……然后……”这类序列词,或者目标需要多个信息源才能达成。

判定器通常是一个轻量模型或规则引擎,它的输出是一个置信度分数,超过阈值走复杂路径。阈值设置是个权衡:设高了,复杂任务被简化;设低了,简单任务被复杂化。我建议在开发调试阶段把阈值调低,强制多走复杂路径,方便观察 Planner 的行为;上线后再根据实际请求分布调优。

4.2 决策点二:工具是否需要沙箱的判定标准

沙箱判定标准通常基于工具声明的能力标签。工具在注册时会声明自己需要哪些能力,比如文件读写、网络访问、代码执行。只要声明了这些能力中的任意一个,就会被判定为需要沙箱。

这个判定的边界有时候会模糊。比如一个工具只是读取环境变量,算不算需要沙箱?严格来说环境变量读取不涉及外部资源,可以不走沙箱。但如果这个环境变量可能包含敏感信息,走沙箱更安全。我的经验是:宁可多走沙箱,不要漏判,因为漏判的代价是安全风险,多判的代价只是性能开销。

4.3 决策点三:子 Agent 派生的触发条件

子 Agent 派生通常发生在两种场景:一是任务可以清晰拆分成独立子任务,且子任务之间不需要频繁共享上下文;二是某个子任务的上下文需求很大,放在主 Agent 里会挤占主上下文窗口。

触发条件一般是:计划中的某个步骤被标记为“可委托”,且该步骤的预估上下文消耗超过阈值。派生出的子 Agent 有独立的上下文空间,执行完把结果摘要回传主 Agent,而不是回传完整上下文。这个设计能有效控制主 Agent 的上下文膨胀。

5. 读代码和调试时的路径追踪技巧

理解了路径地图,接下来讲怎么用它来读代码和排查问题。这部分是我踩了不少坑之后总结的,常规文档里不会写。

5.1 用日志锚点定位当前处于哪个阶段

Hermes Agent 的日志里通常有阶段标记,比如[core]、[planner]、[executor]、[sandbox]。排查问题时,先看日志里最后一个阶段标记是什么,就能知道请求卡在哪个阶段。如果卡在[planner],说明规划阶段有问题;如果卡在[executor]且没有[sandbox],说明是工具执行本身的问题,不是沙箱问题。

我习惯在关键分叉点加临时日志,比如复杂度判定的分数、沙箱判定的结果。这些日志在排查“为什么走了这条路径而不是那条路径”时特别有用。

5.2 断点应该打在哪几个函数上

如果要用调试器,不要在每个函数都打断点,会淹没在调用栈里。我建议只打四个断点:Agent Core 的入口、复杂度判定函数、Planner 的规划入口、Executor 的步骤提交函数。这四个点覆盖了所有关键分叉,从这四个点的命中情况就能还原出完整路径。

5.3 常见路径异常与对应排查方向

异常表现可能卡住的阶段排查方向
请求无响应接入层或收尾阶段检查会话 ID 和资源释放日志
只执行了部分步骤Executor 阶段检查步骤依赖关系和并行提交逻辑
工具报参数错误Tool Registry 校验对比工具 schema 和实际参数
沙箱超时Sandbox Runtime检查资源限制和工具执行耗时
重复执行同一步骤Planner 或记忆写回检查计划校验和记忆检索逻辑

这张表是我在实际排查中积累的,覆盖了大部分常见问题。遇到异常时先对照这张表定位阶段,再去对应子系统里深挖,比盲目追代码高效得多。

6. 子系统之间的耦合点与解耦思路

最后聊聊子系统之间的耦合。Hermes Agent 的子系统划分虽然清晰,但耦合点还是存在的,理解这些耦合点对二次开发很重要。

最明显的耦合是 Executor 和 Tool Registry:Executor 依赖 Registry 提供工具定义,Registry 的变化会直接影响 Executor 的行为。解耦思路是把工具定义做成版本化的契约,Executor 按契约调用,Registry 内部实现变化不影响 Executor。

第二个耦合点是 Planner 和记忆层:Planner 规划前要检索长期记忆,记忆层的检索质量直接影响规划质量。解耦思路是把记忆检索抽象成一个独立服务,Planner 只依赖检索接口,不关心记忆怎么存储。

第三个耦合点是沙箱运行时和工具执行:沙箱的资源限制配置会影响工具能否正常执行。解耦思路是让工具声明自己的资源需求,沙箱按需分配,而不是用统一的硬编码限制。

我在二次开发时的体会是:优先在耦合点做抽象,而不是在子系统内部做优化。因为耦合点的问题会放大到整个执行路径,而子系统内部的问题通常只影响局部。把耦合点理顺了,整个系统的可维护性会有质的提升。

关于执行路径的扩展,还有一个值得关注的方向是路径的可观测性。现在 Hermes Agent 的路径追踪主要靠日志,未来如果能做成结构化的 trace,每个阶段产出标准事件,那排查和优化会方便很多。不过这属于框架层面的演进,作为使用者,我们能做的是在自己的工具和插件里输出规范的事件日志,为将来的可观测性打好基础。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进