Clipping 微信公众号

子 Agent 委托:复杂任务如何拆成并行子任务

by Jameszyh 原文 ↗
Created: 2026-06-27

公众号名称:James的成长日记

作者名称:Jameszyh

发布时间:2026-06-27 15:41

大家好,我是James。

上一篇我们聊了上下文压缩的反直觉设计——为什么压缩不等于截断,结构化摘要怎么在保住缓存命中率的同时把长对话压缩到 1/5 的 token 消耗。那种”压缩即理解”的思路,本质上是在 token 预算和信息密度之间找最优解。

今天进入板块六「隔离与协作」的开篇——子 Agent 委托(Subagent Delegation)。这是 Hermes 最复杂的单文件之一,delegate_tool.py 接近 3000 行,覆盖了从隔离上下文、并行执行、深度控制到可观测性的完整设计。对一个真正可用的 Agent 系统来说,子 Agent 不是“多开几个模型”这么简单,而是一套完整的任务拆解、能力约束、运行时治理机制。

你可能觉得”子 Agent”不就是把任务拆开让多个 AI 各自跑吗?但真正难的不是”拆”,而是拆完之后怎么保证每个子 Agent 不互相影响、不泄露上下文、不跑飞、不重复造轮子。Hermes 的 delegate_tool 用一套”隔离上下文 + 受限工具集 + 深度限制 + 可观测心跳”的架构,把这些问题一次解决。

本文使用版本:Hermes Agent 仓库 2026-06 delegate_tool.py(约 3000 行)


一、为什么需要子 Agent 委托:从”单线程”到”并行工厂”

先看一个典型场景。假设你让 Agent 做这样一个任务:

“调研市面上 Top 5 的向量数据库,对比它们的性能、价格、生态,然后写一份选型报告。”

如果让一个 Agent 从头到尾做,它会:

  1. 一个个查数据库 A → 记录结果 → 查数据库 B → 记录 → 查 C → 记录 → …

  2. 上下文被中间结果撑爆

  3. 总耗时 = 5 个调研步骤串行执行

但如果能拆成 5 个子 Agent 并行跑:

父 Agent: 分配任务 + 汇总结果
├── 子 Agent A: 调研 Milvus
├── 子 Agent B: 调研 Qdrant
├── 子 Agent C: 调研 Pinecone
├── 子 Agent D: 调研 Weaviate
└── 子 Agent E: 调研 Chroma

总耗时 ≈ 最慢的一个子 Agent 的时间,而不是 5 倍。

这就是子 Agent 委托的核心价值:把串行变成并行,把大上下文拆成小上下文

但并行不是免费的——它带来了三个核心挑战:

挑战问题Hermes 的解法
上下文隔离子 Agent 之间不能互相看到对方的中间结果每个子 Agent 有独立的 AIAgent 实例、独立的会话、独立的 task_id
工具集限制子 Agent 不能执行危险操作(发消息、递归委托、写共享文件)DELEGATE_BLOCKED_TOOLS 硬编码黑名单
深度控制子 Agent 不能再创建子 Agent 导致无限递归max_spawn_depth 配置,默认 1(扁平),可提升到 2+

二、delegate_task 的核心设计:两种模式 + 三层隔离

delegate_task 的入口函数支持两种模式:

单任务模式

delegate_task(
    goal="调研 Milvus 的性能指标",
    context="用户需要对比 5 个向量数据库",
    toolsets=["web", "file"],
    role="leaf"  # 默认,子 Agent 不能再委托
)

批量任务模式

delegate_task(
    tasks=[
        {"goal": "调研 Milvus", "toolsets": ["web"]},
        {"goal": "调研 Qdrant", "toolsets": ["web"]},
        {"goal": "调研 Pinecone", "toolsets": ["web"]},
    ],
    role="leaf"
)

批量模式用 ThreadPoolExecutor 并行执行,最大并发数由 delegation.max_concurrent_children 控制(默认 3)。

三层隔离机制

第一层:上下文隔离

每个子 Agent 获得一个全新的 AIAgent 实例,没有父 Agent 的历史消息。父 Agent 看到的只有委托调用和最终摘要,永远不会看到子 Agent 的中间工具调用或推理过程。

# _build_child_agent 的核心逻辑
child = AIAgent(
    model=parent.model,
    provider=parent.provider,
    api_key=parent.api_key,
    base_url=parent.base_url,
    system_prompt=child_system_prompt,  # 聚焦的任务提示词
    tools=child_tools,                   # 受限的工具集
    _delegate_depth=parent_depth + 1,
)

第二层:工具集隔离

子 Agent 的工具集通过 _strip_blocked_tools 过滤,硬编码黑名单包括:

DELEGATE_BLOCKED_TOOLS = frozenset([
    "delegate_task",   # 禁止递归委托(除非 role="orchestrator")
    "clarify",         # 禁止向用户提问
    "memory",          # 禁止写共享 MEMORY.md
    "send_message",    # 禁止跨平台发消息
    "execute_code",    # 禁止直接执行脚本
])

第三层:深度控制

通过 _delegate_depth 追踪嵌套深度,默认 max_spawn_depth=1(扁平结构)。当 role="orchestrator" 且深度允许时,子 Agent 可以保留 delegate_task 工具,形成树状委托。


三、Orchestrator 角色:从扁平到树状委托

扁平委托(所有子 Agent 都是 leaf)适合大多数场景。但有些复杂任务需要分层分解

父 Agent(深度 0)
└── Orchestrator(深度 1,role="orchestrator")
    ├── Worker A(深度 2,leaf)
    ├── Worker B(深度 2,leaf)
    └── Worker C(深度 2,leaf)

Orchestrator 的子 Agent 提示词会额外追加一段”子 Agent 生成指南”:

def _build_child_system_prompt(goal, context, role="leaf", max_spawn_depth=2, child_depth=1):
    # ... 基础提示词 ...
    if role == "orchestrator":
        parts.append("""
### 子 Agent 生成指南
你有权使用 delegate_task 工具创建自己的子 Agent。

何时委托:
- 目标可拆分为 2+ 个独立子任务并行执行
- 子任务推理密集,会撑爆你的上下文

何时不委托:
- 单步机械操作——直接做
- 简单任务,一两个工具调用就能完成
- 把整个任务原封不动转给一个 Worker(纯透传无价值)
""")

关键设计决策:Orchestrator 的提示词是动态生成的,根据 child_depthmax_spawn_depth 精确告知 LLM 它的委托能力边界,避免模型幻觉。


四、并行执行与心跳机制:子 Agent 跑飞了怎么办

子 Agent 在 ThreadPoolExecutor 中运行,父 Agent 通过 _run_single_child 管理每个子 Agent 的生命周期。

超时控制

每个子 Agent 有独立的超时配置:

DEFAULT_CHILD_TIMEOUT = 600  # 10 分钟

通过 delegation.child_timeout_seconds 配置,最低 30 秒。

心跳机制

父 Agent 在等待子 Agent 期间,每 30 秒发送一次心跳:

_HEARTBEAT_INTERVAL = 30  # 秒

# 空闲超时(子 Agent 在轮次之间停滞)
_HEARTBEAT_STALE_CYCLES_IDLE = 15  # 15 × 30s = 450s

# 工具内超时(子 Agent 正在执行一个长工具)
_HEARTBEAT_STALE_CYCLES_IN_TOOL = 40  # 40 × 30s = 1200s

这个设计区分了两种”看起来没动”的情况:

  • 轮次间空闲:子 Agent 可能在等 API 响应,15 个心跳周期(450s)没动静就标记为 stale

  • 工具内执行:子 Agent 可能在跑一个长命令(如 npm install),给 40 个周期(1200s)

可观测性

子 Agent 的运行状态通过 DelegateEvent 枚举实时推送给父 Agent:

class DelegateEvent(str, enum.Enum):
    TASK_SPAWNED = "delegate.task_spawned"
    TASK_PROGRESS = "delegate.task_progress"
    TASK_COMPLETED = "delegate.task_completed"
    TASK_FAILED = "delegate.task_failed"
    TASK_THINKING = "delegate.task_thinking"
    TASK_TOOL_STARTED = "delegate.tool_started"
    TASK_TOOL_COMPLETED = "delegate.tool_completed"

父 Agent 可以在 TUI 中实时看到每个子 Agent 的进度、当前工具、输出预览。


五、子 Agent 的审批安全:自动拒绝 vs 自动批准

子 Agent 运行在 ThreadPoolExecutor 的工作线程中。父 Agent 的 TUI 审批回调(prompt_dangerous_approval)依赖 prompt_toolkit 的 stdin 交互,但工作线程不继承父线程的 TLS 回调。

如果子 Agent 调用了一个需要审批的危险命令,它会尝试 input() 从 stdin 读取——但 stdin 被父 Agent 的 TUI 占着,导致死锁

Hermes 的解决方案是为子 Agent 安装非交互式审批回调

def _subagent_auto_deny(command, description, **kwargs):
    """自动拒绝(安全默认)"""
    logger.warning("子 Agent 自动拒绝了危险命令: %s (%s)", command, description)
    return "deny"

def _subagent_auto_approve(command, description, **kwargs):
    """自动批准(YOLO 模式)"""
    logger.warning("子 Agent 自动批准了危险命令: %s (%s)", command, description)
    return "once"

通过 delegation.subagent_auto_approve 配置切换(默认 false),两种模式都会记录审计日志。


六、凭证继承与多 Provider 回退

子 Agent 默认继承父 Agent 的模型和凭证。但 Hermes 还支持独立的委托凭证配置

delegation:
  provider: "openrouter"
  model: "anthropic/claude-sonnet-4"
  api_key: "sk-xxx"
  base_url: "https://openrouter.ai/api/v1"

当配置了 delegation.provider 时,子 Agent 使用独立的凭证,与父 Agent 解耦。这允许:

  • 父 Agent 用昂贵的旗舰模型做决策,子 Agent 用便宜的模型做执行

  • 子 Agent 使用不同的 Provider 做负载均衡

  • 凭证池轮换,避免限流

凭证解析通过 _resolve_delegation_credentials 完成:

def _resolve_delegation_credentials(cfg, parent_agent):
    provider_cfg = cfg.get("provider")
    if not provider_cfg:
        return None, None, None, None  # 继承父 Agent
    
    # 通过 runtime provider 系统解析完整凭证包
    creds = resolve_provider_credentials(provider_cfg)
    return creds.base_url, creds.api_key, creds.api_mode, creds.model

七、输出提取与结果汇总

子 Agent 完成后,父 Agent 不会看到完整的对话历史,只会看到:

  1. 子 Agent 的最终回复(包含做了什么、发现了什么、创建了什么文件)

  2. 输出尾部摘要_extract_output_tail 提取最后 12 个工具调用的结果预览)

def _extract_output_tail(result, max_entries=12, max_chars=8000):
    """提取子 Agent 对话的最后 N 个工具调用结果"""
    messages = result.get("messages", [])
    # 构建 tool_call_id → tool_name 映射
    # 反向遍历,提取最新的工具结果
    # 返回 [{tool, preview, is_error}, ...]

这个设计保证了:

  • 父 Agent 的上下文不会被子 Agent 的中间步骤撑爆

  • 错误信息能被正确标记(is_error 检测)

  • 输出预览有长度限制(默认 8000 字符)


八、暂停与中断:运行时控制

Hermes 提供了两个运行时控制机制:

暂停新委托

def set_spawn_paused(paused: bool) -> bool:
    """全局暂停/恢复新委托的创建"""

当检测到子 Agent 跑飞时,操作员可以通过 TUI 或 RPC 调用 delegation.pause,阻止新的子 Agent 创建,已运行的继续执行。

中断单个子 Agent

def interrupt_subagent(subagent_id: str) -> bool:
    """请求中断一个正在运行的子 Agent"""
    agent = _active_subagents.get(subagent_id)
    agent.interrupt("Interrupted via TUI")

通过 _active_subagents 注册表追踪所有活跃子 Agent,支持按 ID 精确中断。中断会递归传播到子 Agent 的子 Agent(如果有)。


九、业界成熟 Agent 怎么做子任务委托

只讲 Hermes 自己还不够。真正有价值的问题是:真实世界里的成熟 Agent 产品,都是怎么做“任务拆分 + 子任务隔离”的?

先看结论:越偏消费级产品,越倾向“单 Agent + 少量后台任务”;越偏工程化和编排平台,越倾向“显式委托 + 独立上下文 + 可观测执行单元”。

1)Claude Code / Codex CLI:偏“单主 Agent + 工具调用”,谨慎暴露多 Agent

这类终端型 Agent 大多把重点放在:

  • 单个主 Agent 持续持有上下文

  • 遇到复杂任务时,更多依赖工具链而不是大规模显式子 Agent

  • 就算内部有任务拆分,也会尽量隐藏实现细节,避免用户心智负担过重

为什么这么选?因为 CLI 场景的核心价值是“人机协作连续性”,不是大规模并行编排。用户更在意当前终端状态、文件改动、命令审批,而不是背后到底起了几个 Worker。

2)OpenAI / LangGraph / AutoGen 这一类框架:强调显式多 Agent 编排

框架型产品通常会把“角色分工”作为一等能力:

  • Supervisor / Planner 负责拆任务

  • Researcher / Coder / Reviewer 各自处理独立子问题

  • 通过消息总线、共享状态、图节点或任务队列做汇总

为什么这么选?因为框架服务的是“可编排系统”而不是单次对话。开发者需要的是:可观测、可测试、可重放,所以会明确保留每个 Agent 的边界和责任。Hermes 的 role=orchestrator 就明显属于这一派。

3)Cursor / Devin / 工程型 coding agent:更像“后台任务工厂”

工程型产品往往会把委托问题转成“任务单元隔离”:

  • 每个任务有独立工作区或沙箱

  • 每个任务有预算、超时、日志和审批边界

  • 最终由主控层聚合结果,决定是否继续迭代

为什么这么选?因为代码任务天然有副作用:改文件、跑测试、装依赖、调用外部服务。只要不做强隔离,多任务并发几乎一定会互相污染。Hermes 在子 Agent 里做工具黑名单、审批回调替换、深度限制,本质上就是在向工程型产品的安全约束靠拢。

4)企业工作流平台:优先“稳定可控”,其次才是“智能自主”

企业内部 Agent 平台通常不会追求让子 Agent 无限自治,而是更偏向:

  • 任务拆分有模板或策略约束

  • 权限按任务类型收敛

  • 高风险操作必须显式审批

  • 失败后能追责、能审计、能重试

为什么这么选?因为企业场景里真正昂贵的不是 token,而是事故。一次错误发信、错误写库、错误执行脚本,代价都远高于多花一点模型费用。Hermes 的“默认 auto-deny,再允许配置 auto-approve”就是很典型的企业安全哲学。

产品横评:大家到底怎么选

产品 / 体系子任务形态上下文策略权限策略为什么这么选
Claude Code / Codex CLI隐式拆分或轻量子任务主 Agent 连续上下文为主人在环审批优先CLI 更重连续协作体验
LangGraph / AutoGen显式多 Agent 编排节点/角色独立状态开发者自定义面向可编排、可观测系统
Cursor / Devin 类后台任务单元独立 workspace / sandbox强隔离 + 审批代码任务副作用强,必须防污染
企业 Agent 平台受策略约束的任务流任务级最小上下文审计优先、默认保守企业最怕事故和越权
Hermesdelegate_task + orchestrator/leaf独立 AIAgent + 输出尾部汇总黑名单 + 深度限制 + 非交互审批在“并行效率”和“安全可控”之间取平衡

从这些产品可以提炼出三个规律:

  1. 是否显式暴露子 Agent,取决于产品面向的是“终端用户”还是“系统开发者”

  2. 只要任务有副作用,隔离强度就必须上升,否则并行只会把事故放大。

  3. 成熟产品不会只做“会拆任务”,一定会同时做预算、权限、日志、回收和中断。不会治理的委托,不叫架构,只叫放飞。


总结

  • 子 Agent 委托的核心价值是并行 + 隔离:把大任务拆成独立小任务并行执行,同时保证上下文不泄露

  • 三层隔离机制:独立的 AIAgent 实例(上下文隔离)+ 硬编码工具黑名单(能力隔离)+ max_spawn_depth 深度控制(递归隔离)

  • Orchestrator 角色实现树状委托:通过动态提示词告知 LLM 委托能力边界,解决模型幻觉问题

  • 心跳 + 超时 + 可观测事件:三个机制防止子 Agent 跑飞,实时掌握进度

  • 审批安全:非交互式审批回调避免死锁,审计日志保证可追溯

  • 凭证独立:子 Agent 可以用不同的模型和 Provider,实现成本优化和负载均衡

  • 业界成熟产品的共同规律:子任务委托不是“多开几个模型”,而是拆分、隔离、预算、审批、回收五件事必须一起设计

下一篇我们进入”迭代预算:IterationBudget 如何防止子 Agent 跑飞,父子预算独立的设计哲学”。


关注我,James 的成长日记,持续分享干货,帮你在 AI 时代少走弯路。


内容效果不满意?点此反馈

输入关键词开始搜索