程云来 / 杭州
返回文章·Agent 与 Harness
·约 6 分钟

Subagent:查资料的过程,为什么不必全带回来?

把检查文件交给独立上下文,只把结论带回主流程;看清委派文本、工具白名单、子预算与父流程继续负责的边界。


清单变清楚了,对话却越来越挤

用户交给我们一件小事:“先检查 main.py 和 expected.txt,运行前问我,获准后核对结果。”上一课的清单能记录检查到哪一步,但读过的文件、搜索结果和工具反馈仍然一条条留在模型的历史里。假如检查扩展到几十个文件,主流程最后要决定的只是“现在能不能运行”,每轮却还得带上整段查找过程。

删掉全部记录会丢失依据,全部保留又让一次旁支调查占据主对话。能不能让一个助手单独查完,只带回一段有用的结论?这就是本课的 Subagent(子代理):用独立消息历史完成一项明确任务,再把结果作为主流程的一条工具反馈交回来。

这里的“助手”仍是 Harness 驱动的模型循环。模型提出委派,Harness 创建消息、选择工具并运行子循环。我们先实现同步的一层委派:父流程等子任务返回,再继续判断。

交出去的是问题的一部分

父模型不能只说“帮我看看”,因为子模型没有父对话,连用户到底要检查什么都不知道。委派要带上对象、要回答的问题和必要约束,例如:“读取 main.py 和 expected.txt,说明源码推导的输出与预期是否一致;不要运行。”

Harness 另外提供子模型的工作约定与工具说明。本课只给 read_file;“不要运行”既写在任务里,也落实为没有 run_main 入口。主流程的许可记录、清单更新能力和再次委派能力都不会自动带过去。

DELEGATIONtask textno parent logread / compareFRESH MESSAGESREAD ONLYMAX CALLSCHILD LOOPCHILD OUTCOMEstop statusshort conclusionmodel calls委派文本独立子循环停止结果
父模型把检查写成一条明确委派

用户仍要“获准后运行并核对”。父模型先请求task,只委派读取两个文件并说明预期;运行责任留在父流程。

读取 main.py 与 expected.txt,说明预期;不要运行。
  1. 父模型把检查写成一条明确委派:用户仍要“获准后运行并核对”。父模型先请求task,只委派读取两个文件并说明预期;运行责任留在父流程。
  2. Harness 创建独立消息与只读工具表:子模型收到任务文本、自己的system和read_file说明。父历史、运行工具和再次委派入口没有传入。
  3. 子循环读取证据,留在自己的历史里:read_file仍由Harness执行。文件正文与逐轮工具结果写入子messages,父模型此时同步等待。
  4. 读完后,只返回短结论:子模型回答“源码推导42,预期42;尚未运行”。Harness保留状态和调用次数,把这段结论关联原task请求。
  5. 父流程继续对用户负责:父历史新增一条task结果,内部文件正文不搬回来。父模型重新读取预期文件,运行仍须许可,再根据实际输出核对。

沿图走一遍,注意第四步的返回内容:“源码推导42;尚未运行。”如果改选“预算不足”,子循环只够读第一个文件,就以 call_limit 结束。它们都停了,交给父流程的含义却完全不同。

正常路径里,子模型看到了文件正文与工具反馈,父模型拿到的是短结论、停止状态和调用次数。完整子记录仍可由宿主留存,排查“为什么得出这个结论”时再查,不把它整段塞回主历史。

独立上下文,具体独立在哪里?

先用一段可以运行的短代码观察消息边界。委派文本是新列表的第一条消息,子工具结果只追加到子列表。最后由父流程保存带原调用ID的摘要,父问题保持不变。

PyPython · 浏览器本地运行
未运行
parent = [{"role": "user", "content": "检查文件,获准后运行。"}]
child = [{"role": "user", "content": "读取两个文件,说明预期。"}]
child.append({"role": "user", "content": "文件证据:print(6 * 7),预期42。"})
parent.append({"role": "user", "content": [
    {"type": "tool_result", "tool_use_id": "task_1",
     "content": "源码推导42;尚未运行。"}
]})
print("父问题:", parent[0]["content"])
print("父结果:", parent[1]["content"][0]["content"])
print("子记录:", len(child))
输出

点击行号设置断点,再点“调试”;也可以直接运行。

代码只读;执行发生在浏览器中,不会发送到博客服务器。

运行可看到父问题仍是“检查文件,获准后运行。”,父结果只有结论,子列表另有两条记录。这段代码只演示两个列表的归属,没有调用模型;真正运行时还需要保存父模型的 task 请求,再将结果按同一ID配对,实践篇会保留原循环的完整协议。

独立消息也意味着信息可能不够。若判断必须知道之前用户说过的版本、目录或验收标准,父模型需要把这些事实明确写进委派。把整份父历史复制过去,虽然省了交代背景的工作,也放弃了本课要建立的输入隔离。

结论回来以后,谁决定运行?

子模型可以推导程序“应该输出42”,但这不是运行证据。父流程仍然面对最初的用户问题:取得许可、执行程序、检查退出码和实际输出。子结论不会产生许可,也不能替代程序结果。

本课实践让父流程重新读取预期文件,再请求运行。这是一个有意保留的核对动作:摘要用来引导判断,比较实际输出时仍使用文件中的原始预期。拒绝运行时,父模型回答“未执行”;即使子模型说检查已完成,也不能替用户把运行这一步跳成成功。

如果子任务给了错误结论,父循环也不会因为用了 Subagent 就自动变正确。任务需要可追溯的文件名和证据线索,关键判断要核对原始材料。摘要越短,主上下文越省,丢掉必要细节的风险也越大。

给子循环一条回来的路

父循环有预算,子循环也需要自己的预算。本课子模型最多调用4次,允许配置1到8次;父模型默认4次。子模型报错、协议不符或预算耗尽,都会把原停止状态带回来,结果标记为错误,让父流程知道这次调查没有正常得出结论。

这两个预算限制模型请求次数,不等于总 token 或费用上限。一条模型响应可能请求多个工具;真实项目还要按需要限制工具总数、输出大小、总耗时和费用。禁止子任务再调用 task,则让本课不会递归长出更多子循环。

返回也需要边界。本课只回传最多1200字符的结论;超过就明确截断并标记错误,要求缩小任务。完整子历史可以留给宿主排查,但不作为摘要的隐藏附件继续占满父上下文。示例没有再调用一次模型“总结总结”,否则还需要为这次额外调用安排预算和失败处理。

消息隔离同样不是文件系统隔离。这里的只读能力来自工具白名单,文件读取仍使用原有目录检查。如果以后给子任务写文件工具,修改会落到同一个工作目录,父流程可能马上读到这些变化。想隔离文件副作用,需要另外设计工作目录、权限或沙箱。

什么任务值得交出去?

当一次调查会读很多材料,而主流程主要需要结论时,Subagent 有用,例如定位一项配置来自哪个文件、核对某组测试为什么失败。委派应该能写成一个有输入、有结束条件的小问题,返回内容也能独立判断。

只读一个固定字段,普通函数就够;需要主流程每一步参与判断,分出去反而要反复补背景。子模型自己也消耗请求与时间,同步委派更不会自动加速任务。这里获得的是主历史的清晰边界,是否划算要看省下多少无关上下文,以及结论是否足够可靠。

在实践篇中,把这个边界拆为三个函数:挑选子工具、运行独立循环、封装返回结果。可以直接在代码上设断点,观察预算不足怎样成为父流程可识别的错误反馈。

资料核对于2026-10-19:课程 Subagent提供独立上下文与摘要回传的学习路线;Claude Code 官方子代理文档说明子代理可拥有独立上下文、工作提示与工具范围。本课采用新上下文这一种形式;具体Python接口、只读白名单、预算和摘要限制均是本册教学实现。