Skip to content

D5|Subagents:不自动乱 spawn,回传摘要而不是整段 transcript ​

先别把协作理解成「模型一忙就开分身」 ​

读完 D1–D4,下一件错事是打开 multi_agent 就以为 Codex 会自己把任务拆成一树子进程。然后 TUI 里冒出一堆 explorer,额度被吃光,两个 worker 抢同一份工作区。

判断先说清楚:子代理是显式工具调用开出来的另一条 Thread。 默认协作模式是 ExplicitRequestOnly:用户或 AGENTS.md/Skill 没有点名要求委托,就不要 spawn(multi_agent_mode_instructions.rs:7, 48-50)。子代理从当前 turn 的有效配置出发,继承审批、沙箱、cwd、provider,再叠一层 只能减不能加 的 role。结果用 wait_agent / mailbox 回摘要,不是把子 thread 的 rollout 全贴回父 prompt。

C2 的记忆管道会跳过非 root;C5 的并行 Goal 要 worktree。子代理同样:并行写必须隔离,explorer 才鼓励并行只读。


1. 不自动乱 spawn:模式、工具、深度 ​

Feature::Collab 键是 multi_agent,默认开(features/src/lib.rs:1267-1270)。开的是 协作工具在不在 ToolRouter 里,不是「允许模型自行开分身」,也不是 G5 的 Default/Plan 说明书。默认 MultiAgentMode::ExplicitRequestOnly(app-server thread_state.rs:291)。Proactive 才允许「能并行就委派」(multi_agent_mode_instructions.rs:8)。这是 fragment,不是沙箱旋钮——模型仍可能违抗,但产品默认文案是禁止主动拆。

工具面在 multi_agents handler(模块注释,handlers/multi_agents.rs:1-6):

工具做什么
spawn_agent开子 Thread
wait_agent等完成/超时,收回状态
send_input给已有子代理续话(steer)
close_agent / resume_agent收掉 / 再拉起

spawn_agent 在深度超限时直接回模型:「Agent depth limit reached. Solve the task yourself.」(spawn.rs:68-73)。默认 agent_max_depth = 1(config/mod.rs:250):子代理默认 不能再 spawn 孙子。AgentRegistry 还限制 同一用户 session 下子 thread 总数(registry.rs:18-23),reserve_spawn_slot 触顶返回 AgentLimitReached。V2 配置把并发槽位数写进 usage hint:「包括你自己在内最多 N 个同时活着」(session/multi_agents.rs:122-129)。max_concurrent_threads_per_session 不能配 0(config/mod.rs:3788-3792)。

没有「显式 + 深度/并发帽」会怎样?一次 Regular turn 递归开树,每层都打模型,记忆管道还会误抽子代理独白(C2 已跳过 subagent,就是怕这个)。


2. 三个内置角色:default / explorer / worker ​

省略 agent_type 时用 "default"(role.rs:32-33, 348-353):描述就是「Default agent.」,没有额外 toml。

explorer: 给具体、范围清楚的代码库问题;快、权威;鼓励 多个独立问题并行开多个 explorer;相关问题复用已有 explorer(:356-367)。有内嵌 explorer.toml(文件当前几乎是空壳,角色主要靠这段 description)。

worker: 执行和生产:实现一块功能、修测试、把大重构切成独立块。必须划清 文件所有权,并告知「你不是仓库里唯一的人,别回滚别人的改动」(:370-382)。没有独立 config_file。

awaiter 已从内置表拿掉,只留注释(:384-401)。

Role 不得扩大父 session 的权力(role.rs:1-4)。叠上去的是 developer instructions、模型、effort、personality、以及 关掉 某些 feature(ShellTool / Apps / Plugins / Memories…),技能配置也只保留「禁用」项(:91-117)。不能靠 explorer.toml 把子代理拧成 DangerFullAccess。审批和沙箱从父 turn 继承(handler 模块注释)。测试钉死:spawn explorer 时 approval_policy 保持父级(multi_agents_tests.rs:284)。

自定义角色:各配置层的 config_folder/agents/*.toml(agent-roles/src/loader.rs:76-79, 290-298),也就是用户侧常见的 $CODEX_HOME/agents/,以及项目 .codex/agents/(仍受未信任闸:项目层 disabled 则那层 agents 目录不会作为有效角色源)。[agents.roles] 可声明 config_file。同名冲突打 startup warning。

没有「角色只能减权」会怎样?一个 repo 里的 hacker.toml 给子代理关审批、开全盘写,父 session 的 B3 旋钮被绕开。


3. 回传摘要,不要整段历史 ​

子代理是完整 Thread:自己的 run_turn、自己的 rollout、SessionSource::SubAgent。父模型 不把子 rollout 当 messages 拼接。

收回来的路径:

  • wait_agent: 按 thread id 收集 AgentStatus,可超时(wait.rs:191-204)。给父模型的是 JSON 状态,不是子对话全文。
  • Mailbox: B1 的 InterAgentCommunication。子代理写完一封信,父空闲时 maybe_start_turn_for_pending_work。信是有界消息,不是 dump。
  • send_input: 往已有子代理排队续话(send_input.rs:17-20),等于 A2 的 steer,对象换成子 Thread。

父 prompt 里还有协作 usage hint(并发槽、何时 wait)。这是说明书,真正的硬帽在 Registry。

Explorer 并行读、worker 并行写:后者必须像 C5 一样配 worktree,否则所有权规则只是 prompt。隔离单位仍是 checkout,不是又开一套沙箱政策。


4. V2 是并行架构,不是 V1 spawn_agent 换皮 ​

上一节的工具表是 V1。Feature::MultiAgentV2 键 multi_agent_v2,阶段 Stable,默认关(features/src/lib.rs:1273-1276)。打开之后,Turn 上的 multi_agent_version 变成 V2,广告出去的不再是同一套名字。

spec_plan.rs 按版本分叉(:644-686):

V1V2
开子 threadspawn_agent仍叫 spawn_agent,handler 换成 multi_agents_v2/spawn.rs(create_spawn_agent_tool_v2)
续话send_inputsend_message / followup_task(message_tool.rs:1-4:同一提交路径,差在要不要立刻 wake)
等 / 收wait_agent close_agent resume_agentwait_agent 可关;另有 interrupt_agent、list_agents
拓扑Registry 计数agent-graph-store/ 存 parent/child 边(文件头:storage-neutral topology)
身份角色 toml 减权另有 agent-identity/,给托管身份,不是又一份 spawn

V1 的 mailbox 还在。V2 把「给子代理写信」收成独立工具,并且可以挂自己的 tool namespace(测试里配成 "agents")。子代理能不能再看见 V2 工具,还要看模型自己声明的 multi_agent_version(collab_tools_enabled 的 V2 臂,:655-658)。

判断:V2 是另一套协议。 图、消息工具、可选 namespace,不是给 V1 换皮肤。读者默认抄 V1 的显式 + 减权 + 摘要。打开 V2 之前,先当「并行架构」,不要假设 send_input 还在。

没有这层对照会怎样?你在默认关的仓库里找 send_message 找不到,以为 D5 写错了;或者打开 flag 之后继续用 V1 的 mailbox 语义,图上的边和模型看见的工具对不上。


5. /agents 查看,steer 走工具不是新的 Op ​

TUI:

  • /agents:所有活着的 agent session,可切换(slash_command.rs:132)
  • /subagents(SlashCommand::MultiAgents):当前 session 的子代理(:133)

这是投影。模型侧 V1 用 send_input,V2 用 send_message;人在 TUI 里切过去,打字进的是那条子 Thread 的 Op::TurnInput,同一条 SQ/EQ(B1)。不要给子代理再发明一套协议。

Guardian 评审员、Memories Phase 2 整合工人也是 Session,但会关 Collab / MemoryTool,避免递归(C2、B3)。它们不是用户树上的 worker。


6. 和 Goal、Skill、Hook 怎么选 ​

要做的事用
一条长任务、一个完成条件/goal(C5),不必开子代理
多个独立只读问题并行 explorer
多块可划分所有权的实现worker + worktree
可复用的办事程序Skill(D1),不是新角色
每次工具前机械拦截Hook(D4)
子代理出生/结束时跑脚本SubagentStart / SubagentStop

Skill 禁止把读 SKILL.md 交给子代理(D1)。说明书必须由主 agent 读完再干活。


7. 结语:带走一句话 ​

子代理是显式工具开出的另一条 Thread:默认抄 V1 的不主动拆、深度 1、角色只能减权、收回摘要不是全文;V2 是默认关掉的并行协议,打开之前不要当 V1 换皮。