OpenAI Codex 原理与代码架构详解基于 openai/codex 开源仓库(codex-rs)源码与官方文档整理
一句话概括:Codex 是一个运行在本地的编码 Agent。它把用户输入、项目指令(AGENTS.md)、环境信息和工具定义组装成 Prompt,通过 OpenAI Responses API 流式采样模型输出;模型返回的工具调用(shell、apply_patch、MCP…)在审批策略 + 操作系统级沙箱的约束下执行,结果回填进对话历史,再发起下一次采样,直到模型不再需要工具为止。
核心引擎 codex-core 用 Rust 编写,与 UI 完全解耦,通过 Submission Queue / Event Queue 队列对通信。CLI、TUI、IDE 插件、桌面 App 都只是这个引擎的不同前端。
01Codex 是什么
"Codex" 在 OpenAI 产品线里指一组共享同一 Agent 引擎的产品形态:
Codex CLI / TUI
终端里的交互式 Agent,codex 启动全屏 TUI,codex exec 无交互运行,适合脚本与 CI。
IDE 插件 / 桌面 App
VS Code、Cursor 等编辑器插件和 codex app。底层通过 codex app-server(JSON-RPC)驱动同一个 core。
Codex Web / Cloud Tasks
chatgpt.com/codex,在云端容器里并行执行任务、生成 PR;CLI 可通过 cloud-tasks 访问。
Codex 系列模型
针对软件工程任务训练的 GPT 系列变体(如 GPT-5-Codex 系列),擅长长时程的工具调用、补丁编辑与代码审查。
本文聚焦开源的本地 Agent 实现 codex-rs。早期版本使用 TypeScript 编写,现已整体迁移到 Rust,以获得单一静态二进制、更低内存占用和原生沙箱集成能力。
02整体架构分层
从外到内可以分成五层。每一层只通过明确的协议与相邻层通信:
03核心原理:Agent Loop
官方协议文档(codex-rs/docs/protocol_v1.md)定义了四个核心实体,理解它们就理解了 Codex 的运行模型:
| 实体 | 含义 | 关键约束 |
|---|---|---|
Model | OpenAI Responses API(或兼容 Provider) | 以 SSE / WebSocket 流式返回 ResponseItem |
Codex | 核心引擎,本地运行于后台线程或独立进程 | 通过 SQ/EQ 与 UI 通信,负责请求模型、执行命令、应用补丁 |
Session | 当前配置与状态(模型、cwd、审批策略、沙箱、历史) | 同一时刻最多运行 一个 Task;重新配置会中止当前执行 |
Task | 响应一次用户输入的完整工作 | 由多个 Turn 组成;新的用户输入或 Op::Interrupt 会终止它 |
Turn | 一次"采样 → 执行工具 → 回填"的迭代 | 上一个 Turn 的输出就是下一个 Turn 的输入;无输出则 Task 结束 |
主循环伪代码(对应 core/src/session/turn.rs)
// run_turn(): 一个 Task 内的循环(已简化)
async fn run_turn(sess, turn_ctx, input) {
run_hooks_and_record_inputs(input); // 记录用户输入,执行 hooks
run_pre_sampling_compact(); // 采样前若超出 token 上限,先压缩
loop {
let prompt = build_prompt(history, tools, instructions);
let result = run_sampling_request(prompt).await; // 流式请求 Responses API
// 流中每个 OutputItemDone:
// Message / Reasoning → 推送 AgentMessage 事件,记入历史
// FunctionCall / CustomToolCall / LocalShellCall → ToolRouter 分发执行
// → 生成 FunctionCallOutput 记入历史,needs_follow_up = true
let needs_follow_up = result.model_needs_follow_up || has_pending_input;
if token_limit_reached && needs_follow_up { run_auto_compact(); }
if !needs_follow_up { break; } // 模型只给出最终回答 → 结束
}
emit(EventMsg::TurnComplete { last_agent_message });
}
关键点:决定循环是否继续的唯一信号是 needs_follow_up。只要模型本轮发出了任何工具调用,工具结果就必须回传给模型,因此必然再采样一次。模型只输出文本消息时循环才自然终止。这就是"Agent = 模型 + 循环 + 工具"的最小实现。
流式事件处理
try_run_sampling_request 消费 ResponseEvent 流:Created 记录 response_id;OutputItemAdded 提前通知 UI(例如开始显示工具调用);OutputItemDone 是完整 item,交给 handle_output_item_done 决定是记录消息还是分发工具;文本增量通过 AgentMessageContentDelta 实时推给 UI。多个支持并行的工具调用会被并发执行(tools/parallel.rs),在 drain_in_flight 中统一等待结果。
04Prompt 组装与缓存
每次采样请求都是对 Responses API 的一次完整调用,主要包含三部分:
| 字段 | 内容 | 来源 |
|---|---|---|
instructions | 模型的基础系统指令(行为规范、编辑方式、输出格式) | codex-prompts crate,按模型族选择 |
tools | 当前可用工具的 JSON Schema / 自定义语法定义 | built_tools() 根据配置、MCP、技能、插件动态生成 |
input | 有序的 ResponseItem 列表:权限说明 → 开发者指令 → AGENTS.md 用户指令 → 环境上下文(cwd、shell、日期)→ 历史对话与工具输出 → 本次用户消息 | ContextManager 历史 + 每轮注入的上下文 |
AGENTS.md 发现规则(core/src/agents_md.rs)
- 从 cwd 向上查找,直到遇到
project_root_markers(默认.git),确定项目根目录。 - 从项目根到 cwd(含)沿路收集每一层的
AGENTS.md(支持AGENTS.override.md和project_doc_fallback_filenames)。 - 按从根到叶的顺序拼接,越靠近 cwd 的指令越靠后,因而优先级越高。
- 再与
~/.codex/AGENTS.md等用户级全局指令合并,作为一条 user 角色消息注入。
为什么顺序很重要:Prompt Caching
Responses API 的提示缓存基于前缀精确匹配。Codex 刻意让 Prompt 只"追加"不"修改":静态的 instructions、tools、权限与 AGENTS.md 放在最前面,历史按时间追加在后面。这样每轮采样都能命中前一轮的缓存前缀,推理成本和首 token 延迟都随之下降。中途改变沙箱/模型等配置时,Codex 会追加一条新的上下文更新消息,而不是改写旧消息,同样是为了保护缓存。
无状态设计:Codex 默认每次发送完整历史(而不是依赖服务端 previous_response_id),推理内容以加密形式(encrypted_content)随历史回传。这让它兼容零数据保留(ZDR)组织,也方便切换模型 Provider。
05通信协议:SQ / EQ 与 App Server
core 不直接操作 UI。UI 把 Submission(带 sub_id 的 Op)推入 Submission Queue,core 把 Event(EventMsg)推入 Event Queue。这让同一引擎可以运行在跨线程 channel、stdin/stdout、TCP、WebSocket 等任意双向流之上。
| 方向 | 常见消息 | 用途 |
|---|---|---|
UI → Codex (Op) | UserTurn / TurnInput | 用户输入,附带 cwd、模型、沙箱、审批策略等每轮上下文 |
Interrupt | 中断当前 Turn(Esc / Ctrl-C) | |
ExecApproval / PatchApproval | 回复命令执行或补丁的审批请求 | |
UserInputAnswer / ResolveElicitation | 回答 request_user_input 工具或 MCP elicitation | |
Compact / Review / Shutdown | 手动压缩、代码审查任务、关闭 | |
Codex → UI (EventMsg) | TurnStarted / TurnComplete | 回合边界,包含上下文窗口大小、最终消息 |
AgentMessageContentDelta / AgentMessage | 流式文本与完整消息 | |
ExecApprovalRequest / ApplyPatchApprovalRequest | 请求用户审批 | |
ExecCommandBegin/End、PatchApplyBegin/End | 工具执行进度 | |
TokenCount / Warning / Error | 用量与异常 |
App Server:给富客户端的 JSON-RPC 层
IDE 插件和桌面 App 需要更稳定的接口,于是 codex app-server 在 core 之上包了一层 JSON-RPC 2.0(stdio 或 WebSocket),把对话抽象成三个原语:
会话线程
thread/start、thread/resume、thread/fork、thread/list,对应一份可持久化的对话。
回合
turn/start、turn/interrupt,一次用户输入引发的全部工作,结束时发 turn/completed。
条目
消息、推理、命令执行、文件修改、MCP 调用等,以 item/started → delta → item/completed 的生命周期推送。
06工具系统
模型返回的 FunctionCall / CustomToolCall / LocalShellCall 被 ToolRouter::build_tool_call 解析为统一的 ToolCall,再由 ToolRegistry 找到对应 handler(core/src/tools/handlers/)执行。
| 工具 | Handler | 作用 |
|---|---|---|
shell / exec_command | shell_spec.rs、unified_exec.rs | 执行 shell 命令。unified_exec 基于 PTY,支持长驻进程、write_stdin 交互、输出头尾截断 |
apply_patch | apply_patch.rs | 以自定义 diff 语法(*** Begin Patch / *** Update File)增删改文件,是 Codex 的主要编辑方式 |
update_plan | plan.rs | 维护任务计划清单(步骤 + 状态),UI 展示为 TODO 列表 |
view_image | view_image.rs | 把本地图片附加进上下文 |
web_search | Responses 内置工具 | 由服务端执行的网络搜索 |
| MCP 工具 | mcp.rs、mcp_resource.rs | 通过 rmcp-client 连接 config.toml 中声明的 MCP Server,工具名带命名空间 |
tool_search | tool_search.rs | 工具过多时延迟加载,模型先搜索再获得完整 schema |
request_user_input | request_user_input.rs | 向用户发起结构化提问 |
| 多 Agent 工具 | multi_agents*.rs | 派生子 Agent 线程、相互发送消息、等待结果 |
arg0 技巧:Codex 是单一二进制。当 argv[0] 为 codex-linux-sandbox 时它变身沙箱 helper;当 argv[1] 为 --codex-run-as-apply-patch 时它模拟一个虚拟的 apply_patch CLI。这样沙箱内部也能调用补丁逻辑,而无需额外安装程序(见 codex-arg0 crate)。
07审批与沙箱:安全模型
Codex 的安全由两个正交维度组合而成:审批策略决定"什么时候问人",沙箱策略决定"操作系统层面允许做什么"。
审批策略 AskForApproval
| 值 | 行为 |
|---|---|
untrusted | 未受信任项目:除 execpolicy 显式允许的安全命令外,全部需要审批 |
on-request | 默认值:由模型判断何时请求提升权限(例如需要联网或写出工作区时);on-failure 为其别名 |
granular | 细粒度开关:分别控制 shell 提权、execpolicy prompt、技能脚本、request_permissions、MCP elicitation |
never | 从不询问,失败直接返回模型(适合 CI / codex exec) |
沙箱策略 SandboxPolicy
| 值 | 文件系统 | 网络 |
|---|---|---|
read-only | 全盘只读 | 默认关闭 |
workspace-write | cwd + writable_roots + TMPDIR/tmp 可写;.git、.codex 保持只读 | 默认关闭,可配置 |
danger-full-access | 无限制 | 无限制 |
external-sandbox | 已处于外部沙箱(容器)中,全盘访问 | 按配置 |
平台实现
Seatbelt
通过 /usr/bin/sandbox-exec 加载动态生成的 SBPL profile,按可写根目录放开写权限,屏蔽网络。
bubblewrap + seccomp
--ro-bind / / 默认只读,--bind 叠加可写根,受保护子路径重新 --ro-bind;seccomp 拦截网络 syscall 与 io_uring。Landlock 为旧路径。
受限令牌 / 提权沙箱
非提权模式使用 restricted token;提权模式支持精确读写根与只读子目录。无法强制执行的策略直接失败(fail closed)。
ToolOrchestrator:审批 → 沙箱 → 升级重试
core/src/tools/orchestrator.rs 的注释写得很直接:"Coordinates tool approval, sandbox selection, and retries after sandbox denial."
- 评估审批需求:execpolicy 规则(Starlark
prefix_rule,decision 为 allow / prompt / forbidden)与审批策略共同得出Skip、NeedsApproval或Forbidden。 - 请求审批:需要时发出
ExecApprovalRequest,用户可选 Approved、ApprovedForSession(会话内缓存)、ApprovedExecpolicyAmendment(写入规则)、Denied、Abort。也可由 Guardian 自动审查代为决策。 - 首次沙箱内执行:按
SandboxManager选出的平台沙箱运行。 - 沙箱拒绝后的升级:若执行因沙箱失败,在
untrusted(或 granular 中开启sandbox_approval)下会再次请求审批,批准后脱离沙箱重试(wants_no_sandbox_approval);在never/on-request下不会自动升级,而是把精简的拒绝信息返回给模型,由模型自行决定是否通过require_escalated参数显式申请提权。
旧版本的 on-failure 策略现在只是 on-request 的序列化别名(#[serde(alias = "on-failure")])。
08上下文管理与压缩
历史由 ContextManager(core/src/context_manager/history.rs)维护,存储的是 Responses API 原生的 ResponseItem,并在发送前做归一化(normalize.rs),例如确保每个工具调用都有对应输出、截断超长工具输出。
自动压缩(Auto Compaction)
- 触发:每次采样后统计 token;当已用 token 超过
model_auto_compact_token_limit且仍需继续(needs_follow_up)时触发。采样前也会检查(run_pre_sampling_compact),切换到上下文更小的模型时同样会压缩。 - 生成摘要:本地模式下用压缩提示词让模型总结当前进展;远程模式下调用 Responses API 的压缩能力,返回不透明的
Compactionitem(可保留加密的推理状态)。 - 替换历史:以
SUMMARY_PREFIX+ 摘要 + 最近用户消息(上限约 2 万 token)构成新历史,并在正确位置重新插入初始上下文(权限、AGENTS.md、环境)。 - 继续循环:压缩后同一个 Turn 接着采样,用户无感知。用户也可用
/compact手动触发(Op::Compact)。
09会话持久化与多端
- Rollout:每个会话以 JSONL 写入
~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl,记录所有 ResponseItem 与关键事件。codex resume通过重放 rollout 重建历史(rollout_reconstruction.rs)。 - 配置:
~/.codex/config.toml定义模型、Provider、审批/沙箱默认值、MCP Servers、profiles、features 开关;项目可声明信任级别。 - ThreadManager:同一进程可管理多个线程(Thread),支持 fork、子 Agent(
thread_spawn.rs、agent_communication.rs),每个线程仍遵守"一次只有一个 Task"的规则。 - 可观测性:
otelcrate 输出 OpenTelemetry 指标,涵盖工具决策来源、沙箱结果(denied / escalated)、采样耗时等。
10codex-rs 代码地图
codex-rs 是一个 Cargo workspace,包含上百个 crate。下面是理解主流程最关键的部分:
| Crate / 路径 | 职责 |
|---|---|
cli | codex 多功能入口:子命令 exec、login、mcp、sandbox、app-server、resume、apply 等 |
arg0 | 根据 argv[0]/argv[1] 把同一个二进制分发为 linux-sandbox、apply_patch 等角色 |
tui | 基于 ratatui 的全屏终端界面:聊天视图、diff 渲染、审批弹窗、斜杠命令 |
exec | 无头模式,事件输出为人类可读文本或 --json JSONL |
app-server / app-server-protocol | JSON-RPC 服务及其 schema(Thread / Turn / Item),供 IDE 和桌面端使用 |
protocol | 共享类型:Op、EventMsg、ResponseItem、SandboxPolicy、AskForApproval |
core/src/session/ | Session 生命周期、turn.rs 主循环、turn_context.rs、输入队列、MCP 运行时 |
core/src/tasks/ | SessionTask trait 及 regular、review、compact、user_shell 等任务实现 |
core/src/tools/ | router.rs、registry.rs、orchestrator.rs、parallel.rs、handlers/*、runtimes/* |
core/src/unified_exec/ | PTY 进程管理器:会话复用、stdin 写入、输出缓冲 |
core/src/context_manager/ | 对话历史的存储、归一化与增量更新 |
core/src/compact*.rs | 本地 / 远程上下文压缩 |
core/src/agents_md.rs | AGENTS.md 发现与拼接 |
core/src/client.rs | 模型客户端:Responses API 流式请求、重试、WebSocket 传输 |
sandboxing / linux-sandbox / bwrap | 沙箱策略转换与各平台执行 |
execpolicy | Starlark 命令规则引擎(codex execpolicy check) |
apply-patch | 补丁语法解析与文件写入 |
codex-mcp / rmcp-client / mcp-server | MCP 客户端连接管理,以及把 Codex 本身暴露成 MCP Server |
prompts | 基础指令、压缩提示词、审查提示词、权限说明模板 |
model-provider / ollama / lmstudio | 多 Provider 支持(OpenAI、Azure、本地开源模型) |
login / keyring-store | ChatGPT 登录或 API Key 认证,凭证存储 |
11一次请求的完整时序
示例:用户在 TUI 中输入"修复失败的单元测试",审批策略 on-request,沙箱 workspace-write。
- TUI 发送
Op::UserTurn(文本 + cwd + 模型 + 策略)到 SQ。 - Session 若无运行中任务,启动
RegularTask,发出TurnStarted。 - run_turn 记录输入,组装 Prompt:instructions + tools + [权限说明, AGENTS.md, 环境上下文, 历史..., 用户消息]。
- ModelClient 流式调用 Responses API;模型先输出推理,再发出
exec_command {"cmd":"cargo test"}。 - ToolRouter 解析调用;Orchestrator 判断为工作区内只读类命令,Skip 审批,在 Seatbelt / bwrap 中执行。
- 测试输出(截断后)作为
FunctionCallOutput写回历史,needs_follow_up = true,进入下一次采样。 - 模型阅读失败信息,发出
apply_patch修改源文件;补丁在可写根内,自动通过;UI 收到PatchApplyBegin/End和 diff。 - 模型再次调用
cargo test验证;全部通过。 - 模型输出总结消息且无工具调用 →
needs_follow_up = false,循环结束。 - 发出
TurnComplete,rollout 落盘,TUI 显示最终回答和 token 用量。
若第 5 步命令需要联网(如 npm install),沙箱会拒绝网络;模型可带 require_escalated 重新调用,此时 Orchestrator 发出 ExecApprovalRequest,用户批准后在沙箱外执行。
12设计取舍总结
引擎与 UI 解耦
SQ/EQ 让 TUI、IDE、App、MCP Server 共享同一个 core,一处修改,所有前端受益。
单线程 Task 模型
每个 Session 同时只跑一个 Task,避免并发编辑冲突;需要并行就开多个线程或子 Agent。
纵深防御
execpolicy 规则 + 审批 + OS 级沙箱 + 网络代理,四层叠加;无法强制执行的策略 fail closed。
只追加的 Prompt
静态前缀在前、历史追加在后,最大化 prompt cache 命中;无状态请求兼容 ZDR。
补丁而非整文件重写
apply_patch 语法让模型输出更短、更可审阅,UI 可直接渲染 diff。
Rust 单二进制
启动快、无运行时依赖,arg0 分发让沙箱 helper 与补丁工具都内置在同一文件中。
13参考资料
- github.com/openai/codex:源码仓库(codex-rs)
- codex-rs/docs/protocol_v1.md:Session / Task / Turn 与 SQ/EQ 协议
- codex-rs/core/README.md:各平台沙箱依赖说明
- 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:OpenAI 官方博客,Agent Loop 与 Prompt 缓存