本节摘要:本章把单 agent 扩编成军团,本节解剖三条主干。其一,委派入口——
tools/delegate_tool.py(4963 行)注册的delegate_task工具:单个 goal 或批量 tasks 数组,外加 list/steer/stop 三个同步控制动作。其二,子 agent 生命周期——agent/subagent_lifecycle.py(542 行)暴露插件安全的不可变契约:SubagentLaunchRequest → launch → SubagentHandle → status/wait/cancel/result,八态状态机,HMAC 能力令牌防伪造句柄。子 agent 的隔离是三层:聚焦式系统提示词(不知道父对话)、独立会话与上下文、可选 git worktree 工作区隔离。其三,失控防线与验证——迭代预算(父 500/子 50 各自封顶)、spawn 深度、并发上限;verification 引擎(agent/review_engine.py的 /review)派独立全权子 agent 复核主对话的工作成果。
内容来源:原项目源码
tools/delegate_tool.py、agent/subagent_lifecycle.py、agent/iteration_budget.py、agent/review_engine.py、agent/interrupt_compat.py。
⚠️ 注意:
delegate_tool.py近 5000 行,本节只走主路径(spawn 控制流、子构造、单子执行);诊断转储、摘要预算微调(_apply_summary_budget)、异步派遣批处理等支线建议读函数docstring。另外注意区分两套 API:delegate_task是模型可调的工具,SubagentLifecycleService是插件宿主用的公共 API——后者内部仍委托前者构造子 agent。
阅读完本节,你应当能够:
delegate_task 的参数面:goal/context/tasks/role/background/action/output_schema。delegate_task(delegate_tool.py:3627)的签名注释就是完整说明书:
delegate_tool.py:3637 Spawn modes (action='spawn' or omitted): 3638 - Single: provide goal (+ optional context and role) 3639 - Batch: provide tasks array [{goal, context, role}, ...] 3641 Control modes (synchronous, never backgrounded): 3642 - action='list' -> live children of this conversation's spawn tree 3643 - action='steer' -> queue course-correction text into a running child 3645 - action='stop' -> interrupt a running child early (subagent_id)
参数设计有三个讲究。goal 必须自包含——schema 里直白告诉模型"the subagent knows nothing about your conversation history",要带文件路径、报错信息、项目结构就写进 context。role 是双值的:leaf(默认)剥离委派工具集、不能再生孩子;orchestrator 保留委派能力可以再派工人,受 delegation.max_spawn_depth 约束。background=True 把整个批次作为一个异步单元送上守护线程池,全部完成后以一条合并消息回流会话——聊天不阻塞。schema 本身还是动态的:DELEGATE_TASK_SCHEMA(delegate_tool.py:4767)的描述文本在每次 get_definitions() 时重建,让模型看到的是用户当前配置的并发与深度上限,而非框架默认值——提示词不说谎。
控制平面同样精致:spawn 前有运维「急停闸」(is_spawn_paused,TUI 里按 p 冻结新扇出而不打断在跑的孩子);steer 往运行中的孩子里排队纠偏文本(中途改需求不打断);stop 走 request_hard_interrupt 硬中断。
agent/subagent_lifecycle.py 开宗明义:"deliberately exposes immutable contracts, not AIAgent objects"——插件拿到的永远是冻结 dataclass,摸不到活的 agent 对象。核心状态机:
subagent_lifecycle.py:38 class SubagentState(str, enum.Enum): 39 PENDING = "PENDING" 40 STARTING = "STARTING" 41 RUNNING = "RUNNING" 42 SUCCEEDED = "SUCCEEDED" 43 FAILED = "FAILED" 44 INTERRUPTED = "INTERRUPTED" 45 CANCEL_REQUESTED = "CANCEL_REQUESTED" 46 CANCELLED = "CANCELLED"
launch(subagent_lifecycle.py:198)的流程:解析父 agent → 校验请求(goal ≤ 16000 字符、context ≤ 32000、role 只许 leaf/orchestrator、metadata ≤ 8192 字节、显式拒绝 per-launch timeout 与 working_directory——"Hermes delegates use isolated task environments")→ 委托 _build_child_preserving_parent_tools 构造子 agent → 生成 SubagentHandle → 提交守护线程池(_EXECUTOR,8 工人)执行 _run。防伪是亮点:handle 携带 HMAC 能力令牌——
subagent_lifecycle.py:482 def _capability(cls, subagent_id, parent_session_id, created_at) -> str: 485 value = f"{subagent_id}|{parent_session_id or ''}|{created_at:.6f}".encode() 486 return hmac.new(_SECRET, value, hashlib.sha256).hexdigest()
之后每次 status/wait/result 都用 hmac.compare_digest 重算比对(subagent_lifecycle.py:377),插件伪造不出别人的句柄;结果对象还带 result_hash(SHA-256 over payload)供完整性核对。终态记录保留 1 小时(_TERMINAL_RETENTION_SECONDS = 3600)供轮询,之后清理。cancel 走 request_hard_interrupt,已终态/未知句柄各有明确返回码——契约里没有歧义分支。
隔离第一层是聚焦式系统提示词。_build_child_system_prompt(delegate_tool.py:1173)从" You are a focused subagent working on a specific delegated task"开始,只含 goal/context/workspace 三块。妙处在 workspace 上下文文件:子 agent 构造时 skip_context_files=True(防止装进父提示词),但如果任务给了 workspace_path,会按与主 agent 完全相同的发现与优先级规则补挂 AGENTS.md/CLAUDE.md/.cursorrules——"子 agent 在仓库里干活却不读仓库自己的规矩"是真实踩过的坑;SOUL.md(人格)刻意跳过,"identity belongs to the parent"。orchestrator 角色再追加一段委派能力说明,且深度注记"literal truth (grounded in the passed config)"——不许 LLM 臆造不存在的嵌套能力。
第二层是会话与上下文隔离:每个孩子独立的 messages、独立 iteration_budget(构造参数 iteration_budget=None 起新预算)、独立审批回调(默认自动拒绝危险命令,_subagent_auto_deny)。第三层是可选 worktree 隔离(delegation.worktree_isolation):并行孩子各自从父当前 commit 检出独立 git worktree,写文件互不踩踏。
agent/iteration_budget.py 的 IterationBudget 是个极简的线程安全计数器:
iteration_budget.py:37 def consume(self) -> bool: 38 """Try to consume one iteration. Returns True if allowed.""" 39 with self._lock: 40 if self._used >= self.max_total: 41 return False 42 self._used += 1
父 agent 上限 max_iterations(默认 500),子 agent 上限 delegation.max_iterations(默认 50),各持一份独立预算——源码注释直言"total iterations across parent + subagents can exceed the parent's cap"。那失控靠什么防?靠乘法上限的组合:单子上限 50 × 并发上限 10(_get_max_concurrent_children,超过 10 会打警告"cost multiplies linearly")× 深度默认 1(max_spawn_depth,叶子层不能再生)。异步派遣在容量满时直接拒绝而非排队——"a runaway model can't pile up unbounded background work"。孩子耗尽预算不算失败:exit_reason == "max_iterations" 的结果带 truncated: True 仍返回摘要(delegate_tool.py:3120),父 agent 拿到部分成果再决定续派或改道。运行期还有心跳看门狗(_run_single_child,delegate_tool.py:2509):心跳线程周期性 touch 父 agent 活性时间戳防网关误杀,同时监测孩子的迭代/工具/活动三信号是否冻结——真卡死就停止心跳、放行网关超时。
多 agent 体系的最后一环是验证子 agent 的产出。agent/review_engine.py(299 行,"Shared engine for the /review command — every surface calls this")把验证建模为:取主对话最近 10 条 user/assistant 消息快照(snapshot_recent_messages,单条 12000 字符截断)→ 构造审查任务:
review_engine.py:153 goal = ( 154 "Act as an independent senior reviewer. Thoroughly review the work " 155 "presented in the conversation excerpt ... investigate any code, pull " 156 "request, branch, commit, documentation, design, or other artifact it " 157 "references (open the PR, read the diff, run the code or tests where " 158 "feasible) rather than judging from the excerpt alone. ..."
关键约束有三:审查者必须亲自打开 PR/读 diff/跑测试,不许只看摘要;collect_parent_loaded_skills 从父 agent 的临时提示词与历史 tool_calls 里挖出它加载过的技能(上限 8 个),要求审查者先 skill_view 装载同款技能——"工作是在这些技能的规范下产出的,就必须按同一把尺子评判";模型路由走 auxiliary.review 配置,未配置则继承主模型凭据。审查结果经异步委派通道回流主对话,主 agent 当场可读可改。CLI/gateway/TUI 三个表面都只是薄适配:快照对话、调 start_review、打印派遣单。
💡 循环要点:协作器官没有发明新循环——每个子 agent 内部跑的就是第 2 章的标准内循环,verification 跑的也是。多 agent 的全部新意在于边界管理:提示词边界(孩子不知父事)、预算边界(50/500 分级)、深度边界(叶子不繁殖)、权限边界(工具集只减不增,
allowed_toolsets不得超越父权限集)、身份边界(HMAC 句柄防伪)。委派出去的是执行,收回来的是摘要与验证——这与外循环"沉淀经验而非复制状态"是同一个哲学。
下一节把视角从"父子"抬到"舰队":kanban 看板驱动的多 Agent 工作队列(认领、重试、崩溃恢复)与 MoA 混合多模型答案。