Codex 怎样管理执行循环、沙箱与上下文

Codex 怎样管理执行循环、沙箱与上下文
Asaakii假设 CI 报了一条测试失败:一个金额计算函数 round_amount(2.005) 应该返回 2.01,实际返回了 2.00,这是典型的浮点精度加银行家舍入问题。把这个任务交给 Codex 后,它并不是”一次性写出一段修复代码”就完事。它需要读相关代码和测试,判断改动范围,在受限环境里运行命令,看结果,如果没修好就再来一轮,最后把补丁和验证信息交回给人。
这篇文章不去猜 Codex 某个内部函数叫什么名字,而是沿着这条任务链路,把一个编码 Agent 的执行架构讲清楚:模型到底”怎样提出下一步”,工具怎样在沙箱里执行,什么情况需要审批,以及为什么”代码已经改完”不等于”任务已经完成”。我希望一个刚接触 Agent 的人读完,不只是记住几个名词,而是能自己装上 Codex、看懂它每一步在干什么、并把安全边界配对。
先说明本文的证据边界
Codex 的产品能力会持续迭代。本文把三类信息分开处理,避免把”我的推测”讲成”它的实现”:
| 内容层次 | 本文的处理方式 |
|---|---|
| 官方产品能力 | 作为正文事实,例如沙箱模式、审批预设、网络限制和验证流程,均给出出处 |
| 官方安全设计说明 | 用于解释这些能力解决什么风险 |
| 通用 Agent 机制 | 执行循环、工具调用、观察回传这类不是 Codex 独有、但任何工具型 Agent 都成立的原理 |
| 社区源码阅读 | 仅作为附加阅读,不用来断言当前内部实现 |
| 我的工程判断 | 明确写成实践建议 |
这样写会少一些”内部揭秘”的味道,但更适合长期放在博客上。Codex 的界面、配置项和默认值都会变,真正值得学的是它处理”执行边界”的思路,以及这套思路落到命令行时长什么样。文中出现的 CLI 参数与配置项,均以 OpenAI 面向开发者的公开文档为准。
初学者最该先建立的一个概念:模型不会”自己动手”
这是最容易被跳过、却最关键的一点。很多人以为 Codex 是”一个会自己敲命令、自己改文件的 AI”。更准确的理解是:模型只负责’说’,运行环境负责’做’。
一次任务被拆成很多个”回合”。每一回合里:
- 模型看到当前上下文(任务描述、读过的文件、上一步的命令输出),然后输出一个结构化的意图。这个意图不是自然语言的”我建议你去跑个测试”,而是一个机器能直接执行的动作,通常是两类之一:
- 运行一条 shell 命令(比如
pytest tests/test_billing.py -x); - 对文件打一个补丁(Codex 用一种类似 diff 的
apply_patch格式描述”在哪个文件、把哪几行改成什么”)。
- 运行一条 shell 命令(比如
- Codex 的运行环境(这一层社区常叫 harness 或 runtime)接住这个意图,在沙箱里真正执行它,然后把结果,命令的标准输出、标准错误、退出码,或者补丁是否应用成功,当作”观察”回传给模型。
- 模型读到观察结果,进入下一回合,再提出下一个意图。
这样一轮轮转下去,直到任务完成或需要人介入。用金额计算 Bug 举一次真实回合的样子,会比任何抽象描述都清楚:
1 | ── 第 1 回合 ──────────────────────────── |
上面的命令和补丁是为了讲清”回合制”而构造的示意,不是 Codex 真实输出的逐字记录;但”模型出意图 → 环境执行 → 结果回传”这个循环结构,是任何工具型 Agent 都成立的。
看懂这个循环,后面的所有设计就都有了落点:沙箱约束的是第 2 步里”环境到底允许执行什么”,审批决定的是第 2 步之前”哪些意图要先问过人”,上下文管理管的是第 1 步里”模型每回合能看到多少信息”,验证则是判断”最后一回合的观察结果,能不能算任务完成”。
把这条任务链路画成闭环
把上面逐回合的过程收拢一下,就是下面这张图。它比”模型一次性生成代码”更接近真实使用体验:
flowchart TD
A["任务:修复边界输入的测试失败"] --> B["理解任务与代码<br/>读取相关文件、测试与项目约定"]
B --> C["规划下一步<br/>定位原因,决定要读、改或运行什么"]
C --> D{"这个意图在已授权边界内?"}
D -->|是| E["受限执行<br/>在工作区沙箱中读取、编辑或运行命令"]
D -->|否| F["请求审批或提升权限"]
F --> E
E --> G["观察结果<br/>测试输出、命令退出码、代码 diff"]
G --> H{"验证通过?"}
H -->|否| B
H -->|是| I["交付补丁与验证说明"]
官方将 Codex 描述为可在本地或云端工作、读取和修改代码、运行命令,并在需要时向用户请求批准的编码 Agent。(Codex 产品更新) 图里的分叉 D 就是”审批”介入的地方,H 就是”验证”介入的地方。它们不是可有可无的装饰,而是让这个循环”可治理”的两道闸。
一次修 Bug 时,各环节分别做什么
把循环里的角色拆开对照,能看清哪一层负责哪件事、以及最容易在哪一层出问题:
| 环节 | 金额计算 Bug 中的动作 | 需要注意的点 |
|---|---|---|
| 理解上下文 | 读取函数、调用方和失败测试 | 不要一上来就把整个仓库塞进上下文 |
| 规划 | 判断是精度问题、边界条件还是业务规则错误 | 有歧义时应提出假设或请求澄清,而不是猜 |
| 工具执行 | 搜索代码、编辑文件、运行目标测试 | 每个意图都受工作区与权限约束 |
| 验证 | 先复现失败,再确认新增测试和回归测试通过 | 不能只看代码”看起来合理” |
| 交付 | 输出改动、验证命令和仍需人工确认的风险 | 让 review 有可追溯的证据 |
我认为这里最重要的一条,是把”生成”和”验证”分成两件事看。模型可以提出一个看着很合理的修复,但只有测试、构建或可重复的检查结果,才能说明这个修复在当前项目里真的成立。上面回合走查里,第 2 回合先复现失败、第 4 回合再确认通过,正是这条原则的体现:没有第 2 回合的”红”,第 4 回合的”绿”就说明不了任何问题。
沙箱到底是什么:为什么它比”嘱咐模型别乱来”可靠
对编码 Agent 来说,最大的风险通常不在于它给出了错误解释,而在于它能不能不受限制地读敏感文件、改工作区外的数据、或者往外发网络请求。一个自然的念头是”那就在提示词里写清楚别碰这些”,但提示词是”建议”,模型可能误解、可能被恶意输入绕过。真正可靠的做法是让操作系统来兜底:哪怕模型执意要删你的主目录,系统层面根本不放行。
这就是沙箱。Codex 的沙箱不是它自己发明的一套拦截逻辑,而是复用了操作系统本来就有的隔离机制:
- macOS 上用 Seatbelt(
sandbox-exec背后的机制)。它可以给一个进程定义一份”策略”,规定这个进程能读哪些路径、能写哪些路径、能不能联网。Codex 启动命令时,把命令包在这样一份策略里,于是即便命令本身是rm -rf /,系统也只会允许它在被授权的目录里动手。 - Linux 上用 Landlock 加 seccomp。Landlock 负责限制文件系统访问(哪些目录可读、可写),seccomp 负责过滤系统调用(比如禁掉发起网络连接的那类调用)。两者叠加,效果和 macOS 上的 Seatbelt 类似。
官方对此的表述是:sandbox_mode 决定 Codex “技术上能做什么”,而这一层由操作系统强制执行,macOS 用 Apple Seatbelt,Linux 用 Landlock 加 seccomp;默认配置会限制网络访问和工作区外的写入。(Codex Sandbox 文档、GPT-5-Codex 系统卡)
把它落到金额计算 Bug 上,几层控制各解决一个问题:
| 控制层 | 解决的问题 | 金额计算 Bug 示例 |
|---|---|---|
| 工作区边界(文件系统沙箱) | 防止随意修改无关目录 | 只允许写当前项目的源码与测试 |
| 网络策略(系统调用过滤) | 防止不必要的外部连接与数据外发 | 测试无需联网时保持网络受限 |
| 审批策略 | 让高风险或越界动作显式可见 | 安装依赖、访问额外目录前先问 |
| 验证流程 | 防止未经检查的补丁直接被接受 | 运行目标测试并展示 diff |
要强调的是,这不是”沙箱一定比提示词安全”的口号。沙箱解决的是”进程能不能执行”,审批解决的是”谁来授权”,项目规则解决的是”应该怎么做”。它们覆盖的是不同问题,不能互相替代:沙箱拦不住一个”逻辑上错误但权限上合法”的改动,那得靠验证;提示词也永远拦不住一个越权的系统调用,那得靠沙箱。
三档审批模式:从”什么都问”到”什么都不问”
沙箱定义”技术上能做什么”,审批定义”什么时候停下来问你”。这两个旋钮组合起来,就是你日常使用 Codex 时最该搞清楚的东西。Codex CLI 把常见组合封装成了几档预设,官方文档里的描述如下:(Codex 审批与安全)
| 预设 | 含义 | 适合场景 |
|---|---|---|
| Read Only | Codex 只能读文件、回答问题;改文件、运行命令、联网都要审批 | 让它先帮你看懂一段代码、评估改动范围,还不想让它动手 |
| Auto(默认) | Codex 可以在工作区内读文件、改文件、运行命令;一旦要改工作区外的东西或联网,就请求审批 | 日常在自己项目里修 Bug、写功能,最常用的一档 |
| Full Access | 无沙箱、无审批 | 高风险,仅在你完全清楚后果、且环境本身可弃(如一次性容器)时使用 |
这些预设背后是两个可以单独控制的参数。如果你想更细地调,可以直接用命令行标志:
--ask-for-approval(审批策略):on-request(默认,模型觉得需要时才问)、never(从不问,配合只读或全放开使用)、untrusted(只自动执行已知安全的操作,其余都问)。--sandbox(沙箱模式):read-only(只读)、workspace-write(默认,可写工作区)、danger-full-access(完全放开,别名--yolo)。
所以官方说的”Auto 预设”,展开就是这一条命令:
1 | codex --sandbox workspace-write --ask-for-approval on-request |
而”只读、不打扰”(比如放进 CI 里让它做只读审查)就是:
1 | codex --sandbox read-only --ask-for-approval never |
初学者第一次用,我的建议是老老实实从默认的 Auto 开始,先观察它在什么节点会停下来问你。那些节点,往往正是它自己判断”这一步越界了”的地方,比你凭空想象安全边界更有教育意义。
两个你一上手就会碰的文件:config.toml 与 AGENTS.md
预设和命令行标志适合临时切换,但你不会想每次开会话都敲一长串参数。Codex 的持久配置放在 ~/.codex/config.toml,把你常用的那档预设固定下来:(Codex 配置参考)
1 | # ~/.codex/config.toml |
这样每次启动就是你想要的姿态,不必重复敲标志。需要临时更保守或更放开时,再用命令行标志临时覆盖。
另一个文件是 AGENTS.md,它是 Codex 版的”项目说明书”,放在项目里,会在任务开始前作为项目指令被读进去。它的定位和 Claude Code 的 CLAUDE.md 类似:写团队长期约定,而不是一次性的任务细节。放金额计算这个项目里,一份克制的 AGENTS.md 可能是这样:
1 | # 项目约定 |
关键在于分清”什么该进 AGENTS.md、什么不该”。长期、跨任务、每次都想让它看到的约定(技术栈、精度规则、提交前检查)放进去;某一次任务的复现步骤、某一段很长的报错日志,属于当前对话,任务结束就该随之消失,不要沉淀进 AGENTS.md 把它撑肥。这就自然引出下一个话题:上下文。
上下文管理是一笔持续的账
当任务从”改一个函数”变成”一次跨模块重构”,Agent 会撞上另一个限制:上下文不能无限增长。代码片段、终端日志、工具结果、历史对话都会占据模型每回合能看到的那份”视野”,而这份视野是有上限的。视野越杂,模型越难抓住当前目标,调用成本和返工概率也随之上升。
结合前面的回合制机制看会更具体:每一回合模型看到的不只是你最初的任务,还包括之前每一回合的命令输出。如果第 2 回合你让它跑了一整套测试、回传了三百行日志,这三百行就会一直挂在后续每一回合的视野里,哪怕后面根本用不到。所以组织给 Codex 的任务时,我会按这几条来:
- 一开始就给复现步骤、失败测试和目标模块,而不是从”帮我看看整个项目”起步。给它第 1 回合就能精准落点的信息。
- 把长期约定放进
AGENTS.md,把一次性日志留在当前任务里。前者每次都该看到,后者看完即弃。 - 测试输出很长时,先让它只保留失败项、调用栈和关键环境信息,而不是把全量日志留在对话里滚动。
- 把独立的调查工作拆成子任务,只回传结论,而不是把完整探查过程灌回主线。
这些做法不是 Codex 独有的机制,而是任何工具型 Agent 都会遇到的工程约束。理解了”每回合的观察都会累积进视野”,你就会明白为什么”让它一次读一百个文件”往往比”精确指到三个文件”效果更差:不是它不够聪明,是视野被噪声占满了。
安全配置与效率之间的取舍
受限环境有时会挡住必要操作,比如装依赖、访问内部包源、或者调用本地开发服务。碰到这种情况,正确的反应不是直接切到 Full Access 把门全拆了,而是先问清楚”到底缺哪一格权限”:
- 是只需要多一个可写目录?那就只加那个目录,沙箱模式仍保持
workspace-write。 - 是只需要放行一个域名的网络访问?那就只放这一个,而不是整个网络。
- 是只需要对某一条命令批准一次?那就在它问的时候批这一次,不改默认策略。
OpenAI 在内部部署 Codex 时也把这条讲成同一个意思:低风险的常规操作尽量保持顺畅,越过既定边界的操作则停下来接受审批,并保留可审计的记录。(Running Codex safely at OpenAI)
落到实际配置上,我的姿态是:
- 个人项目:默认就用 Auto(
workspace-write+on-request),只为明确的需求逐项放开,用完收回。 - 团队项目:把可共享的限制和验证命令写进
AGENTS.md与config.toml,别让每个开发者靠口头习惯各自决定安全边界。安全边界一旦靠”记得”来维持,就等于没有。
一个可以自己动手验证的小实验
本文没有逐行验证 Codex 的内部实现。如果你想把这篇的理解落到自己项目上,最好的办法是找一个有稳定测试的 Bug,做两轮对照实验,亲眼看看那些参数的差别:
1 | 准备:一个能稳定复现的失败测试,先记下它现在为什么红 |
这个实验证明不了 Codex 的内部实现,但能回答一个更实际的问题:你的配置,是不是既够安全、又能把活干完。它也能成为面试时更有说服力的实践材料。比起说”我了解 Agent 沙箱”,你能讲清”我在什么场景下把 read-only 换成了 workspace-write、为什么只放开了一个目录而不是全放开”,分量完全不同。
我的结论
我理解 Codex 的重点不是”它内部有几层状态机”,而是它把一次编码任务拆成了可治理的回合:模型每回合只提一个意图,运行环境在明确边界内执行并把结果回传,人则通过审批和验证这两道闸决定”要不要继续”和”能不能算完”。看懂了这个回合制循环,沙箱、审批、上下文、验证就不再是四个孤立名词,而是分别卡在循环的四个位置上。
对想做 Agent 的人,这里有四个可以直接带走的原则:
- 任务上下文要小而具体,因为每回合的观察都会累积进模型视野。
- 工具执行要有技术边界,用操作系统级沙箱兜底,而不是靠提示词嘱咐。
- 权限提升要按最小范围进行,缺哪一格补哪一格,不要一步切到全放开。
- 完成条件必须由测试、构建或可审计的结果定义,”看起来对”不是完成。
模型能力还会变,默认参数和界面也会变,但这四个工程问题不会消失。把它们逐个想清楚,一个 Agent 才会从”演示”走进”日常开发流程”。










