OpenAI-Codex-CLI-智能体编程实战指南-三-

OpenAI Codex CLI 智能体编程实战指南(三)

原文:Agentic Coding with OpenAI Codex CLI

译者:飞龙

协议:CC BY-NC-SA 4.0

并行化什么

在你写下第一行 TOML 之前,决定你的任务是否是好的候选项。每个工作项独立、输入稳定、输出离散的任务非常出色。需要共享可变状态、有序执行或紧密协调的任务是差的候选项,并行化后往往变慢。

表 11-1. 任务类型及其对子代理并行化的适用性

| 任务类型 | 适合并行化? | 备注 |

| --- | --- | --- |

| 文件静态检查 | 是 | 每个文件独立;结果可轻松聚合 |

| 每模块单元测试生成 | 是 | 模块间很少共享可变状态 |

| 翻译和本地化 | 是 | 每个字符串或文件独立 |

| 拉取请求摘要 | 是 | 只读;自然扇出到 PR 列表 |

| 代码审查评论 | 是 | 按文件或差异隔离很直观 |

| 依赖图分析 | 否 | 一步的输出是下一步的输入 |

| 顺序数据库迁移 | 否 | 顺序重要;并发工作代理使回滚复杂 |

| 跨文件重命名重构 | 否 | 工作代理会在共享符号表上冲突 |

| 大文件分块处理 | 有条件 | 块间无相互引用则可行;上下文跨块渗透则失败 |

| 带速率限制的 API 抓取 | 有条件 | 扇出有助吞吐但需要共享速率限制预算 |

用依赖图思考。宽而浅(同一层级有许多独立项)可并行化。深而窄(依赖步骤链)保持顺序。混合型在父代理中并行化宽的部分,保持窄的脊柱顺序。

当你不确定两个任务是否真正独立时,问:“如果工作代理 A 和 B 以不同顺序完成,最终结果会变吗?”如果是,它们有依赖。在父代理的编排逻辑中使该依赖显式化。

何时不并行化

并行化有成本。每个子代理消耗一个 max_threads 槽位,为上下文窗口开销(系统提示、工具描述、脚手架)消耗令牌,并增加父代理等待最慢工作代理的延迟。阿姆达尔定律对代理同样适用。^(⁹⁸)

三种你应该抵制并行化冲动的情况:

任务本身已足够快。 如果顺序代理在四十秒内处理十个文件而截止时间是几分钟,生成十个工作代理的开销(上下文设置、调度、聚合)可能比节省的更多。先测量再优化。

共享状态使隔离不可能。 如果工作代理需要读写同一可变资源(数据库表、共享文件、API 对象),你在锁和冲突解决上花费的精力将超过并行化节省的。重构任务让工作代理写入独立输出(单独文件、单独行、单独端点),然后在父代理中合并。

扇出过宽超出速率限制。 五百个工作代理同时击中同一模型会立即触发限流,运行时的排队反而会串行化执行,只不过现在你加载了五百个上下文。较窄的扇出往往完成得更快,因为它避免了限流惩罚。

从 max_threads 默认值 6 开始,遇到限流再降低。默认值针对你最可能遇到的限制校准;未测量前提高它是过早优化。

并行处理也存在认知成本。并行工作流更难调试,也更难向阅读你 AGENTS.md 的下一个人解释。如果顺序方法清晰且并行方法巧妙,请优先选择清晰度。

CSV 扇出

spawn_agents_on_csv

最常见的并行化模式是 CSV 扇出。spawn_agents_on_csv 接收一个 CSV 文件、一个指令模板和作业控制参数,然后为每一行生成一个工作线程。每个工作线程作为独立线程运行,拥有自己的上下文窗口。

撰写本文时,spawn_agents_on_csv 工作流处于实验阶段,因此参数名称和默认值可能会发生变化。它所依赖的多智能体协作工具由 multi_agent 功能提供,该功能默认启用:

[features]
multi_agent = true

因为 multi_agent 默认开启,通常无需配置。如果你的配置中禁用了它,重新启用将恢复完整的子智能体功能,包括 spawn_agents_on_csv。在未暴露 CSV 工具的运行时中,扇出请求将回退到稍后本节描述的手动生成并等待模式。

参数如下:

| 参数 | 描述 |

| --- | --- |

| csv_path | 输入 CSV 文件路径 |

| instruction | 工作提示词,{column_name} 占位符将按行替换 |

| id_column | 可选,提供稳定的逐项标识符的列 |

| output_schema | 可选,每个工作线程结果必须满足的 JSON Schema |

| output_csv_path | 运行时写入结果 CSV 的路径 |

| max_concurrency | 该作业的最大并发工作线程数 |

| max_runtime_seconds | 每个工作线程的超时时间(秒) |

实战示例

结合本书所在的代码仓库。假设你想对发布分支上的每个提交进行分类,以便从数据而非记忆中编写发布说明。你将提交日志导出到 commits.csv(这些是手稿分支上的真实 SHA;尝试提示词时请替换为你自己的):

sha,author,subject
662e3dd,Daniel Vaughan,reviewed chapter 10
cd0265b,Daniel Vaughan,reviewed chapter 9
fc75f35,Daniel Vaughan,reviewed chapter 8
045e3bb,Daniel Vaughan,reviewed chapter 7
f6ec750,Daniel Vaughan,reviewed chapter 6

spawn_agents_on_csv 是父智能体调用的工具,而非你在 REPL 中输入或写入脚本的语法。你用自然语言向父智能体发出指令,它会发出工具调用。在未暴露该工具的运行时中,父智能体会透明地回退到为每一行生成一个子智能体并等待收集结果。工作流相同;仅中间管道不同。类似这样的提示词:

对 commits.csv 运行 spawn_agents_on_csv,使用 sha、author 和 subject 列。对于每一行,读取提交差异并写一段两句话的备注,描述变更内容及原因。输出到 commit-review.csv。将并发数限制在三个工作线程,每个超时时间设为两分钟。

将生成如下形状的工具调用:

spawn_agents_on_csv(
  csv_path = "commits.csv",
  instruction = "Read commit {sha} by {author}, subject: '{subject}'. Inspect the diff and write a two-sentence note describing what changed and why. Output only the note, no preamble.",
  id_column = "sha",
  output_csv_path = "commit-review.csv",
  max_concurrency = 3,
  max_runtime_seconds = 120
)

运行时生成三个工作线程(每行一个),将列值替换到指令中,并将结果 CSV 写入你提供的路径。

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/agentic-coding-with-openai-codex-cli/img/B40007_11_01.jpg

Figure 11.1 - spawn_agents_on_csv 数据流

输入 CSV 行喂入 spawn_agents_on_csv,后者按行扇出一个工作线程。每个工作线程调用一次 report_agent_job_result,运行时导出的 CSV 将新增 job_id、item_id、status、last_error 和 result_json 列。

占位符使用 {column_name} 语法,而非 Shell 风格的 $INPUT 或 $1。运行时会替换它找到的每一个 {name},因此列名必须是有效的占位符标识符(无空格、无标点,下划线除外)。

报告结果

每个工作线程必须恰好调用一次 report_agent_job_result。该调用是工作线程将输出交回给编排器的方式。如果工作线程退出时未调用它,运行时将为该行记录失败。

设置 output_schema 时,工作线程的结果必须通过模式校验。这是保证工作线程输出结构诚实可靠的最佳方式。进一步推进提交审查扇出:不再要求自由文本备注,而是要求每个工作线程返回结构化记录。修订后的提示词:

使用 output_schema 重新运行提交审查扇出。每个工作线程应返回 sha 字符串、标识提交涉及章节的 chapter 字符串(或 none)、来自 tech_review、rewrite、copy_edit、restructure 或 fix 的 edit_kind,以及描述变更的 note。

将生成携带此模式的工具调用:

output_schema = {
  "type": "object",
  "required": ["sha", "chapter", "edit_kind", "note"],
  "properties": {
    "sha": { "type": "string" },
    "chapter": { "type": "string" },
    "edit_kind": {
      "type": "string",
      "enum": ["tech_review", "rewrite", "copy_edit", "restructure", "fix"]
    },
    "note": { "type": "string" }
  }
}

返回散文而非所需 JSON 对象的工作线程将收到结构化错误,而非破坏结果文件。相同的模式适用于任何扇出:声明一次模式,每个工作线程要么生成符合要求的行,要么被记录为失败。

从结果 CSV 聚合

导出的 CSV 包含每行原始数据外加运行时添加的五列:

| 列 | 含义 |

| --- | --- |

| job_id | 标识整体 spawn_agents_on_csv 调用 |

| item_id | 每行标识符(来自 id_column,若未设则为行索引) |

| status | completed、failed 或 cancelled(终态;从未启动的行不会出现在导出中) |

| last_error | 若 status 非 completed,则为错误信息 |

| result_json | 工作线程的结构化输出(若提供 output_schema 则与其匹配) |

聚合即读取 CSV。没有 wait_agents_all 系列;父智能体不在句柄上阻塞。它启动扇出,运行时写入结果文件,父智能体在需要数据时读取文件。按 status 分区可将成功与失败分开,决定是否重试,然后继续。

扇出完成后父智能体运行的典型聚合循环:

After spawn_agents_on_csv completes:
1. Read commit-review.csv.
2. Separate rows where status == "completed" from rows where status != "completed".
3. For each failure, if last_error contains "rate limit", queue the row for retry.
4. Wait 30 seconds, then re-run spawn_agents_on_csv on the retry queue.
5. Merge retry results into the success set.
6. Group successes by chapter and produce a Markdown release-notes draft, one section per chapter and one bullet per commit.

父智能体能很好地用自然语言处理这种条件重试逻辑。对于更复杂的重试策略(指数退避、死信队列、幂等性检查),请在常规脚本中实现聚合逻辑,让父智能体将其作为工具调用,而非内联推理。

不要指示父智能体无限重试。始终指定最大重试次数。没有终止条件的循环代理会在大致相同的程度上耗尽你的预算和耐心。

跨会话扩展

三个层级

Codex CLI 的并行处理体系是一个三层栈。第一层即本章所述内容。第二、三层用于超出第一层能力的作业:

第一层级:进程内子代理

单个 Codex CLI 会话中最多可运行 max_threads 个并发工作线程,共享该会话的速率限制预算。最适合父代理需要与工作线程紧密协作、并在同一对话中读取聚合结果的任务。

第二层级:本地 Shell 扇出

外部脚本启动多个独立的 Codex CLI 进程,每个进程在各自的 Git 工作树中运行,实现文件系统隔离。工作树是仓库在独立分支上的一个检出副本,它与主检出共享同一个 .git 目录,因此多个进程可以在同一个仓库中编辑文件,而不会发生写入冲突。Shell 脚本、GNU Parallel、xargs 或自定义的 Python 编排工具可启动十到三十个并发的 codex exec 调用,每个调用针对不同的文件或模块。工作树隔离(第 13 章)是关键推动因素:每个实例在自己的分支和工作目录中运行,消除了工作线程间的文件级冲突。

第三层级:云端扇出

Codex 云任务将每个工作单元作为云托管会话运行,拥有独立的算力、上下文窗口和速率限制分配。当本地机器资源成为瓶颈,或需要在夜间运行任务而不必让笔记本保持唤醒时,适用此层级。

从第一层级开始。当你持续触及六线程上限时,升级到第二层级。当本地算力或连接性成为约束瓶颈时,升级到第三层级。

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/agentic-coding-with-openai-codex-cli/img/B40007_11_02.jpg

图 11.2 - 三个并行层级

第一层级在单进程内运行子代理,最多 max_threads 个,共享会话速率限制预算。当你触及六线程上限时,升级到第二层级,它通过带有逐工作线程 Git 工作树隔离的本地 codex exec 进程进行扇出。当本地算力成为瓶颈时,升级到第三层级,Codex 云任务会话各自获得独立的算力和速率限制分配。

利用工作树隔离进行 Shell 扇出

一个典型的第二层级脚本:

#!/bin/bash
##### fan-out.sh — 利用工作树隔离并行处理文件
MAX_WORKERS=20
TASK_FILE="files-to-process.txt"
process_file() {
  local file="$1"
  local worktree
  worktree="$(mktemp -d)"
  git worktree add "$worktree" -b "worker-$(basename "$file")" HEAD --quiet
  cd "$worktree" || exit 1
  codex exec -m gpt-5.4-mini -s workspace-write \
    "根据项目风格指南对 $file 进行 lint 检查并修复" \
    2>"$worktree/codex.log"
}
export -f process_file
xargs -P "$MAX_WORKERS" -I {} bash -c 'process_file "$@"' _ {} < "$TASK_FILE"

这在标准开发机器上可扩展至二十到三十个并发工作线程。每个工作线程在隔离的工作树中操作,因此文件写入永不冲突。所有工作线程完成后,主分支通过合并或挑拣(cherry-pick)收集结果。完整的工作树工作流请参见第 13 章。

运维考量

子代理与 MCP 服务器

当子代理调用 MCP 工具时,MCP 服务器进程并不总能被及时清理。标准 I/O 传输的 MCP 服务器(配置了 command 而非 url)会生成子进程,这些进程可能比启动它们的子代理存活得更久,在长会话中不断累积。

其影响呈乘法关系:子代理数量 × MCP 服务器数量 × 每服务器进程数 × 每进程内存。一个繁重的会话(四十个工作线程,五个 MCP 服务器,每个两个进程)可能在父会话结束前泄露数百个进程,消耗数 GB 内存。

防御模式

在长会话中监控 MCP 进程数量。 一个统计进程数并在超出阈值时告警的看门狗脚本,能在泄露变得痛苦前捕获它:

#!/usr/bin/env bash
###### mcp-watchdog.sh — 当 MCP 进程数超过阈值时告警
THRESHOLD=${1:-50}
COUNT=$(pgrep -f 'mcp' | wc -l)
if [ "$COUNT" -gt "$THRESHOLD" ]; then
  echo "WARNING: $COUNT MCP processes detected (threshold: $THRESHOLD)"
fi

在服务器支持的情况下优先使用 HTTP 传输。 配置了 url 而非 command 的 MCP 服务器不会生成本地子进程:

[mcp_servers.my-server]
url = "https://mcp.example.com/sse"

在大批次之间重启。 对于 CI,建议每个作业使用一次 Codex CLI 调用,而不是持久的代理 Pod。泄露与单个进程内的子代理生成数量成正比,因此全新的进程会重置计数。

为 MCP 密集型工作限制 max_threads。 将 max_threads 从六降至三,可使使用标准 I/O MCP 服务器的任何扇出操作的峰值进程数减半。

如果你未使用标准 I/O MCP 服务器,则无需担心此问题。HTTP 和内置工具调用(文件编辑、Shell、网络获取)不存在此行为。

子代理无助(甚至有害)的场景

并行主动损害效果的两种模式:

具有单一正确答案的任务。 具有特定补丁的 Bug 修复、lint 和格式化通行、确定性迁移以及安全漏洞修复,只有一个正确的输出。多个工作线程探索不同方案只会增加成本而不增值,因为你最终会丢弃除唯一正确答案外的所有内容。应改用高推理力的单个代理运行。

以输出多样性为目标的任务。 头脑风暴、探索性架构草图、多视角代码审查和文档草稿受益于发散性:不同工作线程产出不同框架才是重点。这些是扇出值得其开销的场景。判断你的任务落在哪一侧,是决定并行是助你还是浪费你 Token 的关键判断。

Xu 等人给这一直觉赋予了正式名称:指标自由度,定义为 F = 1 − ρ(D_out, D_score),即工作线程输出多样性与评估分数多样性之间的秩相关性。^99 高 F(输出多样性与分数多样性不相关)意味着存在许多有效答案,并行增加价值。低 F(僵化指标,偏离即受罚)意味着只有一个正确答案,高推理力的单个代理优于扇出。论文测量的技能提升在同一任务的不同指标上从 +28% 摆动到 −2%,取决于 F。对于任何你打算常规并行化的任务,计算 F 值很有必要。

总结

  1. 子代理是单会话内的专用线程。 三个内置代理覆盖大多数一次性委派:default(会话本身)、worker(执行)和 explorer(读密集型探索)。

  2. 自定义代理是单表 TOML。 存储在 ~/.codex/agents/ 或 .codex/agents/ 中,包含三个必填字段(name、description、developer_instructions)以及一小组可选字段,用于覆盖从父会话继承的设置。

  3. 三个会话级限制。 agents.max_threads(默认 6)、agents.max_depth(默认 1)和 agents.job_max_runtime_seconds 约束整个会话,而非单个代理。

  4. CSV 扇出用于批量工作。 spawn_agents_on_csv 接受一个 CSV、一个使用 {column_name} 占位符的 instruction 模板以及作业控制参数。每个工作线程调用一次 report_agent_job_result。结果从导出的 CSV 聚合而来,并补充了 job_id、item_id、status、last_error 和 result_json。撰写时仍处于实验阶段;预计参数会演变。

  5. 三个并行层级。 单会话内的进程内子代理;跨 Git 工作树(第 13 章)中 codex exec 调用的本地 Shell 扇出;面向需要独立于本地资源的云算力或速率限制分配的作业的 Codex 云任务。

6. 两大操作隐患

调用 stdio 传输 MCP 服务器的子代理可能会在长会话中泄露子进程;请改用 HTTP 传输、监控进程数量,或在批次之间重启以限制影响。而且并行并非总是正解:对于只有一种正确输出的任务,由一个高推理力代理处理效果更好,而非让多个工作线程探索不同路径。

7. 任务分解才是难点

TOML 语法易学。判断并行有益处的场景,以及会产生脆弱、昂贵工作流的场景,这种判断力更难培养,也更有价值。

第 14 章 将通过考察多代理工作的成本维度来拓展这一基础:如何预测并控制编排器层与工作层的 API 支出。

练习

  1. 定义一个测试运行器代理。 在 .codex/agents/test-runner.toml 编写自定义代理定义,运行测试文件并返回 JSON 报告。该代理应使用 gpt-5.4-mini、read-only 沙箱,且指令需告知其针对指令中提供的路径运行 pytest --json-report。手动针对三个测试文件调用它,确认结构化输出在各次运行中保持一致。

  2. 将顺序工作流转为扇出模式。 获取一个逐个处理 Markdown 文件列表的顺序父代理工作流,将其转换为 spawn_agents_on_csv 扇出模式。使用 output_schema 固定工作器输出形状。用二十个文件列表分别测量转换前后的墙钟时间。并行版本在文件数达到多少时变得更快?

  3. 审计任务的并行性。 结合你实际工作的代码库或项目,对照表 11-1 中的任务类型进行审查。找出当前串行执行但可从并行化中受益的两个任务。在编写任何代码前,为每个任务编写简要分解计划(输入、代理定义、聚合策略、故障处理)。

第十二章:多代理编排模式

第 11 章 介绍了子代理原语:内置代理、自定义 TOML 定义、spawn_agents_on_csv 扇出,以及 [agents] 会话限制。掌握这些后,你可以生成一个专业化线程,或让上百个工作器跨 CSV 并行。但这些材料尚未说明:如何将这些原语组合成多步流水线,如何从外部驱动 Codex CLI 会话,或如何交给代理一个持久目标,让它跨多轮对话朝完成方向推进。

本章讲的是组合。内容涵盖:生产级 Codex CLI 工作中反复出现的四种编排模式(顺序门控链、基于波次的混合模式、跨模型审查循环、迭代修复循环)、让开发者化身轻量级监督者的会话分支命令(/fork、/side)、将任意模式包裹在持久目标中的 /goal 命令、让外部脚本通过结构化阶段驱动 Codex 会话的 codex remote-control 接口。同时也会标记那些悄悄破坏多步结果的反模式。

学习目标

你将能够:

  1. 根据任务的依赖结构和质量要求,在顺序门控链、基于波次的混合模式、跨模型审查循环、迭代修复循环中做出选择。

  2. 用父代理编排子代理,并在每次交接处用 output_schema 验证,将门控流水线表达出来。

  3. 使用 codex exec -m <model> -s read-only --output-schema 构建跨模型审查循环。

  4. 通过 JSON 串联三次独立的 codex exec 调用,实现迭代修复循环。

  5. 使用 /fork、/side 和 /btw 作为监督者协调并行工作流。

  6. 将任意模式置于带有令牌预算和基于审计的完成检查的 /goal 目标中。

  7. 识别多步编排特有的反模式:无门控交接、无聚合的扇出、无界迭代、拥有写权限的审查者。

组合,而非新原语

第 11 章 以三层全景收尾:进程内子代理、跨工作树的本地 Shell 扇出(第 13 章)、Codex 云任务。本章不增加第四层。它增加的是连接组织:流水线各阶段如何传递结果、审查者如何与构建者分歧并让构建者响应、长期目标如何在上下文压缩中存活、外部脚本如何操控运行中会话。

原语就是你已知的那些。本章讲的是它们如何组装在一起。

起点:Ralph 循环

在命名本章教授的四种模式前,有必要先认识它们生长所源的社区模式。Geoffrey Huntley 于 2025 年 7 月命名了 Ralph 循环,源自《辛普森一家》中的 Ralph Wiggum:靠固执的简单行为产出意外结果的守护圣徒。^(¹⁰⁰) 该模式形态与其名相称。将代理调用包裹在 while 循环中,反复用同一提示针对同一任务运行,直到某事物停止它。移植到 Codex CLI 的 Huntley 原版单行命令:

while :; do cat PROMPT.md | codex exec - ; done

Huntley 的定位是:将 LLM 视为单块单任务处理器,做“每轮一事”。操作员提供架构护栏(规范、计划文件、标准库、PROMPT.md),让模型在其中自行权衡优先级。

用 Huntley 的话说,该模式是“在非确定性世界中确定性地坏”:失效模式可预测,故可调优提示对抗它们。成功建立在“信仰与对最终一致性的信念”上。

它管用,且对小型、边界明确的收敛任务真有用。大多数开发者首次需要代理突破单轮坚持时,都会伸手抓这种东西。

但随着任务变难,它有三种会复合的失效模式。成功检查与代理工作混为一谈:代理决定什么叫“完成”,循环盲目信任。无审计轨迹;你只见最终差分,不见代理为何走每一步。单一模型包办所有阶段,导致你付旗舰费率去 grep 那些廉价模型能抓的问题,再付廉价费率去应用那些需要深度推理的修复。

本章的四种模式(顺序门控链、基于波次的混合模式、跨模型审查循环、迭代修复循环)是为解决上述一项或多项失效而设计的工程化替代方案。每种都比裸循环更结构化,并用可审计性、可恢复性或成本优化的模型路由为结构买单。Ralph 循环仍是结构会成为累赘的微小任务的正确工具;其余场景皆落入四模式之一。

模式 1:顺序门控链

最简单拓扑是链。阶段 A 产出的输出成为阶段 B 的输入;阶段 B 喂给阶段 C。每阶段有一个上游生产者和一个下游消费者。当最后阶段交付最终制品时,链终止。

朴素版本是危险的。子代理以微妙方式失败:摘要器遇二进制源返回空串;分类器返回约定词表外的标签;翻译器无法识别输入时原样返回源语言。若将这些未经检查地向下传递,故障会传播至最终制品出错,且除非重跑整条链,几乎无法追溯错误。

模式 1:顺序门控链

门控 变体会在每次交接处插入一个验证步骤。该门控不是另一次 LLM 调用。它是在上游输出流向下游之前进行的确定性检查。在 Codex CLI 中,你能编写的最廉价的门控是上游阶段上的 output_schema(第 11 章):如果工作器的结构化输出无法通过验证,运行时会为该行记录一次失败,而不是产出一个格式错误的制品。

请看一个实战示例。假设你想将原始发布分支的提交转化为可发布的变更日志。该管道包含三个阶段:

  1. 分类。 一个扇出操作(详见 第 11 章)按章节和编辑类型对分支上的每个提交进行分类,写入 commit-review.csv。

  2. 分组。 父代理读取结果 CSV,按章节对成功的行进行分区,并生成一个 chapter-summaries.csv,每章一行。

  3. 撰写。 一个 worker 代理接收分组后的摘要,撰写 Markdown 格式的变更日志草稿。

阶段 1 和阶段 2 之间的门控是扇出操作上声明的 output_schema:任何 result_json 不符合规范的行都会被记录为 failed,而不是被传递下去。阶段 2 和阶段 3 之间的门控是父代理用自然语言执行的结构性检查:“在将摘要传递给撰写器之前,确认每个章节至少有一个要点”。

将管道以编号计划的形式表达给父代理。只要每个阶段的预期输出形状明确,Codex CLI 的父代理就能很好地处理散文中的条件逻辑:

针对当前发布分支运行一个三阶段管道。
阶段 1:在 commits.csv 上生成一个 spawn_agents_on_csv 扇出,配置 output_schema (sha, chapter, edit_kind, note),写入 commit-review.csv。并发数 3,超时 120 秒。
阶段 2:读取 commit-review.csv,丢弃 status 不为 completed 的行,按 chapter 分组,写入 chapter-summaries.csv(每章一行,包含一个 bullets 列,汇总该章每个提交的 note)。如果分组后任何预期章节的行数为零,则使管道失败。
阶段 3:针对 chapter-summaries.csv 生成一个 worker,撰写 Markdown 变更日志草稿。保存至 CHANGELOG-draft.md。

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/agentic-coding-with-openai-codex-cli/img/B40007_12_01.png

图 12.1 - 顺序门控链管道

流程图展示 commits.csv 经阶段 1(分类)流经 Schema 门控,然后进入阶段 2(分组)流经结构门控,最后进入阶段 3(撰写)输出 CHANGELOG-draft.md。Schema 校验失败的行被丢弃;结构门控失败将终止管道。

该链路具有可组合性、可审计性(每个阶段的中间文件都落盘)和可恢复性:如果阶段 3 失败,你无需重新运行阶段 1 和 2。

当阶段 N 确实无法在阶段 N-1 产出可验证制品之前启动时,顺序链是正确的选择。权衡在于延迟:墙钟时间是各阶段延迟之和,链路内部无并行性(尽管某个阶段本身可能是一个扇出,如此处的阶段 1)。

最廉价的门控是你能表达的最严格门控。在产出 JSON 的阶段上设置 output_schema 只需在工具调用中写一行,即可免费捕获所有形状错误。当数据流经你无法控制的阶段时,自然语言断言(“确认每个章节至少有一个要点”)是你的后备方案。

模式 2:基于波次的混合模式

许多现实任务既非纯顺序也非纯并行。它们形成一个依赖图:部分工作依赖于先前的工作,但在每个层级内部,工作是独立的。基于波次的混合模式将工作分组为波次。在一个波次内,每个任务均可并行化。波次之间,后一波次依赖于前一波次完成。

一个经典示例是单体仓库重构。在函数签名变更完成前,你无法更新调用方;但一旦签名确定,你就可以并行更新所有调用方。波次 1 修改 lib/core.py 中的签名;波次 2 扇出至 api/、cli/ 和 tests/。

使用一个 CSV 驱动波次 2,该 CSV 枚举每个调用方目录以及证明重写后其调用点仍能编译通过的测试命令:

directory,test_command
api,pytest api/tests -q
cli,pytest cli/tests -q
tests,pytest tests -q

将每个波次表达为独立的扇出。父代理运行波次 1 至完成,验证结果,然后携带波次 1 的输出作为输入启动波次 2。这就是模式 1 中的门控应用于波次边界:

波次 1:生成一个 worker,根据 spec.md 更新 lib/core.py 中 process_batch 的签名。将新签名行保存至 signature.txt。如果 signature.txt 包含除单个函数定义外的任何内容,则停止并报告。
波次 2:仅当波次 1 产出有效的 signature.txt 时,在 caller-locations.csv 上运行 spawn_agents_on_csv。对于每一行,更新 {directory} 下 process_batch 的每个调用点以匹配 signature.txt 中的新签名,然后运行 {test_command} 并捕获退出码。使用要求 directory、callsites_updated(整数)、tests_passed(布尔值)和 notes 的 output_schema。写入 caller-updates.csv。并发上限三个 worker,每个超时 300 秒。

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/agentic-coding-with-openai-codex-cli/img/B40007_12_02.jpg

图 12.2 - 单体仓库签名重构的波次计划

流程图展示波次 1 产出 signature.txt,通过一个验证其包含单个函数定义的门控,随后三个并行的波次 2 worker 分别更新 api/、cli/ 和 tests/ 中的调用方,全部写入 caller-updates.csv。

波次边界是显式的质量门控。如果波次 N 中的任意任务失败,编排器会在启动波次 N+1 前停止;否则波次 N+1 将带着过时数据启动,导致错误级联。

波次模式是四种模式中能力最强、对依赖最敏感的一种。其主要失效模式是依赖配置错误:你声明 B 依赖于 A,却忘记 C 也需要 A,导致 C 在错误的波次中运行并使用了过时输入。在用散文编码计划前,先绘制依赖图。

第 11 章中的可交换性测试可递归应用:如果波次内部的任务按不同顺序运行结果会改变吗?若是,你存在一个隐藏依赖,应使其显式化,通常方法是将该波次拆分为两个。

模式 3:跨模型审查循环

模式 1 和 2 假设每个阶段使用相同模型。实践中最显著的质量提升来自于将不同模型路由到构建者和审查者角色。模型审查自己的输出会遭受谄媚偏差:产生 Bug 的同一推理捷径会在审查时为其辩护。坐在批评席位上结构不同的模型拥有不同的训练数据、不同的盲区和独立的判断。当两者都标记同一问题时,置信度很高。当只有一方标记时,分歧本身就是信号。

Codex CLI 无需插件即提供构建此循环所需的一切。codex exec -m <model> -s read-only --output-schema <reviewer-schema> 对任意你指向的制品运行一次性的、沙箱化的、结构化输出的审查。编排器(父代理或 Shell 脚本)将审查者的发现反馈给构建者,限制迭代次数,并退出。

在 Shell 层面表达的最小审查循环:

#!/usr/bin/env bash
#### cross-review.sh: bounded cross-model review loop
MAX_ROUNDS=3
BUILDER_MODEL=gpt-5.5
REVIEWER_MODEL=gpt-5.4
ARTEFACT=$1
for round in $(seq 1 "$MAX_ROUNDS"); do
  codex exec -m "$REVIEWER_MODEL" -s read-only \
    --output-schema reviewer-schema.json \
    -o "review-$round.json" \
    "Review $ARTEFACT. Return findings per schema."
  verdict=$(jq -r '.verdict' "review-$round.json")
  if [ "$verdict" = "PASS" ]; then
    echo "Converged after $round round(s)"
    exit 0
  fi
  codex exec -m "$BUILDER_MODEL" -s workspace-write \
    "Address findings in review-$round.json and update $ARTEFACT."
done
echo "Failed to converge after $MAX_ROUNDS rounds. Escalate to human review."
exit 1

编排脚本保存迭代状态,因为 Codex CLI 不具备此功能。

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/agentic-coding-with-openai-codex-cli/img/B40007_12_03.png

Figure 12.3 - Cross-model review loop

流程图展示了一个以只读模式运行并发出裁决的审查器。PASS 以“已收敛”退出;CONCERNS 检查轮次计数器,如果达到上限则升级至人工,否则将发现结果反馈给构建器,后者更新制品并循环回审查器。

重入性是陷阱。 一个其自身完成会触发另一次审查的审查器将永远不会终止。请在编排器中保存迭代状态(计数器、--max-rounds 标志、 state.json 文件)。Codex CLI 并未为任意编排器脚本内置重入保护;你需要自己编写防护逻辑。

审查器的 output_schema 是让循环收敛的关键。返回散文(“看起来没问题,但也许考虑一下……”)的审查器给构建器没有任何可执行的依据。返回按顺序排列的类型化发现(“严重级别:高,文件:src/auth.py,行:47,问题:令牌在哈希前被记录”)的审查器给构建器一份优先级工作清单。每一轮针对特定发现进行修复,而不是在未定义的标准中摸索前行。

适用场景: 高风险输出,此时独立判断比吞吐量更重要。安全敏感代码、公共 API 变更、数据库迁移,任何已交付 Bug 的成本远高于额外五分钟 Agent 时间的场景。对于拥有廉价确定性验证的输出(静态检查、类型检查),请跳过此模式;这些检查已提供独立判断。

模式 4:迭代修复循环

迭代修复循环是对本章开头提到的 Ralph 循环的直接工程化回应。它针对同一个问题(制品需要在可执行检查下收敛至正确性),并一次性解决了 Ralph 循环的三种失败模式:成功检查与工作分离、每一步均可审计、每个阶段均可运行在最适配的模型上。

该模式将单一的 Ralph 提示词拆分为三个阶段(审查、修复、验证),作为独立的 codex exec 调用运行,并通过结构化输出在它们之间传递数据。OpenAI Cookbook 通过输出 Schema 和收敛检测将该模式形式化。^(¹⁰¹)

  1. 审查。 在不编辑或执行任何操作的情况下检查当前制品。返回结构化发现:包含严重级别、描述和建议修复方向的问题数组。保持审查为只读模式,维护了使循环可审计的“判断与行动分离”原则。

  2. 修复。 接收发现及任何先前的验证反馈。对制品的副本进行聚焦、有界的编辑,以便原始文件可被对比和回滚。

  3. 验证。 执行真实检查(测试套件、静态检查器、类型检查器、契约验证器)。失败结果变为下一轮修复的结构化数据,而非主观意见。

连接各阶段的脚本很短。每个阶段仅需一次带有自身 --output-schema 的 codex exec 调用,写入已知的 JSON 文件;下一阶段读取它。

#!/usr/bin/env bash
#### repair-loop.sh: Review-Repair-Validate against a single artefact
MAX_ITERATIONS=4
ARTEFACT=$1
WORK_COPY="$ARTEFACT.repair"
cp "$ARTEFACT" "$WORK_COPY"
prev_repair_hash=""
for i in $(seq 1 "$MAX_ITERATIONS"); do
  # Phase 1: Review (read-only)
  codex exec -s read-only \
    --output-schema review-schema.json \
    -o "review-$i.json" \
    "Review $WORK_COPY and the most recent validate-*.json. Return findings."
  # Stop condition 4: human review escalation
  if jq -e '.findings[] | select(.requires_human == true)' "review-$i.json" >/dev/null; then
    echo "Iteration $i flagged findings for human review. Stopping."
    exit 2
  fi
  # Phase 2: Repair (writes to the work copy only)
  codex exec -s workspace-write \
    --output-schema repair-schema.json \
    -o "repair-$i.json" \
    "Apply review-$i.json findings to $WORK_COPY. Touch no other files."
  # Stop condition 3: no improvement between iterations
  repair_hash=$(sha256sum "$WORK_COPY" | cut -d' ' -f1)
  if [ "$repair_hash" = "$prev_repair_hash" ]; then
    echo "Iteration $i made no change. Stopping."
    exit 3
  fi
  prev_repair_hash=$repair_hash
  # Phase 3: Validate (executes real checks)
  codex exec -s workspace-write \
    --output-schema validate-schema.json \
    -o "validate-$i.json" \
    "Run the project's test, lint, and type-check commands against $WORK_COPY. Report results."
  # Stop condition 1: validation passes
  if [ "$(jq -r '.status' "validate-$i.json")" = "pass" ]; then
    mv "$WORK_COPY" "$ARTEFACT"
    echo "Converged after $i iteration(s)"
    exit 0
  fi
done
#### Stop condition 2: max iterations reached
echo "Reached $MAX_ITERATIONS iterations without convergence. Work copy left at $WORK_COPY."
exit 1

与跨模型审查循环一样,编排器脚本保存迭代状态。工作副本独立于原始文件;仅验证通过的路径会覆盖原始制品。

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/agentic-coding-with-openai-codex-cli/img/B40007_12_04.png

Figure 12.4 - Iterative repair loop with three phases

流程图展示了审查、修复和验证的一次迭代。审查后,requires_human 发现会停止循环。修复后,工作副本哈希值未变会停止循环。验证后,pass 状态表示收敛并覆盖原始文件;否则由迭代计数器决定是循环回审查还是在达到最大迭代次数时停止。

生产级循环需要四个停止条件:

  1. 验证通过: 成功路径。

  2. 达到最大迭代次数: 安全上限,通常为三到五次。

  3. 未检测到改进: 剩余差异与上一轮迭代完全相同。

  4. 标记需人工审查的问题: 部分发现存在歧义,不应自动修复。

收敛检查不可妥协。没有它,你就会在 Agent 空转上浪费 Token。Cookbook 食谱显示,大多数制品在两到三次迭代内收敛,三次之后边际效益递减。

将 Ralph 循环拆分为阶段带来三大工程收益:

  1. 可审计性: 审查员发现的问题与修复员所做的更改可分别检查。

  2. 阶段专用模型路由: 验证用廉价模型,修复用旗舰模型。

  3. 可组合的验证: 无需修改修复逻辑即可替换验证阶段的检查器。

对于简单的收敛任务,裸 Ralph 循环仍然适用,前提是成功检查真正是确定性的且中间步骤无需关注,例如针对小型孤立 Bug 的“运行测试直到通过”。一旦涉及审计追踪、成本优化或独立验证,请采用结构化修复循环。

四种模式的选择

三个问题可快速缩小选择范围:工作单元是否独立?部分单元是否依赖其他单元?任务是否需要结构上独立的审查?

Table 12-1. Orchestration pattern comparison

| Dimension | Sequential Gated Chain | Wave-Based Hybrid | Cross-Model Review Loop | Iterative Repair Loop |

| 标准用例 | 多阶段文档流水线 | 具有依赖层级的单体仓库重构 | 安全敏感型审查 | 在测试下收敛的 API 迁移 |

| :--- | :--- | :--- | :--- | :--- |

| 构建者/审查者分离 | 同一模型端到端 | 同一模型端到端 | 不同模型 | 同一模型,分离阶段 |

| 终止条件 | 最后阶段产出最终制品 | 所有波次完成 | PASS 裁决或达轮次上限 | 验证通过或满足停止条件 |

| 故障模式 | 门控失败中断链路 | 波次失败中断后续波次 | 轮次上限内未收敛 | 迭代间无改进 |

| 延迟 | 各阶段延迟之和 | 各波次延迟之和 | 每轮 +30 至 90 秒 | 每阶段 +60 至 180 秒,乘以迭代次数 |

这些模式是可组合的,而非互斥的。基于波次的计划可在任意波次任务内使用门控链。门控链可在任何需要独立判断的阶段调用跨模型审查循环。迭代修复循环本身就是一种特殊的链,其阶段恰好是审查、修复、验证。

如果你的任务并不明显匹配任何单一模式,请列出约束条件(独立与依赖单元、单模型与多模型、确定性与 LLM 验证),通常模式自然就会浮现。

对话分支:无需框架的督导模式

上述模式描述了父代理或外部脚本所编排的内容。Codex CLI 还通过三条斜杠命令 /fork、/side 和 /btw(/side 的别名)支持在单个会话内进行更轻量级的协调。

/fork

/fork 将当前对话分叉为一个新线程。用它来分叉活动会话并探索另一种方法,而不会丢失当前的记录。你可以通过线程选择器切换回原始线程;分叉会作为独立线程持久化存在。

/fork 是督导模式的斜杠命令等价物。开发者(或作为督导的父代理)持有高层计划,并将有界工作单元分叉到各自的线程中,每个线程凭借自己的上下文追踪一个子任务。分叉并行进行;督导审查并合并其输出。

/side 和 /btw

/side(/btw 为别名)启动一个临时的侧向对话:一个不会干扰主线程记录的聚焦后续跟进。用它来提出快速澄清问题、验证假设或核对事实,而较长时间运行的任务继续在主线程中进行。侧向对话在另一个侧向对话内不可用,在审查模式下也不可用。

这三条命令按从重到轻形成一个光谱。/fork 创建一个拥有自身追踪目标的并行线程。/side 打开一个不影响主线程即可关闭的旁白。当你需要并发工作时,请使用 /fork;当你需要快速查阅时,请使用 /side。

具体实现细节(/fork 是否克隆完整历史、 /side 是否仅驻留内存、两者如何与 Token 预算交互)仍在演进中。经过验证的接口是命令集、别名以及审查模式下不可用这一约束。请将超出此范围的任何内容视为值得在依赖脚本前对照实时 REPL 进行核实的事项。

分支何时足够,何时不足

当并行工作流数量较少(六个以下)、督导是人类或单个父代理,且子任务足够异质以至于共享提示模板无法提供帮助时,对话分支就足够了。开发者提供分解与集成;代理提供吞吐量。

当工作单元数量众多、同质,或拥有自动波次调度能比人工监督更好地表达的依赖图时,请采用编程式编排(spawn_agents_on_csv、上述四种模式)。

二者可组合。使用 /fork 的督导可将某个分叉委托给 CSV 扇出,以处理大型任务中的批量处理部分。

/goal:持久化目标

上述每种模式都作用于具有明确开始和结束的离散任务。/goal 引入了一种不同的抽象:一种跨轮次、会话、终端崩溃和机器重启持久存在的目标,它朝着完成方向工作,直到代理宣布目标达成、Token 预算耗尽,或外部阻碍停止进展。

目标模式在 Codex CLI 中默认启用。^¹⁰² 可通过在 config.toml 中设置 [features] goals = false 来禁用(例如在 CI 中)。

五条命令

/goal "<objective>"  # 设定新目标;Codex 立即开始追踪
/goal                # 查看当前目标摘要与状态
/goal pause          # 暂停;保留审计上下文
/goal resume         # 恢复;从中断处继续
/goal clear          # 清除目标;恢复单轮行为

目标内容必须非空且最多 4,000 字符。没有 /goal status 子命令(单独输入 /goal 即显示状态),也没有 /goal budget 子命令(预算在 config.toml 中配置)。

生命周期才是关键

目标会经历五个离散状态。转换由模型发起的结构化 update_goal 工具调用驱动,而非自由文本输出。

  1. pursuing(追踪中):主动工作,规划下一步

  2. paused(已暂停):由用户操作挂起;等待 /goal resume

  3. achieved(已达成):完成审计通过;工作停止

  4. unmet(未达成):外部依赖阻碍完成;代理报告其无法解决的事项

  5. budget_limited(预算受限):Token 预算耗尽;代理总结进度与剩余工作

achieved、unmet 和 budget_limited 之间的区别即是架构红利。目标可以有用地失败:它保留进度,解释剩余事项,并交给你一个干净的继续切入点。对话式提示循环会将这三者坍缩为一个模糊的“完成”。

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/agentic-coding-with-openai-codex-cli/img/B40007_12_05.jpg

图 12.5 - /goal 生命周期

/goal 生命周期的状态图。设定目标进入 pursuing 状态,可移至 paused(经 /goal pause)并回到 pursuing(经 /goal resume)。从 pursuing 出发,目标到达三个终态之一:完成审计通过时为 achieved,外部依赖阻碍完成时为 unmet,Token 预算耗尽时为 budget_limited。

完成审计

目标模式中最具深远影响的设计决策在于:继续提示词禁止代理接受代理信号作为完成证据。仅测试通过并不能证明目标已达成,如果目标包含测试覆盖率之外的需求。在使用 complete 状态调用 update_goal 之前,代理必须将目标转化为具体交付物,构建一份将每个需求映射到证据的清单,检查实际产出(磁盘文件、命令输出、测试结果),并全面核实覆盖率。

这就是用 Shell 脚本包装的 Ralph 循环与运行时管理的目标循环的区别。Ralph 循环信任其自身的“完成”信号。目标模式不信任。

Token 预算

Token 预算防止单个目标消耗整个周配额。在 ~/.codex/config.toml 中配置默认值:

[goals]
default_token_budget = 500000  # 每个目标 50 万 Token

当预算近乎耗尽时,运行时会注入一个预算限制提示词,指示代理总结进度而非开展新工作。预算耗尽触发 budget_limited,而非 achieved。

对于范围明确的目标,起步建议 200,000 至 500,000 Token。复杂迁移可能需要更多。监控前几次运行并调整。

/goal 相对于四种模式的定位

目标模式

目标模式是包裹上述四种编排模式任意一种的外层循环。它不会取代它们。在一个目标内部,你仍然可以运行门控链、基于波次的计划、跨模型评审循环或迭代修复循环。目标模式增加的内容是:

  1. 一个持久化的目标,智能体会在每个轮次边界重新检查

  2. 一个令牌预算,限制在人工评审前可以进行多少工作

  3. 一个审计步骤,在接受任何完成声明之前执行

用 Ralph 循环的术语来说,目标模式是 Codex 内置的长期运行智能体循环版本。当一个线程可以完成一项规格明确的工作时,请使用 /goal。当你需要多个智能体、每次迭代使用新会话、容器隔离、每任务一次提交工作流,或在每一步进行跨模型评审时,请求助外部编排工具。

一个薄弱的目标会无声地消耗预算。成功与否的单一最大决定因素是目标陈述。一个好的目标陈述会明确指出:预期结果、验证面(证明正确性的单个命令)、写入范围、停止条件,以及应触发智能体暂停并提问的条件。如果你无法回答这五个问题,说明目标尚未就绪。

一个完整示例

对比完成同一项工作的两种尝试:将代码库中每个 React 类组件转换为带有 Hook 的函数组件。

薄弱版本
/goal "Modernise our React components"

它没有可衡量的结果、没有验证命令、没有边界、没有停止条件。智能体可能会以不同的风格重写组件、更改 API 契约,或者在表面通过后就宣布成功。更糟的是,它会一直运行直到令牌预算耗尽,因为目标中没有任何内容告诉它何时停止。

强健版本

强健版本在一个陈述中应用了五问评估标准:

/goal "Convert all 23 React class components in src/components/
to functional components with hooks. Preserve every existing test
in src/__tests__/components/. Run 'npm test -- --watch=false' after
each conversion and continue only if it exits zero. Touch no files
outside src/components/. Track converted_count and remaining_count
on every turn. Stop when remaining_count reaches zero, or when any
component would require changing a public prop type, leave that
component untouched and report it as requiring manual review."

对应评估标准:

  1. 结果: 23 个组件已转换,所有测试仍然通过。

  2. 验证命令: 每次转换后运行 npm test -- --watch=false。

  3. 写入范围: 仅限 src/components/。

  4. 停止条件: remaining_count 归零。

  5. 暂停触发器: 需要更改公共 prop 类型。

相同的结构也适用于其他长周期工作,在这些工作中,“更好”必须转化为可观察的证据:带有故障安全停止条件的覆盖率目标、连续 N 次尝试无改进后触发熔断机制的基准阈值,或对照 npm run spec:validate 套件验证每个需求的规格到实现的通过过程。^(¹⁰³)

目标需要一个位于智能体之外的验证面。一个通过重读自己的输出来自我验证的目标,不过是披着运行时外衣的 Ralph 循环。如果你无法指出一个命令,其退出代码或结构化输出能回答“完成了吗?”,那么该任务还不适合设定目标。

外部编排:codex remote-control

迄今为止的模式假设编排发生在 Codex CLI 内部,或通过独立的 codex exec 调用进行。对于外部进程需要驱动一个正在运行的 Codex 会话经历多个阶段的情况,存在另一种接口:codex remote-control。

该命令(撰写时标记为实验性)确保本地应用服务器守护进程正在运行,并启用了远程控制支持。守护进程 (codex app-server) 通过 stdio、WebSocket 或 Unix 套接字接受连接,并可选择通过能力令牌或签名持有者令牌进行 WebSocket 认证。守护进程运行后,交互命令 codex、codex resume 和 codex fork 可以通过 --remote ws://host:port(也支持 wss://、unix:// 或 unix://PATH)指向远程应用服务器端点。其他子命令拒绝远程模式。

这介于第 1 层(第 11 章中的进程内子智能体)和第 2 层(独立 codex exec 进程的 Shell 扇出)之间。第 1 层将所有内容保留在一个父智能体内;第 2 层为每个工作单元启动独立进程;而 remote-control 允许外部编排器驱动一个持久的 Codex 会话经历多个共享上下文的阶段。编排器可以是 CI 流水线、Shell 脚本、Python 编排工具,或另一个智能体,且本身不是 Codex CLI 智能体。

最自然的契合点是 CI/CD 集成,流水线需要在特定步骤进行智能体推理,而无需为每次运行都支付全新会话的上下文构建成本。第 11 章介绍了在 CI 流水线中用于无状态单次任务的 codex exec;remote-control 是其在多步骤工作流中跨步骤上下文累积至关重要时的补充。

remote-control 命令和应用服务器协议接口仍在演进中。请在 CI 中固定 Codex CLI 版本,保持自定义客户端精简,并优先使用文档化的 --remote 传输,而非逆向工程线路协议。

调试编排故障

编排故障具备单智能体故障所不具备的一个特性:你观察到的错误通常远离其成因。一个在波次 1 中静默返回空字符串的工作智能体,直到波次 3 尝试解析其输出时才可能产生可见错误。一个在轮次 1 中批准了有缺陷产物的评审智能体,会导致构建智能体在轮次 2 中修复错误的东西。

有三种技术可以驾驭此类问题。

坚持在每次交接时设置关卡

最廉价的关卡是对生成 JSON 的阶段施加 output_schema。第二廉价的是用文字表达的父级检查(“确认文件存在且非空”)。两者都比诊断由上游静默导致的下游故障便宜得多。

将中间状态检查点保存到磁盘

每个阶段都写入具名 JSON 或 CSV 文件的流水线是可恢复的。阶段间通过智能体散文传递结果的流水线则不可恢复。模式 4 中的修复循环对此具体化了:每个阶段写入自己的文件,下一阶段读取它。模式 2 中的波次执行器也应对 caller-locations.csv 等产物做同样的处理。如果第 N 阶段失败,你只需重新运行第 N 阶段,而非整个流水线。

为高风险阶段保留语义验证

当工作智能体返回格式良好但语义错误的响应时(例如,无论工单内容如何,都返回 {"category": "billing", "suggested_priority": "P1"}),结构化关卡将无法捕捉。一个抽取 10% 输出进行采样并标记分歧的轻量级评判智能体是最可靠的防御。将其作为一个独立阶段运行,记录分歧,并每周复查。

静默语义失败是最难捕捉的一类编排 Bug。在开发期间对工作智能体输出进行采样并目测检查,然后为那些“犯错成本高于再增加一次智能体调用成本”的情况,在运行时添加一个执行相同操作的评判阶段。

反模式

有五种反模式频繁出现,值得专门论述。

无关卡交接

阶段间的每一次交接都是一个信任边界。上游智能体的输出可能是畸形的、被截断的,或是语义错误的。请始终插入一个关卡,哪怕只是检查输出非空和 JSON 有效。

无聚合计划的扇出

在编写第一行扇出代码前,先写好聚合逻辑。明确你需要的是合并列表、汇总产物、共识投票,还是应用于共享文件的差异。如果你无法用一句话描述聚合方式,说明任务分解有误;请在并行化前重新审视。

无界迭代。 没有终止条件的循环会以大致相同的程度耗尽您的预算和耐心。每个审查循环、修复循环、重试循环和目标都需要一个显式的上限:最大轮数、最大 Token 数、最大墙钟时间。运行时不会替您停止。

拥有写入权限的审查者。 能够修改代码库的审查者可以掩盖其自身的发现,或引入您和构建者均未授权的更改。Codex CLI 的 -s read-only 沙箱是强制手段(在 Linux 上通过 bubblewrap 实现内核级隔离,在 macOS 上通过 Seatbelt 实现,详见第 16 章)。请将其用于任何扮演审查者角色的智能体。

并行 Worker 间的共享可变状态。 如果将共享可变对象(每个 Worker 追加写入的文件、每个 Worker 递增的计数器、每个 Worker 更新的数据库行)传入扇出阶段,就会产生竞态条件。请使用第 11 章中的“收集-再归约”模型:Worker 写入隔离的输出(或导出 CSV 的行),聚合在所有 Worker 完成后一次性进行。

省略 max_concurrency 是本章材料中最常见的单一操作错误。第 11 章已涵盖此内容;此处重申是因为这四种模式在其阶段内部隐藏了并发性。请始终在任何扇出阶段(包括位于波次或链内部的扇出)上设置显式的 max_concurrency。

总结

  1. 四种编排模式。 顺序门控链(每个交接点都有验证门控的线性流水线)、基于波次的混合模式(将依赖任务分组为可并行的波次,波次边界作为质量门控)、跨模型审查循环(结构不同的模型担任评论员,将结构化发现反馈给构建者)、迭代修复循环(审查、修复和验证作为通过机器可读 JSON 连接的不同 codex exec 阶段)。

  2. 模式可组合。 一个波次可以包含一条链。一条链可以在任意阶段调用审查循环。修复循环本身就是一条链,其阶段恰好是审查、修复、验证。命名约束条件(独立 vs 依赖工作、单模型 vs 多模型、确定性 vs LLM 验证),模式通常自然确定。

  3. 对话分支是更轻量的协调面。 /fork 创建并行线程;/side 和 /btw 开启临时旁白。当监督者是人类或单一父智能体且子任务异构时,使用分支。

  4. 大规模时采用程序化编排。 当工作单元众多、同质,或具有自动调度能比人工监督更好表达的依赖图时使用。

  5. /goal 将任意模式包装为持久目标。 Token 预算加上基于审计的完成检查。目标可以通过 unmet 或 budget_limited 失败并提供有用信息,而不是模糊地坍缩为“完成”。目标模式默认开启;在受限的 CI 环境中可通过 [features] goals = false 禁用。

  6. codex remote-control 是外部编排接口。 CI 流水线、Shell 脚本或非智能体编排器通过应用服务器协议驱动有状态的 Codex CLI 会话跨越多个阶段。它位于 Tier 1(进程内子智能体)和 Tier 2(独立进程的 Shell 扇出)之间。该命令和协议接口仍在演进中;请在 CI 中固定 Codex 版本。

  7. 五种应避免的反模式。 无门控交接、无聚合计划的扇出、无界迭代、拥有写入权限的审查者、并行 Worker 间的共享可变状态。嵌套在四种模式内部时,每种都会静默放大。

  8. 纪律在于严谨,而非原语。 始终设门控、始终设边界、始终隔离、始终记录谁决定了什么。

第 14 章转向多步工作的成本维度:如何在编排器、子智能体和审查循环扩展时预测、监控和控制 API 支出。

练习

  1. 构建门控链。 接受模式 1 中描述的三阶段发布说明流水线。针对本仓库分支上的提交实现它。在扇出阶段使用 output_schema 强制执行 (sha, chapter, edit_kind, note) 结构。在第 2 阶段和第 3 阶段之间添加一个自然语言门控:如果任何章节的要点为零,则停止流水线。跨三次运行记录每个阶段的墙钟时间和失败率。

  2. 将重构表达为波次计划。 选取您自己代码库中一个在至少三个目录中拥有五个或以上调用者的函数。绘制依赖图。将重构编码为两个波次(签名变更,然后并行处理调用者)。决定波次间门控检查什么以及如何向父级表达该检查。运行它并记录波次边界在何处捕获了如果没有它会级联放大的问题。

  3. 实现跨模型审查循环。 按照模式 3 中的模板编写 cross-review.sh 脚本。使用结构化的 reviewer-schema.json,要求包含 verdict (PASS 或 CONCERNS)、findings(包含 severity、file、line、issue 的数组)和 summary。在一个故意植入 Bug 的 diff 上测试,并验证循环能在轮数上限内收敛。添加章节所述的重入保护,并验证格式错误的审查者输出不会导致静默循环。

  4. 针对陈旧依赖运行迭代修复循环。 选取一个包含单个失败测试的小项目。编写三个 codex exec 提示词(审查、修复、验证)和三个 --output-schema 文件。用 Shell 脚本将它们连接成一个具有四次迭代上限和无改进停止条件的循环。记录收敛(或失败)时的迭代次数。与不分阶段直接重试相同提示词的 Ralph-loop 版本对比;报告哪个收敛更快、哪个产出的结果可交付。

  5. 将模式包装进目标。 取上述四种模式中的任意一种,将其表达为 /goal 目标。在启动前对目标陈述应用五问评估法(结果、验证命令、写入范围、停止条件、暂停触发器)。设置保守的 Token 预算。运行目标并记录其终止时的生命周期状态(achieved、unmet 或 budget_limited)。调整目标陈述并重跑;记录变化。

  6. 从外部驱动会话。 使用 codex remote-control 启动本地应用服务器,并从第二个终端通过 codex --remote unix://... 连接。发送三个依次构建的提示词(每个均引用前一个提示词产生的产物)。对比总 Token 成本与三次独立的 codex exec 调用(从头重建上下文)的差异。报告差异以及何种条件下哪种方式更优。

第十三章:Worktree 与隔离执行

第 12 章回答了智能体按什么顺序运行的问题。本章解决下一个问题:它们在哪里运行。当两个智能体同时写入同一源码树时,它们不仅面临合并冲突,更有破坏工作区状态的风险。Git worktree(工作树)无需单独克隆或容器即可解决此问题。本章涵盖 worktree 是什么、为何适合智能体工作流、如何将其与 Codex CLI 结合,以及如何管理其完整生命周期。

学习目标

您将能够:

  1. 从命令行手动创建、使用和删除 worktree

  2. 设计一个多智能体工作流,为每个智能体分配一个 worktree 并将结果合并回 main

  3. 配置一个 CI 作业,自动预配和销毁 worktree

Git Worktree 简述

自 Git 2.5 起,git worktree 允许你将多个工作树关联到单个对象数据库。^(¹⁰⁴) 每个工作树拥有自己已检出的分支、索引和 HEAD 指针;所有工作树共享对象数据库、打包的引用和仓库配置。

git worktree add ../feature-auth -b feature/auth # 创建
git worktree list # 检查
git worktree remove ../feature-auth # 移除

Git 强制规定每个工作树只能对应一个分支;两个工作树无法同时检出同一个分支。^(¹⁰⁵) 正是这一约束,使得工作树成为并行代理安全操作的基础:在显式合并之前,每个代理正在进行的写入对其他所有代理都是不可见的。

工作树为何对代理工作流至关重要

设想两个 Codex CLI 代理在没有隔离的情况下,并发地在同一仓库上工作:

  1. 代理 A 检出 main 并开始编辑 src/auth/login.ts。

  2. 代理 B 检出 main(相同文件)并开始编辑 src/auth/session.ts。

  3. 代理 A 向 src/auth/utils.ts 写入一个辅助函数。

  4. 代理 B 在同一时刻读取 src/auth/utils.ts,却看到了一个写了一半的文件。

即使功能在逻辑上是独立的,共享的工作树也会让正在进行的写入立即可见。结果就是产生几乎无法复现的非确定性故障。

简单的补救方法是给每个代理分配各自的克隆副本:

git clone https://github.com/acme/app agent-clone-1
git clone https://github.com/acme/app agent-clone-2

这虽可行,但浪费带宽和时间。每次克隆都要下载完整的对象数据库(可能高达数 GB),且一个克隆中的提交在推送和拉取前,对其他克隆不可见。

工作树同时解决了这两个问题。对象数据库是共享的(无冗余下载),提交通过共享的引用存储立即可见,且每个工作树检出不同的分支,因此磁盘上的文件完全隔离。这种文件隔离正是并发代理安全的关键:每个代理只能看到自己的写入,因此共享工作树带来的非确定性故障便不复存在。

在典型的中型单体仓库中,git worktree add 可在一秒内完成;而 git clone 需要数十秒至数分钟。^(¹⁰⁶) 在 CI 规模下,每批合并队列可能需配置数十个工作树,这一差距会被放大。

如果你的仓库使用 Git LFS,工作树仍共享 LFS 指针文件。实际的 LFS 对象在检出时获取,并存储在 .git/lfs 中,该目录也是共享的。这意味着检出包含 LFS 文件分支的第二个工作树,仅需承担检出的开销,而无需重新下载。

一代理,一工作树:隔离原则

核心设计原则:一个代理在同一时刻仅在一个工作树中操作,且没有两个活跃代理共享同一个工作树。

这提供了三重保障:

  1. 文件级隔离。 在提交并显式合并或挑拣之前,一个代理正在进行的写入永远不会被其他代理看到。

  2. 索引隔离。 每个工作树拥有独立的暂存区,因此一个代理的 git add 和 git commit 不会干扰另一个代理的暂存状态。

  3. 分支锁定。 Git 拒绝在另一个工作树中已检出的分支上再次检出,从而防止代理意外覆盖彼此的分支指针。

无论你在本地、远程机器还是容器中运行代理,该原则均适用。只要每个工作树映射到不同的目录,文件系统层面的隔离就得以保证。

一个边缘情况是 Git 工作树之外的共享资源。SQLite 数据库、本地 .env 文件或共享缓存目录不在工作树隔离的覆盖范围内。对于这些资源,你需要代理级别的命名空间隔离(如每个代理使用独立的数据库文件)或协调机制(如文件锁)。工作树解决的是源代码隔离问题;它们不是通用的沙箱。

如果你的构建工具将生成的文件写入工作树外部的目录(例如,仓库根目录下的共享 node_modules 或全局 Gradle 缓存),这些写入就不受隔离保护。请将工具缓存固定在工作树内部的路径,或使用按代理 ID 命名的独立缓存目录,以避免交叉污染。

运行时隔离的缺口

这个边缘情况值得更深入的探讨,因为它是团队在扩展并行代理工作树时最常遇到的失败模式。Git 工作树隔离的是代码状态(文件、索引、分支指针),而非执行状态。当多个代理在并行工作树中运行应用栈时,会发生几种无声的冲突:^(¹⁰⁷)

  1. 端口冲突。 两个工作树都期望使用 localhost:3000。第二个代理的开发服务器无法绑定端口,或者更糟,连接到了错误的实例。

  2. 数据库碰撞。 共享的数据库实例同时接收来自多个分支的迁移,导致模式状态损坏。

  3. 环境变量泄漏。 通过共享的 Shell 或 dotenv 状态,本属于某一分支的凭证或配置泄漏到了另一分支。

  4. Docker 守护进程共享。 共享单个 Docker 守护进程的工作树之间,容器名称和端口发生冲突。

  5. 浏览器状态。 共享的会话、缓存的认证令牌和 Cookie 导致代理针对错误的应用实例进行测试。

代理会放大这些问题,因为它们运行速度远超人类(无暇停下来察觉端口冲突)、自动迭代(因错误状态导致错误复合),且无法可靠地将测试结果归因于特定分支。针对错误运行实例却通过的测试套件,会产生正确性的虚假证据。^(¹⁰⁸)

实用缓解措施:

| 资源 | 隔离方法 |

| :--- | :--- |

| 端口 | 为每个工作树分配唯一的端口范围(例如 BASE_PORT=$((3000 + TASK_INDEX * 100))) |

| 数据库 | 使用每个工作树独立的 SQLite 文件,或每个分支独立的 PostgreSQL 模式 |

| 环境变量 | 引用特定于工作树的 .env.$TASK_ID 文件 |

| Docker 容器 | 以任务 ID 为前缀命名容器:docker run --name ${TASK_ID}-api |

| 浏览器会话 | 为每个代理使用独立的浏览器配置文件或无痕模式 |

对于运行三个或更多并行代理的团队,手动管理端口和数据库命名空间容易出错。Coasts 就是一款解决这一缺口的工具:它提供带有规范端口镜像的动态端口分配、特定服务的重分配策略(数据库保持稳定,而前端在分支切换时热重载)、实例级数据隔离,以及一个本地可观测性 UI(Coast Guard),用于监控跨工作树的运行实例。^(¹⁰⁹)

运行时隔离属于操作层面的隔离,而非安全隔离。Coasts 及类似工具可防止工作树间的意外交叉污染;它们无法防御不可信代码、容器逃逸或提示词注入。团队仍需在运行时编排之外,部署分层安全控制(内核沙箱、网络策略)。

图形化界面从另一个角度解决了这一缺口。在撰写本文时,Codexia 内置了 Git 工作树管理功能,通过 GUI 配置隔离的工作目录,减少了大规模维护“一代理一工作树”原则所需的手动 Shell 操作。^(¹¹⁰)

CLI 工作树工作流

在命令行中,工作树管理结合了标准的 git worktree 命令与 Codex CLI 的 --cd 标志,以将代理导向隔离的目录。

在撰写本文时,Codex CLI 尚未提供专用的 --worktree 标志或 codex worktree 子命令组。下述工作流直接使用 git worktree,并通过 --cd 将生成的路径传递给 codex。

为任务创建工作树

供给工作树并在其中启动代理

TASK_ID=$(uuidgen | tr '[:upper:]' '[:lower:]' | cut -c1-8)
git worktree add .codex/worktrees/$TASK_ID -b codex/$TASK_ID
codex --cd .codex/worktrees/$TASK_ID \
 "add OAuth2 support to the login flow"

为重复使用,将其封装在 shell 函数中:

codex-worktree() {
 local TASK_ID=$(uuidgen | tr '[:upper:]' '[:lower:]' | cut -c1-8)
 git worktree add ".codex/worktrees/$TASK_ID" -b "codex/$TASK_ID"
 codex --cd ".codex/worktrees/$TASK_ID" "$@"
}

手动生命周期管理

##### 创建工作树但不启动代理
git worktree add .codex/worktrees/auth-oauth -b codex/auth-oauth
##### 列出活动工作树
git worktree list
##### 在现有工作树中启动代理
codex --cd .codex/worktrees/auth-oauth "implement PKCE flow"
##### 合并后移除工作树
git worktree remove .codex/worktrees/auth-oauth
git branch -d codex/auth-oauth

并行运行代理

包装函数自然地与 shell 作业控制配合,实现本地并行执行:

codex-worktree "refactor the database layer to use connection pooling" &
codex-worktree "add end-to-end tests for the checkout flow" &
codex-worktree "update all dependencies to their latest minor versions" &
wait

每次调用都会获得自己的分支和工作目录。当所有三个作业完成后,你将得到三个独立的分支,随时可供审查和合并。对于更结构化的编排,请使用第 12 章中的模式;底层原语始终相同:每个任务一个工作树,每个工作树一个代理。

检查工作树状态

##### 显示特定工作树的差异
git -C .codex/worktrees/auth-oauth diff
##### 在工作树中运行测试,无需切换主工作树
git -C .codex/worktrees/auth-oauth stash && \
 (cd .codex/worktrees/auth-oauth && npm test)
##### 在工作树中打开 shell(用于调试)
cd .codex/worktrees/auth-oauth && exec $SHELL

git -C <path> 在 CI 脚本中特别有用:它在工作树内运行 git 命令,而无需更改步骤的工作目录。

将代理工作合并回主分支

任务复杂度各异,因此代理完成时间不同。你需要一种合并策略,能处理已完成分支的异步到达。

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/agentic-coding-with-openai-codex-cli/img/B40007_13_01.png

图 13.1 - 选择合并策略

一个已批准任务分支的决策树。第一个分支取决于同时完成的代理数量:高并发路由到合并队列,而低并发询问项目是否强制要求线性历史。"是"通向变基合并,"否"通向顺序合并。所有三条路径最终都以分支合并到 main 结束。

顺序合并(简单情况)

对于低并发工作流,立即将每个已批准的任务分支合并到 main:

git checkout main
git merge --no-ff codex/task-a3f9c1
git worktree remove .codex/worktrees/task-a3f9c1
git branch -d codex/task-a3f9c1

--no-ff 标志将每个任务分支保留为一个独立的合并提交,保持历史可读。

变基合并(线性历史)

如果你的项目强制要求线性历史,在合并前将任务分支变基到当前的 main 上:

git -C .codex/worktrees/task-a3f9c1 rebase main
git checkout main
git merge --ff-only codex/task-a3f9c1

变基可能会产生冲突。Codex 可以提供帮助:codex --cd .codex/worktrees/task-a3f9c1 "resolve any rebase conflicts against main" 调用代理来解释并解决它们,这对于语义冲突特别有用,即两个代理出于不同原因修改了同一个函数。

合并队列(高并发情况)

当许多代理同时完成时,合并队列可防止因分支针对陈旧的 main 进行测试而导致集成失败。GitHub 的合并队列(通过仓库设置启用)会自动为通过 CI 的分支处理此问题。(¹¹¹) 使用标准工具推送并打开 PR:

git -C .codex/worktrees/auth-oauth push origin codex/auth-oauth
gh pr create --head codex/auth-oauth --title "feat: add OAuth2 PKCE support"

如果启用了合并队列,一旦 CI 通过,PR 会自动入队。工作树会保留,直到 PR 被合并或关闭,届时你可以使用 git worktree remove 和 git branch -d 进行清理。

将 git worktree prune 作为每夜 cron 任务或合并后钩子运行,以清理其分支已登陆 main 的工作树。如果置之不理,陈旧工作树会累积文件的工作副本并消耗磁盘空间,尽管它们不影响仓库正确性。

工作树设置脚本与本地环境

原始工作树包含源文件,但没有工作环境。没有设置,代理会浪费轮次去发现缺失的依赖。通过一个在创建工作树后立即运行的脚本来解决此问题:

#!/usr/bin/env bash
##### .codex/worktree-setup.sh
set -euo pipefail
cd "$1" # 工作树路径作为第一个参数传递
npm ci
npm run db:migrate

添加超时包装器以防止挂起无限期阻塞代理:

timeout 120 .codex/worktree-setup.sh ".codex/worktrees/$TASK_ID"

对于设置成本高的项目(庞大的依赖树、缓慢的数据库种子),考虑通过符号链接 node_modules 或使用共享包缓存来跨工作树缓存构件。

对于清理,决定当工作树的分支被合并或放弃时会发生什么。最简单的策略是立即移除。为了调试失败的代理运行,将工作树移动到归档目录,以便在手动删除前检查最终状态。

CI 中的工作树生命周期

在 CI 中,工作树管理必须完全自动化:在作业步骤开始时创建,使用它,无论结果如何在最后删除。

基础生命周期脚本

#!/usr/bin/env bash
set -euo pipefail
TASK_ID="${1:?task id required}"
BRANCH="codex/${TASK_ID}"
WORKTREE_PATH=".codex/worktrees/${TASK_ID}"
##### 设置
git worktree add "$WORKTREE_PATH" -b "$BRANCH"
##### 即使代理步骤失败,teardown 也会运行
trap 'git worktree remove --force "$WORKTREE_PATH"; git branch -D "$BRANCH"' EXIT
##### 运行代理
codex --cd "$WORKTREE_PATH" "$TASK_PROMPT"

trap ... EXIT 习语确保即使代理以非零状态退出或 CI 运行器被中断,也能进行清理。git worktree remove 上的 --force 标志在工作树包含未提交更改时是必需的;在 CI 中,你通常希望丢弃这些更改,而不是让清理步骤失败。

GitHub Actions 示例

jobs:
 agent-task:
 runs-on: ubuntu-latest
 steps:
 - uses: actions/checkout@v5
 with:
 fetch-depth: 0 # 工作树需要完整历史
 - name: Provision worktree
 id: worktree
 run: |
 TASK_ID=$(echo "${{ github.run_id }}-${{ github.run_attempt }}" | sha256sum | cut -c1-8)
 echo "task_id=$TASK_ID" >> "$GITHUB_OUTPUT"
 git worktree add ".codex/worktrees/$TASK_ID" -b "codex/$TASK_ID"
 - name: Run Codex agent
 run: |
 codex \
 --cd ".codex/worktrees/${{ steps.worktree.outputs.task_id }}" \
 "${{ inputs.task_prompt }}"
 - name: Push task branch
 if: success()
 run: |
 git -C ".codex/worktrees/${{ steps.worktree.outputs.task_id }}" \
 push origin "codex/${{ steps.worktree.outputs.task_id }}"
 - name: Cleanup worktree
 if: always()
 run: |
 git worktree remove --force \
 ".codex/worktrees/${{ steps.worktree.outputs.task_id }}" || true
 git branch -D "codex/${{ steps.worktree.outputs.task_id }}" || true

注意 checkout 步骤上的 fetch-depth: 0。浅克隆缺乏足够的变基和某些工作树操作所需的历史。(¹¹²) 对于完整获取速度过慢的仓库,请使用 --filter=blob:none(无 blob 克隆)而不是 --depth。

复用工作区的 CI runner(自托管 runner 池中常见)如果上一个作业的清理步骤被跳过,可能会累积过时的 worktree 注册。在每个作业开始时、配置新 worktree 之前,运行 git worktree prune 以移除不再存在的目录的注册。

并行矩阵作业

GitHub Actions 矩阵作业自然地映射到“一 Agent 一 worktree”的原则:

strategy:
  matrix:
    task:
      - "migrate user service to async/await"
      - "add rate limiting to the API gateway"
      - "write property-based tests for the parser"
    max-parallel: 3

每个矩阵分支在独立的 runner 中运行:默认即拥有真正的文件系统隔离。共享对象数据库是远程 origin,所有 Agent 独立推送至该处。无共享本地状态,执行期间无需协调,完成后通过 PR 合并。

按团队规模划分的 Worktree 工作流模式

表 13-1 总结了随着团队规模和并发度增加,worktree 工作流通常如何演进。

表 13-1. 按团队规模划分的 Worktree 工作流模式

| 团队规模 | 并发度 | Worktree 策略 | 合并策略 | 清理方式 |

| :--- | :--- | :--- | :--- | :--- |

| 个人 (1 dev) | 1-3 agents | 手动 git worktree add + codex --cd | 顺序 git merge --no-ff | 手动 git worktree remove |

| 小型 (2-5 devs) | 3-10 agents | CLI git worktree add + 封装脚本 | 每 worktree 一个 PR,人工审查 | PR 关闭时运行脚本 |

| 中型 (5-20 devs) | 10-30 agents | CLI + shell 扇出 | 每 worktree 一个 PR,合并队列 | 每个作业中的 CI 清理步骤 |

| 大型 (20+ devs) | 30+ agents | CI 矩阵作业 | 合并队列 + 自动变基 | trap EXIT + git worktree prune |

这一演进反映了一个一致的主题:随着并发度增长,你需要自动化更多的生命周期环节。底层基元(一分支、一 worktree、一 Agent)在所有团队规模下保持不变。

总结

  1. Worktree 以无需克隆的代价实现隔离。 Git worktree 为每个 Codex Agent 提供独立的仓库检出视图,同时共享对象数据库,因此无冗余下载,也无需等待完整克隆。

  2. “一 Agent 一 worktree”是核心铁律。 隔离原则可防止并行执行期间的文件级冲突、索引损坏和意外分支覆盖。Git 的“一 worktree 一分支”约束强制执行了这一点。

  3. Worktree 隔离的是代码状态,而非执行状态。 端口冲突、数据库碰撞和环境变量泄漏均无法通过 worktree 隔离解决。它们需要额外的运行时隔离:每个 worktree 专用的端口范围、独立的数据库实例,或 Coasts 等专用工具。

  4. CLI 工作流即 git worktree add 加 codex --cd。 没有专门的 worktree 子命令。你用 git 创建 worktree,再用 --cd 将每个 Agent 导入其中。

  5. 三种合并策略覆盖大多数场景。 将 Agent 成果合回 main 遵循顺序合并、变基合并,或由 CI 支撑的合并队列,取决于团队规模和历史偏好。

  6. 随并发度增长自动化生命周期。 在 CI 中,任务前创建 worktree,任务后用 trap ... EXIT 删除,启动时 prune。这能保持 runner 工作区整洁,防止过时注册累积。

另请参阅: 第 27 章 (Harness Engineering/工程化编排系统)。 Symphony harness 架构在舰队规模上使用按 Issue 隔离的 git worktree:每个 Linear Issue 一个 worktree,每个 worktree 一个专用 Codex Agent,PR 合并后清理。该章涵盖完整的 harness 模式,并展示 WORKFLOW.md 策略、工作量证明要求、基于检查点的状态持久化如何与本章介绍的 worktree 隔离相结合。

在并行 worktree 中运行大量 Agent 不仅成倍增加吞吐量,也成倍增加成本:每个并发 Agent 意味着更多并发 API 调用。下一章将转向这一“第二预算”,探讨 Codex CLI 配额如何运作、如何估算团队工作流成本,以及如何构建控制与监控,防止并行执行演变成账单冲击。

练习

  1. 基础 worktree 练习。 使用 git worktree add 创建两个 worktree,分别用 codex --cd 运行 Codex Agent,以逻辑上独立的方式修改同一文件。确认两个 Agent 均无干扰完成,将两个分支合并到 main,并在 git log 中识别出那两个合并提交。

  2. 冲突解决练习。 创建一个场景:两个 Agent worktree 均修改同一�数。第一个分支落地后,变基第二个分支,并使用 codex --cd <worktree-path> "resolve the rebase conflict against main" 解决冲突。对比 Agent 的解决方案与你手动解决的差异。

  3. CI 流水线练习。 编写一个 GitHub Actions 工作流:配置三个并行 worktree,各运行一个 Codex Agent,仅推送测试通过的分支。添加带 if: always() 的清理步骤,即使失败也移除 worktree。

  4. 生命周期审计练习。 运行 git worktree list 并与 git branch --list 'codex/*' 对比。编写 shell 脚本清理过时分支并运行 git worktree prune,再将其设为每周 cron 任务。

  5. 团队扩容设计练习。 以表 13-1 为起点,设计一个从 5 人扩容到 20 人的 worktree 策略。涵盖 worktree 命名约定、CI 作业结构、合并队列配置、清理自动化。

第十四章:成本管理与配额策略

第 12 章 讲解了如何将子 Agent 组合成多步流水线、审查循环和持久化目标。每种模式都提升能力,也增加并发 API 调用。第 7 章 讲解了上下文窗口预算:单线程会话如何填满、如何管理。本章讲解另一预算:资金。上下文与成本相关(更多上下文意味着更多 token 意味着更高成本),但它们不是同一问题。会话可能远未触及上下文窗口上限,却仍产出惊人账单。

本章涵盖:Codex CLI 配额如何运作、如何估算团队工作流成本、如何配置成本控制、如何构建监控以避免账单冲击。

学习目标

你将能够:

  1. 估算启用 Codex CLI 的工程团队月度 API 成本

  2. 通过命名配置文件配置模型路由以实现成本控制

  3. 构建 Stop Hook 将会话成本记录到文件或 webhook

  4. 应用成本-质量决策矩阵,为给定任务类别选择合适模型

  5. 最大化提示缓存命中率,争取高达 90% 的缓存输入 token 折扣

  6. 利用 Plan Mode 的上下文用量可见性,防止昂贵的上下文膨胀会话

  7. 评估 AWS Bedrock 和 Azure OpenAI Service 作为企业部署的替代计费路径

Codex CLI 配额如何运作

配额运作方式取决于你如何访问服务。

订阅配额 (ChatGPT Plus 和 Pro)

ChatGPT Plus 和 Pro 订阅包含 Codex CLI 额度,以积分计价,并按模型设定每百万 token 积分单价(输入、缓存输入、输出)。gpt-5.5 单 token 积分高于 gpt-5.4;gpt-5.4 高于 gpt-5.4-mini。^113

订阅上运行两个节奏,区分它们很有必要:

  1. 账单节奏。 订阅本身按月(或年)收取固定每席位费用。该费用不取决于 token 用量。

2. Usage-limit cadence

在订阅内,Codex CLI 的使用量按滚动的 5 小时窗口计量。本地消息和云任务共享同一个 5 小时配额池;其上可能还会叠加每周限额。

其他属性
  1. 配额以积分为单位,不同模型的费率不同。订阅用量没有逐 token 美元定价;您已经为席位付费。

  2. 配额不会在窗口之间结转。

  3. 当达到使用限额时,Plus 和 Pro 用户可以购买额外积分以继续工作,或回退到 API 密钥计费,按标准逐 token 费率付费。

Pro 计划在相同的窗口结构下提供 Plus 配额的 5 倍或 20 倍。企业版和教育版采用灵活定价,无固定速率限制;用量随工作区购买的积分而扩展。

对于个人开发者,订阅配额通常足够。对于团队则通常不够,因为按用户订阅的账户不会在团队成员间聚合。

Codex-only seats

对于需要 API 级访问但不需要完整 ChatGPT 订阅的团队,OpenAI 提供 Codex-only seat:零席位月费,加上按已发布 API 费率计费的 token 消耗,管理员可通过组织仪表盘监控用量。

Per-tier cost comparison

表 14-1 估算了当前 API 费率下不同使用档位的日均和月均成本。在做财务预测前请核对实时定价页面。^(¹¹⁴)

表 14-1. 按使用档位估算的日均和月均 API 成本

| Usage profile | Typical daily tokens | Est. daily cost | Est. monthly cost (22 days) |

| --- | --- | --- | --- |

| Light (exploration, Q&A) | 50K to 150K | $1 to $3 | $22 to $66 |

| Moderate (feature work, reviews) | 300K to 600K | $4 to $8 | $88 to $176 |

| Heavy (multi-agent, CI pipelines) | 1M to 3M | $10 to $25 | $220 to $550 |

| Team of 5 (orchestrator/worker) | 5M to 15M aggregate | $50 to $125 | $1,100 to $2,750 |

| Team of 50 (enterprise) | 50M to 150M aggregate | $500 to $1,250 | $11,000 to $27,500 |

这些估算假设编排使用 gpt-5.4,Worker 使用 gpt-5.4-mini。所有任务都跑 gpt-5.5 的团队,编排部分成本应预期约为 2 倍,但会被 GPT-5.5 的 token 效率提升部分抵消。所有任务都跑 gpt-5.4 的团队,Worker 部分成本应预期为 3 到 5 倍。社区讨论中有时引用的“每周十亿 token”数字是团队级聚合值;标准工作流下的单个开发者不会达到该量级。

API pricing

当您使用 API 密钥搭配 Codex CLI 时,计费按已发布费率逐 token 进行。^(¹¹⁵)

表 14-2. Codex CLI 会话的大致模型成本比率

| Model | Input (per 1M tokens) | Output (per 1M tokens) | Relative cost vs. gpt-5.4 |

| --- | --- | --- | --- |

| gpt-5.5 | $5.00 | $30.00 | ~2x |

| gpt-5.4 | $2.50 | $15.00 | 1.0x |

| gpt-5.4-mini | $0.75 | $4.50 | 0.3x |

| gpt-5.4-mini (reasoning) | $1.10 | $4.40 | ~0.6x (plus reasoning tokens) |

| gpt-5.4 (high reasoning) | $10.00 | $40.00 | ~5x |

GPT-5.5 定价为输入/输出各 $5/$30 每百万 token,大约是 GPT-5.4 双向 2 倍。^(¹¹⁶) 标价虽高,但 OpenAI 报告其 token 效率更好:许多任务上该模型用更少 token 即可交付相当结果。若 GPT-5.5 能一遍通过而非两遍,或用 800 而非 1,500 输出 token 解决问题,任务有效成本可大致持平。在决定升级前,请跑代表性任务并对比 GPT-5.4 的总 token 用量。对于常规工作,成本更低的 GPT-5.4 仍是更高性价比之选。

关键观察在于 gpt-5.4-mini 成本约为 gpt-5.4 的三分之一,却能很好地覆盖大部分智能体用例。^(¹¹⁷)

Fast mode and the credit multiplier

Codex CLI 提供更高吞吐的推理路径:在 gpt-5.4 和 gpt-5.5 上输出速度约快 1.5 倍,代价是更高的积分消耗率。^(¹¹⁸)

OpenAI 文档将该功能称为 Fast mode;运行时将其作为名为 priority 的服务层暴露,备选为 default。可用 /fast 在会话间切换,它会在两者间翻转并打印新状态,例如 Service tier set to priority。要持久化选择,请在 ~/.codex/config.toml 中设置 service_tier = "fast"(这会映射到内部的 priority 请求层)。

积分乘数仅影响订阅计费:priority 服务对 gpt-5.5 按标准费率 2.5 倍消耗积分,对 gpt-5.4 按 2 倍消耗。它在 IDE 扩展、CLI 和 Codex App 中对使用 ChatGPT 计划登录的用户可用。API 密钥计费不可用,无论何时都适用标准逐 token 定价,斜杠命令无效。

权衡因此大致是在每任务积分收支平衡的同时减少墙钟时间。Fast mode 以标准费率 2x 到 2.5 倍的积分率/秒运行,却能快约 1.5 倍完成:投入相似积分,减少开发者等待时间。它适合开发者等待时间本身有成本的交互式会话,不适合墙钟延迟无关紧要的批处理或后台工作。

定价文档还描述了单独的 Codex-Spark 模型(gpt-5.3-codex-spark),定位于 Fast mode 下一层:一个快速、能力较弱的 Codex 模型,用于近乎即时的实时迭代,有自己的用量限制。撰写时它仅对 ChatGPT Pro 开放,且不出现在低档位的模型选择器中,因此大多数读者仍会在 gpt-5.4/gpt-5.5 上使用 Fast mode。Codex CLI 选择器默认暴露三个模型:gpt-5.5、gpt-5.4 和 gpt-5.4-mini。

What counts as usage

在智能体会话中,token 由以下项目消耗:

  1. 系统提示词。包含 AGENTS.md 内容、活动配置文件配置,以及 Codex CLI 系统提示词本身。会话中每次 API 调用都会计费。

  2. 对话历史。上下文窗口中仍保留的每条过往消息、工具调用和工具结果,在每次 API 调用中均作为输入 token 计费。

  3. 任务描述。您的提示词。

  4. 工具调用结果。文件内容、命令输出、搜索结果,均在后续 API 调用中作为输入 token 返回。

  5. 模型输出。智能体的响应、推理 token(推理模型)及任何生成内容。

智能体会话的昂贵部分不是模型输出,而是累积的对话历史。第 7 章称之为二次增长问题:每一轮都重发全部过往历史,因此会话累计发送 token 与轮数平方大致成正比,长会话成本显著偏高。

这使得第 7 章中的每一个上下文管理杠杆同时也是成本杠杆。/compact 通过用较短摘要替换冗长详细历史,降低了后续每次 API 调用的 token 成本。子智能体委派在独立窗口运行 token 密集型子任务并向父级返回简洁摘要,保持计费前缀精简。Plan Mode 的全新上下文边界阻止实现会话继承规划会话的累积权重。两章描述的是同一机制的两个后果:费用保持有界,理由与窗口保持可用完全相同。

Estimating Team Costs

抽象定价表不如具体示例实用。下面给出一个真实团队部署的估算演示。

Scenario: five-engineer team, orchestrator/worker pattern

Assumptions:

成本估算与控制

基准估算:五人工程师团队

假设:

  1. 5 名工程师,每人每天运行 Codex CLI 活跃会话约 3 小时。

  2. 编排器/工作器模式:每个任务使用一次编排器调用(在 gpt-5.4 上),派生出 2 到 3 个工作器代理(在 gpt-5.4-mini 上)。

  3. 平均编排器会话:40,000 个输入 token,4,000 个输出 token。

  4. 平均工作器会话:25,000 个输入 token,3,000 个输出 token。

  5. 每个任务平均 2.5 个工作器。

  6. 每位工程师每天 15 个任务(长短任务混合)。

每位工程师每日成本:

  • 编排器调用(gpt-5.4):

    15 x (40,000 x $2.50/1M + 4,000 x $15.00/1M) = 15 x ($0.10 + $0.06) = 15 x $0.16 = $2.40

  • 工作器调用(gpt-5.4-mini):

    15 x 2.5 x (25,000 x $0.75/1M + 3,000 x $4.50/1M) = 37.5 x ($0.01875 + $0.0135) = 37.5 x $0.03225 = $1.21

  • 每位工程师每日总计:$2.40 + $1.21 = $3.61

团队月度成本(22 个工作日):

$3.61 x 5 名工程师 x 22 天 = $397/月

这是一个粗略估算,但确立了数量级。对于一个五人工程师团队,在编排器/工作器模式下,Codex CLI 的 API 成本大致相当于单个云服务实例。

该估算也说明了模型路由的杠杆作用。如果团队让所有工作器都运行在 gpt-5.4 而非 gpt-5.4-mini 上,工作器成本将从每位工程师每日 $1.21 跃升至约 $4.03,工作器组件成本增加 3.3 倍,总成本从 $397 上升至约 $707/月。除缓存外,模型路由是最大的成本控制杠杆。

账单透明度问题

两个反复出现的用户反馈刻画了心智模型与实际消耗之间的鸿沟。用户普遍反映 token 消耗速度比直觉预测的快 5 到 10 倍。^([¹¹⁹)] 且用户难以区分代理发起的 API 请求与用户发起的请求,这使得按任务分摊账单变得困难。^([¹²⁰)] 企业团队期望仪表盘提供细粒度的按会话和按任务成本归因,但目前尚不支持。

根因是前文所述的累积历史问题:每次后续的 API 调用都会重新处理整个历史记录。一个感觉像“几个问题”的会话,到第十次 API 调用时可能已消耗 200,000 个或更多输入 token。

一个具体例子:针对一个 7,000 行的代码库,使用 GPT-5.4 的 xHigh 推理模式,发出一个宽泛提示(“浏览此代码库并预防性地修复任何 macOS 问题”),在 7 分钟内跨 34 次 API 请求消耗了约 400 万 token,花费约 $3.50。在非平凡代码库上,宽泛、开放式的提示遇上最大推理力度的强力模型,会产生令人惊讶的巨额账单。

用户预期与实际消耗之间的差距被三个因素放大:订阅配额显示剩余百分比而非 token 计数、缺乏按会话的成本细分、以及多代理工作流的复合效应(每个子代理都有自己的上下文累积曲线)。

务实的应对是监控。使用稍后本章介绍的 Stop 钩子,记录每会话的 token 计数。每周将这些计数与您的预测进行对比。如果实际消耗持续超过预测 2 倍以上,您的会话深度假设需要修正。

Codex CLI 会发出 codex_compaction_event 分析记录,追踪 /compact 触发的频率及节省的 token 数。将其与 PreL1-Bullet/PostL1-Bullet 钩子配对,可在使用仪表盘提供该功能前,构建本地的压缩频率图景。

浪费性支出:代理式 PR 失败率

对 33,000 个代理撰写的拉取请求(PR)研究发现,28.5% 的代理式 PR 无法合并。^([¹²¹)] 主导的失败模式并非糟糕的代码,而是社交与工作流的错位:审查者放弃(占失败的 38%)、重复提交(23%)和不需要的功能(4%)。每个失败的 PR 都代表了用在从未发布的工作上的 token 支出。

代理引入的符号中位存活期仅 3 天,而人类引入的为 34 天;代理代码的代码更动率为 7.33%,而人类代码为 4.10%。^([¹²²)] 即使合并了,代理代码修订得也更快,这进一步增加了每存活行代码的有效 token 成本。

将代理式 PR 失败率从 28.5% 降至 15%(可通过缩小 PR 范围、提交前 CI 验证、AGENTS.md 中的显式范围约束来实现),将消除约一半的代理式浪费支出。第 15 章 基于这些发现介绍了 CI/CD 流水线设计。

用于审查循环的 SLM 裁判分层

第 12 章 中的跨模型审查循环,将结构不同的模型置于批评者席位。在仅使用前沿模型的配置下,经济账算很贵。一个三提供商审查循环(一个前沿推理模型加两个旗舰评审模型)每个合并请求成本约 $0.47 到 $0.50。按每位开发者每月 40 个合并请求计算,仅审查成本就是 $20/开发者/月,一个 50 人团队一年约 $12,000。^([¹²³)]

近期关于小语言模型(SLM)作为裁判的研究表明,微调后的 SLM 能达到与体量大 5 到 25 倍的模型竞争的裁判性能。^([¹²⁴)] 使用 gpt-5.4-mini(最小的 Codex 推荐模型)的 SLM 裁判分层会预筛选每个合并请求。只有被标记为潜在问题的请求(通常为常规 MR 的 25% 到 35%)才会进入完整的前沿模型审查。这将平均审查成本从约 $0.27(双模型)降至约 $0.09/MR,实现 3 倍降本。

双模型审查循环(用一个模型实现,另一个审查)是成本与质量的平衡点。第三个评审模型增加成本的速度快于质量提升;研究显示,超过两个独立评审者后收益递减。应投资于 SLM 裁判分层和提示缓存(本章稍后介绍),而非增加第三个前沿模型评审者。

技能评估成本

投资技能发展的团队应单独为评估预算。技能基准测试工具(第 10 章)每次基准运行消耗约 74,000 个 token。团队迭代五轮基准测试约消耗 370,000 个 token,按 gpt-5.4 输入价格约 $0.93,按 gpt-5.4-mini 约 $0.28。一个合理的默认值是将总 Codex CLI 支出的 5% 分配给技能开发与评估。

需调整的预测变量

影响估算幅度最大的变量:

  1. 会话深度。 编排器 40,000 输入 token 的假设属中等水平。在大型遗留代码库中进行大量文件读取的会话可能达到 100,000 个或更多输入 token。请使用 --debug 输出测量实际会话并调整。

  2. 每任务工作器数。 并行度越高成本越高。三个工作器比 2.5 个贵 20%;五个工作器成本翻倍。

  3. 任务频率。 运行 Codex CLI 进行发现和探索(许多短会话)的工程师,其成本概况与进行深度任务完成(较少的长会话)者不同。探索性会话虽短,但每会话产生的开销比例更高。

配置成本控制

Codex CLI 的主要成本控制手段是模型路由:哪个任务命中哪个模型,以及使用何种推理力度。运行时未将硬性的按会话 token 上限作为配置键暴露,因此成本纪律体现在第 6 章介绍的配置文件系统,以及下文所述的压缩相关限制中。

通过配置文件进行模型路由

成本感知配置文件与监控

第 6 章 全面介绍了命名配置文件机制:每个配置文件位于各自的文件 ~/.codex/<name>.config.toml 中,包含顶层键,而 --profile <name> 会将该文件覆盖到 ~/.codex/config.toml 上,优先级链为 CLI 标志 > 配置文件 > 项目配置 > 用户配置 > 默认值。本章将同一机制应用于成本控制:每个配置文件固定一种廉价、均衡或高性能的配置,使合适的成本形态仅在一个标志之遥。

##### ~/.codex/config.toml
model = "gpt-5.4"
model_reasoning_effort = "medium"
##### ~/.codex/explore.config.toml
model = "gpt-5.4-mini"
model_reasoning_effort = "low"
##### ~/.codex/commit.config.toml
model = "gpt-5.4"
model_reasoning_effort = "medium"
##### ~/.codex/reason.config.toml
model = "gpt-5.4-mini"
model_reasoning_effort = "high"

使用 --profile(或 -p)调用配置文件:

codex --profile explore "browse the auth module and explain how the session middleware works"
codex -p commit "implement the new user authentication flow"
codex -p reason "design a back-pressure scheme for the ingest pipeline"

顶层 model 和 model_reasoning_effort 键在未指定配置文件时作为默认值。explore 配置文件是最廉价的设置,适用于浏览代码和快速实验。commit 配置文件使用全性能模型处理将产生实际输出的任务。reason 配置文件在 gpt-5.4-mini 上支付“推理税”(本章后文介绍),适用于那些受益于扩展推理且令牌成本低于同等推理力度下的 gpt-5.4 的任务。根据任务类别添加任意数量的配置文件;在它们之间切换仅需一个标志。

限制工具输出与压缩

运行时提供三个键来限制单个会话的膨胀程度:

  1. tool_output_token_limit 限制单次工具结果的令牌预算。否则,浏览器快照或单体仓库的 grep 结果可能在一次调用中就消耗 20% 的上下文窗口。

  2. model_auto_compact_token_limit 设置触发自动压缩的阈值。将其降低至上下文容量的 80% 到 85%,可给摘要生成器留出更多空间,从而生成质量更高的摘要,减少后续压缩次数。

  3. compact_prompt 覆盖默认的压缩摘要提示词。一个能保留文件路径、函数签名和决策理由的结构化交接提示词,能生成模型无需重读文件即可恢复的摘要。

这些是配置层面最接近“防止我在失控会话上烧钱”的设置。若想要真正的上限,请使用下一节的监控钩子在事后检测失控消耗,并在下一次会话前调整配置文件。

其他地方已有文档记录了第三方智能体框架中无声的成本累积:某社区构建的工具报告称,因后台心跳机制在无任务运行时持续消耗令牌,导致空闲会话产生每月 $86 的费用。^(¹²⁵)

无论运行时如何,请利用下一节的钩子自行构建成本可视化闭环。将遥测缺失视为可能存在无声烧费的信号。

使用钩子进行监控与告警

Stop 钩子在轮次停止时触发,包括你离开 Codex CLI 会话前的最后一轮。第 9 章 涵盖钩子生命周期、注册机制和完整载荷结构;本节提供一个可直接投入使用的配置及其三个变体。

入门级 Stop 钩子

在 ~/.codex/hooks.json(或项目根目录下的 .codex/hooks.json)中注册钩子。第 9 章 介绍了这两种来源、信任审查流程,以及如何使用 /hooks 检查已加载的钩子。

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash ~/.codex/scripts/cost-log.sh",
            "statusMessage": "Logging session cost...",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

将匹配的脚本保存至 ~/.codex/scripts/cost-log.sh 并执行 chmod +x:

#!/usr/bin/env bash
##### ~/.codex/scripts/cost-log.sh: 每个停止的轮次追加一行 JSONL。
##### Stop 载荷包含 session_id、turn_id、model、cwd 和 transcript_path。
##### 令牌总数位于 transcript_path 的执行记录中,因此我们在此遍历它。
set -euo pipefail
LOG_DIR="${HOME}/.codex/cost-logs"
mkdir -p "$LOG_DIR"
PAYLOAD="$(cat || echo '{}')"
TIMESTAMP="$(date -u +%FT%TZ)"
SESSION="$(jq -r '.session_id // "unknown"' <<<"$PAYLOAD")"
TURN="$(jq -r '.turn_id // "unknown"' <<<"$PAYLOAD")"
MODEL="$(jq -r '.model // "unknown"' <<<"$PAYLOAD")"
CWD="$(jq -r '.cwd // empty' <<<"$PAYLOAD" || echo "$PWD")"
TRANSCRIPT="$(jq -r '.transcript_path // empty' <<<"$PAYLOAD")"
BRANCH="$(git -C "${CWD:-$PWD}" rev-parse --abbrev-ref HEAD 2>/dev/null || echo none)"
# 遍历执行记录以获取用量并求和。转录记录中的字段名在版本间略有差异;
# 此处涵盖常见结构
# (input_tokens / output_tokens / total_tokens; prompt_tokens / completion_tokens)。
USAGE='{"input":0,"output":0,"total":0}'
if [ -n "$TRANSCRIPT" ] && [ -r "$TRANSCRIPT" ]; then
  USAGE="$(jq -s '
    [ .[] | .. | objects | select(has("input_tokens") or has("prompt_tokens")) ]
    | reduce .[] as $u (
        {input:0, output:0, total:0};
        .input  += (($u.input_tokens // $u.prompt_tokens // 0) | tonumber? // 0)
        | .output += (($u.output_tokens // $u.completion_tokens // 0) | tonumber? // 0)
        | .total  += (($u.total_tokens // 0) | tonumber? // 0)
      )' "$TRANSCRIPT" 2>/dev/null || echo "$USAGE")"
fi
jq -nc \
  --arg ts "$TIMESTAMP" \
  --arg session "$SESSION" \
  --arg turn "$TURN" \
  --arg model "$MODEL" \
  --arg cwd "$CWD" \
  --arg branch "$BRANCH" \
  --argjson usage "$USAGE" \
  '{ts:$ts, session:$session, turn:$turn, model:$model, cwd:$cwd, branch:$branch, usage:$usage}' \
  >> "$LOG_DIR/cost-log.jsonl"

运行一次 Codex CLI 会话并退出。随着 Stop 钩子触发,~/.codex/cost-logs/cost-log.jsonl 应会增长,每行携带时间戳、会话 ID、轮次 ID、模型、cwd、分支,以及从执行记录汇总得到的 usage 对象。若想要每个会话恰好一条记录,稍后按 session 聚合,并保留每个会话的最终或最大总量。

稍后使用 jq 进行聚合。最近七天的按模型细分,包含总输入和输出令牌:

SINCE="$(date -u -v-7d +%FT%TZ 2>/dev/null || date -u -d '7 days ago' +%FT%TZ)"
jq -c --arg since "$SINCE" 'select(.ts > $since)' ~/.codex/cost-logs/cost-log.jsonl \
  | jq -s 'group_by(.model)
      | map({model: .[0].model,
             turns: length,
             sessions: (map(.session) | unique | length),
             input:  (map(.usage.input  // 0) | add),
             output: (map(.usage.output // 0) | add),
             total:  (map(.usage.total  // 0) | add)})'

Stop 标准输入载荷目前不直接包含令牌计数。入门脚本遍历 transcript_path 处的执行记录来恢复它们,这很稳健但敏感于版本:Codex 将 transcript_path 文档化为便利字段,而非稳定的钩子接口,且执行记录在各版本中曾同时使用 input_tokens/output_tokens 和 prompt_tokens/completion_tokens。上述 jq 归约器同时处理两者。第 9 章 对更广泛的钩子载荷表面提出了相同的警示。首次运行后,jq .usage ~/.codex/cost-logs/cost-log.jsonl | tail 可确认总计是否正在填充;若仍为零,说明你安装版本中的字段名已漂移,归约器需要再添加一个别名。

变体:发送到 Webhook 以供团队聚合

将脚本主体替换为 curl 而非 jq 追加。在 Shell 配置文件中一次性设置 CODEX_COST_WEBHOOK,以将 URL 保留在源码控制之外:

!/usr/bin/env bash

~/.codex/scripts/cost-webhook.sh

set -euo pipefail

[ -z "${CODEX_COST_WEBHOOK:-}" ] && exit 0

PAYLOAD="$(cat || echo '{}')"

TIMESTAMP="$(date -u +%FT%TZ)"

SESSION="\((jq -r '.session_id // "unknown"' <<<"\)PAYLOAD")"

TURN="\((jq -r '.turn_id // "unknown"' <<<"\)PAYLOAD")"

curl -sS -m 5 -X POST "$CODEX_COST_WEBHOOK" \

-H 'content-type: application/json' \

-d "$(jq -nc \

--arg ts "$TIMESTAMP" \

--arg session "$SESSION" \

--arg turn "$TURN" \

--arg user "${USER:-unknown}" \

--argjson payload "${PAYLOAD:-null}" \

'{ts:\(ts, session:\)session, turn:\(turn, user:\)user, payload:$payload}')" \

/dev/null || true

将 Stop 钩子命令指向此脚本。|| true 和 exit 0 保护遵循第 9 章中的“优雅处理缺失依赖”建议:Webhook 故障应静默退出 0,而不是退出 2 并在轮次拆解期间显示为错误。

变体:会话跨越 Token 阈值时发出警报

Token 阈值检查,适用于在会话累积成本之前捕获失控会话。可通过 CODEX_COST_ALERT_TOKENS 调优:

#!/usr/bin/env bash
##### ~/.codex/scripts/cost-alert.sh
set -euo pipefail
THRESHOLD="${CODEX_COST_ALERT_TOKENS:-500000}"
PAYLOAD="$(cat || echo '{}')"
SESSION="$(jq -r '.session_id // "?"' <<<"$PAYLOAD")"
TRANSCRIPT="$(jq -r '.transcript_path // empty' <<<"$PAYLOAD")"
TOTAL=0
if [ -n "$TRANSCRIPT" ] && [ -r "$TRANSCRIPT" ]; then
  TOTAL="$(jq -s '
    [ .[] | .. | objects | select(has("input_tokens") or has("prompt_tokens")) ]
    | map(
        ((.input_tokens // .prompt_tokens // 0) | tonumber? // 0)
        + ((.output_tokens // .completion_tokens // 0) | tonumber? // 0)
      )
    | add // 0' "$TRANSCRIPT" 2>/dev/null || echo 0)"
fi
if [ "${TOTAL:-0}" -gt "$THRESHOLD" ]; then
  MSG="Codex session $SESSION used $TOTAL tokens (threshold $THRESHOLD) in $(pwd)"
  command -v notify-send >/dev/null && notify-send "Codex cost alert" "$MSG"
  [ -n "${CODEX_SLACK_WEBHOOK:-}" ] && curl -sS -X POST "$CODEX_SLACK_WEBHOOK" \
    -H 'content-type: application/json' \
    -d "$(jq -nc --arg t "$MSG" '{text:$t}')" > /dev/null || true
fi

通过在 "Stop" 数组下注册多个 Stop 条目来堆叠这三个钩子。同一事件的匹配命令钩子可以并发启动,因此不要依赖注册顺序。如果日志记录必须发生在 Webhook 投递或警报之前,请从一个包装器脚本中按顺序调用这些脚本,并仅将该包装器注册为钩子。

对于每次压缩的可观测性,将这些 Stop 钩子模式与 PreL1-Bullet 钩子配对(相同的 JSON 形状,使用 "PreL1-Bullet" 代替 "Stop"),记录时间戳和载荷字段。十五分钟内三次自动压缩意味着存在循环,应拆分会话。第 9 章记录了 PreL1-Bullet 的 stdin 字段(session_id、turn_id、cwd、model、transcript_path 和 trigger,其值为 manual 或 auto)以及保存和恢复模式的工作示例;当你需要特定成本形状时请返回此处。

成本-质量决策矩阵

许多代理工程任务非常适合由更便宜的模型完成,正确路由它们可以在不牺牲质量的情况下降低成本。

决策框架

表 14-3. 任务类别和模型推荐

| 任务类别 | 推荐模型 | 推理 |

| --- | --- | --- |

| 代码搜索和导航 | gpt-5.4-mini | 查找内容需要阅读和模式匹配,而非综合 |

| 格式化和 Lint 遍历 | gpt-5.4-mini | 确定性转换;质量差异可忽略不计 |

| 已知模式的测试生成 | gpt-5.4-mini | 适合成熟习惯用语的优秀模型 |

| 文档生成 | gpt-5.4-mini | 流畅度比深度推理更重要 |

| 探索性重构 | gpt-5.4-mini | 草稿质量可接受;工程师审查 |

| 复杂多文件重构 | gpt-5.4 | 跨多文件的连贯性需要完整能力 |

| 架构设计和审查 | gpt-5.4 配合 high 推理力度 | 高风险推理;成本合理 |

| 安全审计 | gpt-5.4-mini 配合 xhigh 推理力度 | 对抗性推理受益于扩展推理 |

| 新算法实现 | gpt-5.4-mini 配合 xhigh 推理力度 | 硬问题受益于推理税 |

| 大型代码库中的 Bug 诊断 | gpt-5.4 | 上下文连贯性很重要;mini 可能丢失线索 |

原则:有强模式的任务使用更便宜的模型;模型解决新问题的任务使用更强的模型。

高力度配置上的推理税

配置为 xhigh 或 high 推理力度的模型会在生成输出前“思考”,产生作为输出 Token 计费但不向用户显示的推理 Token。这种推理税使它们每次调用的成本比同一模型在 medium 力度下更高,但对于需要这种推理的问题(逻辑错误、复杂状态机、多约束优化),质量提升证明成本是合理的。

对于常规任务,推理税不值得支付。以 xhigh 运行 gpt-5.4 生成标准 REST 端点并不会比 gpt-5.4 在 medium 下产生更好的结果。根据任务是否受益于扩展推理来路由,而不是基于哪个模型在基准测试中“最好”。

一个快速启发式:如果你能在代理运行前写出正确输出的精确规范,该任务可能不需要高推理力度。如果代理需要探索解空间并权衡取舍,推理税就能赚回成本。

每推理力度 Token 消耗

更高推理力度的成本乘数比大多数团队预算的要陡峭,且质量回报是非线性的。

表 14-4. 按推理力度级别观察到的 Token 消耗 (gpt-5.4, 代表性编码任务)

| 力度级别 | 输入 Token | 输出 Token | 推理 Token | 总 Token | 相对成本 (对比 medium) |

| --- | --- | --- | --- | --- | --- |

| Minimal | ~12,000 | ~800 | ~200 | ~13,000 | ~0.15x |

| low | ~15,000 | ~1,200 | ~1,500 | ~17,700 | ~0.35x |

| medium | ~20,000 | ~2,500 | ~8,000 | ~30,500 | 1.0x (基准) |

| High | ~25,000 | ~4,000 | ~35,000 | ~64,000 | ~3.5x |

| Xhigh | ~30,000 | ~6,000 | ~120,000 | ~156,000 | ~12x |

推理 Token 是成本驱动因素。在 xhigh 下,推理 Token 占总消耗的 75% 以上。这些 Token 作为输出 Token 计费,意味着 xhigh 的有效会话成本主要由用户在响应中永远看不见的不可见计算主导。

KV 缓存经济学

KV 缓存未命中(模型必须重新计算缓存的键值对,而不是从上一轮重用它们)的成本大约是缓存命中的 10 倍。实际上,会话中断(网络断开、进程重启、/compact 操作)带有隐藏成本乘数。持续运行的会话受益于累积上下文的缓存命中;重启的会话支付完整的重新计算成本。

实际含义:为长时间运行的任务最小化会话重启。当必须重启时,使用 codex resume --last 保留尽可能多的缓存上下文,而不是开始新会话。

双峰 ROI 分布

大规模开发者研究

大规模开发者研究报告称,AI 编码工具带来的生产力提升呈现双峰分布:中位数收益为 10% 至 15%,前四分位数看到 40% 至 60% 的收益,而后四分位数则看到微乎其微或负面影响。^(¹²⁶) 以中位数为基准制定预算,将更高回报视为额外收益。那些以全体开发者 40% 的收益为依据为 AI 工具投资定价的团队将会失望。

工具悖论

生产环境评审中发现了一个反直觉的结果:在受评审的部署中,将可用工具数量从 46 个减少到 19 个,使代理准确率提高了 44%。更多工具会产生更多的上下文开销和更高的决策复杂度。每个工具描述在每次 API 调用中都会消耗 Token(参见第 7 章),而每增加一个工具都会增加错误选择的概率。

连接到会话的每个非必要 MCP 服务器都是一个成本倍增器。增加的决策复杂度会导致更长的推理链和更频繁的工具使用错误。

提示词缓存:最大的单一成本杠杆

提示词缓存可降低你所选模型的成本。对于前缀超过 1,024 个 Token 的任何 API 请求,OpenAI 的提示词缓存机制会自动生效。缓存的输入 Token 按高达 90% 的折扣计费(取决于模型;gpt-5.5 缓存输入的路由费率约为标准输入费率的 10%),且无需显式加入。^(¹²⁷)

在典型的 Codex CLI 会话中,系统提示词、工具定义、AGENTS.md 内容以及早期的对话轮次构成了一个稳定的前缀,并在每次 API 调用时重新发送。在默认设置和稳定配置下可实现 85% 的缓存命中率,这使得输入 Token 的有效成本在会话生命周期内大幅下降,通常超过一半。^(¹²⁸)

节省效果随模型路由而复合。一个将探索任务路由至 gpt-5.4-mini 并实现高缓存命中率的团队,支付的成本仅为天真使用未缓存 gpt-5.4 基准成本的一小部分。

最大化缓存命中的四大实践

  1. 保持系统提示词稳定。 系统提示词中的动态内容(时间戳、构建编号、会话标识符)会在每个会话中使前缀失效。请将易变数据移至用户消息中。

  2. 将 AGENTS.md 置于开头并保持其确定性。 AGENTS.md 内容被聚合到开发者角色前缀中。如果这些文件包含自动生成的元数据(行数、最后修改日期),每次调用都会产生不同的前缀。将 AGENTS.md 的发现固定到特定路径,并避免动态生成的内容。

  3. 保持工具顺序一致。 MCP 服务器发现顺序必须是确定性的。在会话中途添加、移除或重新排序 MCP 服务器会改变前缀并使缓存状态失效。在 config.toml 中预先声明每个服务器,并倾向于禁用而非移除。

  4. 在稳定配置下批量处理相似任务。 从同一配置文件启动、针对同一仓库的并行会话共享相同的系统指令和工具定义。每个项目使用一个配置文件,而不是临时的命令行覆盖,这样连续调用将命中相同的缓存键,并受益于预热的前缀。

有关缓存机制、前缀布局、反模式和监控的详细处理,请参阅 OpenAI 开发者文档中的提示词缓存部分。

规划模式下的上下文使用可见性

Codex CLI 的规划模式将规划与实现分离开来。规划输出包含一个上下文使用指示器,显示规划阶段消耗了多少上下文窗口。

如果仅规划就消耗了 60% 的上下文窗口,实现阶段将从有限的剩余空间开始,导致早期压缩(这会重置提示词缓存)或上下文溢出(这会终止会话)。任一结果都会浪费 Token。

实用工作流:

  1. 在规划模式下运行任务。

  2. 检查规划输出中的上下文使用指示器。

  3. 如果上下文使用率超过 50%,在批准实现前将任务拆分为更小的子任务。

  4. 如果上下文使用率低于 30%,任务拥有舒适的剩余空间,可按原样继续。

这避免了代理会话中成本最高的失败模式:任务深入消耗上下文窗口,触发压缩,丢失缓存收益,随后产生需要第二个会话才能完成的不完整输出。在规划阶段捕获上下文膨胀——在花费任何实现 Token 之前——严格比在十次工具调用后发现它更便宜。

目标模式与长周期成本

目标模式(第 12 章)解决了一个特定的成本问题:跨越多个会话或经受上下文压缩的任务。如果没有持久化目标,像“将认证模块从 JWT 迁移到 PASETO”这样的长周期目标只存在于对话上下文中,而压缩可能会悄无声息地丢弃目标的子任务和完成状态。代理随后会重复已完成的工作,这是多会话工作流中成本最高的失败模式。

目标状态位于应用服务器内的专用存储层中,而非易失性会话状态中。进度跟踪可在会话重启、上下文压缩和终端崩溃后存活。当你使用 /goal "将认证模块从 JWT 迁移到 PASETO" 设定目标时,目标、其子任务及其完成状态会被写入专用数据库。

成本影响有三方面:

  1. 无重复工作。 目标状态独立于上下文窗口持久化,因此压缩永远不会丢弃进度。以前因压缩重置代理对已完成工作的理解而需要两个会话的任务,现在可在一个会话中完成。

  2. 按目标跟踪 Token 消耗。 目标按目标跟踪 Token 消耗,让你了解哪些任务昂贵、哪些便宜。如果一个迁移目标在三个会话中消耗了 40 万 Token,你就拥有了下一个形状相似迁移的校准估算值。

  3. 预算感知的续行。 目标续行在使用限制处停止,而非无限制运行。当目标会话达到其 Token 预算时,代理会检查点进度并停止。下一个会话从检查点恢复。

对于从本章提示词和模型路由部分构建成本优化栈的团队,目标模式增加了第四个维度:时间成本摊销。一个跨三个短小精悍会话完成的目标,消耗的总 Token 可能比触及压缩边界的单次马拉松式会话更少,因为每个短会话都能受益于完整的提示词缓存,并避免了压缩后的上下文重建惩罚。

替代云计费路径

Codex CLI 不必直接调用 OpenAI API。对于云支出已集中在 AWS 或 Azure 的企业,通过匹配的云服务商路由推理,可将 Codex CLI 成本保留在同一张发票上,并解锁直接 API 计费不存在的云服务商承诺折扣。撰写时有两家服务商提供第一方 Codex CLI 支持:AWS Bedrock(内置)和 Azure OpenAI Service(配置为自定义服务商)。GCP Vertex AI 和 Anthropic Claude 目前不作为原生服务商支持。

Amazon Bedrock

Bedrock 是 Codex CLI 的内置模型服务商之一。通过它路由可为你提供:

  1. 承诺使用折扣。 带有承诺支出协议的 AWS 预置吞吐量定价,可为具有可预测量的团队将实际单 Token 成本降低至低于公开的按需费率。

  2. AWS 账单合并。 Codex CLI 推理费用与计算、存储及其他基础设施出现在同一张 AWS 发票上,简化了成本归因,避免了单独的 OpenAI 账单关系。

企业级定价层级

拥有 AWS 企业折扣计划 (Enterprise Discount Programs) 的组织,可能会在 Bedrock 推理上获得额外折扣,而这些折扣无法通过直接的 OpenAI API 计费获得。

配置 Codex CLI,使其通过内置的 amazon-bedrock 提供商路由配置文件:

##### ~/.codex/bedrock.config.toml
model = "openai.gpt-5.4"
model_provider = "amazon-bedrock"
[model_providers.amazon-bedrock.aws]
profile = "my-aws-profile"   # optional; falls back to default credential chain
region = "us-west-2"

认证使用标准 AWS 凭证链 (SigV4);模型标识符遵循 Bedrock 约定 (openai.gpt-5.4, openai.gpt-5.5),而非裸 OpenAI 别名。提示缓存在 Bedrock 上得以保留,因为运行时将其视为与 OpenAI 兼容的端点。

Azure OpenAI 服务

Azure OpenAI 服务并非内置提供商,但可通过通用的 [model_providers.*] 配置获得全面支持。TOML 结构如下:

##### ~/.codex/azure.config.toml
model = "gpt-5.4"
model_provider = "azure-openai"
[model_providers.azure-openai]
name = "Azure"
base_url = "https://YOUR-RESOURCE.openai.azure.com/openai"
env_key = "AZURE_OPENAI_API_KEY"
wire_api = "responses"
query_params = { "api-version" = "2025-04-01-preview" }

在环境中设置 AZURE_OPENAI_API_KEY。name = "Azure" 这一行至关重要:运行时在决定提示缓存是否可用时,会检查是否存在 Azure 形态的提供商,字符串匹配会启用缓存路径。模型标识符与您的 Azure 部署名称匹配;您将名为 gpt-5.4 的部署映射到 Azure 门户中的相应模型。

Azure 路由将 Codex CLI 推理置于您现有的 Azure 订阅上,该订阅可能已包含企业协议折扣或承诺消费定价,这与直接的 OpenAI 计费截然不同。

权衡

无论选择哪个提供商,权衡点在于运维方面:与 OpenAI 直接 API 相比,云托管端点可能会增加延迟,且在新模型发布上可能滞后。请在您选择的提供商上端到端地验证提示缓存行为;缓存在 Bedrock 和 Azure 上均可工作,但缓存命中率取决于因提供商而异的基础设施。

经验法则:Codex CLI 推理月支出大约超过 $5,000 时,仅云提供商的承诺使用折扣即可抵消运维开销,而账单合并带来的收益也能实现自我回报。低于该门槛时,直接 OpenAI API 计费更简单,且能更快获得新功能。

通过代理进行 Token 压缩

本节中的技术属于社区工具,而非 Codex CLI 功能。请将其视为值得针对您自身工作负载进行调查的建议,而非部署推荐:纸面上的成本核算看似诱人,但失效模式切实存在,因此审慎试用是正确的下一步。

代理概念

Token 压缩代理位于 Codex CLI 与模型 API 或生成工具输出的 Shell 之间。无论它位于何处,同四种核心操作发挥作用:

  1. 过滤。 丢弃不承载信号的行、字段或 token:模型可推断的警告、ANSI 转义序列、重复的堆栈帧、包安装噪音。

  2. 分组与摘要。 将重复行折叠为 N x message;用最终状态替换冗长的进度流。

  3. 按结构截断。 在有意义的边界处截断(模型已理解的函数体、大型差异的中间部分),而非在任意字节数处截断。

  4. 跨轮次去重。 发现重复出现的相同文件路径、错误消息或树片段,并通过引用代替重复。

这些正是您在将冗长的 Shell 输出粘贴到聊天窗口前,手动会进行的操作。代理在模型为摄取原始输出付费前,一致地应用这些操作。

最具体的社区示例是 RTK (Rust Token Killer),它作为本地包装器安装,并将观测量大的 Shell 命令 (git status、pytest、大文件读取) 重写为紧凑摘要。^(¹²⁹) 其自身文档报告称,在常见开发者命令上可实现 60% 至 90% 的减少。同一追踪器上的调试报告还记录了一种失效模式:静默重写减少了输入 token,却导致模型输出 token 增加约 50%,使受影响任务的总成本上升约 18%。

教训是标准的:在采用任何压缩层用于热路径前,请衡量每任务的端到端成本,而非仅看标题上的输入节省。

原始人模式:压缩模型输出

第二种模式压缩对话的另一侧:模型的散文。社区工具 squeez 作为 Codex CLI 钩子安装,并在会话开始时注入一个简洁的人设;其约定俗成的名称为原始人模式。^(¹³⁰) 该人设要求模型去除冠词、填充词和客套话,精确保留技术术语,并原样保留代码块和错误消息。其意图是减少输出 token,因为输出 token 的按 token 计费单价高于输入。

概念上,这是位于提示词顶部的单条钩子注入指令,大致如下:

以原始人模式回复:简练。去除冠词、填充词和客套话。精确保留代码块、文件路径、错误消息和技术术语。仅在提交信息和 PR 描述中使用正常散文。

这可通过 SessionStart 或 UserPromptSubmit 钩子 (第 9 章) 实现,该钩子将指令写入 stdout 以供注入。像 squeez 这样的社区实现将人设和一些相关调整打包在一起;自行编写只需几行 bash 脚本。

杠杆虽小,但副作用显而易见。与工具输出压缩相比,同样的警示更适用于此:模型输出是读者或下一轮代理所见的内容,因此削减其连接组织属于质量风险。一项公开的“原始人语言”实验报告称,其节省 token 的代价是质量严重下降,以至于作者不敢将其用于严肃代码编写。

适合尝试原始人模式的场景:常规实现循环、状态更新、探索性工作(简练沟通可接受的场景)。不适合的场景:教学材料、架构解释、安全敏感型审查、任何缺失连接组织会改变读者理解的场景。

如何调查任一层

若您想在不承诺投入生产的前提下评估这些工具:

  1. 选取一项工作负载(一种重复性任务类型,而非您最重要的 PR)。

  2. 运行基准会话,并从本章前文的 Stop 钩子捕获每会话总量。

  3. 仅为该工作负载启用代理或钩子;专用配置文件 (见 第 6 章) 可将影响面控制在容器内。

  4. 再次运行相同工作负载,并对比总 token 成本、总时钟时间以及输出的任何定性差异。

这两个层均为独立维护的社区项目,拥有各自的发布节奏和 Bug;若采用任一工具,请固定版本,并将其视为命令或钩子路径中的不可信代码。在开发者会话的同一主机上运行,保留原始输出的逃生通道,并为那些精确输出即产物的命令禁用压缩:迁移、部署、安全扫描、生成文件,以及任何用于事件报告或审计记录的内容。

上下文健康度即成本可观测性

上下文退化在成为质量问题之前,首先是一个成本问题。一个陷入压缩循环的会话——模型进行压缩,随即再次填满上下文,并反复压缩——会在重复读取已处理文件和重复生成已产出摘要上浪费 Token。^(¹³¹) 前文介绍的三个与压缩相关的键(tool_output_token_limit、model_auto_compact_token_limit、compact_prompt)是配置侧的防御手段;PreL1-Bullet 钩子则是遥测侧的防御手段。

实用原则:在上下文使用率达到 60% 时压缩,而非 95%。等待自动触发意味着压缩在压力下运行,留给高质量摘要的空间更小。在自然任务边界处手动压缩——完成一个子任务后、开始新文件前——能给摘要器更多喘息空间,并降低整个会话生命周期内的总 Token 开销。

本地推理:GB10 盈亏平衡点

对于 Codex CLI API 使用量大的团队,本地推理可以完全消除按 Token 计费的成本。NVIDIA 的 GB10 级硬件(DGX Spark 系列、128 GB 统一内存、Blackwell GPU)是入门门槛。^(¹³²)

按公开硬件价格与每月 $800 API 支出进行的数量级盈亏平衡测算,回本周期约为 6 个月;若为每月 $400,则接近 12 个月。GB10 硬件运行的开放权重模型在常规编码任务上具有竞争力(Qwen3.6-35B-A3B 在 SWE-bench Verified 上报告得分 73.4%,速度约 240 tokens/s),尽管它们在前沿基准测试上落后于 GPT-5.5。

对大多数企业而言,成本最优模式是分层路由:将常规任务(Lint、测试生成、文档编写)路由到运行开放权重模型的本地 GB10,将复杂任务(架构推理、多文件重构)路由到云端 API 以使用前沿模型。第 17 章将详细介绍部署拓扑。

##### ~/.codex/local.config.toml: 常规工作在本地硬件上运行
model = "qwen3.6-35b-a3b"
model_provider = "local_gb10"
##### ~/.codex/cloud.config.toml: 复杂工作在前沿模型上运行
model = "gpt-5.5"
model_provider = "openai"

盈亏平衡分析是容易的部分;运营权衡才是其余难点。本地推理将支出从运营支出转为资本支出,增加了硬件刷新周期,并需要安排专人负责模型更新、GPU 驱动兼容性和推理服务器正常运行时间。在 API 支出低于盈亏平衡点时,这很少是正确答案。超过该阈值后,决策通常不再关乎数学计算,而是非成本约束:禁止代码或提示词离开网络的数据驻留规则、云端根本无法触达的物理隔离环境,或前沿模型的边际能力不值得其流出成本或延迟的工作负载。

对大多数团队而言,正确的形态是上述两个配置文件所示的混合模式:本地产能吸纳常规负载,云端处理需要前沿能力的调用,配置系统使路由显式化且可审计。第 17 章涵盖了周围部署拓扑,包括网络、模型生命周期,以及混合模式如何与企业安全控制交互。

总结

  1. 两个计费面。 ChatGPT 订阅按月按固定席位费收费;席位内,Codex CLI 用量按滚动五小时窗口以积分计量,可选每周限额。API 计费独立,按公开美元费率按 Token 计费。一个由五名工程师组成的编排者/工作者团队,API 账单通常在每月 $350 至 $450 区间,其中运行 gpt-5.4-mini 的工作者按 Token 成本约为 gpt-5.4 的三分之一。

  2. 主要成本驱动因素是累积历史,而非输出。 上下文管理(包括 /compact)是一项财务决策;第 7 章的二次增长分析解释了其形态与杠杆。

  3. 成本控制核心在于路由。 通过命名配置文件进行模型路由(第 6 章)是最有效的杠杆:将探索性工作路由到 gpt-5.4-mini,将复杂工作路由到 gpt-5.4 并开启高推理力度,让推理税物有所值。极速模式以 2 倍至 2.5 倍的积分费率倍数(仅限订阅制)换取约 1.5 倍吞吐量;在开发者等待时间本身有成本的交互式会话中使用,批处理工作则跳过。

  4. 提示词缓存是最大的单一成本杠杆。 缓存输入 Token 最高可享 90% 折扣;结合路由,稳态输入成本仅为原始未缓存费率的一小部分。

  5. 使用钩子监控。 Stop 钩子是主要机制:在本地记录会话负载、推送到 Webhook 进行团队聚合,或在超阈值时告警。PreL1-Bullet 和 PostL1-Bullet 钩子既是成本遥测,又能捕捉压缩循环。将 model_auto_compact_token_limit 设为上下文容量的 80% 至 85%,限制 tool_output_token_limit,并在 60% 时手动压缩,而非等待自动触发。第 9 章有实现细节。

  6. 大部分成本浪费藏在工作流中,而非代码。 研究报告约 28.5% 的智能体 PR 无法合并,主因是审查者放弃、重复提交和不需要的功能。后续隐性成本是人工审查时间,随智能体产出增加而上升,因概念性失败而复合。^(¹³³) 跨模型审查循环上的 SLM 分流层可将纯审查成本降低约 3 倍;双模型审查循环是成本-质量的最佳平衡点,第三个审查者增加成本快于质量。对策是收紧提交纪律,加上将低风险变更路由至自动审查,保留人工审查用于高风险变更。

  7. 长周期工作需要计划模式和目标模式。 计划模式在实施开始前暴露上下文用量,让你在 Token 消耗前拆分膨胀任务。目标模式跨会话持久化目标,消除重复工作失败模式,并增加按目标的 Token 追踪和预算感知续行。

  8. 计费和压缩存在替代路径。 AWS Bedrock(内置)和 Azure OpenAI 服务(自定义提供商)将推理合并到现有云账单上,并解锁承诺用量折扣。社区压缩工具处于两层(工具输出代理如 RTK;输出风格人格如 squeez 的原始人模式),值得针对单一工作负载调研后再部署。衡量总任务成本,而非标题式的输入节省;过度压缩可能强制要求额外解释或后续调用。

第 15 章将这一成本感知基础延伸至 CI/CD:在流水线中无头运行 Codex CLI、openai/codex-action GitHub Action、GitLab 集成,以及 PR 审查、测试生成和依赖更新的生产模式。

练习

  1. 构建团队成本估算。 以“估算团队成本”节中的演算示例为模板。构建您自己团队过去一周 Codex CLI 用量的估算。若无实际用量数据,取五个典型会话并使用 --debug 输出测量各会话类型的平均 Token 数。然后将您的估算与 OpenAI 用量仪表盘上的实际成本对比。记录模板中哪些假设与您的现实差异最大。

2. 配置基于概况的路由

将三个概况配置为单独的文件(~/.codex/explore.config.toml、~/.codex/commit.config.toml、~/.codex/reason.config.toml):一个用于探索(gpt-5.4-mini),一个用于提交(gpt-5.4),一个用于推理任务(gpt-5.4-mini 且 effort 为 high)。添加一个 Stop 钩子,将会话成本数据追加到本地日志文件中。在两天内使用适合各任务的概况运行至少十个会话。最后,分析日志并计算如果每个会话都使用提交概况,总成本会是多少。

3. 构建团队成本归因系统

如果您的团队共享一个 API 密钥或使用 Codex Cloud,请设计一个成本归因系统。为至少三种不同的用例(交互式开发、CI 流水线、自动化审查)创建单独的 API 密钥(或通过 OpenAI API 元数据字段设置成本标签)。编写一个简短的脚本或 Shell 别名,根据上下文设置正确的密钥或标签。一周后,从 OpenAI 使用仪表板拉取使用明细并验证归因是否生效。计算每个用例的成本,并确定哪个用例单位 token 成本最高,哪个用例总成本最高。

4. 审计您成本最高的会话

从您的使用仪表板或本地成本日志中,确定本月您运行的成本最高的 Codex CLI 会话。打开该会话的 transcript.jsonl(位于 ~/.codex/sessions/ 中)并分析 token 细分:系统提示词占多少,结束时的对话历史占多少,工具调用结果占多少。找出最大的单一成本贡献者,并设计一种工作流变更(提示词、上下文管理或配置),使类似会话的成本至少降低 20%。

第十五章:CI/CD 集成

前几章在人工监督每一步的交互模式下运行 Codex CLI。本章将其移入流水线,由代理无人值守运行:无头 codex exec、openai/codex-action GitHub Action、带有 Duo Agent Platform 的 GitLab CI/CD,以及包括 PR 审查、测试生成、依赖更新和安全修复在内的生产自动化模式。在代理驱动的流水线中,流水线的设计比模型的选择更重要。

学习目标

您将能够:

  1. 编写一个完整的 GitHub Actions 工作流,以自动化 PR 审查员身份运行 Codex CLI。

  2. 实现一个测试生成作业,在每次合并到 main 分支时提交新测试。

  3. 配置一个具有沙箱网络访问权限并能自动创建拉取请求的依赖更新代理。

  4. 使用 Duo Agent Platform 和 codex exec 在 GitLab CI/CD 流水线中设置 Codex CLI。

  5. 使用 --ignore-user-config 和 --ignore-rules 实现确定性、隔离的 CI 执行。

  6. 在托管的 requirements.toml 中配置主机名模式策略,以限制开发、预发布和生产运行器中允许的沙箱模式。

无头运行 Codex CLI

codex exec 接受单个提示字符串,运行代理至完成,并以零或非零状态码退出。它是本章所有模式构建的基础原语,无论是被 GitHub Action 封装、嵌入 GitLab 流水线,还是直接从 Shell 脚本运行。[1]

CI 中重要的标志:

审批策略和沙箱

第 5 章 详细介绍了审批模式(untrusted、on-request、never)和沙箱级别(read-only、workspace-write、danger-full-access);在 CI 中,您通过命令行而非 TUI 设置它们。将 -c approval_policy=never 与 -s workspace-write 配对用于自主作业,或将 -c approval_policy=untrusted -s read-only 配对用于只读分析。

--model

固定特定模型以实现可复现的运行。

--json

结构化的 JSONL 事件流。一次运行以携带 thread_id 的 thread.started 事件开始,每轮对话以携带 token 用量的 turn.completed 事件结束:

{"type":"thread.started","thread_id":"019ec094-7865-7bf0-b1a4-7a12da9c0e40"}
{"type":"turn.completed","usage":{"input_tokens":4821,"cached_input_tokens":0,"output_tokens":612}}

每行都是一个包含每次调用的时间和 token 指标的自包含对象,因此下游步骤可以在无需缓冲的情况下处理事件。这是流水线成本监控的基础:将 token 计数发送到您的可观测性栈,并在工作流超出预算时发出警报。

--output-schema

将代理的最终响应约束为 JSON Schema。结合 -o(--output-last-message),它将结果写入磁盘供下游阶段使用,无需解析和提取:直接使用后续步骤消费的已验证 JSON:

codex exec "Analyse src/ for security vulnerabilities" \
 --output-schema ./schemas/vuln-report.json \
 -o ./reports/vulnerabilities.json \
 -c approval_policy=untrusted

codex exec resume(和 --last)

重新打开之前的会话,保留完整的对话上下文,包括文件读取、工具调用和推理。这支持多阶段 CI,其中每个阶段都建立在经过验证的先前工作之上:

##### 阶段 1:生成迁移
SESSION=$(codex exec --json "Migrate auth module to OAuth2" \
-s workspace-write -c approval_policy=never \
 | jq -r 'select(.type=="thread.started") | .thread_id')
##### 阶段 2:恢复并验证
codex exec resume "$SESSION" "Run the test suite and fix any failures" \
 -s workspace-write -c approval_policy=never

--last 无需显式 ID 跟踪即可定位最近的会话,而 codex exec resume 接受 --output-schema,因此工作站上的交互式探索可将结构化输出直接馈送到流水线。远程执行器使用标准 Codex 认证进行注册,而非单独的注册表凭据,这简化了使用远程执行的 CI/CD 设置。[2]

管道上下文

通过 stdin 直接将外部数据馈送给代理:

git diff origin/main...HEAD | codex exec "Review this diff for correctness and security issues" \
 -c approval_policy=untrusted --output-schema ./schemas/review.json

此模式是 PR 审查工作流的基础:diff 提供范围上下文,approval_policy=untrusted 防止文件修改,结构化输出确保审查可被机器解析。

警告:切勿在由分叉 PR 触发的工作流中使用 --dangerously-bypass-approvals-and-sandbox(别名 --yolo);它会造成远程代码执行漏洞。[3]

提示:将大型代码库拆分为范围限定的子任务(每模块一个会话),而不是用一个会话处理所有事情。

write_stdin 非 TTY 限制

当 codex exec 在非 TTY 环境(CI 运行器、Docker 容器、管道输入)中运行时,write_stdin 工具不可用。[4] 代理无法写入正在运行进程的 stdin,因此期望键盘输入的交互式程序将会挂起。变通方法:

  1. 使用 echo "input" | command 代替交互式 stdin 写入。

  2. 配置工具以接受参数或环境变量,而非交互式提示。

  3. 对于真正交互式的工具,使用 expect 风格的封装器。

设计 CI 提示词以避免交互式工具模式。

确定性、密封的运行

CI 运行器会发生漂移。它们从缓存的主目录、Docker 层和共享镜像继承陈旧设置。三个标志组合使用,可使运行接近密封状态,因此无论哪个运行器执行作业,相同的提交、提示词和模型都会产生相同的行为。[5]

  1. --ignore-user-config:抑制 ~/.codex/config.toml,防止开发者特定的设置影响 CI 运行。

--ignore-rules

--ignore-rules:跳过当次运行的所有用户和项目 execpolicy .rules 文件,移除仓库级别的 execpolicy。Execpolicy .rules 文件是 Starlark 规则集,决定如何将提议命令与执行策略匹配(第 18 章 介绍了使用 codex execpolicy check 编写和验证它们)。该标志会传播给子代理,因此多代理编排保持持续隔离。AGENTS.md 指令仍会加载,因此这是与配置和 execpolicy 的隔离,而非与项目指导的隔离;完全密闭的运行也意味着控制检出内容包含的 AGENTS.md。

--sandbox workspace-write

--sandbox workspace-write:将文件系统访问限制在当前工作目录。

为获得最可复现的 CI 运行,请组合使用这三个标志:codex exec --ignore-user-config --ignore-rules --model gpt-5.4-mini "your prompt"。这消除了本地开发与 CI 之间、不同运行器镜像之间、以及随时间推移的流水线运行之间的配置漂移。

按环境限制沙盒模式

主机名模式沙盒策略根据运行器主机名改变允许的沙盒模式:开发运行器可使用 workspace-write 进行自动修复,而暂存和生产运行器则被限制为 read-only。它们位于托管配置(requirements.toml)中,作为 [[remote_sandbox_config]] 条目,其 hostname_patterns 与运行器的解析主机名匹配:

##### requirements.toml
allowed_sandbox_modes = ["read-only"]
[[remote_sandbox_config]]
hostname_patterns = ["dev-runner-*"]
allowed_sandbox_modes = ["read-only", "workspace-write"]

模式不区分大小写,接受 *(任意序列)和 ?(单字符)。需求源中第一个匹配的 [[remote_sandbox_config]] 条目生效;若无匹配项,则应用顶层 allowed_sandbox_modes,因此未匹配的运行器默认为最严格的策略。每个条目设置该主机上允许的模式;运行仍通过 -s 选择其实际模式。主机匹配仅用于选择策略,并非经过认证的设备证明,因此切勿单独依赖它作为安全边界。

将主机名模式策略与 --ignore-user-config 和 --ignore-rules 结合,可实现确定性、环境感知的 CI:标志消除配置和 execpolicy 漂移,主机模式则限制各环境可用的沙盒模式。

openai/codex-action GitHub Action

openai/codex-action Action 封装了 codex exec 并提供 GitHub 感知的默认值:CLI 安装、密钥注入和输出暴露。^(¹³⁹)

最简单的集成是只读 PR 总结器:Codex CLI 用通俗语言描述变更内容,后续步骤将该总结作为评论发布。这是向仓库引入该 Action 的安全方式,因为代理仅读取代码,而唯一的写入操作(评论)通过 GitHub API 进行,而非通过代理。

- uses: openai/codex-action@v1
  id: codex
  with:
    prompt: "Summarize what changed in this PR in three bullet points"
    sandbox: read-only
  env:
    OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
- name: Post summary as PR comment
  uses: actions/github-script@v7
  with:
    script: |
      await github.rest.issues.createComment({
        issue_number: context.issue.number,
        owner: context.repo.owner,
        repo: context.repo.repo,
        body: process.env.CODEX_OUTPUT
      });
    env:
      CODEX_OUTPUT: ${{ steps.codex.outputs.final-message }}

每个 PR 的自动化代码审查

这是最高价值的首选模式:每个拉取请求在打开或更新的那一刻即进行首轮审查。作业构建差异,请求 Codex CLI 进行结构化评论(逻辑错误、缺失错误处理、安全顾虑、测试覆盖缺口),并将其作为评论发布。以 read-only 运行使其保持咨询性质,为人工审查者标记问题而非修改代码。在审查延迟成为瓶颈的团队中最为有用,因为当人工打开 PR 时,代理的审查已在等待。

本节仅涵盖流水线连线:作业何时触发、如何界定差异范围、如何返回结果。使审查本身值得阅读的内容是 第 20 章 的主题,涵盖 AI 审查可靠捕获的缺陷类别(以及其无法判断的类别)、添加到 AGENTS.md 以传播团队标准的 ## Review guidelines 块、 /review 命令模式,以及多代理对抗性审查。将上述四行提示视为占位符,真正的审查评分卡在 第 20 章 中开发:流水线相同,但评论质量源自 第 20 章。

name: Codex PR Review
on:
  pull_request:
    types: [opened, synchronize, reopened]
permissions:
  contents: read
  pull-requests: write
jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Generate diff
        run: git diff origin/${{ github.base_ref }}...HEAD > /tmp/pr.diff
      - name: Run Codex review
        id: review
        uses: openai/codex-action@v1
        with:
          prompt: |
            You are a senior engineer reviewing a pull request.
            The diff is in /tmp/pr.diff.
            Identify: (1) logic errors, (2) missing error handling,
            (3) security concerns, (4) test coverage gaps.
            Format as GitHub-flavored Markdown. Be concise.
          sandbox: read-only
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
      - name: Post review comment
        uses: actions/github-script@v7
        with:
          script: |
            await github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `### Codex Review\n\n${process.env.CODEX_OUTPUT}`
            });
          env:
            CODEX_OUTPUT: ${{ steps.review.outputs.final-message }}

添加 paths 过滤器以在仅文档变更时跳过审查作业。

合并时生成测试

此模式自动弥补覆盖缺口。每次合并到 main 时,作业列出上次提交中变更的源文件,为新增或修改的函数生成单元测试,并打开一个包含结果的 PR。新测试在审查后合入而非直接推送到 main,因此生成质量不高仅导致 PR 被拒,而非测试套件损坏。适用于功能常无测试合入且覆盖率下滑的代码库。

name: Codex Test Generation
on:
  push:
    branches: [main]
permissions:
  contents: write
  pull-requests: write
jobs:
  generate-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2
      - name: Identify changed source files
        id: changed
        run: |
          git diff HEAD~1 HEAD --name-only \
          | grep -E '\.(ts|js|py|go)$' \
          | grep -v '\.test\.\|_test\.' \
          > /tmp/changed-source.txt
          echo "count=$(wc -l < /tmp/changed-source.txt)" >> "$GITHUB_OUTPUT"
      - name: Generate tests
        if: steps.changed.outputs.count != '0'
        uses: openai/codex-action@v1
        with:
          prompt: |
            For each source file in /tmp/changed-source.txt,
            write unit tests covering the public API of functions
            added or modified in the most recent commit.
            Use existing test framework and conventions.
          sandbox: workspace-write
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
      - name: Open PR with generated tests
        if: steps.changed.outputs.count != '0'
        uses: peter-evans/create-pull-request@v6
        with:
          branch: codex/tests-${{ github.sha }}
          title: "test: add generated tests for ${{ github.sha }}"
          body: "Codex-generated tests. Review before merging."
          labels: codex-generated, tests

依赖更新代理

这使得依赖保持最新,且无需像普通版本升级机器人那样需要人工照管。计划的每周作业会在安全范围内更新过时的包(仅限次要版本,绝不升级主版本),运行测试套件,回滚导致测试失败的任何依赖,并打开一个合并的 PR。其优于机械升级工具之处在于测试-回滚循环:你得到的是一个已通过测试的绿色更新 PR,而不是一堆需要分类的版本升级。gpt-5.4-mini 使得这类主要为机械操作的工作的单次运行成本保持在低位。

name: Codex Dependency Update
on:
  schedule:
    - cron: '0 9 * * 1'
  workflow_dispatch:
permissions:
  contents: write
  pull-requests: write
jobs:
  update-deps:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '22'
      - name: Run dependency update agent
        id: depupdate
        uses: openai/codex-action@v1
        with:
          prompt: |
            Update all outdated npm dependencies to latest minor versions
            (never bump major). Run npm install and npm test. If tests fail,
            revert the causing dependency. Report as JSON: updated, skipped, failed.
          sandbox: workspace-write
          model: gpt-5.4-mini
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
      - name: Open consolidated PR
        uses: peter-evans/create-pull-request@v6
        with:
          branch: codex/deps-${{ github.run_id }}
          title: "chore: weekly dependency updates"
          labels: dependencies, codex-generated

npm install 会执行更新包中的生命周期脚本。请使用网络沙箱运行器,并将出口流量限制为仅 npm 注册表。

扩展评审:项目技能与堆叠 PR

两种模式将自动化与其作用的代码一起进行版本控制,这使得自动化随着代码库的变化而保持诚实(即与时俱进)。

codex-pr-body 是一个随 CLI 代码库一起发布的项目级技能。它根据差异、提交历史和关联的 Issue 生成 PR 描述。三个设计决策使其成为 CI 集成技能的模板:

技能随代码库一起发布。 技能定义位于 .agents/skills/ 中,与其描述的代码进行版本控制。当 PR 模板变更时,技能在同一次提交中随之变更。

Sapling CLI 限制为只读。 该技能调用 Meta 的 Sapling (sl) 进行差异和堆叠分析,但将其限制为只读操作:禁止 sl push,禁止 sl rebase。此限制通过 AGENTS.md 说明强制执行(属于建议性,非内核强制)。对于生产用途,请结合 -s read-only -c approval_policy=untrusted 以实现操作系统级别的强制执行。

感知堆叠 PR 的描述。 该技能理解堆叠 PR(一串多个相互依赖的 PR),并生成引用父 PR 和子 PR 的描述,使依赖图对评审者显性化。

堆叠 PR(一串小的、相互依赖的 PR)将大型变更分解为可评审的增量,但会带来开销:每个 PR 都需要解释其在链中角色的描述,且随着堆叠变基,描述必须保持一致。gh stack 扩展自动化了机制操作(分支链、变基、PR 元数据)。将其与代理生成的描述配合,开销可降至近乎为零:

name: Stack Description Update
on:
  pull_request:
    types: [opened, synchronize]
jobs:
  update-description:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Generate stack-aware description
        uses: openai/codex-action@v1
        with:
          prompt: |
            This PR is part of a stacked PR chain.
            Analyse the diff relative to the parent branch,
            identify what this specific slice accomplishes,
            and generate a description that includes:
            - What this PR does (2-3 sentences)
            - How it relates to the parent and child PRs
            - What reviewers should focus on
            Format as GitLab-flavored Markdown.
          sandbox: read-only
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}

这种组合将堆叠 PR 从高级用户技巧转变为默认工作流。评审吞吐量增加,因为每个 PR 都足够小,可在一次会话中完成评审,而代理生成的描述消除了理解每个切片定位的上下文切换成本。

GitLab CI/CD 集成

上述模式使用 GitHub Actions,但 Codex CLI 同样能与 GitLab 配合良好。GitLab 的 Duo Agent Platform(自 2026 年 1 月起正式可用)支持 Codex CLI 作为 GitLab 18.8+ 上的外部代理。[6] 当开发者在 Issue 评论或合并请求讨论中提及 @codex 时,代理会读取仓库上下文并内联响应。认证通过 GitLab 的 AI 网关处理,因此无需单独的 OPENAI_API_KEY。[7]

.gitlab-ci.yml 中的代码质量

要在每个合并请求上进行系统分析,请将 codex exec 直接嵌入管道。关键技术是基于标记的提取:指示 Codex CLI 在分隔符之间包裹其输出,然后使用 awk 解析。[8]

codex_review:
  stage: codex
  image: node:24
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
  script:
    - npm -g i @openai/codex@latest
    - FILE_LIST="$(git ls-files | sed 's/^/- /')"
    - |
      codex exec -s workspace-write -c approval_policy=never "Review this repository and output a
      GitLab Code Quality report in CodeClimate JSON format.
      OUTPUT MUST BE A SINGLE JSON ARRAY between markers:
      === BEGIN_CODE_QUALITY_JSON ===
      <JSON ARRAY>
      === END_CODE_QUALITY_JSON ===
      Each issue needs: description, check_name, fingerprint,
      severity, location with path and lines.begin.
      Only report issues in: ${FILE_LIST}" \
      | tee artifacts/codex-raw.log >/dev/null
    - |
      sed -E 's/\x1B\[[0-9;]*[A-Za-z]//g' artifacts/codex-raw.log \
      | awk '/BEGIN_CODE_QUALITY_JSON/{g=1;next}
      /END_CODE_QUALITY_JSON/{g=0}g' \
      > gl-code-quality-report.json
  artifacts:
    reports:
      codequality: gl-code-quality-report.json
    expire_in: 14 days

codequality 制品类型使发现结果直接出现在合并请求部件中,与原生 GitLab 扫描器结果并列。[9]

在解析前从原始日志中剥离 ANSI 转义码。codex exec 可能会发出终端颜色序列,从而破坏 JSON 提取。

相同的 codex exec 方法扩展至安全修复(将 SAST 扫描结果与 Codex 生成的补丁配对)以及受益于 LLM 分析的任何其他管道阶段。对于需要 GitLab API 访问的交互式会话,请使用 codex mcp add --url 配置 GitLab 原生 MCP 服务器。[10]

GitLab 集成比 openai/codex-action 需要更多手动配置,但提供同等能力。codex exec 标志、审批模式和结构化输出在两个平台上完全相同;仅管道语法和制品格式不同。

从自动化到自主:自愈管道

迄今为止的模式将代理视为任务执行者。更成熟的集成将其视为管道健康的持续参与者。自愈 CI/CD 成熟度模型将其形式化为三个级别,每个级别映射到一种审批模式:观察者、把关者和治愈者,自主性依次递增。

级别 1:观察者(只读)

代理以只读模式监控管道日志,解析构建失败和测试输出,然后将分析作为 PR 评论或 Slack 消息发布。它绝不修改文件或重新触发作业。

使用 -s read-only -c approval_policy=untrusted 配置。代理无法执行 Shell 命令或写入文件。所有输出通过 --json 流出。

级别 2:把关者(workspace-write,有闸控)

代理提出修复(编辑源文件、更新配置、调整测试固件),但不能执行任意 Shell 命令。它对合并就绪性进行投票:若检测到无法修复的问题则发布阻断性评审,若其补丁解决了故障则批准。

使用 -s workspace-write -c approval_policy=on-request 进行配置

Agent 可以在工作区中写入文件,但在跨越沙箱边界前必须进行升级。这自然地映射到创建修复分支并打开 PR 的 CI 作业。

第 3 级:Healer(完全自主模式)

Agent 拥有完全的自主权,可端到端地诊断、修补、测试并重新触发流水线。此级别仅适用于除了 API 端点和包注册表之外无网络访问权限的物理隔离运行器,且写入权限限定在专用的 Bot 账户上。

在网络沙箱运行器上使用 -s workspace-write -c approval_policy=never 进行配置,或在命名配置文件中编码相同设置。结合 第 16 章 中的文件系统和沙箱限制,以限制影响范围。

大多数团队应在升级到第 3 级之前,在第 2 级运行数月。CI 流水线过早引入自主性会以机器速度放大错误。利用流水线可观测性来衡量 Agent 的修复准确率,然后再移除人工审批关卡。

最简单的自主模式

在完整的修复循环之前,最小的自主形式是单次 codex exec,它检测问题、修复问题并验证:

codex exec "Fix all lint errors in src/" \
 -s workspace-write -c approval_policy=never \
 --model gpt-5.4-mini

-s workspace-write -c approval_policy=never 组合授予文件写入自主权,而无需 Shell 命令审批。对于模型能力非瓶颈的机械性修复,请使用 gpt-5.4-mini。

流水线医生模式

流水线医生将 Healer 级别实现为由 workflow_run 触发的作业,该作业监视失败并尝试自主修复。^(¹⁴⁵)

https://github.com/OpenDocCN/freelearn-dl-zh/tree/master/docs/agentic-coding-with-openai-codex-cli/img/B40007_15_01.jpg

Figure 15.1 - 流水线医生修复循环

由 workflow_run 触发的修复循环流程图。失败的 CI 工作流运行触发仓库检出,随后使用 gh run view --log-failed 获取失败日志。Codex 随后诊断根因,应用最小修复,并在 workspace-write 配合 drop-sudo 下运行测试。决策节点询问修复是否高置信度:若否,则仅报告诊断且不修改任何文件;若是,则通过 create-pull-request 打开修复 PR,供人工审查并合并。

name: Pipeline Doctor
on:
  workflow_run:
    workflows: ["CI"]
    types: [completed]
jobs:
  diagnose-and-fix:
    if: ${{ github.event.workflow_run.conclusion == 'failure' }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Fetch failure logs
        run: |
          gh run view ${{ github.event.workflow_run.id }} --log-failed \
          > /tmp/failure.log
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      - name: Diagnose and fix
        uses: openai/codex-action@v1
        with:
          prompt: |
            The CI run failed. Logs are in /tmp/failure.log.
            1. Identify the root cause.
            2. Apply the minimal fix to the source code.
            3. Run the relevant test suite to confirm the fix.
            If you cannot fix it with high confidence, do NOT
            modify any files — just report the diagnosis.
          sandbox: workspace-write
          safety-strategy: drop-sudo
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
      - name: Open fix PR
        uses: peter-evans/create-pull-request@v6
        with:
          branch: codex/pipeline-doctor-${{ github.event.workflow_run.id }}
          title: "fix: auto-remediate CI failure from run ${{ github.event.workflow_run.id }}"
          body: "Pipeline Doctor diagnosis and fix. Review before merging."
          labels: codex-generated, pipeline-doctor

三点使其安全。workspace-write 沙箱是刻意为之:医生可以编辑工作区内的源文件,但无法触及工作区外的任何内容。safety-strategy: drop-sudo 输入会不可逆地移除 sudo,防止权限提升。且修复通过 PR 进行,保留了人工审查。

一个更严格的变体运行在一次性运行器上,通过 head_sha 检出失败的提交,并在同一作业内重新运行测试套件以确认补丁,然后再打开 PR,这样实际上未通过测试的修复永远不会到达审查者手中。GitLab CI 的等效方案直接使用 codex exec,配合基于标记的输出提取和 git apply --check 验证。^(¹⁴⁶) 随时间验证准确性的团队可以移除 PR 关卡,并使用 -c approval_policy=never -s workspace-write 端到端运行循环。

两个提示约束对自动修复质量至关重要。首先,始终指示 Codex CLI 进行最小变更:“不要重构无关代码”可防止范围蔓延。其次,将 git ls-files 输出提供给提示词,以防止路径幻觉。

AutoLoop:CI 中的有界优化

AutoLoop 将 Karpathy 的 autoresearch 模式(21K+ GitHub stars)推广为集成在 CI 中的有界优化循环。^(¹⁴⁷) 传统 CI 是二元的(通过/失败),而 AutoLoop 增加了迭代改进:Agent 运行有界实验,根据基准衡量适应度,并仅提交经验证的收益。

AutoLoop 将实验结果持久化到磁盘的 .autoloop/experiments/ 下,可在会话压缩和上下文限制后存活。每个实验记录变更了哪些文件、指标结果以及裁决(保留/丢弃/重跑):

.autoloop/
├── config.toml # Eval/guardrail commands, metric parsing
├── baseline.json # Starting metric snapshot
├── learnings.md # Auto-refreshed experiment insights
└── experiments/ # Timestamped iteration history
 ├── 001-reduce-alloc.json
 └── 002-batch-queries.json

安装 Codex 封装并在 CI 中运行有界优化:

##### Install AutoLoop's Codex integration
autoloop install codex
##### Run bounded optimisation
codex exec "Use autoloop-run to reduce benchmark latency. \
 Keep behaviour unchanged. Use at most 5 experiments." \
 -a never -s workspace-write

三个约束使其在 CI 中安全:优化目标(延迟)、行为不变量(正确性:所有测试必须通过)和实验预算(5 次迭代)。.autoloop/config.toml 中的护栏会自动丢弃破坏测试的实验:

[eval]
command = "cargo bench --bench api_latency"
primary_metric = "p99_latency_ms"
direction = "lower_is_better"
[guardrails.tests]
command = "cargo test"
pass_threshold = "all"

AutoLoop 精确记录每个实验中变更的文件。keep 仅提交那些路径;discard 精确还原尝试过的内容,而非整个工作树。这使其可以安全地在 CI 分支中运行,而不影响无关变更。.autoloop/ 配置适用于 Codex CLI、Claude Code、Gemini CLI、Cursor 和 OpenCode,因此使用混合 Agent 的团队可以共享实验历史;循环基础设施与 Agent 无关。

CI Agent 的安全策略

自主性提高了糟糕运行的赌注,因此护栏不是可选的。

选择正确的审批模式。 untrusted 用于只读分析。on-request 用于写文件的 Agent。approval_policy=never(通过 -c approval_policy=never 传递)仅在运行器完全物理隔离时使用。第 5 章 详细阐述了模式、沙箱级别和命名配置文件。

将 PR 内容视为不受信任的输入。 将差异写入文件;在提示词中引用该文件。切勿直接插值原始 PR 标题或正文。^(¹⁴⁸)

管理密钥。 将 OPENAI_API_KEY 作为加密的仓库密钥存储。仅在需要它的步骤上设置。

限制权限。 使用 permissions 块设置最小 GitHub 令牌作用域。^(¹⁴⁹)

流水线可观测性:Token 使用量、引导元数据和线程上下文

结构化输出为企业级 CI/CD 集成提供了所需的可观测性:面向 Agent 驱动流水线的成本归因、审计追踪和计费透明度。^(¹⁵⁰)

Token 使用量追踪。 每个 codex exec 轮次都以 turn.completed 事件结束,该事件在 usage 对象下携带 Token 计数(input_tokens、cached_input_tokens、output_tokens、reasoning_output_tokens)。无需外部插桩,即可使用这些数据进行按作业的成本归因。

引导元数据。 引导元数据捕获了会话期间影响 Agent 行为的配置:活跃模型、推理力度、审批模式以及加载了哪些 AGENTS.md 文件。在 CI/CD 流水线中,它为 Agent 为何做出特定决策提供了审计追踪,而不仅仅是它做了什么。


  1. ¹³⁴ ↩︎

  2. ¹³⁵ ↩︎

  3. ¹³⁶ ↩︎

  4. ¹³⁷ ↩︎

  5. ¹³⁸ ↩︎

  6. ¹⁴⁰ ↩︎

  7. ¹⁴¹ ↩︎

  8. ¹⁴² ↩︎

  9. ¹⁴³ ↩︎

  10. ¹⁴⁴ ↩︎

posted @ 2026-09-23 20:35  绝不原创的飞龙  阅读(20)  评论(0)    收藏  举报