拆.
多 Agent——AI 怎么'协作' · 第 08

防递归 spawn

避免 AI 无限召唤自己

4500读完约 23 分钟craft:B+发布于 2026-06-21

一、原理:递归 subagent 的崩溃风险

5.1-5.7 章我们看 multi-agent 的各种好处——context 隔离、角色专精、并行加速、灵活 fork。但 multi-agent 有个严重的潜在风险——递归 spawn

具体说:

  • 主 agent 调用 task 工具,派生子 agent A
  • 子 agent A 也能调用 task 工具——派生孙子 agent B
  • B 也调 task——派生重孙子 C
  • C 调 task……

理论上深度无限。每层 agent 都启动新的 LLM 会话、消耗 token、占用 context。

实际中这种递归很容易意外发生

意外 1:LLM 没意识到层级

子 agent 不知道自己是子 agent(5.3 章设计)——它以为自己是独立 agent。看到 task 工具——觉得"我可以派生 subagent 处理这个细分任务"。

结果是子 agent 派生了孙子 agent——LLM 没意识到自己已经在"分包链"里。

意外 2:任务表述模糊

主 agent 派给子 agent 的任务可能很大——"探索 auth 模块"。子 agent 觉得"这任务我自己做不完"——继续派生孙子分包。

意外 3:LLM 倾向"分包"

经过 SFT 训练,LLM 学会"复杂任务应该分解"。看到 task 工具——它的本能是"分解给 subagent"——即使任务可以自己做。

这 3 种意外会产生指数级 spawn

主 agent 派 3 个子 agent
每个子 agent 派 3 个孙子
每个孙子又派 3 个曾孙
...

3 层后就 27 个 agent 同时跑。5 层 243 个。每个都是独立 LLM 调用、独立 token 消耗、独立 context。

资源爆炸——token 几分钟烧完一个月预算。

更糟糕的是——深层 agent 可能再次派生回到主任务。比如曾孙 agent 决定"我需要确认一下原任务的意图"——派生子 agent 问"原任务是什么"——形成循环引用。

这种循环引用让递归 spawn 不止指数增长——它变成无限增长。

opencode 必须防这个。我们看具体怎么做。

二、案例:opencode 的双层防护(task.txt 教学 + 权限默认 deny)

opencode 防递归 spawn 的策略是双层防护

  • 第一层:通过 task.txt 描述教 LLM 不要乱 spawn
  • 第二层:通过权限系统默认 deny subagent 调 task 工具

我们一个个看。

第一层防护:task.txt 教学

task.txt 里有几条专门防递归的指引:

"When NOT to use the Task tool:

  • If you want to read a specific file path, use the Read or Glob tool instead
  • If you are searching for a specific class definition, use the Grep tool instead
  • If you are searching for code within a specific file or set of 2-3 files, use the Read tool instead
  • If no available agent is a good fit for the task, use other tools directly"

这 4 条反例的潜在含义——"如果可以用其他工具做就用其他工具,不要 task"。

但这是"应该不会乱 spawn"——不是"绝对不会"。LLM 还是可能在某些场景下乱 spawn——比如它判断"这任务复杂、值得 task"。

需要第二层硬保障

第二层防护:subagent 权限默认 deny task

R5-B 阶段的研究告诉我们——opencode 在 subagent-permissions.ts 里有个特殊逻辑:

function deriveSubagentSessionPermission(parentPermission, agentType) {
  return [
    // 继承父 session 的 deny 规则和 external_directory 规则
    ...parentPermission.filter(rule => 
      rule.effect === "deny" || rule.permission === "external_directory"
    ),
    
    // ★ 关键:默认 deny 几个特定工具
    { permission: "todowrite", action: "*", effect: "deny" },
    { permission: "task", action: "*", effect: "deny" },
  ]
}

注意最后两行——subagent 默认 deny task 工具

这意味着——主 agent 派生子 agent 时,子 agent 看不到 task 工具(被 deny 的工具不出现在 tool list)。子 agent 想 spawn 孙子——发现没这能力——只能自己做。

这是个硬保障——不是教学("你应该不要 spawn"),是禁止("你没有 spawn 的能力")。

LLM 不能违反"不能调用没暴露给我的工具"——这是协议层的保障。

把两层合起来:

  • 主 agent:能调 task,但 task.txt 引导它"克制使用"
  • 子 agent:默认不能调 task——硬阻断递归

递归深度被限制在 1 层——主 agent → 子 agent,到此为止。子 agent 不能再派生。

这种"1 层限制"满足了 multi-agent 的大部分需求——context 隔离、角色专精、并行加速都能做。但避免了递归爆炸——成本可控、调试简单、不会失控。

为什么是"1 层"而不是"3 层"

opencode 团队可能考虑过——允许更深递归(比如 2-3 层)。但每多一层——复杂度、成本、bug 风险都增加。

1 层是个保守安全的选择。如果用户真需要更深递归——可以自己配置 agent permission 允许(绕过默认 deny)——但是 opt-in 行为。

opencode 默认安全、用户能选择放开——这是个合理的产品姿态

我们还要注意一个细节——继承 deny 规则

...parentPermission.filter(rule => 
  rule.effect === "deny" || rule.permission === "external_directory"
)

这一段表达——子 agent 继承父 session 的 deny 规则

具体说,如果父 session 配置了 "deny bash"——子 agent 也不能用 bash。

为什么?因为父 session 的 deny 反映了用户的安全偏好。子 agent 不应该"突破"——派生子 agent 不能成为"用户拒绝的工具的绕过路径"。

这是安全继承——子 agent 权限只能比父 session 更严格,不能更宽。

如果父 session 允许 bash——子 agent 默认也允许(不在 deny 列表里)。但如果父 session 禁了 bash——子 agent 必然禁。

这种单向继承让权限系统可推理——你不会因为 spawn 链路上的某个 agent 而 escape 主 session 的安全规则。

三、设计启示:每个"无限"的入口都需要刹车

这一章的核心论点:Multi-agent 是强大但危险——任何可能"无限"的入口都要有刹车机制

设计 multi-agent 系统时,下面几条原则有用:

1. 默认限制递归深度

最简单的是深度 = 1:主 agent 派子 agent、子 agent 不能再派。

如果你的场景需要更深——明确允许(不要默认开),让用户 opt-in。

2. 用工具权限做硬阻断

不要只靠 prompt 教学——LLM 会违反。

用权限系统硬 deny spawn 工具——子 agent 看不到它,物理上无法调用。

3. 权限单向继承

子 agent 的权限只能比父更严格——不能更宽。

避免 spawn 链路成为"绕过限制"的路径。

4. 监控 spawn 数量

每个 session 跑的过程中——记录 task 调用次数。如果异常高(比如 > 10 次)——告警。

可能是 LLM 在乱 spawn——需要 debug。

5. 设置硬上限

即使权限继承允许递归——给个绝对上限(比如最深 3 层、最多 20 个 subagent)。

超过上限——拒绝、记日志、告诉用户原因。

不要让"理论上无限"变成实际无限。

6. cost monitoring 分层

主 agent 的 cost + 所有 subagent 的 cost——分别统计。

如果 subagent 的 cost 远超主 agent——说明 multi-agent 设计有问题。

7. 调试 multi-agent 要看 spawn 树

logging / tracing 要展示 spawn 的树结构——"主 agent 在 turn X spawn A,A 在 turn Y spawn B..."

没有这种 trace——multi-agent 出问题没法 debug。

8. 测试 LLM 在边界场景的行为

故意给 LLM 看模糊任务——看它会不会乱 spawn?

故意限制工具——看它会不会找其他绕路?

边界测试发现潜在递归风险。

第 5 篇小结

第 5 篇我们看了 multi-agent 编排的 8 章。核心论点贯穿全篇:

Multi-agent 是单 Agent 解决不了时才用——不是默认选择

围绕这个论点:

  1. 3 种驱动力(5.1)—— context 隔离、角色专精、并行加速
  2. 专家咨询 vs 代理人(5.2)—— opencode 选了前者
  3. 隐瞒身份(5.3)—— 子 agent 不知道自己是子 agent
  4. XML 包装(5.4)—— 父子 agent 的沟通契约
  5. Foreground vs Background(5.5)—— 同步默认,异步 experimental
  6. Hidden Agent(5.6)—— 用户看不见的系统级 AI
  7. Fork(5.7)—— session 级版本控制
  8. 防递归 spawn(5.8)—— 双层防护避免无限召唤

这些机制有共同的产品哲学

  • 克制——multi-agent 是工具不是目的
  • 信息隔离——子 agent 视野有限
  • 权限单向继承——派生不能突破限制
  • 失败局部化——子 agent 失败不影响主任务

读完第 5 篇你应该有了 AI Agent 的"协作模型"——理解多个 AI 怎么配合、什么时候用、怎么避免风险。

接下来第 6 篇我们看 multi-agent 的另一面——协议与适配。当你的产品要跨多个 LLM provider,怎么处理"协议的真实差异"?