多 Agent 协作:委托、调度与看板

delegate_task、cron、kanban 三大支柱,让 Hermes 从单兵变成团队

系列:通过 Hermes 探秘 Agent 工程 · 补遗篇 关联篇:安全防护体系:当 Agent 拥有终端时如何防止「做出格」的事


问题:一个 Agent 干不了所有事

前 10 篇文章讲的都是"单个 Agent 如何运行得很好"。但真实场景中,单 Agent 有三个根本性瓶颈:

  1. 上下文爆炸:一个大型任务(比如审查 100 个文件)的中间工具结果会撑爆上下文窗口
  2. 时间跨度:有些任务需要等待(部署后验证、定时检查),一个持续运行的 Agent 不现实
  3. 并行度:多个独立子任务串行执行太慢,而一个 Agent 同一时间只能做一件事

Hermes 的解决方案是多 Agent 协作——不是让一个 Agent 变强,而是让多个 Agent 协同工作。它有三个支柱:delegate_task(子代理委托)、cron(定时任务)、kanban(看板调度)。


支柱一:delegate_task 子代理委托

核心思想

当你面对一个大项目(比如"重构这个 10 个文件的模块"),主 Agent 不亲自做,而是把任务分解成 3 个独立的子任务:

Main Agent
  ├─ delegate_task("审查 file1-3 的接口设计", role="leaf")
  ├─ delegate_task("审查 file4-6 的接口设计", role="leaf")
  └── delegate_task("审查 file7-10 的接口设计", role="leaf")

三个子代理并行运行,各自用独立的上下文窗口。完成后,主 Agent 收集三份总结报告,整合成最终结果。

关键收益:主 Agent 的上下文窗口里只看到"委托了三件事"和"收到三份总结",看不到中间几百次工具调用的细节。

子代理隔离

每个子代理获得:

  • 全新的空白对话——不继承父 Agent 的历史(防止上下文泄漏)
  • 自己的 task_id——独立的终端会话、文件操作缓存
  • 受限的工具集——禁止递归委托、禁止用户交互、禁止修改共享记忆、禁止写文件(execute_code

受限工具集的工程实现是白名单+“减法”:从父 Agent 已开启的工具里去掉 DELEGATE_BLOCKED_TOOLS 列表中的几个(delegate_taskclarifymemoryexecute_codecronjob)。

子代理类型

leaf(默认):专注工人——拿到任务,自己完成,返回总结。不能再 spawn 子代理。

orchestrator:协调者——拿到任务后可以自己分解成更小的子任务,委托给孙代理。但有深度限制(max_spawn_depth,默认 1),防止树形嵌套失控。

安全默认

子 Agent 在后台线程里跑,没有 TUI 交互。当执行到危险命令时,默认行为是自动拒绝——避免"子代理 hung 在 input() 上等待永远不会出现的确认"的 deadlock。

配置 delegation.subagent_auto_approve: true 可以切换到自动批准(opt-in)但会打印审计日志。

超时与心跳

子代理默认没有硬性超时——合理的重度任务可能运行很久。替代方案是心跳监控:

  • 每 30 秒发送一次心跳
  • 如果 15 个周期(450 秒)没有新的 API 调用 → 判定为卡住
  • 如果 40 个周期(1200 秒)还在执行同一个工具 → 也判定为卡住

这比固定超时更灵活:快速失败的任务不会被误杀,真正的僵死任务也能被发现。


支柱二:Cron 定时任务

核心思想

有些任务不是即时响应的——“每天凌晨 3 点检查服务器状态”、“每周五下午生成本周总结”。Cron 是 Gateway 级别的定时任务系统。

两种执行模式

no_agent 模式:不调用 LLM。直接执行 script 字段的 shell 脚本,stdout 作为输出。适合纯运维任务(日志清理、备份、健康检查)。

agent 模式:用 LLM 执行自然语言 prompt。每一轮是独立的 Agent 运行,有独立的上下文。

Cron 任务通过 delivery 字段路由输出到任意渠道(Telegram、Discord、飞书……),实现了"任务执行"和"结果消费"的解耦。

Delivery 机制

Cron Job 的结果通过 gateway/delivery.py 路由到目标平台。关键设计:cron 输出不在 Gateway session 历史里——避免消息交替违规,也避免 cron 日志污染真正的对话。

Cron 审批的特殊规则

Cron Job 没有人在场确认。Cron 的审批模式由 approvals.cron_mode 独立控制,默认 deny。这是一个 fail-closed 的设计——无人值守时,宁可任务失败,也不能执行危险操作。


支柱三:Kanban 看板调度

核心思想

delegate_task 是"父 Agent 主动分解",Kanban 是"中央调度器自动分配"——一个多 Agent 的工作队列系统。

架构

Orchestrator(编排者)
  │
  ├─ /kanban create "实现用户认证模块"
  ├─ /kanban assign task-001 worker-1
  ├─ /kanban list
  │
  ▼
┌──────────────────────────────┐
│       SQLite 数据库           │
│  tasks / events / runs / subs │
└──────────┬───────────────────┘
           │
     ┌─────┴─────┐
     ▼           ▼
 Dispatcher    Notifier
 (每 60s)      (每 5s)
     │           │
     ▼           ▼
 spawn worker   Telegram /
 Discord / Slack 通知
     │
     ▼
 Worker (hermes chat -q)

任务生命周期

unassigned → assigned → in_progress → completed
                                  → blocked(遇到障碍,等待人工介入)

每个状态变迁都记录在 SQLite 里,实现持久化 + 可审计

Worker 完整启动流程

调度器(每 60s)→ 争抢文件锁(.flock Singleton 锁)→ 7 个步骤:

  1. 回收僵尸:清理上次 spawn 但已死掉的 worker PID
  2. 释放过期 claim:worker 超过 TTL 仍持有任务锁 → 强制释放
  3. 检测心跳超时:450 秒没有新的 API 调用 → 判定为卡住
  4. 检测进程死亡:worker 的 PID 已不存在(被 OOM / Ctrl+C)→ crashed
  5. 强制 max runtime:任务有最大运行时长限制,超时强制结束
  6. 重新计算就绪:检查依赖——前置任务全部完成 → 从 todo 晋升到 ready
  7. 分配 worker:按优先级遍历 ready 任务 → 原子性 claim + spawn

每次 spawn 调用 subprocess.Popen(hermes -p <profile> chat -q)——一个完整的 Hermes 子进程。Worker 被 spawn 后和调度器不再通信,只通过数据库状态判断完成。

Worker 隔离

Kanban worker 不是普通 Agent——它们被 HERMES_KANBAN_TASK 环境变量打上"单一任务一辈子"的印记:

  1. 工具集受限——只有 kanban 工具集 + 核心工具。不能操作其他任务
  2. session 固定——一个 worker 对应一个 task_id
  3. 工作目录隔离——每个 worker 的终端 CWD 指向任务的专属目录
  4. 任务所有权保护——HERMES_KANBAN_TASK 与参数里的 task_id 不匹配时,调用被拒绝(纵深防御:prompt injection 不能影响其他任务)

心跳与自动回收

调度器的心跳检测:如果 worker 卡住没有心跳 → 自动把任务标记为"orphan"(可被重新分配的孤儿)。kanban 任务可以被另一个 worker 接手,这是和 delegate_task 的最大区别。


核心对比:delegate_task vs kanban

类比

  • delegate_task = 函数调用:传参数、等返回值、用完即弃
  • kanban = 消息队列:发到队列、消费者异步处理、结果存在队列里随时可查

什么时候用哪个?

用户在线 + 任务小 + 结果要立刻汇总?
  → delegate_task

用户离线 + 任务多 + 失败要重试 + 状态要追踪 + 人工要介入?
  → kanban

核心区别

delegate_taskkanban
同步/异步同步——父 Agent 阻塞等子代理完成异步——调度器周期扫描,spawn 后不管
临时/持久临时——任务完成,子 Agent 销毁持久——状态在 SQLite,随时可查
调度者父 Agent 自己判断(声明式)中央调度器自动扫描
失败恢复父 Agent 自己重试调度器自动回收/重试/重新分配
审计跟踪完整——每次运行/失败/event 都有日志
人工介入不支持中断/暂停支持 blocked 状态,人工 /kanban unblock

六大 kanban 使用场景

① 长任务需要持久化

“这个项目有 8 个模块要重构。“用户关了终端,子 Agent 完成了也找不到父 Agent 汇报。kanban 状态存在数据库里,用户下次上线时通过通知收到结果。

② 任务需要人工中间介入

“review 这个 PR → 如果有问题 block → 等待开发者修复 → 修复后重新 review”。delegate_task 无法"暂停,等明天人工修复后再继续”。kanban 支持 blocked 状态。

③ 优先级队列 + 路由

5 个 bug 修复(高优先级)、3 个 feature 开发(中优先级)、不同 assignee 路由给不同 profile。

④ 大量并行超出 delegate_task 并发限制

默认 delegate_task 最多同时跑 3 个子代理。大项目可能有 20+ 个独立子任务。kanban 的 max_spawn / max_in_progress 灵活控制全局并发度,每种 profile 也有独立并发限制(max_in_progress_per_profile)。

⑤ 审计跟踪

“这个任务是谁做的?做了多久?失败了几次?为什么被 block?"——kanban 数据库里每次运行都有 event 日志。

⑥ 自动分解(optional)

用户在看板里创建 triage 任务"优化项目性能”,辅助 LLM 可以自动将其分解为"分析 CPU 瓶颈”、“分析内存使用”、“优化数据库查询"等子任务(auto_decompose 功能)。

通知机制

Notifier Watcher 每 5 秒扫描数据库里的事件表。当发现新事件(completed / blocked / crashed),沿着订阅表找到"谁要被通知到这个 chat” → 通过对应平台适配器发消息。投递后推进 cursor 去重——不会出现重复通知。


三支柱对比

delegate_taskcronkanban
触发方式LLM 判断时间 / 人工看板调度器
执行者子 Agent 实例Agent / ScriptWorker Agent
上下文全新独立 session全新 session固定 task 专属
生命周期任务完成即销毁持久化持久化
交互方式同步(父等待子完)无人值守无人值守
失败恢复无(父 Agent 自己重试)重试策略自动重新分配

工程启示

1. 并发不只是性能,是功能

execute_code 通过 RPC 让模型可以使用工具,主要价值不在性能——它让 LLM 可以做"有副作用的事"而不污染上下文。并发执行之于 Agent 不只是快,是让单 Agent 变成多 Agent 组织的前提。

2. 隔离是信任的基础

子代理不继承父 Agent 历史、kanban worker 不能操作其他任务——这些隔离不是"不信任模型",而是给 prompt 攻击设置纵深。

3. 声明式编排

Hermes 没有"多 Agent 协调框架"的硬编码流程。delegate_task / cron / kanban 都是工具,由 LLM 自己决定什么时候用。场景变了,不需要改框架,换一个 prompt 就够了。

4. 人类退出,系统继续运转

kanban 和 cron 对"无人值守"的特殊处理提醒我们:设计 Agent 系统时,不是所有人都在看屏幕——系统必须有能力在人类不在场时安全运行。


本系列的完结

补写了这最后一篇后,系列的完整地图如下:

#主题核心工程概念
1Agent Loop主循环、迭代预算、中断
2工具系统自注册、toolset、并发执行
3System PromptStable/Semi-Stable/Volatile 三层缓存
4上下文压缩边界对齐、结构化摘要
5记忆系统内置记忆、外部 Provider 语义查询
6工具调度registry 单例、搜索桥延迟加载
7安全防护工具守卫、审批、结果清洗
8Gateway 网关Session Key、双层守卫
9Provider 抽象层Profile 声明式、Fallback 链
10沙箱与代码执行RPC 存根、环境变量清洗
11技能系统程序化记忆、渐进式披露
12多 Agent 协作委托分解、看板的声明式编排

从循环到调度、从安全到扩展、从记忆到经验——这就是 Hermes 的 Agent 工程全景。