Subagent Delegation(子代理委派)
delegate_task 工具会生成子 AIAgent 实例,每个实例拥有独立的上下文、受限的工具集和各自的终端会话。每个子代理获得全新的对话并独立工作——只有其最终摘要会进入父代理的上下文。
单个任务
delegate_task(
goal="Debug why tests fail",
context="Error: assertion in test_foo.py line 42",
toolsets=["terminal", "file"]
)并行批量处理
默认最多 3 个并发子代理(可配置,无硬上限):
delegate_task(tasks=[
{"goal": "Research topic A", "toolsets": ["web"]},
{"goal": "Research topic B", "toolsets": ["web"]},
{"goal": "Fix the build", "toolsets": ["terminal", "file"]}
])子代理上下文工作原理
:::warning 关键点:子代理一无所知
子代理以完全全新的对话开始。它们对父代理的对话历史、之前的工具调用或委派之前讨论的任何内容一无所知。子代理的唯一上下文来自父代理调用 delegate_task 时填充的 goal 和 context 字段。
:::
这意味着父代理必须在调用中传递子代理所需的一切:
# 错误——子代理不知道"这个错误"是什么
delegate_task(goal="Fix the error")
# 正确——子代理拥有所需的所有上下文
delegate_task(
goal="Fix the TypeError in api/handlers.py",
context="""文件 api/handlers.py 在第 47 行有一个 TypeError:
'NoneType' object has no attribute 'get'。
函数 process_request() 从 parse_body() 接收一个字典,
但 Content-Type 缺失时 parse_body() 返回 None。
项目位于 /home/user/myproject,使用 Python 3.11。"""
)子代理会收到一个聚焦的系统提示,由您的目标和上下文构建,指示它完成任务并提供一个结构化的摘要,说明做了什么、发现了什么、修改了哪些文件以及遇到了什么问题。
实用示例
并行研究
同时研究多个主题并收集摘要:
delegate_task(tasks=[
{
"goal": "Research the current state of WebAssembly in 2025",
"context": "Focus on: browser support, non-browser runtimes, language support",
"toolsets": ["web"]
},
{
"goal": "Research the current state of RISC-V adoption in 2025",
"context": "Focus on: server chips, embedded systems, software ecosystem",
"toolsets": ["web"]
},
{
"goal": "Research quantum computing progress in 2025",
"context": "Focus on: error correction breakthroughs, practical applications, key players",
"toolsets": ["web"]
}
])代码审查与修复
将审查和修复工作流委派给新上下文:
delegate_task(
goal="Review the authentication module for security issues and fix any found",
context="""项目位于 /home/user/webapp。
认证模块文件:src/auth/login.py, src/auth/jwt.py, src/auth/middleware.py。
该项目使用 Flask、PyJWT 和 bcrypt。
重点关注:SQL 注入、JWT 验证、密码处理、会话管理。
修复发现的所有问题并运行测试套件(pytest tests/auth/)。""",
toolsets=["terminal", "file"]
)多文件重构
将大型重构任务委派出去,避免冲击父代理的上下文:
delegate_task(
goal="Refactor all Python files in src/ to replace print() with proper logging",
context="""项目位于 /home/user/myproject。
使用 'logging' 模块,logger = logging.getLogger(__name__)。
将 print() 调用替换为适当的日志级别:
- print(f"Error: ...") -> logger.error(...)
- print(f"Warning: ...") -> logger.warning(...)
- print(f"Debug: ...") -> logger.debug(...)
- 其他 print -> logger.info(...)
不要更改测试文件或 CLI 输出中的 print()。
运行 pytest 验证没有破坏任何内容。""",
toolsets=["terminal", "file"]
)批量模式详情
当您提供一个 tasks 数组时,子代理使用线程池并行运行:
- **最大并发数:**默认 3 个任务(可通过
delegation.max_concurrent_children或DELEGATION_MAX_CONCURRENT_CHILDREN环境变量配置;下限为 1,无硬上限)。超过限制的批次会返回工具错误,而非静默截断。 - **线程池:**使用
ThreadPoolExecutor,将配置的并发限制作为最大工作线程数 - **进度显示:**在 CLI 模式下,树视图实时显示每个子代理的工具调用,附带每任务完成行。在网关模式下,进度会分批中继到父代理的回调
- **结果排序:**结果按任务索引排序以匹配输入顺序,无论完成顺序如何
- **中断传播:**中断父代理(例如发送新消息)会中断所有活动子代理
单任务委派直接运行,无需线程池开销。
模型覆盖
您可以通过 config.yaml 为子代理配置不同的模型——适用于将简单任务委派给更便宜/更快的模型:
# 在 ~/.hermes/config.yaml 中
delegation:
model: "google/gemini-flash-2.0" # 为子代理使用更便宜的模型
provider: "openrouter" # 可选:将子代理路由到不同的提供商如果省略,子代理使用与父代理相同的模型。
工具集选择技巧
toolsets 参数控制子代理可以访问哪些工具。根据任务选择:
| 工具集模式 | 使用场景 |
|---|---|
["terminal", "file"] | 代码工作、调试、文件编辑、构建 |
["web"] | 研究、事实核查、文档查阅 |
["terminal", "file", "web"] | 全栈任务(默认) |
["file"] | 只读分析、不执行的代码审查 |
["terminal"] | 系统管理、进程管理 |
无论您指定什么,某些工具集对子代理是禁用的:
delegation(委派)——对叶子子代理禁用(默认)。对role="orchestrator"的子代理保留,受max_spawn_depth限制——参见下方的深度限制与嵌套编排clarify(澄清)——子代理不能与用户交互memory(记忆)——不允许写入共享持久记忆code_execution(代码执行)——子代理应逐步推理send_message(发送消息)——不能产生跨平台副作用(例如发送 Telegram 消息)
最大迭代次数
每个子代理有一个迭代次数限制(默认:50),控制它可以进行多少轮工具调用:
delegate_task(
goal="Quick file check",
context="Check if /etc/nginx/nginx.conf exists and print its first 10 lines",
max_iterations=10 # 简单任务,不需要很多轮
)子代理超时
如果子代理静默时间超过 delegation.child_timeout_seconds 挂钟秒数,将被视为卡死而终止。默认值为 600 秒(10 分钟)——从早期版本的 300 秒提升,因为高推理能力的模型在处理复杂研究任务时中途被终止。按需调整:
delegation:
child_timeout_seconds: 600 # 默认值对快速的本地模型降低此值;对处理难题的慢速推理模型提高此值。计时器在子代理每次进行 API 调用或工具调用时重置——只有真正空闲的工作线程才会触发终止。
:::tip 零调用超时的诊断转储
如果子代理在进行了零次 API 调用时超时(通常:提供商不可达、认证失败或工具架构拒绝),delegate_task 会向 ~/.hermes/logs/subagent-timeout-<session>-<timestamp>.log 写入结构化诊断信息,包含子代理的配置快照、凭证解析轨迹和任何早期错误信息。比之前的静默超时行为更容易排查。
:::
监控运行中的子代理(/agents)
TUI 提供了 /agents 覆盖层(别名 /tasks),将递归的 delegate_task 扇出转化为一等审计界面:
- 运行中和最近完成的子代理的实时树视图,按父代理分组
- 每分支的成本、令牌和触及文件汇总
- 终止和暂停控制——在不中断兄弟子代理的情况下取消特定子代理
- 事后审查:逐步查看每个子代理的逐轮历史,即使它们已返回父代理
经典 CLI 仅将 /agents 打印为文本摘要;TUI 才是覆盖层大放异彩的地方。参见 TUI — 斜杠命令。
深度限制与嵌套编排
默认情况下,委派是扁平的:父代理(深度 0)生成子代理(深度 1),这些子代理不能进一步委派。这防止了失控的递归委派。
对于多阶段工作流(研究→综合,或子问题的并行编排),父代理可以生成编排者子代理,这些子代理可以委派它们自己的工作线程:
delegate_task(
goal="Survey three code review approaches and recommend one",
role="orchestrator", # 允许此子代理生成自己的工作线程
context="...",
)role="leaf"(默认):子代理不能进一步委派——与扁平委派行为相同role="orchestrator":子代理保留delegation工具集。受delegation.max_spawn_depth限制(默认 1 = 扁平,因此role="orchestrator"在默认设置下是空操作)。将max_spawn_depth提高到 2 以允许编排者子代理生成叶子孙代理;3 为三层(上限)delegation.orchestrator_enabled: false:全局关闭开关,强制所有子代理成为leaf,无论role参数如何
成本警告: 设置 max_spawn_depth: 3 和 max_concurrent_children: 3 时,树可达 3×3×3 = 27 个并发叶子代理。每增加一层都会成倍增加开销——请有意识地提高 max_spawn_depth。
生命周期与持久性
:::warning delegate_task 是同步的——不具有持久性
delegate_task 在父代理的当前轮次内运行。它会阻塞父代理,直到所有子代理完成(或被取消)。它不是后台任务队列:
- 如果父代理被中断(用户发送新消息、
/stop、/new),所有活动子代理被取消并返回status="interrupted"。它们正在进行的工作被丢弃。 - 子代理在父代理轮次结束后不会继续运行。
- 被取消的子代理返回结构化结果(
status="interrupted",exit_reason="interrupted"),但由于父代理也被中断,该结果通常永远不会出现在用户可见的回复中。
对于必须能承受中断或在当前轮次之后继续运行的持久性长时间工作,请使用:
cronjob(action=create)——安排一个独立的代理运行;不受父代理轮次中断的影响terminal(background=True, notify_on_complete=True)——后台运行的 shell 命令,代理在处理其他事务时继续运行 :::
关键属性
- 每个子代理获得自己的终端会话(与父代理分离)
- 嵌套委派是选择加入的——只有
role="orchestrator"的子代理可以进一步委派,且仅在max_spawn_depth从其默认值 1(扁平)提高时。通过orchestrator_enabled: false全局禁用 - 叶子子代理不能调用:
delegate_task、clarify、memory、send_message、execute_code。编排者子代理保留delegate_task,但仍不能使用其他四个 - 中断传播——中断父代理会中断所有活动子代理(包括编排者下的孙代理)
- 只有最终摘要进入父代理的上下文,保持令牌使用效率
- 子代理继承父代理的 API 密钥、提供商配置和凭证池(允许在速率限制时进行密钥轮换)
委派 vs code_execution
| 因素 | delegate_task | execute_code |
|---|---|---|
| 推理能力 | 完整的 LLM 推理循环 | 仅 Python 代码执行 |
| 上下文 | 全新的隔离对话 | 无对话,仅脚本 |
| 工具访问 | 带推理的所有非禁用工具 | 通过 RPC 的 7 个工具,无推理 |
| 并行性 | 默认 3 个并发子代理(可配置) | 单个脚本 |
| 最佳用途 | 需要判断力的复杂任务 | 机械式的多步骤流水线 |
| 令牌成本 | 较高(完整 LLM 循环) | 较低(仅返回 stdout) |
| 用户交互 | 无(子代理不能澄清) | 无 |
**经验法则:**当子任务需要推理、判断或多步骤问题解决时使用 delegate_task。当需要机械式数据处理或脚本化工作流时使用 execute_code。
配置
# 在 ~/.hermes/config.yaml 中
delegation:
max_iterations: 50 # 每个子代理的最大轮数(默认:50)
# max_concurrent_children: 3 # 每批次的并行子代理数(默认:3)
# max_spawn_depth: 1 # 树深度(1-3,默认 1 = 扁平)。提高到 2 以允许编排者子代理生成叶子代理;3 为三层
# orchestrator_enabled: true # 禁用以强制所有子代理为 leaf 角色
model: "google/gemini-3-flash-preview" # 可选的提供商/模型覆盖
provider: "openrouter" # 可选的内置提供商
api_mode: anthropic_messages # 可选;对于 anthropic_messages 端点从 base_url 自动检测
# 或使用直接的自定义端点替代提供商:
delegation:
model: "qwen2.5-coder"
base_url: "http://localhost:1234/v1"
api_key: "local-key"
# api_mode: "anthropic_messages" # 可选。base_url 的有线协议覆盖("chat_completions"、"codex_responses" 或 "anthropic_messages")。空 = 从 URL 自动检测(例如 /anthropic 后缀)。对于启发式方法无法分类的端点显式设置(Azure AI Foundry、MiniMax、Zhipu GLM、LiteLLM 代理等)当 base_url 指向兼容 Anthropic 的端点时——例如以 /anthropic 结尾的路径、Azure Foundry Claude 路由或 MiniMax /anthropic 代理——api_mode 会被自动检测为 anthropic_messages,以便子代理使用正确的有线格式,无需您进行任何设置。当自动检测猜测错误时(很少见),请显式设置 api_mode。
:::tip 代理会根据任务复杂度自动处理委派。您无需明确要求它委派——它会在合理时自行进行。 :::