父 Agent 为了回答一个局部问题,可能读取大量文件、运行多条命令。这些中间过程如果全部留在父对话里,会持续占用上下文,即使最终只需要一句结论。
Subagent 提供了一条新的执行路径:把边界清楚的子任务交给一段全新的对话,子 Agent 独立探索,最后只把结果返回父 Agent。
父 Agent messages[]
│ task(prompt)
▼
子 Agent messages=[prompt]
│ 独立调用基础工具
▼
最终文本作为 tool_result
│
▼
父 Agent 继续工作核心收益是上下文压缩和任务聚焦,不是凭空增加新的执行能力。
子 Agent 本质上是嵌套 Agent Loop
在当前课程中,task 和普通工具一样被注册:
TASK_TOOL = {
"name": "task",
"description": "Run a subagent with fresh conversation context.",
"input_schema": {
"type": "object",
"properties": {
"prompt": {"type": "string", "minLength": 1},
},
"required": ["prompt"],
},
}
TOOLS = [*BASE_TOOLS, TASK_TOOL]
TOOL_HANDLERS = {**BASE_HANDLERS, "task": run_subagent}父模型生成 task 调用后,handler 并不是直接执行某条系统命令,而是启动另一段“模型响应 → 工具调用 → 工具结果回填”的循环。
这说明 Subagent 不是特殊模型类型。它是 Harness 对同一套 Agent Loop 的再次组合:使用不同的初始消息、system prompt 和工具集合,再把输出包装成父循环的工具结果。
新上下文里有什么,没有什么
子循环从一条新消息开始:
messages = [{"role": "user", "content": prompt}]它不会自动继承父 Agent 的完整 messages[]。因此父对话中的用户要求、已做决定、错误输出和隐藏在早期消息中的文件路径,除非被写进 prompt 或能从共享资源重新读取,否则子 Agent 并不知道。
这既是优点也是风险:
- 优点:无关历史不会挤占子任务上下文;
- 风险:委派提示遗漏约束时,子 Agent 会在信息不足的情况下自行推断。
一个好的委派提示至少应包含:
- 明确目标和不在范围内的内容;
- 可读取的权威来源或入口文件;
- 允许使用的工具和副作用边界;
- 期望输出格式;
- 完成条件与需要返回的证据。
“看看这个模块”不是一个可靠子任务;“只读检查认证模块的 token 刷新路径,返回入口、关键调用链和带文件位置的风险,不修改文件”才更接近可执行契约。
隔离的是消息,不是进程和文件系统
当前教学实现中,父子循环共享:
- Python 进程;
- 模型客户端;
WORKDIR;- 基础工具 handler;
- 权限与生命周期 Hooks;
- 文件和命令造成的外部副作用。
因此,子 Agent 修改文件后,父 Agent 能看到修改;子 Agent 删除或覆盖文件,影响的也是同一个工作区。
“fresh context”不能理解为安全沙箱。上下文隔离、权限隔离、进程隔离和工作区隔离是四个不同维度:
| 维度 | 当前课程示例 |
|---|---|
| 对话消息 | 隔离 |
| System prompt | 使用独立子提示 |
| 工具集合 | 子集化 |
| 进程 | 共享 |
| 工作目录 | 共享 |
| 文件副作用 | 共享 |
| 权限 Hooks | 共享 |
如果任务可能写入同一批文件,仅有新 messages[] 不足以避免冲突。更强的隔离需要独立工作树、容器、临时目录、文件锁或由父 Agent 统一合并变更。
为什么子 Agent 没有 task 工具
课程通过两套工具集合限制委派深度:
SUB_TOOLS = list(BASE_TOOLS)
SUB_HANDLERS = dict(BASE_HANDLERS)父 Agent 拥有 BASE_TOOLS + task,子 Agent 只有基础工具。因为子集合中没有 task,它无法继续创建孙 Agent。
这是能力收窄的简单例子:不是在 prompt 中要求“不要再委派”,而是根本不向子模型暴露该工具。
生产系统还可以按角色提供不同能力:
- 探索型子 Agent:只读文件和搜索;
- 验证型子 Agent:只运行允许列表中的测试;
- 编辑型子 Agent:只写指定目录;
- 外部研究 Agent:允许联网,但拿不到本地凭据;
- 高风险执行 Agent:每个副作用都需要批准。
工具不可见通常比仅靠文字禁止更可靠,但 handler 仍需执行运行时授权。
当前实现是同步的,不是并行的
task handler 调用 run_subagent() 后,父循环会等待子循环返回。子 Agent 完成前,父 Agent 不会继续处理后面的推理。
即使父模型在一次响应中生成多个 task block,当前分发代码也是按顺序遍历,因此这些子任务仍会串行执行。
同步设计容易理解,也减少并发写入冲突。若要并行化,还需要额外解决:
- 每个子任务的取消、超时和预算;
- 结果与调用 ID 的稳定关联;
- 多个子 Agent 同时写文件的冲突;
- 共享终端和人工批准请求的交互竞争;
- 部分成功、部分失败时如何汇总;
- 父任务结束后是否取消仍在运行的子任务。
并行不是把循环放进线程池就结束了,它会改变状态所有权和失败语义。
30 轮上限限制的是什么
当前子循环使用:
for _ in range(30):
response = client.messages.create(...)
tool_calls = [block for block in response.content if block.type == "tool_use"]
if not tool_calls:
return extract_text(response.content) or "(no summary)"一次循环对应一次模型响应。响应中可以有零个、一个或多个工具调用,所以 30 轮不等于 30 个工具调用。
上限用于避免模型持续调用工具而永不结束,但数字 30 只是教学参数。完整的资源治理还应限制:
- 总模型调用次数与 token 预算;
- 单工具和整项子任务的墙钟时间;
- 命令 CPU、内存、进程数和输出大小;
- API 重试次数和退避;
- 用户取消信号;
- 全局任务预算,避免每个子 Agent 都耗尽独立上限。
达到上限后返回一段“已停止”文本,只是可观察的失败结果,不应被父 Agent 当成任务成功。
只返回最终文本是一种有损压缩
子 Agent 的中间消息保存在自己的局部列表中,不会逐条加入父对话。结束时,Harness 提取最终响应中的文本,作为 task 的 tool_result 返回父 Agent。
这正是节省上下文的来源,但也会丢失信息:父 Agent 看不到子 Agent 为何得出结论、查过哪些文件、哪些命令失败过。
因此最终结果最好包含可核验的摘要,而不是只有一句判断。可以约定返回:
{
"status": "completed",
"summary": "认证刷新由 refresh_session 处理",
"evidence": ["src/auth/session.py:84"],
"changes": [],
"verification": ["pytest tests/auth -q: passed"],
"open_questions": []
}结构化返回并不保证结论正确,但能让父 Agent 更容易核验、合并和决定下一步。对于高风险任务,父 Agent 应重新检查关键证据,而不是盲信子 Agent 的自然语言总结。
权限必须在每个执行入口生效
当前课程让父子循环共同经过 execute_tool(),因此两者触发同一组 PreToolUse 和 PostToolUse Hooks。这说明权限控制应放在实际分发路径上,而不是只写在父 Agent 的 system prompt 中。
但“共享 Hooks”仍不等于完整安全:
- 字符串黑名单无法可靠约束任意 Shell;
- 人工批准必须明确展示是哪个子任务发起的;
- 子 Agent 读取的仓库内容可能包含提示注入文本;
- 子 Agent 的总结属于不可信输入,父 Agent 不应把其中指令当作更高优先级规则;
- 本地凭据、网络权限和环境变量仍需在进程或容器层隔离。
更稳妥的默认值是让子 Agent 拥有完成任务所需的最小能力,探索任务优先只读,写入和外部操作按任务单独授权。
Python 作用域和 Schema 的两个小问题
旧版笔记关注了两个语法点,它们可以保留为实现补充。
Python 的 for 循环不会创建新的块级作用域,因此循环内赋值的 response 在同一函数后面仍可访问。但如果循环一次都没有执行,变量就不会被赋值;使用固定正数范围虽然规避了当前情况,提前初始化或在循环内直接返回仍更清楚。
工具 Schema 中多个属性分别写 "type": "string" 也不是重复:每个属性都必须声明自己的类型。属性名、属性类型和面向模型的 description 位于不同层级,各自承担不同作用。
建议覆盖的测试
Subagent 至少应验证:
- 子消息不包含父对话历史;
- 委派 prompt 包含目标、范围和输出契约;
- 子工具集合不包含
task; - 父子循环都经过权限与审计入口;
- 子任务正常结束、无文本结束和达到轮次上限;
- 一轮多个工具调用时的结果关联;
- 子 Agent 修改文件后父 Agent 可见;
- 只读角色无法调用写入工具;
- 多个
task在当前实现中按顺序执行; - API、工具和人工批准发生异常或取消;
- 结果包含证据,父 Agent 能识别失败状态;
- 并行扩展时的写冲突、预算和取消传播。
小结
Subagent 的价值,是把一个边界清楚的子任务放进新对话,让中间探索留在子上下文,只把压缩后的结果交还父 Agent。它能减少父上下文污染,也便于为不同任务配置不同工具。
但新对话不等于新沙箱,当前示例也不是并行系统。父子 Agent 仍共享进程、工作目录和文件副作用。要把 Subagent 用于真实工程,需要同时设计委派契约、能力收窄、权限检查、资源预算、失败传播、证据回传和必要的执行环境隔离。