OpenAI Codex 原理与架构:一个 while 循环与五层工程体系
拆到最小,Codex 就是一个本地编码 Agent:用户输入、AGENTS.md、环境信息与工具定义被组装成 Prompt,经 Responses API 流式采样;模型返回的工具调用在审批策略与操作系统沙箱约束下执行,结果回填历史再采样,直到模型不再需要工具。
循环本身并不神秘,难的是周围的工程。官方协议用 Model、Codex、Session、Task、Turn 五个实体描述运行模型:Session 同一时刻只允许一个 Task,Task 由多个 Turn 组成,Turn 之间严格因果——上一轮输出就是下一轮输入。
决定体验的是循环之外的取舍:引擎与 UI 通过一对队列(Submission Queue / Event Queue)解耦,一份 core 支撑 TUI、IDE 插件、桌面 App 与 MCP Server;Prompt 只追加不改写以命中前缀缓存;改动以补丁表达;安全上则用审批与沙箱两个正交维度,再叠加 execpolicy 与网络代理。
一个 while 循环:输出即下一轮输入
core/src/session/turn.rs 里的 run_turn() 是引擎心脏。去掉遥测与错误处理,结构大致是:
1 | async fn run_turn(sess, turn_ctx, input) { |
是否继续采样只取决于 needs_follow_up。本轮只要出现工具调用,结果就必须回传模型,因此必然再采样一次;模型只给文本消息时循环自然收敛。流式处理中,OutputItemDone 携带完整 item,由 handle_output_item_done 决定是记录消息还是分发工具,文本增量走 AgentMessageContentDelta;多个允许并行的工具调用交给 tools/parallel.rs 并发执行,再统一收取结果。
五层架构:一份 core,多种前端
从外到内可以切成五层,相邻层只通过明确协议通信:
| 层 | 关键组件 |
|---|---|
| ① 前端 / UI | tui 全屏终端、exec 无头模式、IDE 插件、桌面 App、mcp-server |
| ② 接入 / 协议 | cli 与 arg0 角色分发、app-server(JSON-RPC,stdio 或 WebSocket)、protocol(Op、EventMsg、ResponseItem) |
| ③ 引擎 core | ThreadManager、Session、SessionTask(regular / review / compact)、run_turn、ContextManager、compact、rollout |
| ④ 工具 / 执行 | ToolRouter、ToolRegistry、ToolOrchestrator、unified_exec(PTY)、apply-patch、codex-mcp、execpolicy |
| ⑤ 平台 / 外部 | macOS Seatbelt、Linux bubblewrap + seccomp、Windows 受限令牌、network-proxy、Responses API、Ollama / LM Studio |
前端与引擎之间是一对队列:UI 把带 sub_id 的 Op 推进 Submission Queue,core 把 EventMsg 推进 Event Queue。正因为接口足够窄,同一份 core 才能同时驱动终端、IDE 与桌面端。面向富客户端的 app-server 在此之上又包了一层 JSON-RPC,把对话抽象成 Thread(thread/start、thread/resume、thread/fork)、Turn(turn/start、turn/interrupt)和 Item(item/started → delta → item/completed)三个原语。
执行模型的约束写在协议文档里:
| 实体 | 含义 | 关键约束 |
|---|---|---|
Codex |
核心引擎,跑在后台线程或独立进程中 | 只通过 SQ / EQ 与 UI 通信 |
Model |
Responses API 或兼容 Provider | 以 SSE 或 WebSocket 流式返回 ResponseItem |
Session |
当前配置与状态:模型、cwd、审批、沙箱、历史 | 同一时刻最多运行一个 Task,重新配置会中止当前执行 |
Task |
响应一次用户输入的完整工作 | 由多个 Turn 组成,新输入或 Op::Interrupt 会终止它 |
Turn |
一次“采样 → 执行工具 → 回填”的迭代 | 上一轮输出即下一轮输入,无输出则任务结束 |
一个 Session 只跑一个 Task,是为了避免并发编辑互相覆盖;需要并行时开独立 Thread 或子 Agent(thread_spawn.rs、agent_communication.rs),每个 Thread 仍遵守同样的规则。编辑走 apply_patch 的自定义 diff 语法(*** Begin Patch),比整文件重写输出更短、也更容易审阅。
审批策略 × 沙箱策略:两个正交维度
安全模型由两个维度组合而成:审批策略决定“什么时候问人”,沙箱策略决定“操作系统层面允许做什么”。
AskForApproval |
行为 |
|---|---|
untrusted |
除 execpolicy 显式放行的命令外,一律需要审批 |
on-request |
默认值,由模型判断何时申请提权;on-failure 是它的序列化别名 |
granular |
按类别细分开关:shell 提权、execpolicy prompt、技能脚本、MCP elicitation 等 |
never |
从不询问,失败直接返回模型,适合 CI 与 codex exec |
SandboxPolicy |
文件系统 | 网络 |
|---|---|---|
read-only |
全盘只读 | 默认关闭 |
workspace-write |
cwd、writable_roots 与临时目录可写,.git、.codex 只读 |
默认关闭,可配置 |
danger-full-access |
无限制 | 无限制 |
external-sandbox |
已在外部容器中,全盘访问 | 按配置 |
core/src/tools/orchestrator.rs 把两者编排成一条执行链:
- 判定:execpolicy 的 Starlark
prefix_rule(allow / prompt / forbidden)与审批策略共同得出Skip、NeedsApproval或Forbidden。 - 审批:需要时发出
ExecApprovalRequest,用户可选Approved、ApprovedForSession(会话缓存)、ApprovedExecpolicyAmendment(写成规则)、Denied或Abort;Guardian 也可自动审查代为决策。 - 执行与升级:先按
SandboxManager选出的平台沙箱运行;若因沙箱失败,untrusted(或 granular 中开启沙箱审批)会再次请求审批、批准后脱离沙箱重试;on-request与never不自动升级,只把精简的拒绝信息回给模型,由模型带require_escalated显式申请。
平台实现各有取舍:macOS 用 /usr/bin/sandbox-exec 加载动态生成的 SBPL profile;Linux 用 bubblewrap 以 --ro-bind / / 打底、--bind 叠加可写根,再用 seccomp 拦网络系统调用与 io_uring;Windows 用受限令牌或提权沙箱,强制不了就 fail closed。再叠加 execpolicy 与 network proxy,构成规则、审批、OS 沙箱、网络四层防御。
上下文:只追加的 Prompt、压缩与落盘
每次采样都是一次完整的 Responses API 调用:instructions(codex-prompts 按模型族挑选)、tools(built_tools() 依配置、MCP、技能生成)、input(权限说明 → 开发者指令 → AGENTS.md → 环境上下文 → 历史 → 本次用户消息)。AGENTS.md 在 core/src/agents_md.rs 里发现:从 cwd 向上找到 project_root_markers(默认 .git)定根,再从根到 cwd 逐层收集、按根到叶拼接,最后合并 ~/.codex/AGENTS.md,注入为一条 user 消息。
这个顺序是为缓存服务的:Responses API 按前缀精确匹配缓存,Codex 因此让 Prompt 只追加不改写,静态部分在前、历史在后;中途切换沙箱或模型也以追加消息表达。请求默认无状态,每次发送完整历史,推理内容以 encrypted_content 加密回传,兼容零数据保留(ZDR)组织。
历史由 ContextManager 维护原生 ResponseItem,发送前经 normalize.rs 归一化。token 超过 model_auto_compact_token_limit 且仍需继续时触发自动压缩:本地用压缩提示词让模型总结,远程由 Responses API 返回不透明的 Compaction item;再用 SUMMARY_PREFIX 加摘要与最近用户消息(上限约 2 万 token)替换历史,重插初始上下文,同一个 Turn 接着跑。会话以 rollout JSONL 落在 ~/.codex/sessions/ 下,codex resume 重放恢复。
三种读法:详解、动画与演示文稿
同一套机制有三种呈现方式,按由静到动的顺序看更顺。
架构详解:逐章拆开 Agent Loop、Prompt 组装与缓存、SQ / EQ 协议、工具系统、审批与沙箱、压缩与 codex-rs 代码地图,适合对照本文细读:OpenAI Codex 原理与代码架构详解。
原理动画:以“在 TUI 里修复失败的单元测试”为例,21 步走完 5 次采样、1 次沙箱拒绝、1 次提权审批和 1 次上下文压缩,右侧同步显示 needs_follow_up、当前工具、执行环境与 token 占用。可单步、播放或点时间轴跳转:
演示文稿:17 页幻灯片,按“产品形态 → Agent Loop → 分层 → 实体 → Prompt → 协议 → 工具 → 安全模型 → 平台沙箱 → Orchestrator → 上下文压缩 → 完整时序 → 代码地图 → 设计取舍”组织,适合分享或快速过一遍全貌。方向键翻页,O 键看总览,F 键全屏:
参考与延伸
- openai/codex:codex-rs 源码仓库
- codex-rs/docs/protocol_v1.md:Session / Task / Turn 与 SQ / EQ 协议
- codex-rs/linux-sandbox/README.md:bubblewrap + seccomp 的沙箱实现
- codex-rs/execpolicy/README.md:命令策略语言
- developers.openai.com/codex:官方文档(CLI、配置、安全、AGENTS.md)
- Unrolling the Codex agent loop:官方博客,Agent Loop 与 Prompt 缓存
本系列的其它两篇与总入口:
