12. Subagent

派个分身去干活:上下文隔离、终检与「理解必须收敛」

上一篇 Plan Mode 解决的是「一个 Agent 动手前先想清楚」,始终是它一个人在谋划。这一篇讲的 Subagent,是另一种组织方式:把一些又重又独立、还容易搅乱主线的活,派给「分身」去隔离地干

👆你在这里
👆你在这里

🪧 主 Agent 的上下文,是一张不该被弄乱的桌面

我想让 Agent 在动笔前做一轮调研,翻它几十篇素材、几十个网页,找论据、找案例

可问题来了:这些原始素材一旦都堆进主写作的上下文里,会发生什么?一是噪声淹没正事,模型的注意力被大量细节稀释,写作本身反而模糊了;二是烧 token,几十篇原文塞进去,这一轮的开销陡增;三是更隐蔽的,上下文被撑到触发压缩,而压缩很可能把我们前几篇拼命保住的画像给挤掉(第 2、4 篇的老问题)

主 Agent 的上下文,得当成一张干净的写作桌面来护着。调研是要做的,但调研那堆乱糟糟的草稿纸,不该摊在这张桌面上。理想的做法是:派个分身,去隔壁房间翻资料,翻完了,只把整理好的一页摘要递回来。 隔壁房间怎么乱是它的事,我这张桌面要始终清清爽爽

这个「隔壁房间的分身」,就是 Subagent

🧭 基础概念:Subagent = 一个跑在隔离房间里的分身

Subagent 是一个独立的 agent 实例,跑在一个全新的、隔离的上下文里,干完活,只把最终结论(那条 final message)递回给主 agent。 它的核心价值就俩字:隔离

这里要特别跟第 6 篇的 Skill 分个清楚,因为这俩我曾经搅混过:

一轻一重、一共享一隔离,用途自然就分开了。Skill 适合「教 agent 怎么写某类文章」这种要就地用的方法论;Subagent 适合「翻资料、做质检」这种又重、中间过程又全是噪声、只有结论有用的独立活

那「隔离」到底带来了什么,值得我费这个劲?Harness Book 讲多智能体时有个观点:多个分身真正解决的,不是「更快」,而是「失败可定位」。 一个 agent 把调研、写作、检查全炖在一锅上下文里,出了问题很难按层排查;而拆成一个个隔离的分身,研究归研究、检查归检查,哪一层出岔子一目了然。所以使用 subagent,不仅仅是为了提速(纯串行分拆反而更慢),更关键的是把混乱关进隔离的房间里,让整个系统可拆解、可排查

主 Agent & Sub Agent
主 Agent & Sub Agent

🔩 Claude Code:主 agent 派活给子 agent 的硬规则

「隔离」的价值想明白了,具体怎么落地?先看 Claude Code 原生是怎么设计这套协作机制的。Claude Code 有一套成熟的多 agent 协作机制,Harness Book 专门有一章讲它,标题就叫「靠分工来驾驭不稳定性」。主 agent 通过一个「派活」工具,把一段任务甩给一个 sub-agent;sub-agent 在隔离上下文里跑完,结果作为这个工具的返回值回到主 agent 手里。Learn Claude Code 的源码解读系列第六篇,也专门拆了 subagent 机制,对理解这套隔离模型很有帮助

这套机制里有几条硬规则,是我后面所有定制化设计的地基,得先说清楚:

Harness Book 里反复强调,我觉得说的挺对:一个设计良好的分身机制,会把子 agent 的状态默认隔离干净,子 agent 里那些临时的、混乱的决策,绝不许回流污染父线程。用它的原话说,子代理的价值,正在于「能容纳局部的混乱」。这跟前面的比喻「隔壁房间怎么乱是它的事」是一个意思,只不过它是从系统设计的角度说的

🛠 SDK:怎么定义一个分身,以及我为什么选「用代码定义」

上面这些是 Claude Code 原生跑起来的规则;但 SmartWriter 是通过 SDK 集成写作 Agent 的,同一套隔离机制,到了开发者手上还是要自己配置、自己调用。到了 SDK 这层,定义一个 sub-agent 有两条路:

本次项目里使用的是编程定义,因为编程定义支持在运行时按场景动态地造分身:比如按当前题材给它配不同的工具、按任务的严格程度给它挂不同的模型。文件定义是死的,做不到这种「临场按需组装」

定义好了,还得保证主 agent 在该派活的时候真的会派,这里有三个要点,缺一不可:

  1. 那个「派活」工具,必须在允许清单里,否则主 agent 想派也派不动(会落到审批、或直接被拒,然后它就自己憋着把活干了)
  2. 每个分身的描述要写清楚「什么时候该用它」,因为主 agent 就是靠这句描述来判断该不该派活的。含糊的描述,等于分身白造
  3. 关键节点要显式点名,再用流程兜底。对「必须派」的环节,别只靠模型自觉,该在流程里硬性触发(参考第 10 篇「关键环节用 hook 焊死」)

最后一个小配置,也是个容易踩的坑:SDK 从 v2.1.198 起,把「派活是否阻塞主线」的默认值,从阻塞式改成了跟 Claude Code 本身一致的非阻塞,主 agent 不等分身干完就继续往下走。这个默认值本身没毛病:面对复杂的编程任务,主 Agent 派个分身去处理一个独立子问题,确实可以先忙别的,等分身回话了再回头跟进,效率更高。但写作场景不太一样,调研、质检、事实核查都得先拿到结论才能往下走,所以我把每个分身定义都显式改回了阻塞式,还额外挂了个 PreToolUse hook 强制注入阻塞双保险(光显式设置不一定生效,belt-and-suspenders)

👥 SmartWriter 的三个分身:各司其职

SmartWriter 里养了三个分身,各干各的活:

分身 干什么 配的工具 用的模型 回传什么
research-assistant 调研:翻大量素材 / 网页,找论据案例 Read / WebSearch / WebFetch 默认 Sonnet 一段自由文本摘要
reflection-checker 终检:全文质检(错别字 / 逻辑 / 风格 / 画像对齐 / 技术准确 / 结构) 只有 Read 默认 Sonnet 一份结构化 JSON 报告
fact-checker 事实核查:联网核实文中的数据、引文 Read / WebSearch / WebFetch 默认 Sonnet 一份结构化 JSON

这张表里有几个决策值得展开:

一、按活的分量配模型。 三个分身默认用 Claude Sonnet 5 / Deepseek V4 Flash,因为调研要综合、核查要判断、终检要挑刺,需要具备基础的推理能力,前期测试下来这两个模型基本够用。而因为使用的是编程定义,后续如果想做得更精细化,比如根据任务分量动态指派模型,也没有问题,但文件定义就做不到这种临场调整了

二、终检这个分身,工具只有一个 Read 它的职责是发布前的兜底「检查」,不是「改稿」。所以我一个能改文件的工具(Write、Edit)都不给它,能跑命令的(Bash)、能再派活的(Agent)也统统不给。这背后是 Harness Book 那条「验证必须独立于实现」的原则:改稿的和验稿的,得是两拨「人」,一个只读、绝不下场改,才能保持它作为「第二道质量门」的独立性。「我改了」和「改对了」之间,隔着一条宽河,模型很擅长在这条河上搭纸桥自欺,因此验证类任务我更倾向于用独立 Subagent 去干

三、终检不联网做事实核查,我把它拆出去了。 早期我图省事,想让终检顺便联网核实一下文中的数据、引文。后来砍掉了,事实核查另建了一个独立的 /fact_check。原因有三:功能重叠、重复烧 token、以及用户不可控(终检时被动联网,用户没预期)。终检只保留「读稿时发现明显的年代错乱、数据自相矛盾」这类不联网的轻量提示。一个分身只干一件定义清晰的事,尽量不让它什么都沾一点,这也是「失败可定位」的前提

对了,你可能注意到画像分析不在这张表里。它看起来像个第四分身,但我没把它注册成 SDK subagent,它走的是业务层的并发 query()(首启时固定流程、或用户自主触发,不需要上下文隔离,直调 query 拿结构化结果就够了)。并不是所有「派出去的活」都要一概变成 SDK 分身,只在需要上下文隔离时才该花那个开销

🎯 研究可以分布,理解必须收敛

三个分身摆好了,但真正让我对「分工」这件事理解更深一层的,是 Harness Book 里的一条最佳实践建议:研究可以分布,理解必须收敛

你可以把「调查、研究」这类活,分布给多个分身并行去干,这没问题;但把这些分身带回来的一堆 findings,消化成一个连贯的、可执行的下一步,这个「理解」的动作,必须收敛回主 agent 一个人手里

一开始读到这句话,其实我没太注意,直到后面真正踩到坑,才明白它的重要性

🔧 避坑 · 我把「理解」这道工序,错派给了用户

在早期的调研和事实核查,链路是这样的:分身干完,产出一份报告,前端把这份报告原样展示给用户,完事,就没有然后了

但用户拿到这份报告后呢?他得自己读一遍,理解每一条的意思,然后手动敲一句 prompt:「请按上面第 2、5 条的结论,修改我的作品……」。相当于「把一份报告消化成对稿子的具体修改」这个最费脑子的活,我甩给了用户

这恰恰违反了「理解必须收敛」。用户被迫成了那个「协调者」,替我的主 agent 做了本该它自己做的消化工作,累,门槛又很高。一个正常写作用户,不会想去研究怎么把一份报告翻译成一句有效的修改指令

正解是把这道工序收回来:主 agent 拿到子 agent 的报告后,自己把它转成一份「可执行的修改 prompt」(带上每条问题的具体位置、描述、建议、证据,以及明确的「改哪、怎么改」指令),前端给用户一个「应用」按钮,点一下,修改就落到稿子上。研究的噪声散在分身那头,理解的收敛落在主 agent 这头,最后用户只需要一个动作:看一眼,点「应用」

把这个「收敛」做到位了,分工才真正省心,否则只是把复杂度从模型转嫁给了用户,治标不治本

🛡 让 LLM 稳稳吐出一个 JSON:三层防御,和一个甩不掉的上游坑

说回「结论要交给程序消费」这件事:上面提到,终检、事实核查、画像分析回传的都是「结构化 JSON」。但 LLM 是出了名的「自由散漫」,怎么才能让一个 Subagent,稳稳地、每次都吐出一个格式正确的 JSON?

SDK 本身是自带一层机制的,走结构化查询时,你可以给它绑一个 JSON schema,SDK 会在「模型吐的 JSON 不符合 schema」时自动重新提问、让它重吐,直到对为止。这本是开箱的一道保险。但问题是 Subagent 用的 AgentDefinition,它目前还不支持直接绑 schema,所以它只能靠 prompt 引导着吐 JSON,实测下来非常不稳定

于是我在 SDK 的外面又叠了三层应用层防御,自己来做兜底:

Subagent 的 JSON 输出校验
Subagent 的 JSON 输出校验

📐 深挖 · CLI 的「包裹」bug,和我用「白名单」把它根治的思路

底层那个 CLI 有个公开已知的 bug(社区有 issue 在跟):它偶尔会抽风,把模型本该规规矩矩返回的整个结构化输出,序列化成一个字符串,塞进一个单独的占位 key 里。更坑的是,这个占位 key 的名字还不固定,我见过 $PARAMETER_NAME__unparsedToolInputoutputdescription……各种奇形怪状的变体。实测触发率不低,某个版本的 CLI 上大概三次里有一次中招,换个更新的版本反而更糟。而这属于上游的 bug,等它修好,还不一定是什么时候呢

我最早的应对,是列一个「已知坏 key 名」的黑名单去匹配。很快发现这是个打地鼠游戏:上游的 key 名千奇百怪、还会冒新的,黑名单永远列不全

后来我把思路反过来,不去猜「坏 key 长什么样」,只认「好 key 长什么样」。 我的 schema 里,该有哪些字段我是完全确定的。所以我立一条规则:如果收到的是一个「只有单个 key 的 dict,而这个 key 根本不在我的 schema 字段清单里」,那它大概率就是被包裹了,直接把它解开、取出里面真正的内容。这样一来,不管上游给我包一个多离谱的 key 名,都能兜得住

这个思路我觉得挺有普适性:面对一个不可控的上游 bug,与其被动地枚举它所有的错法(防不胜防),不如站在「我确定知道的东西」(我的 schema)上,去反推那些「我不确定的东西」(乱七八糟的 key),把主动权攥回自己手里

🧯 分身也会失败:把每一种失败都当回事

还有一个容易被忽略、但对真实产品很重要的点:分身是会失败的。 它可能超时、可能返回空、可能吐出一个怎么都解析不了的东西

不能让这些失败「静默」过滤掉,我的做法是把分身的各种失败模式正儿八经地列成了一张矩阵(超时、agent 报错、没输出、输出为空、解析失败……),每一种,都对应一个给用户看的友好错误态。终检、调研、事实核查这些又都是反复迭代的行为(改稿、再终检、再改),所以我把每一次执行的结果,都按序号存成独立的文件(而不是互相覆盖),这样历史上每一次终检的报告都追得回来。这些听着琐碎,但「一个分身罢工了,用户能不能得到一句人话的交代」,恰恰是 demo 和产品的分水岭

⚖️ 垂直落点:分工不是为了炫技,是为了「隔离噪声 + 收敛理解」

收个尾,回到贯穿全专栏的对照:

⚖️ 取舍现场 · 用分身这件事,通用和垂直在想什么

"偷懒"的做法 SmartWriter 为什么
调研这种重活 主 agent 自己在主上下文里翻 派给隔离的分身,只收一页摘要 护住主写作桌面,不被噪声淹没
分身的报告 原样展示给用户 主 agent 消化成「一键应用」的修改 理解必须收敛,别外包给用户
结构化结论 信 LLM 能吐对 三层防御 + schema 白名单兜底 LLM 会散漫,上游还有 bug
验证 / 终检 谁写谁顺手检查 独立分身、只读、绝不下场改 验证要独立于实现,才靠得住

写到这,「编排与分工」这个板块就整个讲完了。回头看它这两篇,其实是在两个维度上,对抗「单个 Agent 面对复杂任务时的手忙脚乱」:Plan Mode 是时间上的编排,动手之前先把顺序想清楚;Subagent 是空间上的分工,把独立又吵闹的活,隔离到别处去干,只把干净的结论收敛回来。一个管「先后」,一个管「内外」,合起来,才让一个 Agent 有能力从容地啃下一篇复杂的长文写作,而不是从头乱到尾

到这一篇,我们把 12 个机制,从引擎、记忆、驾驭、交互,一路拆到编排与分工,全都拆完了。这台机器的每一个零件,都拿出来好好看过了。但比较残酷的是,这一切精心琢磨的细节,用户是完全感知不到的。 他打开应用,看到的只是一个界面、一个输入框、一份稿子。怎么把这套精密又复杂的机器,包装成一个可能连 Agent 是什么都不知道的写作者,也能顺手用起来的产品?

这,就是最后一个板块「嵌入垂直业务」要回答的。从下一篇起,我们接着掰扯产品流程,看看这些机制是怎么被串成一条真正的写作业务动线的。咱们继续聊