Skip to content

子代理派生

源码版本v2026.7.20

职责

delegate_tool 让主 agent 把一个子任务派生给隔离的子代理:子代理有自己的对话、自己的终端、自己的工具集,跑完只把最终结果交回父代理。这实现了「零上下文成本」的流水线——父代理上下文不被子任务的过程细节污染。子代理在 ThreadPoolExecutor worker 里运行,危险命令默认 auto-deny(可配置 auto-approve)。

设计动机

长流水线会爆,不是因为模型不行,而是因为父代理看完了所有工具调用的中间过程。grep 出 200 行、patch 失败的 diff、试错性的 build——这些细节塞进父代理上下文后,后面决策就被稀释。delegate_task 的赌注是「子代理的过程对父代理不可见」,只让最终文本回填。代价是失去可观测性,所以补了一套观测层:DaemonThreadPoolExecutor 让子代理线程不阻塞进程退出,_active_subagents 注册表让 TUI 能列出/暂停/打断活着的子代理,max_concurrent_childrenmax_spawn_depth 给并发与深度硬上限。隔离边界是用「能看见、能停」换来的。

关键文件

数据流

  1. 主循环 provider 返回 tool_call: delegate
  2. delegate_task:2426 被调用,拿到子任务描述与可选 toolsets。
  3. DaemonThreadPoolExecutor:2651 里起一个 worker:
  4. 子代理完成后只把最终文本交回,delegate_task 经 tool_result:798 封装回填给父代理。
  5. 父代理上下文只增长一条 tool 结果,不被过程污染。

为什么要装审批回调?注释里写得直白:CLI 的交互式审批回调存在 terminal_tool.pythreading.local() 里,worker 线程不继承,不装回调会 fallback 到 input(),从 worker 线程读 stdin 会和父代理的 prompt_toolkit TUI 死锁。所以默认是 deny:

python
def _subagent_auto_deny(command: str, description: str, **kwargs) -> str:
    """Auto-deny dangerous commands in subagent threads (safe default)."""
    logger.warning(
        "Subagent auto-denied dangerous command: %s (%s). "
        "Set delegation.subagent_auto_approve: true to allow.",
        command, description,
    )
    return "deny"

派生执行用 DaemonThreadPoolExecutor 而不是标准库,理由在 daemon_pool.py 模块开头:标准库的 worker 是非守护线程,_python_exit 会无条件 join,一个卡死的 worker 就能让进程退出卡几分钟。Daemon 版本 spaw daemon 线程并跳过 _threads_queues 注册。派生入口 delegate_task 第一步是查暂停开关 is_spawn_paused(),这个开关由 TUI 的 p 键或 delegation.pause RPC 触发,发现失控派生树时冻结新派生但不动已经在跑的子代理:

python
if is_spawn_paused():
    return tool_error(
        "Delegation spawning is paused. Clear the pause via the TUI "
        "(`p` in /agents) or the `delegation.pause` RPC before retrying."
    )

深度也硬限,默认 MAX_DEPTH = 1,父(0)到子(1)就到底,孙子会被拒,要开多级得显式配置 delegation.max_spawn_depth

边界与失败

  • 预算泄漏:子代理有自己的 max_iterations,但 token 花费对父代理不可见,批量派生 N 个子代理时,N 倍 provider 费用直接堆上去。max_concurrent_children 默认 3 是软上限,配置高了会一次性烧穿预算。
  • 嵌套死锁:默认深度是 1,显式调高 max_spawn_depth 后,orchestrator 角色的子代理能再派生,任意中间层卡住都会拖住整条链。DaemonThreadPoolExecutor 只解决进程退出卡死,不解决运行时挂起。
  • 审批回调用错人:subagent_auto_approve=true 是 process 级配置,开了之后所有子代理都 YOLO。没有 per-job 粒度,批量场景容易误开。
  • 结果可信度:父代理只拿最终文本,中间过程不在上下文里。子代理把幻觉当事实写进最终回复,父代理没办法基于过程纠偏,长链路派生时幻觉会逐层放大。

小结

子代理派生 = 上下文隔离 + 危险命令管控。父代理只看结果,不看过程,这让长流水线不被过程噪声撑爆。安全默认 auto-deny,批量/cron 场景可 opt-in auto-approve。终端执行落在沙箱后端

非官方社区学习站,内容以 MIT 许可的 NousResearch/hermes-agent 源码为依据。