1. 子 Agent 的一生
上一篇讲清了「为什么拆」与「拆开之后谁负责什么」。这一篇跟着一个子 Agent 走完它的一生,看每一步具体要做什么决定。
父级决定拆。 它先把一个任务切成几个子任务,判断哪一个适合交出去。判据在上一篇讲过——需要隔离上下文、需要收窄权限、需要独立归因。
组装任务描述。 父级写一段话交给子级。这段话是子级能看到的全部信息,写得好不好直接决定子级能不能做事。
创建子级。 选择一个运行环境:全新建一个会话,或者从父级历史分叉,或者交给另一个运行时。
子级读到什么。 它拿到任务描述,加上自己的系统提示与工具集。它看不到父级的思考过程(除非分叉),也看不到另外几个子级的进展。
执行与回报。 子级干活,完成后把结果交回。结果的形式取决于回报结构:一段自由文本,还是一个带状态、证据、未决项的结构化对象。
父级据以决策。 继续、重试、换一个子级、或者终止。
销毁。 子级会话结束。如果它有产物,产物要能被父级访问;如果它有结论,结论要已经在回报里。
这一生的每一站都对应一个设计问题。
| 站 | 要回答的问题 | 可选答案 |
|---|---|---|
| 创建子级 | 子级如何创建 | 全新建、从父级历史分叉、跨运行时远程 |
| 子级读到什么 | 看多少父级历史 | 零继承、继承摘要、继承完整前缀 |
| 子级能用什么 | 工具集怎么给 | 父级全量、按角色裁剪、按任务声明 |
| 执行与回报 | 结果怎么回来 | 自由文本、结构化对象、工具回报 |
| 父级决策 | 同时能跑几个 | 一个、固定上限、动态配额 |
五个问题是设计顺序,不是分类维度:前一个的答案会收窄后一个的可选范围。
2. 五问在四家身上的分布
同一个问题,四家给了不同答案。差异不在谁做得更好,在于答案之间的约束让它们落在三条已经收敛的路线上。
接下来五节逐问展开:有哪些可选答案、四家各选了什么、这个选择的代价是什么。
3. 子级如何创建
三种方式,差别在「新会话从哪里获得初始状态」。
全新建。 子级从零开始,只有调用方显式传入的任务描述。优点是干净,缺点是需要交代。
从父级历史分叉。 子级复制父级的上下文前缀。优点是省去交代,缺点是子级会继承父级的错误假设。
Claude Code 的 fork 语义值得细看。它的提示词里写了两条与常规理解相反的约束。
第一条,分叉时提示词应当写成指令,不写成情境复述。原话的意思是:子级已经继承了你的上下文,所以任务描述要说「做什么」,不必再说「现在是什么情况」。这条约束直接解决分叉的典型误用——把已经在上文里的背景再抄一遍,既浪费 Token,也容易与上文冲突。
第二条,不要抢答。发出分叉后,父级对结果一无所知,提示词明确禁止「捏造或预测分叉的结果」;如果用户在通知到达前追问,应当告诉用户还在跑。这条约束针对一个具体故障:父级按自己的预期编造子级的结论。
跨运行时。 子级是另一个 Agent 运行时。DeepSeek Harness 的委派后端覆盖这种形态:进程内新会话、进程内分叉、ACP 协议、Codex、Claude Code、SDK 六种。
跨运行时的代价是能力取交集。父级只能使用子级运行时支持的机制:如果对面不支持「中途发消息」,这条能力在跨运行时下就消失;如果对面把工具结果格式化成不同结构,回报解析就要写两套。
三种方式的选择取决于一件事:任务描述能否自包含。能,用全新建;不能但可以摘要,用分叉;父级本身就是另一个生态,用跨运行时。
4. 看多少父级历史
这一问与第一问强耦合,但值得单独看,因为它决定了子级能不能判断「我读过什么」。
| 策略 | 子级能看到 | 直接后果 |
|---|---|---|
| 零继承 | 只有任务描述 | 任务必须自包含,否则返工 |
| 继承摘要 | 一段压缩过的父级结论 | 摘要遗漏处即为盲区 |
| 继承完整前缀 | 父级全部上下文 | 无法区分「我读过的」与「父级读过的」 |
第三行是分叉最难处理的一处。子级继承了父级的完整历史,因此它看到的所有工具结果都像是自己读的。当它需要判断「这个文件我改过没有」,只能从历史里推断,而历史里既有父级的动作也有它自己的。
对这个问题,Claude Code 的处理是把分叉定位成「继续同一件事」,不要求子级做归属判断:分叉的用途是多文件重构这类需要同一份历史的延续性工作,判断「谁做过」在这种场景里不重要。
反过来,探索型任务用零继承,子级看到的所有内容都确实是它自己读的,归属判断不存在歧义。
这一问的实用结论是:继承越多,子级的归属判断越难。因此需要归属判断的任务(例如「检查我有没有漏改」)应当用零继承加显式传参。
5. 能用什么工具
权限收窄有三种粒度,各自的能力与盲区不同。
| 粒度 | 表达能力 | 盲区 |
|---|---|---|
| 按角色禁用工具 | 「这个角色不能写文件」 | 不能表达「只能写某些路径」 |
| 按任务声明 | 「本任务允许写 src/」 | 依赖调用方写对 |
| 按权限动作 | 「能用 shell,shell 内写操作单独授权」 | 依赖每个工具正确声明 |
Claude Code 用第一种。它的 Explore 在定义里禁用了编辑、写文件、Agent 工具与计划模式退出,只留只读检索类。这个粒度挡不住「读了不该读的文件」,但能挡住「改了不该改的代码」。
Codex 用协作模式限定,接近第一种但支持命名空间维度:tool_namespace 让工具按命名空间分组,disable_native_tools 可以整体关掉原生工具。
OpenCode 用第三种,粒度最细。每个叶子工具自己声明需要什么权限动作,检查时按声明匹配。代价是声明错了就等于没限制。
DeepSeek Harness 让后端配置工具集,进程内后端可继承父级并收窄,跨运行时后端的权限由对方决定。
三种粒度都有一个共同的上限:都做不到参数级白名单。没有一家能表达「只能对 src/ 下的路径调用写入」。这类限制靠沙箱或文件系统权限实现,不在工具层的职责内。
理解这一点很重要,因为它决定了权限分离能买到什么。买到的是「减少能力面」,买不到的是「精确控制数据范围」。
6. 把任务写成子级能用的形式
五个问题里,最难做对的是「写任务描述」。它是零继承策略下唯一的输入,写不好,前面四个问题的答案都白费。
Claude Code 的提示词里有一整套写法要求,值得逐条看,因为每一条都对应一种具体的失败。
6.1 起点:假设对方什么都不知道
「像一个刚走进房间的聪明同事那样交代任务——它没看过这段对话,不知道你试过什么,也不理解这件事为什么重要。」
这句话给出三条必写内容:要达成什么以及为什么、已经知道了什么或排除了什么、足够它自己做判断的背景。
第三条最重要。前两条是事实,第三条是判断依据。缺了第三条,子级只能按字面执行,遇到需要变通的地方就卡住或乱猜。
6.2 按任务类型给不同的东西
这条规则很精确:查询类任务交出确切的命令,调查类任务交出问题本身。
理由是:查询类任务的步骤是确定的,交出命令能保证执行一致;调查类任务的前提可能本身就是错的,如果预先规定步骤,那套步骤会变成负担——子级会照着走,即使第一步就已经发现前提不成立。
这个区分解释了为什么「统一模板」在委托里行不通。同一个模板要么对调查类太具体,要么对查询类太含糊。
6.3 不要转交理解
「不要写『根据你的发现去修这个缺陷』或『根据研究结果去实现它』。写能证明你自己理解了的提示词:包含文件路径、行号、具体要改什么。」
这条针对的是一种看起来省事、实际代价最高的写法。把「理解」转交出去,等于让子级从头建立判断,而它缺少父级已经积累的全部上下文。
一个对照:
弱:根据你查到的情况,修复登录失败的问题。
强:登录失败的原因是 src/auth/session.ts 第 42 行把过期时间写成了固定值,
应当改为从配置读取(配置项在 config/session.ts 的 ttl 字段)。
改这两处,不要动 refreshToken 的逻辑。强版本传递的是判断,任务本身只是一句话。它把「改哪里」「为什么改」「不要动什么」都写清了,子级不需要重新推导。
6.4 需要短回答就明说
「如果你需要简短回答,说出来(例如『200 词以内汇报』)。」
这条对应回报结构的体量问题。不说,子级会按自己的判断决定详细程度;说了,体量可控。
6.5 分叉的三条额外约束
分叉继承父级历史,所以写法不同,而且多了三条与常规直觉相反的约束。
写成指令,不写情境复述。 子级已经有上下文,任务描述要说做什么,不必再说现在是什么情况。把上文背景再抄一遍的代价是浪费 Token,而且容易与上文冲突。
不要偷看。 工具结果里有一个输出文件路径,提示词明确禁止去读或 tail 它,除非用户明确要求查看进度。「你会收到完成通知,相信它。」
这条针对的是一种具体行为:父级因为不确定进度,反复去读输出文件。代价是额外的读取调用,而且读到的通常是半成品。
不要抢答。 「发出之后你对分叉查到的东西一无所知。绝不要捏造或预测分叉的结果。如果用户在通知到达前追问,告诉用户还在跑。」
这条针对的故障是父级按自己的预期编造结论。它的危害比等待更大——用户会以为已经有结果了。
7. 结果怎么回来
回报结构的差异看起来是格式问题,实际决定了主上下文体积的增长曲线。
自由文本。 子级自己组织最终消息。灵活,但体积不可控:同一个子级两次运行,可能一次给结论加来源,一次只给结论。
结构化对象。 约定字段(状态、结论、证据、未决项)。可校验,但要求子级按 schema 输出。
工具回报。 子级通过工具把结果写回,父级按需读取。信息量最大,但要求有回报通道。
体量差异可以估。设子级自由返回约 2,000 字,父级要读全部;结构化汇报的「结论、证据、未决问题」三段各 200 字,父级只读 600 字。三次子任务就差 4 KB 以上上下文。
这个差距在单次任务里不明显,在长任务里会累积成窗口压力与缓存失效。
Claude Code 的回报偏自由文本,靠提示词约束(例如要求探索子代理返回绝对路径),不强制 schema。Codex 是消息加等待,父级用 wait 取回一条消息。OpenCode 返回任务输出。DeepSeek Harness 用工具回报,另有控制类工具用于中止与列举。
8. 同时能跑几个
并发上限不只是资源限制,它定义了协作形态。
单调阻塞。 父级发出委派后本轮等待,并行只能靠「一次消息里发多个调用」。Claude Code 属于这一类:并行三个探索,必须在同一轮同时发出三个 Agent 调用,分三轮发就串行了。
固定上限加超时。 Codex 的配置给出三个数:默认并发上限 4 个线程,等待超时默认 30 秒,可配范围 10 秒到 1 小时。这三个数共同定义了一种形态——同时最多四个子级在跑,每次等待最多半小时,等待超时不算失败只是「还没好」。
独立任务。 OpenCode 把子代理作为独立任务管理,可列举、可等待,并发受提供方速率限制约束。
三个数(4、30 秒、1 小时)值得记,因为它们体现了非阻塞的代价:父级要自己维护「哪个子级对应哪个任务」的映射,而这个映射需要超时来兜底——超时太短会误判,太长会失去非阻塞的意义。
9. 答案之间的约束联动
五个问题不能自由组合。几条明确的约束。
继承前缀 → 无法判断自己读过什么。 分叉的子级看到父级的工具结果,也看到自己的,两者在上下文里没有区分标记。因此需要归属判断的任务不能用分叉。
全新建 → 任务必须自包含。 零继承的子级没有父级上下文,任务描述写不全就会返工。这条约束把压力转移到了派发方:它必须在派发前想清「要传什么」。
工具收窄 → 需要按角色定义工具集。 按角色禁用工具要求每个角色有一份显式清单,加一个角色就多一份配置。
结构化回报 → 需要 schema 与校验。 约定字段之后,父级要能处理字段缺失与格式错误,这增加了父级的复杂度。
并发上限 → 需要任务映射与超时。 大于一个子级就要维护映射,有映射就要有超时与清理。
这些约束不是设计缺陷,是它们共同把设计空间收敛到了三条已经成熟的路线。
9.1 约束的传递方向
约束不是对称的。有的约束是单向的,有的是双向的。
| 约束 | 方向 | 说明 |
|---|---|---|
| 继承前缀 → 无法判断归属 | 单向 | 反过来不成立:不继承也可以有归属问题(见下) |
| 全新建 → 任务必须自包含 | 单向 | 分叉不要求自包含 |
| 工具收窄 → 需要角色清单 | 单向 | 全量授权不需要清单 |
| 结构回报 → 需要 schema | 单向 | 自由文本不需要 |
| 并发上限 → 需要任务映射 | 双向 | 有映射也常催生并发需求 |
第一行值得展开。零继承也会有归属问题,只是形式不同:零继承的子级不知道自己读过什么,因为它从零开始,什么都没读。它的归属问题是「我不知道父级读过什么」,而分叉的归属问题是「我分不清哪些是我读的」。
两种归属问题都要求「需要归属判断的任务要用显式传参」这一条。区别在于传的内容:零继承传「父级已知的结论」,分叉传「哪些结论是父级的」。
第五行是唯一双向的。这个双向性解释了一个常见的架构演变:先做单 Agent,因为要并行加了并发,因为并发加了任务映射,因为映射需要状态又拆出了任务对象。
9.2 约束推导出的设计路径
把五问的答案与约束串起来,能复现一条真实的设计路径。
起点:任务需要并行处理三个独立的探索方向。 第一问:全新建(探索不需要父级历史)。 第二问:零继承(同上)。 第三问:只读工具(探索不改东西)。 第四问:结构化回报(父级要汇总三份结果)。 第五问:并发三个。
约束联动:并发三个 → 需要任务映射与超时;结构化回报 → 需要 schema 与字段校验;只读工具 → 需要一份只读清单。
这条路径的每一步都是被前一步推出来的,没有一步是「觉得这样更好」。这是五问作为设计顺序的价值——它把设计从「选型」变成了「推导」。
反过来,如果从「我要用多 Agent」开始,就会在五个问题上都倾向于选「功能更全」的答案:继承前缀、全量工具、自由回报、不限并发。这四个选择共同导致一个后果:成本高、难归因、并发时互相干扰。
10. 三条收敛路线
按任务特性组合,四家的实现分成三条路线。
探索型。 全新建、零继承、只读工具、小模型、并行多开、只回结论。
Claude Code 的 Explore 是典型:禁用编辑工具、不加载项目规则、外部用户用小模型、要求并行发起搜索。设计目标是速度——提示词里写「你是一个快速返回结果的代理」。
不加载项目规则这一条值得单独说:探索只需要理解代码,不需要理解「提交前要跑 lint」这类与执行相关的约定。把项目规则塞进探索上下文会浪费时间在无关规则上。
执行型。 全新建或不继承、可写工具、独占资源、串行或少开、结构化回报。
Claude Code 的 GeneralPurpose、Codex 的 spawn 加协作模式、OpenCode 的执行类子代理都属于这一类。设计目标是不出错,因为子级会真正改动东西。
复核型。 继承前缀、只读、独立结论、不参与执行、一般单开。
Claude Code 的 Verification 是典型,它的价值来自「独立」:不复用执行者的自检结论,自己重新核对。继承前缀让复核者能看到执行者的权衡过程,只读让它无法「帮忙改一下」从而破坏独立性。
三条路线的存在说明:五问的答案组合虽然理论上很多,实践中收敛到了少数几个稳定配置。新做一个多 Agent 系统时,先判断自己落在哪条路线上,比从头讨论五问更省时间。
11. 六项横向对照
把五问加上「隔离粒度」,构成六项对照。
| 维度 | Claude Code | Codex | OpenCode | DeepSeek Harness |
|---|---|---|---|---|
| 创建方式 | 角色化子代理 + 进程内分叉 | 任务式派生 | 配置化子代理类型 | 注册表化委派后端 |
| 隔离粒度 | 会话 | 会话 + 协作模式 | 会话 | 按后端:会话或进程 |
| 上下文继承 | 全新或全前缀 | 默认全新 | 按类型定义 | 按后端:全新或已完成历史 |
| 权限收窄 | 按角色禁用工具 | 按协作模式与命名空间 | 权限动作声明 | 按后端配置 |
| 回报结构 | 自由文本 | 消息 + 等待 | 任务输出 | 工具回报 |
| 并发与配额 | 单调阻塞,一次多调 | 4 线程上限,30 秒默认等待 | 任务管理 | 按后端 |
两处细节需要单独指出。
DeepSeek Harness 的分叉只取已完成历史。 不包含当前进行中的回合。理由是让子级看到一个自洽的过去——如果包含正在进行的回合,子级会看到「父级刚说了一半」的状态,难以判断哪些已经确定。
Codex 的配额兜底逻辑。 当配置了 agents.max_concurrent_threads_per_session 时,实际值是配置值加一,多出的一个留给根代理本身。这个细节说明配额计的是「线程总数」,子级数比它少一个。
12. 跨运行时的额外代价
跨运行时委派是第六条路线,只有 DeepSeek Harness 覆盖。代价集中在三处。
能力取交集。 父级只能用对面支持的机制。对面不支持中途发消息,就没有中途干预;对面没有列举接口,父级就不知道还有谁在跑。
回报格式转换。 每个运行时的结果结构不同,父级要写适配层。适配层的错误表现为「结果丢了某个字段」,而且很难定位。
权限范围不透明。 父级无法确知对面给了子级什么权限。它能做的只是「拒绝挂载权限过宽的后端」,这是一个粗粒度的控制。
这三处代价换来的能力是复用已有运行时的强项:让 Claude Code 做需要长上下文的代码理解,让 Codex 做需要严格沙箱的执行,父级只做编排。
13. 并发槽位与人机争用
一个容易被忽略的设计点:并发配额怎么在「Agent 之间」和「Agent 与人的请求之间」分配。
多数系统把并发做成一个池子,先到先得。这带来一个具体问题:当所有槽位被自动任务占满时,人的请求要排队。对一个交互式工具,这直接表现为「我打字没反应」。
有系统显式解决了这一点:并发上限之外留一个额外槽位,专供人的消息与状态查询使用,并且人的唤醒优先于新任务启动。这条规则的效果是「新工作不能阻止人继续已有工作」。
这个设计的价值在于它把一个隐含假设写成了显式规则:自动化任务的紧急程度永远低于人的直接请求。没有这条规则,系统会在最需要人的时候把人挡在外面。
14. 五问的判断顺序
实际做一个多 Agent 系统时,按这个顺序回答五问,能避免反复。
第一问,任务描述能否自包含。 能,走全新建;不能,判断缺的是「可查的」还是「不可查的」。可查的(代码、日志)交给子级自己查;不可查的(决策意图、判断标准)必须显式传。
第二问,子级需要判断归属吗。 需要(例如「检查有没有漏改」),走零继承;不需要,可以用分叉省去交代。
第三问,子级会改动东西吗。 会,按角色或任务收窄工具,并把回报结构化;不会,可以给全量工具但保持只读。
第四问,父级需要在中途干预吗。 需要,选非阻塞加消息通道;不需要,阻塞式更简单。
第五问,会有几个人同时用。 有并发需求,必须给人类请求留独立通道。
五问的答案组合完成后,再对照第 10 节的三条路线,看自己落在哪一条。落在某一条上说明配置是常见的,有现成经验可循;落在组合之外说明有特殊需求,应当先把特殊之处写清楚再动手。