一、为什么要研究 Codex 源码?
最近在研究 OpenAI 开源的 Codex 项目:
GitHub:https://github.com/openai/codex
Codex 不只是一个 AI 代码生成工具。它更像是一个能够理解代码、操作文件、执行命令、运行测试,并根据执行结果继续调整的代码 Agent。
如果只从产品角度看,我们看到的是:告诉 AI 一个开发任务,它就能帮我们修改代码。
但从工程角度看,真正值得研究的是另一个问题:
当 Agent 需要持续工作、调用工具、处理异常、管理上下文时,系统应该如何组织这些能力?
结合 Codex 的 Rust 源码,我重点关注了以下模块:
codex-rs/core/src/session/turn.rs:核心回合执行。codex-rs/core/src/tasks/regular.rs:常规任务调度。codex-rs/core/src/agents_md.rs:项目指令发现与加载。codex-rs/core/src/tools/mod.rs:工具系统组织。codex-rs/core/src/tools/sandboxing.rs:沙箱与执行限制。codex-rs/core/src/tools/handlers/multi_agents.rs:多 Agent 协作。codex-rs/app-server/:客户端与核心能力之间的服务接入层。
源码会持续演进,以上路径以仓库当前版本为准。本文重点讨论这些设计背后的工程思想,不把 Codex 的具体实现直接等同于所有 Agent 都必须采用的标准架构。
二、启示一:Agent 不只是一次模型调用,而是一个持续运行的控制循环
很多初级 Agent 项目,结构大致如下:
response=llm.invoke(user_input)ifresponse.tool_calls:result=execute_tools(response.tool_calls)response=llm.invoke(result)这个实现可以验证工具调用的基本原理,但距离工程化还有一段距离。
真实任务通常需要经历多个步骤:
- 理解用户目标。
- 检查当前环境。
- 选择下一步行动。
- 调用工具执行。
- 观察执行结果。
- 根据结果决定继续、修复还是结束。
例如,让 Agent 修复一个 FastAPI 项目的测试失败问题。
它可能先读取测试报告,再检查相关代码,修改实现,运行测试,然后根据新的错误信息继续调整。
这不是一次模型调用就能完成的事情,而是一个持续迭代的过程。
Codex 的run_turn正是理解这一机制的重要入口。它负责协调模型采样、工具反馈、上下文状态和回合结束条件;RegularTask::run则承担更外层的任务调度职责。
这里有一个值得注意的区别:
- Task:管理一项工作及其生命周期。
- Turn:管理当前一轮模型交互与工具执行。
- Tool Call:执行某个具体操作。
它们不是同一个概念。
对我们开发 Agent 的启示
不要把所有逻辑都塞进一个Agent.run()方法。
至少应该在设计上区分任务管理、回合调度和工具执行。
一个简化的控制循环可以这样表达:
whilenottask.is_finished():context=prepare_context(task)response=call_model(context)ifresponse.has_tool_calls:results=execute_tools(response.tool_calls)record_results(task,results)continueifshould_stop(response,task):break这只是用于说明控制流的伪代码,并不是 Codex 源码。
真正的实现还需要补充取消机制、错误分类、上下文压缩、权限检查和待处理输入管理。
核心观点:Agent 的工程价值,不只在于模型能够思考,更在于系统能够可靠地组织多轮思考与实际执行。
三、启示二:任务调度与 Agent Loop 应该分层
研究 Codex 源码时,一个容易忽略的细节是:外层任务循环与内层回合循环并不是一回事。
RegularTask::run调用run_turn,并在回合结束后检查终止状态和待处理输入。
run_turn则负责当前回合内部的模型采样、工具反馈和继续执行条件。
为什么要这样设计?
假设一个 Agent 正在生成企业标书。
当前回合中,它可能需要检索历史项目资料、提取技术参数、生成章节草稿并检查引用证据。
与此同时,用户可能追加要求:
“把项目实施周期调整为六个月,并重新检查相关章节。”
如果任务管理和模型回合完全混在一起,系统很容易出现输入覆盖、状态混乱或者重复执行的问题。
把两者分开,可以更清晰地管理:
- 当前任务是否结束。
- 当前回合是否结束。
- 是否存在新的待处理输入。
- 是否出现终止性错误。
- 是否需要重新准备上下文。
对我们开发 Agent 的启示
对于复杂业务 Agent,可以建立三个层次:
| 层次 | 主要职责 |
|---|---|
| Task Manager | 管理任务生命周期、取消和待处理输入 |
| Agent Runtime | 管理模型交互、工具调用和回合循环 |
| Tool Executor | 执行具体工具并返回结构化结果 |
对于短小的原型,可以暂时合并部分模块;但随着功能增加,应该避免让任务生命周期、模型推理和工具执行互相纠缠。
四、启示三:上下文工程不等于 RAG
我们经常把企业 Agent 的上下文问题理解为 RAG 问题。
例如:
用户提出问题 → 向量检索 → 召回文档 → 拼接 Prompt → 调用模型。
这只是上下文管理的一部分。
研究 Codex 的turn.rs,可以看到它还需要处理会话历史、模型输入格式、上下文窗口、采样前压缩以及采样后的 token 状态。
与此同时,agents_md.rs负责项目指令的发现与加载,让 Agent 能够遵循与当前工作目录相关的开发规则。
这意味着,Agent 的上下文至少包括以下几类:
| 上下文类型 | 示例 | 解决的问题 |
|---|---|---|
| 任务上下文 | 用户目标、验收条件 | 当前要完成什么 |
| 历史上下文 | 之前的推理和工具结果 | 已经做了什么 |
| 项目上下文 | 项目规则、目录结构、接口约束 | 应该遵循什么 |
| 外部知识 | 企业知识库、合同、业务文档 | 可以依据什么事实 |
| 执行上下文 | 当前配置、工作目录、权限状态 | 当前允许如何执行 |
这些信息并不适合全部采用同一种检索方式。
例如,长期有效的项目开发规则可以通过AGENTS.md管理;大量企业文档适合使用 RAG;当前任务状态则更适合由任务对象显式维护。
对我们开发 Agent 的启示
不要试图通过不断增加向量数据库和 Prompt 长度解决所有上下文问题。
更好的思路是:
先区分上下文的来源、生命周期和用途,再决定如何存储、检索、注入和更新。
这对于 RAG、文档处理和多步骤业务 Agent 都很重要。
五、启示四:工具调用必须有独立的执行与权限边界
在很多 Agent Demo 中,工具调用只是一个函数映射:
tools={"search_documents":search_documents,"calculate":calculate,"update_record":update_record,}模型生成工具调用,程序直接执行。
这种方式适合快速验证,但在企业环境中存在明显风险。
假设 Agent 可以操作合同、客户资料和订单数据库。
检索一份合同,与修改正式合同,不应该拥有相同的权限要求。
同样,生成退款建议与实际执行退款,也应该被视为不同级别的操作。
Codex 的工具模块将工具注册、路由、处理器、审批和沙箱等职责组织为不同部分。具体执行规则取决于工具类型、配置和运行环境。
它体现出的一个重要原则是:
模型可以提出行动,但模型提出行动不等于系统必须执行。
我们可以将工具执行设计成以下流程:
模型提出工具调用 ↓ 检查工具名称与参数 ↓ 检查用户权限和业务规则 ↓ 判断是否需要审批 ↓ 在受控环境中执行 ↓ 记录结果与副作用 ↓ 将结果返回 Agent例如,企业标书 Agent 可以自动读取资料、提取参数、生成草稿,但正式发布文件之前仍然可以要求人工审核。
业务数据修改、资金操作、合同签署等高风险操作,则应该设置更严格的授权与审计机制。
对我们开发 Agent 的启示
不要把安全机制写成系统提示词中的一句“请勿执行危险操作”。
应该在工具执行层建立真正的权限边界。
提示词可以影响模型的行为,但不能替代服务端权限检查。
六、启示五:异常处理是 Agent 工程化的重要组成部分
一个简单的 Agent Demo,往往只考虑成功路径:
调用模型 → 执行工具 → 返回结果。
真实系统还需要考虑:
- 模型请求超时。
- 工具执行失败。
- 输入参数不合法。
- 用户取消任务。
- 上下文超限。
- 外部服务不可用。
- 同一工具调用被重复触发。
- 任务执行过程中出现新的用户输入。
Codex 的turn.rs和regular.rs展示了对取消、上下文压缩失败、终止错误和待处理输入等不同状态的处理。
这里最值得借鉴的不是某一种具体异常处理代码,而是设计理念:
不同类型的失败应该拥有不同的恢复策略。
例如:
| 异常 | 可考虑的策略 |
|---|---|
| 参数错误 | 返回明确错误,让模型修正参数 |
| 临时网络错误 | 有条件地重试 |
| 权限不足 | 终止该操作或请求授权 |
| 上下文超限 | 压缩历史或减少输入 |
| 用户取消 | 停止后续工作,并处理正在运行的操作 |
| 外部写操作超时 | 先核实执行结果,再决定是否重试 |
| 不可恢复错误 | 终止任务并保留诊断信息 |
特别需要注意:对有副作用的工具,不能简单地遇到超时就重试。
例如,支付请求超时并不意味着支付失败。系统应先查询交易状态,或者使用可靠的幂等机制,再决定是否继续操作。
这类问题与模型能力无关,却直接决定 Agent 能否安全地进入生产环境。
七、启示六:项目规则应该独立于具体任务
Codex 的AGENTS.md机制,是另一个很值得借鉴的设计。
一个项目通常有很多长期有效的约束:
- 使用什么语言和框架。
- 代码放在哪些目录。
- 如何运行测试。
- 哪些文件不允许随意修改。
- API 设计遵循什么规范。
- 修改完成后需要执行哪些检查。
如果每次开发任务都重新把这些规则写进提示词,不仅效率低,也容易产生不一致。
项目指令文件可以集中管理这些长期规则,让 Agent 在相关工作目录下发现并使用它们。
但要注意:项目指令并不等于真正的安全边界。禁止访问某个文件的要求,仍然需要在工具和执行环境中落实。
结合 Spec 编程,可以怎样组织?
我更倾向于把项目开发资料分为五类:
| 文档 | 主要职责 |
|---|---|
AGENTS.md | 长期项目规则 |
PRD.md | 业务目标和需求范围 |
SPEC.md | 技术约束和验收条件 |
PLAN.md | 具体实施步骤 |
| Git 历史 | 实际变更与回滚依据 |
这样做的好处是:
- 项目规则不必在每次任务中重复编写。
- 需求变化可以通过文档和版本历史追踪。
- Agent 能够根据明确的验收条件执行任务。
- 代码审查可以检查实现是否符合 SPEC,而不是只看模型是否认为自己完成了工作。
需要强调,这是一种与 Codex 项目指令机制相结合的工程实践,并不是 Codex 强制采用的文档体系。
八、启示七:多 Agent 不等于多开几个模型
Codex 的multi_agents.rs展示了子 Agent 创建、发送输入、恢复、等待和关闭等协作操作。
这说明多 Agent 系统不仅要解决任务拆分,还要管理子任务的生命周期和通信。
假设我们开发一个企业 AI 文档分析系统。
可以考虑将工作拆分为:
- 文档结构分析 Agent。
- 事实与证据检索 Agent。
- 内容生成 Agent。
- 质量审核 Agent。
但并不是所有任务都适合这样拆分。
如果多个 Agent 同时修改同一份文档,可能出现冲突;如果每个 Agent 都重复读取大量资料,也会增加成本;如果子 Agent 的结果缺乏统一格式,主 Agent 就很难可靠地整合它们。
因此,多 Agent 设计至少要明确:
- 任务如何拆分。
- 子任务如何分配。
- 状态和结果如何传递。
- 执行权限如何控制。
- 失败如何恢复。
- 最终结果由谁验收。
对我们开发 Agent 的启示
先把单 Agent 的工具执行、状态管理和错误处理做好,再考虑引入多 Agent。
只有当任务具有明确的可分解性,且并行执行确实能够提升效率或质量时,多 Agent 才值得引入。
架构越复杂,不代表 Agent 越先进。
九、把这些设计应用到企业 Agent,应该怎么做?
结合上述分析,我认为可以将 Agent 工程分为三个阶段。
第一阶段:构建可靠的单 Agent
优先实现:
- 清晰的任务与回合状态。
- 可控的工具调用。
- 结构化的执行日志。
- 超时、取消和异常处理。
- 可重复运行的测试。
此时不必急于引入复杂的多 Agent 架构。
第二阶段:完善上下文与业务约束
根据实际业务补充:
- RAG 检索和证据引用。
- 项目规则与业务规则。
- 上下文预算控制。
- 结构化输出与验收条件。
- 权限检查与人工审批。
例如,AI 标书生成系统不仅要生成内容,还应该能够追溯关键技术参数来自哪份原始文件,并在缺乏证据时明确提示。
第三阶段:引入任务编排与多 Agent
当单 Agent 已经能够稳定完成明确任务后,再考虑:
- 长任务编排。
- 子任务并行执行。
- 持久化任务状态。
- 失败恢复。
- 自动评测与回归测试。
- 多 Agent 结果合并和质量验收。
这样可以避免在基础执行机制尚不可靠时,就引入更难调试的协作结构。
十、总结:Agent 工程的核心是把不确定性控制在系统边界内
研究 OpenAI Codex 源码后,我认为有七点值得记住:
- Agent 是持续运行的执行系统,而不只是一次模型调用。
- 任务调度、回合循环和工具执行应该有清晰边界。
- 上下文工程不仅包括 RAG,还包括项目规则、任务状态和历史管理。
- 工具调用必须受到独立的权限与执行策略约束。
- 取消、异常和恢复机制是生产级 Agent 的基础能力。
- 项目规则、需求规范、实施计划和代码历史应该各司其职。
- 多 Agent 应该由真实的任务分解需求驱动,而不是为了追求架构复杂度。
最后,用一句话概括:
模型决定下一步做什么,Agent Runtime 负责组织执行,工具层落实权限与操作,验证机制判断结果是否真的满足要求。
对于正在从 Demo 走向企业应用的 Agent 开发者来说,这比单纯追求更强的模型或更复杂的 Prompt 更值得投入精力。