你 / 前端界面
发送新任务、补充当前要求,或把下一项任务排队。
先看架构:谁负责接收,消息存在哪,什么时候交给模型。再用动画走一遍。
界面接收你的操作。Codex 运行程序保存输入、请求模型并执行工具。模型只根据本次收到的内容生成下一步。
发送新任务、补充当前要求,或把下一项任务排队。
独立工作的测试助手等,发回发现、进度或完成通知。
队列 = 程序保存的待处理列表。三类暂存区的规则不同,模型不能直接翻看。
在允许的位置取输入 → 更新对话历史 → 准备下一份模型请求
拿到模型输出后:需要工具就执行工具并继续;完成任务就收尾。
读取本次请求,生成回答或工具调用。新到消息由运行程序带入后续请求。
运行命令、读写文件等。结果先回到运行程序,再交给模型。
这是职责与数据流示意,不表示工具一定运行在本机,也不表示连接的数量。
请求模型 → 执行测试 → 带结果再请求模型 → … → 完成
沿用会话历史,开始新任务
“下一次请求模型”可能还在做同一件事;“下一轮工作”才是开始另一项任务。
像三份用途不同的待办列表。它们都由运行程序管理,但不共用一套取出规则。
这是一个允许接收输入的位置上的合并顺序。不是所有消息按到达时间统一排序,也不是能随时抢占正在执行的工具。
会话空闲且启动条件允许时,取队首任务,开始新一轮工作。启动成功后才删除排队记录;它不会混进当前工作的普通输入合并。
进入暂存区 → 程序取出 → 加入对话历史 → 随后续模型请求发出。暂存区变空,只说明消息被取走,不能单凭这一点判断模型已读。
本页只讨论这三类与消息调度有关的暂存区;不代表 Codex 内部总共只有三个队列。下面的动画采用同一套名称。
选择场景,然后点击“下一步”。默认暂停;每次只推进一个事件。
输入要求、查看结果
消息从这里发出
接收消息、组织工作
根据收到的内容生成
另一个 agent
图中话语为教学简化,不是协议原文。
此处不显示还在等待的消息。
还没有发出模型请求。
只展示与例子有关的内容,省略规则、其他历史等;不是完整 API 请求。
在本例的普通来信模式下,邮件留在会话邮箱里,不会自己启动一轮新工作。后续有新任务时,旧邮件才可能被取出。
启动时可能先把邮件搬到当前任务的追加输入里;这仍不代表首个模型请求已经读到。请求启动新回合的邮件、可唤醒的持久等待等情况另有处理。
服务端校验时找不到活动回合,就会拒绝这次补充;若活动回合已经换了,也可能回合不匹配。它不会自动变成“下一项任务”。
界面需要处理失败,并根据用户意图决定是否启动新工作。
例子里的代码、测试结果与对话均为教学假设。本页跟踪三类与消息调度有关的暂存区,不是内部所有通道和队列的总清单。规则依据本地源码 50d77959bf92(核查:2026-09-23);具体版本与功能开关可能不同。
持久化保存 → 等待 thread 空闲 → 启动新 turn
活动 turn 校验 → turn-local buffer → 下一次允许的请求
投递 → 唤醒或等待 → 按投递阶段加入当前或后续 turn
客户端 / 其他 agent
├─ Queue API ── A 持久队列 ── 空闲调度 ── 新 turn 初始输入
├─ Steer ───── B 当前 turn 追加输入 ──────────────┐
└─ Agent mail ─ C Session 邮箱 ─────────────────┤
↓
允许消费输入的边界:B + C
↓
hooks / 记录 history / 构建模型请求
↓
模型流式输出 → 工具执行及结果
↓
需要继续或仍有输入?继续循环 : 完成 turn按“进入 agent 的业务输入存储”计,这里讲 3 类。只看 core 的 InputQueue 相关内存存储则是 B、C 两个;A 在 queue extension / storage 层。把控制通道、输出通道、工具 future 和前端缓存都算上,数字会变,因此不能笼统说“一个 thread 一共只有 3 个队列”。
| 发生位置 | 已确认的规则 | 不能推导成 |
|---|---|---|
| 常规输入 drain | B 的已有输入在前,随后追加 C 的邮箱消息;各自保留其存储顺序。 | 用户消息永远抢占任何 agent 消息。 |
| 订阅活动时的初始检查 | 先看符合条件的 pending steer,再看 mailbox。watch 的后续通知是最新活动值。 | watch 是不会合并事件的 FIFO 消息队列。 |
| 空闲启动竞争 | start_if_idle 遇到 trigger_turn 邮件会拒绝本次启动,让待触发邮箱工作先处理。 | 任何普通邮件都比用户 Queue 优先。 |
| 新 turn 第一轮采样 | 显式初始输入先采样;通常不会先把后来积累的追加输入一起 drain。 | 每次 HTTP / WebSocket 请求前都无条件清空所有输入。 |
| 自动压缩后的续跑 | 若模型/工具还需要 continuation,可先续跑,再消费 steer。 | 压缩会把消息队列全部删除。 |
| Interrupt | 取消走控制/取消令牌路径,影响正在执行的 turn。 | Interrupt 是一个排在 B、C 前面的普通聊天消息。 |
| 事件 | A 用户队列 | B 当前 turn 输入 | C Agent 邮箱 |
|---|---|---|---|
| 创建 | 用户排队写入持久存储 | ActiveTurn 创建 TurnState | Session::InputQueue 创建 |
| 消费 | 成功启动新 turn 后删除对应记录 | 取出并记录到历史 | drain 到输入批次或 turn-local pending |
| 普通 turn 结束 | 满足条件则派发下一项 | 完成流程处理剩余输入;状态释放 | 晚到消息可继续留存至下一轮 |
| 用户中断 | Interrupted 空闲事件不会自动派发 | 执行取消、等待者和 pending 清理;不是可靠消息存储 | Session 级 mailbox 不等同于 turn-local clear;后续启动仍受 trigger/durable sleep 等规则约束 |
| 前端断开 | 后端持久记录不因 UI 断开自动删除 | 取决于后端 session / task 是否仍存活 | 取决于后端 Session 是否仍存活 |
| 进程崩溃 / 重启 | 持久化记录可恢复;还需重新调度 | 未消费内存数据无自动恢复保证 | 未消费内存邮件无自动恢复保证 |
“已入队”“已写历史”“已发给模型”“模型已回答”是不同的时刻。网络 RPC id 用于关联请求响应,turnId / itemId 标识业务实体,都不是队列优先级。
tx_sub / rx_sub 是容量 512 的 bounded async_channel,由 submission_loop 读取控制操作;tx_event / rx_event 是 unbounded 输出事件通道,给宿主/客户端消费。它们不是 B、C 的业务输入优先级层。
TurnState 里有按 ID 索引的 pending_approvals、pending_request_permissions、pending_user_input、pending_elicitations、pending_dynamic_tools 等 HashMap + oneshot。返回的审批/答案找到对应等待者,解开那个工具等待;这不是“把用户回答排到普通 steer 队列末尾”。
采样过程中可用 FuturesOrdered 管理 in-flight 工具 future;InputQueue 的 watch 通道负责通知“有活动”,正文留在 B/C;前端草稿/待发送队列属于客户端状态。三者都不应与核心三个输入容器混为一谈。
运行时取走队列消息,把它们转换并记录为模型上下文项,然后提交新的模型请求。模型不直接轮询 VecDeque、SQLite 或 HashMap。agent 邮件的模型侧编码可能随模型/供应商/协议能力变化;本页不把内部 InterAgentCommunication 当作通用公开 Responses API 类型。
以下源码链接固定到本次阅读的 commit,便于复核;官方文档用于对照公开接口,不能替代内部调度实现。
阅读建议:先看整体结构,再选择与你的问题相同的消息场景,最后展开对应队列与源码。