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整体架构分层

从外到内可以分成五层。每一层只通过明确的协议与相邻层通信:

① 前端 / UI
tui 全屏终端界面exec 无头模式IDE 插件桌面 Appmcp-server 作为 MCP 服务暴露
② 接入 / 协议
cli 多功能入口 + arg0 分发app-server JSON-RPC(stdio / WebSocket)protocol Op / EventMsg / ResponseItem
③ 引擎 core
ThreadManagerSessionSessionTask(regular / review / compact)run_turn 主循环ContextManager 历史compact 压缩rollout 持久化
④ 工具 / 执行
ToolRouterToolRegistryToolOrchestrator 审批→沙箱→重试unified_exec PTY 进程apply-patchcodex-mcp / rmcp-clientexecpolicy Starlark 规则
⑤ 平台 / 外部
macOS Seatbelt(sandbox-exec)Linux bubblewrap + seccomp(Landlock 旧路径)Windows restricted token / elevated sandboxnetwork-proxyOpenAI Responses APIOllama / LM Studio 等 Provider
TUIcodex execIDE / App Submission QueueEvent Queue codex-core Sessionrun_turn 循环ContextManagerToolRouterToolOrchestratorCompactionRollout(JSONL) Responses APISandbox 执行器MCP Servers

03核心原理:Agent Loop

官方协议文档(codex-rs/docs/protocol_v1.md)定义了四个核心实体,理解它们就理解了 Codex 的运行模型:

实体含义关键约束
ModelOpenAI 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)

  1. 从 cwd 向上查找,直到遇到 project_root_markers(默认 .git),确定项目根目录。
  2. 从项目根到 cwd(含)沿路收集每一层的 AGENTS.md(支持 AGENTS.override.md 和 project_doc_fallback_filenames)。
  3. 按从根到叶的顺序拼接,越靠近 cwd 的指令越靠后,因而优先级越高。
  4. 再与 ~/.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

会话线程

thread/start、thread/resume、thread/fork、thread/list,对应一份可持久化的对话。

Turn

回合

turn/start、turn/interrupt,一次用户输入引发的全部工作,结束时发 turn/completed。

Item

条目

消息、推理、命令执行、文件修改、MCP 调用等,以 item/started → delta → item/completed 的生命周期推送。

06工具系统

模型返回的 FunctionCall / CustomToolCall / LocalShellCall 被 ToolRouter::build_tool_call 解析为统一的 ToolCall,再由 ToolRegistry 找到对应 handler(core/src/tools/handlers/)执行。

工具Handler作用
shell / exec_commandshell_spec.rs、unified_exec.rs执行 shell 命令。unified_exec 基于 PTY,支持长驻进程、write_stdin 交互、输出头尾截断
apply_patchapply_patch.rs以自定义 diff 语法(*** Begin Patch / *** Update File)增删改文件,是 Codex 的主要编辑方式
update_planplan.rs维护任务计划清单(步骤 + 状态),UI 展示为 TODO 列表
view_imageview_image.rs把本地图片附加进上下文
web_searchResponses 内置工具由服务端执行的网络搜索
MCP 工具mcp.rs、mcp_resource.rs通过 rmcp-client 连接 config.toml 中声明的 MCP Server,工具名带命名空间
tool_searchtool_search.rs工具过多时延迟加载,模型先搜索再获得完整 schema
request_user_inputrequest_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-writecwd + writable_roots + TMPDIR/tmp 可写;.git、.codex 保持只读默认关闭,可配置
danger-full-access无限制无限制
external-sandbox已处于外部沙箱(容器)中,全盘访问按配置

平台实现

macOS

Seatbelt

通过 /usr/bin/sandbox-exec 加载动态生成的 SBPL profile,按可写根目录放开写权限,屏蔽网络。

Linux

bubblewrap + seccomp

--ro-bind / / 默认只读,--bind 叠加可写根,受保护子路径重新 --ro-bind;seccomp 拦截网络 syscall 与 io_uring。Landlock 为旧路径。

Windows

受限令牌 / 提权沙箱

非提权模式使用 restricted token;提权模式支持精确读写根与只读子目录。无法强制执行的策略直接失败(fail closed)。

ToolOrchestrator:审批 → 沙箱 → 升级重试

core/src/tools/orchestrator.rs 的注释写得很直接:"Coordinates tool approval, sandbox selection, and retries after sandbox denial."

  1. 评估审批需求:execpolicy 规则(Starlark prefix_rule,decision 为 allow / prompt / forbidden)与审批策略共同得出 Skip、NeedsApproval 或 Forbidden。
  2. 请求审批:需要时发出 ExecApprovalRequest,用户可选 Approved、ApprovedForSession(会话内缓存)、ApprovedExecpolicyAmendment(写入规则)、Denied、Abort。也可由 Guardian 自动审查代为决策。
  3. 首次沙箱内执行:按 SandboxManager 选出的平台沙箱运行。
  4. 沙箱拒绝后的升级:若执行因沙箱失败,在 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)

  1. 触发:每次采样后统计 token;当已用 token 超过 model_auto_compact_token_limit 且仍需继续(needs_follow_up)时触发。采样前也会检查(run_pre_sampling_compact),切换到上下文更小的模型时同样会压缩。
  2. 生成摘要:本地模式下用压缩提示词让模型总结当前进展;远程模式下调用 Responses API 的压缩能力,返回不透明的 Compaction item(可保留加密的推理状态)。
  3. 替换历史:以 SUMMARY_PREFIX + 摘要 + 最近用户消息(上限约 2 万 token)构成新历史,并在正确位置重新插入初始上下文(权限、AGENTS.md、环境)。
  4. 继续循环:压缩后同一个 Turn 接着采样,用户无感知。用户也可用 /compact 手动触发(Op::Compact)。

09会话持久化与多端

10codex-rs 代码地图

codex-rs 是一个 Cargo workspace,包含上百个 crate。下面是理解主流程最关键的部分:

Crate / 路径职责
clicodex 多功能入口:子命令 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-protocolJSON-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.rsAGENTS.md 发现与拼接
core/src/client.rs模型客户端:Responses API 流式请求、重试、WebSocket 传输
sandboxing / linux-sandbox / bwrap沙箱策略转换与各平台执行
execpolicyStarlark 命令规则引擎(codex execpolicy check)
apply-patch补丁语法解析与文件写入
codex-mcp / rmcp-client / mcp-serverMCP 客户端连接管理,以及把 Codex 本身暴露成 MCP Server
prompts基础指令、压缩提示词、审查提示词、权限说明模板
model-provider / ollama / lmstudio多 Provider 支持(OpenAI、Azure、本地开源模型)
login / keyring-storeChatGPT 登录或 API Key 认证,凭证存储

11一次请求的完整时序

示例:用户在 TUI 中输入"修复失败的单元测试",审批策略 on-request,沙箱 workspace-write。

  1. TUI 发送 Op::UserTurn(文本 + cwd + 模型 + 策略)到 SQ。
  2. Session 若无运行中任务,启动 RegularTask,发出 TurnStarted。
  3. run_turn 记录输入,组装 Prompt:instructions + tools + [权限说明, AGENTS.md, 环境上下文, 历史..., 用户消息]。
  4. ModelClient 流式调用 Responses API;模型先输出推理,再发出 exec_command {"cmd":"cargo test"}。
  5. ToolRouter 解析调用;Orchestrator 判断为工作区内只读类命令,Skip 审批,在 Seatbelt / bwrap 中执行。
  6. 测试输出(截断后)作为 FunctionCallOutput 写回历史,needs_follow_up = true,进入下一次采样。
  7. 模型阅读失败信息,发出 apply_patch 修改源文件;补丁在可写根内,自动通过;UI 收到 PatchApplyBegin/End 和 diff。
  8. 模型再次调用 cargo test 验证;全部通过。
  9. 模型输出总结消息且无工具调用 → needs_follow_up = false,循环结束。
  10. 发出 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参考资料