前言:“让 Agent 一直做下去”听起来像一个开关,真正实现时却至少包含计时唤醒、完成判断、临时并行、跨重启调度和持久工作流。本文先把 Goal、Loop、Heartbeat、Cron 的生命周期分开,再进入 Delegation 与 Kanban 两套多 Agent 系统。
系列导航:这组文章按“总览 → 入口 → 运行时 → 扩展 → 状态 → 协作 → 安全”的顺序展开;第一次阅读可以从总览开始,带着具体问题也可以直接跳到专题。
- 01|Hermes Agent 架构总览:从 Agent Loop 到智能体平台
- 02|Hermes Desktop 架构:Electron 如何连接本地 Python Agent
- 03|Hermes Agent 运行时:AIAgent、模型协议与工具执行
- 04|Hermes Agent 扩展体系:Skill、Plugin 与 MCP
- 05|实战:同一个 Markdown 检查器的 Skill、Plugin 与 MCP 三种实现
- 06|Hermes Agent 的持续学习系统:Memory、Review、Skill 与 Curator
- 07|Hermes Agent 的长会话架构:Prompt Cache、压缩与 SessionDB
- 08|Hermes Agent 的多 Agent 与自动化:从 Goal 到 Kanban
- 09|Hermes Agent 安全架构:审批、沙箱、回滚与凭据隔离
持续执行不是一个开关
| 机制 | 谁触发下一步 | 状态与上下文 | 适合的生命周期 |
|---|---|---|---|
/heartbeat | 固定间隔到期,且会话空闲 | 同一 Session;状态在 SessionDB.state_meta,漏掉的 Tick 会合并 | 让当前对话偶尔醒来检查,必须有 owning process 驱动 |
/loop | 固定或自适应计时器;可附 --until Judge | 同一 Session 的普通 User Turn;默认最多 100 Tick | 会话内重复检查或操作,重视当前上下文 |
/goal | 每轮结束后的 Auxiliary Judge 返回 continue / wait / done | 目标、contract、subgoal、gate 与等待屏障都进 Session 状态;默认 20 个 continuation Turn | 有明确完成条件、需要自动迭代的单会话任务 |
| Delegation | 父 Agent 显式 fan-out | 子 AIAgent 隔离上下文;完成摘要回流父会话 | 当前任务里的临时并行认知工作 |
| Cron | Gateway 持久 Scheduler | Job 持久化;每次 Fire 创建新隔离 Session,也可完全跳过 LLM | 无人值守、跨进程重启的固定计划 |
| Kanban | Dispatcher 原子认领 ready card | SQLite Board、独立 Worker 进程、Profile、run、heartbeat 与 handoff | 跨角色、可恢复、可审阅的持久协作 |
几种会话内控制器都遵守同一个消息流不变量:合成 Prompt 只在 Turn 边界作为普通 User Message 进入,用户真实输入优先,不在模型运行中偷偷改 System Prompt。Goal 活跃时拥有会话,Loop Wakeup 会延后;Goal 若因后台进程而 park,Loop 又可以利用空闲窗口继续。这样既保住 Prompt Cache,也避免两个控制器同时写同一条历史。
多 Agent:一次性委派与持久协作是两套系统
先给结论:Hermes 的“多 Agent”不是把同一份消息历史复制给多个模型。delegate_task 会创建新的 AIAgent、新的对话上下文和新的 task_id;父 Agent 只传入明确的 goal 与 context,最后也只接收子 Agent 的总结。对于必须跨进程重启、跨团队角色持续流转的任务,Hermes 另有 SQLite-backed Kanban 队列。两者解决的生命周期完全不同。
| 并行机制 | 隔离单位 | 结果如何返回 | 适合什么 |
|---|---|---|---|
| 并行 Tool Call | 仍在同一 Agent Turn | 按原始 Tool Call 顺序写回 | 彼此独立的只读调用 |
delegate_task | 新 AIAgent、上下文、task_id 与 Terminal session | 子 Agent 最终总结进入父会话 | 需要判断、研究或独立上下文的子问题 |
| Orchestrator 子 Agent | 允许继续派生下一层子 Agent | 等待自己的 workers,再综合成一个结果 | 研究—比较—综合等树形工作流 |
| Kanban Worker | 持久任务、Profile、Board 与 Worker 进程 | 通过任务状态、评论、附件和 Review 流转 | 需要重启恢复和多人长期协作的工作 |
delegate_task 的对象关系
模型看到的是 delegate_task Tool Schema;真正的创建逻辑在 tools/delegate_tool.py,主 Agent 还会在 run_agent.py 的专用分发路径接管模型调用。工具既支持单个 goal,也支持 tasks: [...] 批量 fan-out。当前官方源码把顶层委派做成异步:先返回 delegation handle 与 transcript 路径,子任务完成后再由 completion queue 把汇总结果送回原会话;只有 Orchestrator 内部的下一层 worker 会同步等待,因为它必须先取得 workers 的输出才能综合。
flowchart TB
Parent[父 AIAgent] --> Dispatch[delegate_task 分发器]
Dispatch --> Registry[活动子 Agent Registry]
Dispatch --> ChildA[Child A 独立 AIAgent]
Dispatch --> ChildB[Child B 独立 AIAgent]
ChildA --> TaskA[独立 context / task_id / terminal]
ChildB --> TaskB[独立 context / task_id / terminal]
ChildA --> SummaryA[最终总结]
ChildB --> SummaryB[最终总结]
SummaryA --> Queue[完成队列
Completion Queue]
SummaryB --> Queue
Queue --> Parent隔离的目的有两个。第一,子任务的搜索结果、试错和中间工具输出不会把父上下文挤满;第二,父 Agent 必须把问题重新描述成自洽的 goal/context,子 Agent 不会继承“我们刚才讨论的那个错误”这类隐含指代。这个约束看起来麻烦,实际上是多 Agent 可控性的来源。
子 Agent 继承什么,又被拿走什么
Child 会继承父会话已经允许的模型、Provider、凭据池与 Toolset 基线,但不能借委派扩大权限。源码随后执行第二次降权:默认 leaf 会移除 delegate_task、clarify、memory、send_message 和 cronjob 等会造成递归、用户交互或跨会话副作用的工具;危险命令审批默认自动拒绝,只有显式配置才会自动放行。
| 角色 | 能否继续委派 | 典型用途 | 关键限制 |
|---|---|---|---|
| leaf | 不能 | 代码审查、资料检索、独立方案评估 | 默认角色;不能澄清、写共享 Memory 或代表父会话发消息 |
| orchestrator | 可以,但受深度限制 | 把大问题拆成 workers,再做综合 | 只有 max_spawn_depth 大于 1 时才真正得到下一层委派能力 |
并发与深度都来自 delegation: 配置,业务代码不应该假设一个固定数字。在本文核验的官方 main 源码中,未配置时并发 fallback 是 10,默认深度仍是 1,也就是一层扁平 fan-out;继续加深没有硬上限,但每一层都会把成本近似乘上并发因子。
delegation:
max_concurrent_children: 4
max_spawn_depth: 2
orchestrator_enabled: true
child_timeout_seconds: 1200
subagent_auto_approve: false从 spawn 到回流,一次委派经历什么
- 解析请求:单任务读取 goal/context,批任务为每个 task 创建独立描述;可选
output_schema会成为子 Agent 的输出契约。 - 检查预算与深度:确认当前节点仍可派生、没有超过并发限制,并取得父 Agent 的迭代预算与会话身份。
- 构造 Child:创建新的
AIAgent,生成独立task_id,恢复父侧模型配置,但重新计算子 Agent 工具集合。 - 运行与记录:Child 在自己的 Conversation Loop 工作;活动 Registry 保存状态,append-only transcript 记录模型文本、工具和结果。
- 校验结果:如果声明了 JSON Schema,父侧验证最终答案;失败时只允许一次有界纠正,避免无限修格式。
- 清理与汇总:关闭 Terminal 等资源,累计子任务 token/cost,触发
subagent_stopHook,只把最终结果送回父会话。
运行中控制:list、steer、stop
多 Agent 如果只能“发出去然后等”,很难称为可运营。当前 Schema 还提供 action=list、steer 和 stop:父 Agent 可以列出 live children,给指定 subagent_id 追加方向修正,或请求它在安全边界停止。steer 不会粗暴切断正在执行的工具,而是在下一次迭代边界把文本拼到最新 Tool Result 后;父 Agent 被中断时,中断信号也会递归传给 children 和 grandchildren。
下面的状态名来自插件可用的公开 SubagentLifecycle API;它描述 lifecycle handle,不是说 delegate_task 的 Tool Result 会逐项返回这些枚举。
stateDiagram-v2
[*] --> PENDING
PENDING --> STARTING
STARTING --> RUNNING
RUNNING --> RUNNING: steer 在下一安全边界生效
RUNNING --> CANCEL_REQUESTED: stop 或父级 interrupt
RUNNING --> SUCCEEDED
RUNNING --> FAILED
CANCEL_REQUESTED --> CANCELLED
CANCEL_REQUESTED --> INTERRUPTED
SUCCEEDED --> [*]
FAILED --> [*]
CANCELLED --> [*]
INTERRUPTED --> [*]插件若要自己启动子会话,不应该 import delegate_tool 的内部全局变量。公开边界是 agent.subagent_lifecycle 和 ctx.subagent_lifecycle:它返回可序列化的 opaque handle,支持 status、wait、cancel、result 与 reconnect。不过 handle 的状态目前仍保存在进程内一段时间,进程重启后不会“猜测性重建”子任务。
为什么还需要 Kanban
delegate_task 是会话内的临时计算树,不是持久工作流引擎。需要跨重启、依赖关系、认领、Review、阻塞恢复和多个 Profile 协作时,源码使用 hermes_cli/kanban.py、tools/kanban_tools.py 与 Kanban dispatcher。任务进入 SQLite Board,dispatcher 原子认领 ready task,按 assignee/Profile 启动 worker;worker 只在 Kanban 任务里得到专用 kanban_* Toolset。
flowchart LR
Creator[用户或 Orchestrator] --> Board[SQLite 看板
SQLite Kanban Board]
Board --> Depends[依赖与 Ready 状态]
Depends --> Dispatcher[Dispatcher 原子认领]
Dispatcher --> WorkerA[配置档案 A 工作器
Profile A Worker]
Dispatcher --> WorkerB[配置档案 B 工作器
Profile B Worker]
WorkerA --> Updates[评论 附件 心跳]
WorkerB --> Updates
Updates --> Review{Review}
Review -->|通过| Done[完成]
Review -->|修改| Board
Dispatcher --> Reclaim[回收 stale claim]
Reclaim --> Board所以选择很简单:希望当前会话把几个认知子问题并行算完,用 delegate_task;希望工作成为可查询、可认领、能在重启后继续的组织状态,用 Kanban;只是把十次机械工具调用做循环和过滤,则优先用 execute_code,没必要为每一步付出一个完整 Agent Loop。
可观测性也围绕这两种生命周期展开:临时子 Agent 有活动 Registry、逐任务 transcript、token/cost 汇总和 TUI /agents 树;Kanban 有持久任务、runs、heartbeat、comments 与 Review 状态。观察类 Hook 失败通常不该阻断主流程,而预算、工具降权、租约和 Board 认领这些正确性边界更倾向于 fail-closed。
参考资料与源码入口
题图来源:《湖、悬崖与岩石》,创作者 FrankyFromGermany,来自 Pixabay;依据 Pixabay Content License(Pixabay 内容许可)使用。