很多人第一次看 Codex 的多 agent 机制时,会下意识把 subagent 理解成 “main agent 调了一个内部函数”。这个理解不对。
在 Codex 里,subagent 更接近于:主线程之外又开了一个独立聊天线程。它有自己的 thread id、上下文、工具调用过程、执行状态和 mailbox。main agent 和 subagent 不共享完整上下文,它们靠 Codex runtime 转发内部消息协作。
这篇文章不讲 Rust 语法,只讲一条闭环:
先看总览图:
先记住一个模型
可以先把 Codex 的 subagent 理解成下面这个结构:
所以 subagent 不是 main agent 内部的一个函数。它有自己的:
- thread id
- 上下文
- 工具调用过程
- 执行状态
- mailbox
这件事决定了 main agent 和 subagent 的关系不是同步函数调用,而是多个独立线程之间通过内部信件协作。
先看通信闭环
main agent 能控制 subagent 的方式,不是直接读写它的内存,而是调用一组协作工具:
这些工具本质上都是 function call。模型输出 function call,Codex runtime 收到后执行内部逻辑。
把完整流程压成一条主链路,大致就是:
| |
这里最重要的三点是:
subagent是真的独立 thread,不是内部函数。send_message和followup_task都是在投递内部消息,只是后者会顺带触发目标 agent 开始下一轮执行。wait_agent等的是 mailbox 活动,不是同步拿子函数返回值。
例如 main agent 创建 subagent 时,可能只是发出这样一个调用:
但 runtime 真正做的是:
而 subagent 回结果时,走的也不是“返回值”,而是再发一封内部信:
所以从运行时视角看,Codex 的多 agent 更像:
wait_agent 为什么经常被误解
wait_agent 最容易被误解成“同步等待 subagent 返回值”,其实它更像:
| |
比如:
它的意思是:
超时返回类似:
这里一定要分清:
子 agent 可能还在继续跑。runtime 不会自动替 main agent 做决策,它不会自动:
- kill 子 agent
- interrupt 子 agent
- 判定子 agent 失败
- 自动重派任务
它只是把 timed_out=true 交还给 main agent。接下来 main agent 自己决定下一步:
所以 main agent 在这套机制里,本质上更像调度者。
这条闭环在源码里怎么落地
如果只看一张源码总览图,可以先记住这条链:
按这个链路往下看,源码可以分成 6 个位置。
第一层:工具暴露给模型
文件:
这里分别负责两件事:
spec_plan.rs决定当前 turn 要不要把多 agent 工具暴露给模型multi_agents_spec.rs定义这些 function call 的参数 schema
也就是说,main agent 能不能调用 spawn_agent、send_message、wait_agent,第一步先看这里。
第二层:main agent 的控制入口
文件:
| |
这层可以理解成 “main agent 的控制台”:
spawn.rs负责接住spawn_agentmessage_tool.rs负责接住send_message和followup_taskwait.rs负责wait_agentinterrupt_agent.rs负责打断入口AgentControl是统一控制中心spawn_agent_internal(...)真正创建 child threadAgentRegistry记住有哪些 agent、各自的 thread id 和 path
所以“main agent 怎么找到 subagent、怎么把任务发过去”,主要都在这层。
第三层:内部消息的数据结构
文件:
| |
关键结构:
| |
它可以直接理解成一封内部信,里面最关键的是:
authorrecipientcontenttrigger_turn
例如:
第四层:消息如何进入 mailbox
文件:
这里是真正的 mailbox 落点:
handlers.rs里的inter_agent_communication(...)收到Op::InterAgentCommunicationinput_queue.rs里的enqueue_mailbox_communication(...)把消息放进 mailbox- 如果
trigger_turn=true,runtime 会尝试启动目标 agent 的新一轮 turn
所以 send_message 和 followup_task 的本质区别,就落在 trigger_turn 上。
第五层:mailbox 怎么进入模型上下文
文件:
这里负责两件事:
turn.rs在每轮请求模型前,从InputQueue取 pending inputmod.rs里的record_inter_agent_communication(...)把消息写进历史和 rollout
也就是说,agent 间消息不只是内存里短暂经过,它还会:
- 进入下一轮模型上下文
- 写入会话历史
- 被客户端看到
第六层:状态和展示
文件:
这层负责把运行中的事实变成用户能看到的东西:
status.rs把事件映射成Running、Completed、Errored等状态thread_history.rs把协作事件转成客户端能展示的ThreadItem- TUI 相关文件负责找出主线程下有哪些 subagent,以及怎么在界面里切换和缓存它们
所以你在 app-server 或 TUI 里看到的“创建了哪个 agent”“谁给谁发了消息”“wait 有没有超时”,最终都依赖这层映射和展示。
最短源码定位表
| 你想看什么 | 看哪个文件 |
|---|---|
| 工具怎么暴露给模型 | codex-rs/core/src/tools/spec_plan.rs |
| function call 参数怎么定义 | codex-rs/core/src/tools/handlers/multi_agents_spec.rs |
spawn_agent 入口 | codex-rs/core/src/tools/handlers/multi_agents_v2/spawn.rs |
| 创建 child thread | codex-rs/core/src/agent/control/spawn.rs |
| main 如何控制 agent | codex-rs/core/src/agent/control.rs |
| agent 列表和 metadata | codex-rs/core/src/agent/registry.rs |
send_message / followup_task | codex-rs/core/src/tools/handlers/multi_agents_v2/message_tool.rs |
| 内部信件结构 | codex-rs/protocol/src/protocol.rs |
| mailbox 队列 | codex-rs/core/src/session/input_queue.rs |
| mailbox 进入模型上下文 | codex-rs/core/src/session/turn.rs |
| 通信记录进历史 | codex-rs/core/src/session/mod.rs |
wait_agent 超时逻辑 | codex-rs/core/src/tools/handlers/multi_agents_v2/wait.rs |
| agent 状态 | codex-rs/core/src/agent/status.rs |
| app-server 展示 | codex-rs/app-server-protocol/src/protocol/thread_history.rs |
| TUI subagent 导航 | codex-rs/tui/src/app/agent_navigation.rs |
最后收一句
如果只记一句话,我建议记这句:
| |
从原理上看,这是一个“多线程聊天 + 内部邮箱”的协作模型;从源码上看,这条链路则落在 tool handler、AgentControl、session mailbox、turn loop、app-server 和 TUI 几层里,刚好首尾闭环。