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

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

原文:Agentic Coding with OpenAI Codex CLI

译者:飞龙

协议:CC BY-NC-SA 4.0

工具模式在大多数设置中主导静态前缀。一个拥有十个工具的小型 MCP 服务器约占 1,500 个 Token,但大型服务器(例如六十工具的 GitHub 客户端)会在会话开始前消耗 30,000 到 40,000 个 Token。应用和连接器更甚:单个功能完备的连接器通常消耗 10,000 到 35,000 个 Token,而安装了 GitHub、日历、邮件、聊天和文档连接器的典型桌面环境,仅模式就可能消耗超过 100,000 个 Token。如果你的工作流不使用某个连接器,请用 include_apps_instructions = false 抑制其模式,或在 config.toml 中禁用该连接器。选择性加载既是上下文规范,也是性能规范。

自定义系统提示词

表 7-1 中的系统提示词行标记为“部分可自定义”,因为 Codex CLI 提供了杠杆来控制该槽位的内容,尽管默认值对大多数会话效果良好。当默认提示词对你的工作流造成浪费时(例如针对不需要 OpenAI 产品说明的本地开源模型运行),或当你有经过策划的提示词应完全替换 Codex 默认值时,可使用这些选项。

以下是一个实战示例,源自一个真实的本地模型配置文件,该配置通过 Ollama 运行 Qwen3 以对接 Codex CLI:

##### ~/.codex/local.config.toml
model = "qwen3:32b"
model_provider = "local"
model_instructions_file = "~/prompts/minimal-codex.md"
include_apps_instructions = false
include_permissions_instructions = false

每个配置文件都是包含顶层键的独立文件,而非 config.toml 内部的 [profiles.<name>] 表。运行 codex --profile local 会先加载 ~/.codex/config.toml,再覆盖加载 ~/.codex/local.config.toml:

##### ~/.codex/config.toml
##### 所有会话共享的基础键位于此处

此处有四个键在起作用,外加一个值得了解的相关设置,它们属于同一配置层面:

  1. model_instructions_file 指向一个 Markdown 文件,可整体替换默认模型指令。当针对非 OpenAI 后端运行(其系统提示词完全不应像默认值),或当你花时间调整了极简提示词并希望其全局生效时,此项很有用。

  2. developer_instructions 是一段额外指导字符串,会被前置到提示词中而不替换默认值。适用于一两条项目级规则,它们应与默认行为并存而非覆盖它。

  3. include_apps_instructions 和 include_permissions_instructions 是布尔值,用于抑制默认提示词的子章节。当你不使用 Apps,或已在默认流程外配置权限导致该子章节成为累赘时,此项很有用。

  4. personality (none、friendly、pragmatic) 交换默认提示词中的沟通风格块。本身不是上下文预算杠杆,但作为同一配置层面的一部分值得了解。

示例中的四个键共同将默认指令替换为调优的极简提示词,并丢弃了原本会闲置在系统提示词中的两个子章节。第 4 章 涵盖 AGENTS.md,它贡献于同一个“Codex 在你的提示词前读取的所有内容”整体表面;这四个键位于 AGENTS.md 之上一层,作用于系统提示词本身而非项目文档。

/compact 命令与自动摘要

/compact 命令指示模型生成对话的结构化摘要,用该摘要替换累积的历史记录,并从压缩后的状态继续。

/compact 能够很好保留的内容

  1. 会话开始时陈述的高层级目标

  2. 已做出的决策及其陈述的理由

  3. 被识别为重要的文件(按名称和大致用途)

  4. 任何进行中任务的当前状态(已完成什么,还剩什么)

/compact 无法保留的内容

  1. 已读取文件的完整内容(摘要按名称引用文件,但不保留其内容)

  2. 确切的错误消息或编译器输出(这些以散文形式进行了总结)

  3. 工具调用的确切顺序和中间状态

  4. 通过对话语气或未明说的假设隐性建立的任何上下文

执行 /compact 后,您可能需要重读之前依赖其内容的文件。摘要是有损压缩,而非无损归档。

切勿依赖 /compact 来保留确切的代码片段或差异内容。 在压缩前将未保存的更改写入磁盘;仅存在于对话历史中的差异将被简化为散文描述。

Codex CLI 还支持自动压缩。当上下文达到令牌阈值时,CLI 会自动触发摘要生成。在 ~/.codex/config.toml 中调整此阈值:

model_auto_compact_token_limit = 110000 # 在此令牌数触发压缩

较低的限制在压缩后提供更多工作空间,但意味着压缩频率更高。较高的限制保留更多历史,但有在压缩完成前耗尽空间的风险(压缩本身也需要令牌)。

/compact 可能会丢弃关键指令。 当上下文被总结时,模型决定保留什么和压缩什么。嵌入在早期对话轮次中的安全关键规则(例如“执行前务必确认”、“未经询问绝不删除”)可能会被总结而消失。在一个广为人知的事件中,一位 AI 安全研究员的个人代理有一条要求在任何行动前确认的规则。

上下文压缩总结了早期对话后,该代理完全忘记了该规则,并开始自主删除电子邮件。被质问时,它承认违反了该约束。教训是:必须在压缩中幸存的指令应放在系统提示词或 AGENTS.md 中,而不应放在可能被压缩总结得荡然无存的对话轮次中。

生命周期钩子:自动化上下文保留

PreCompact 和 PostCompact 生命周期钩子为压缩的有损特性提供了编程式解决方案。PreCompact 钩子在压缩开始前触发,给脚本一个机会将关键状态(变量映射、文件校验和、任务进度)保存到上下文窗口外的 JSON 文件中。PostCompact 钩子在压缩完成后触发,将该状态作为系统消息重新注入新的上下文中。

{
  "hooks": [
    {
      "event": "PreCompact",
      "command": ["bash", "scripts/save-context-state.sh"]
    },
    {
      "event": "PostCompact",
      "command": ["bash", "scripts/restore-context-state.sh"]
    }
  ]
}

这种压缩-记忆模式确保了您无法承受丢失的状态能在不依赖模型总结判断的情况下跨越压缩边界。完整的 PreCompact/PostCompact 参考(包括 stdin JSON 字段:session_id、turn_id、trigger)和工作示例请参见第 9 章。

后台前缀压缩

手动 /compact 和阈值触发的自动压缩都是被动的:它们在上下文压力已经很大时才触发。Codex CLI 通过后台前缀压缩对其进行补充,该功能在代理继续工作时预热稳定早期轮次的摘要。当窗口随后接近容量时,该预热摘要已准备就绪,因此从完整历史到压缩状态的过渡比即时即席总结要短得多。对用户的实际效果是长会话感觉更流畅;对预算而言,本章关于阶段边界和委派的其他指导仍然适用。

子代理委派作为上下文管理

第 12 章 全面涵盖了子代理编排。这里我们从另一个角度审视委派:作为一种刻意的上下文管理技术。

另请参阅: 第 11 章 (子代理) 了解 TOML 定义、spawn_agent/wait_agent 工具、并行执行模式和内置代理角色。

当您为子任务生成一个子代理时,它在一个独立的上下文窗口中运行。子代理从零开始:它不继承您会话的累积历史、读取的文件或工具结果。它仅接收您在委派指令中传递的内容。当子代理完成时,它向父会话返回一个摘要,通常为 200 到 800 个令牌,无论子代理内部消耗了多少令牌。

假设您正在跨四个包重构 20 个服务文件。在一个会话中处理所有 20 个文件可能在任务中途达到 80,000 个令牌。每个包派遣一个子代理会改变经济账:每个子代理读取其文件、进行编辑、运行测试并返回简短摘要。父会话收到四个摘要,总计可能 2,000 个令牌,而令牌密集型工作发生在隔离的子窗口中。

在为大型任务设计委派边界时,要考虑父会话需要知道什么与需要记住什么。父会话需要结果,而非逐字记录。编写委派指令时,应要求简洁、结构化的摘要,而不是每一步都详述的冗长记录。

委派本身也有开销:每次调用需要一条指令(100 到 400 个令牌),且返回的摘要会消耗父上下文。一个粗略的启发式规则:当子任务需要读取超过三四个大型文件、运行超过少量的 Shell 命令,或迭代超过两轮错误修复循环时进行委派。低于该阈值时,开销与内联成本相当。

大型代码库的策略

有几种策略可帮助您在大型代码库中保持在预算内。

定向文件包含

仅读取与任务直接相关的文件。先读取目录结构和 grep 输出,然后仅读取被识别为相关的特定文件。每一次投机性的文件读取都是您必须支付的令牌成本,无论该文件最终是否有用。

滚动总结

定期执行 /compact,而不是等待自动压缩。完成每个主要阶段后(例如,在开始下一个模块之前完成对某个模块的所有更改)手动执行 /compact。这捕获的是连贯的阶段边界,而非任意的任务中间状态。

项目文档文件

一份编写良好的 AGENTS.md 通过提前提供以下内容,可替代数十次探索性的文件读取:目录结构、正在使用的约定、构建/测试/lint 命令以及非显而易见的约束。这些节省会在项目的每个会话中累积。

AGENTS.md 文件可以嵌套。根级别的 AGENTS.md 提供项目级上下文,而子目录中的 AGENTS.md 文件提供模块级上下文,仅在代理进入该目录时加载。利用此层级结构保持根级文件简洁,并将模块特定细节推迟到本地文件中。

project_doc_max_bytes 配置键

对于 AGENTS.md 开销超过任务本身令牌成本的窄任务,可在 config.toml 中限制或禁用加载:

### 完全禁用 AGENTS.md 加载
project_doc_max_bytes = 0
### 或按调用覆盖
codex -c project_doc_max_bytes=0 "run the payment-service unit tests and report failures"

使用场景:自动化调用

在自动化调用中最为实用,此时任务已在提示词中完全指定。在不熟悉的代码库上进行交互式会话时,几乎总是希望加载 AGENTS.md。

作用域划分的 MCP 服务器加载

仅加载与当前任务相关的服务器。如果任务仅涉及文件系统操作和 Shell 命令,则无需加载数据库检查或浏览器自动化服务器。

监控上下文使用情况

/status 命令会打印活动会话的配置和当前上下文窗口利用率:

>_ OpenAI Codex (v0.137.0)
Visit https://chatgpt.com/codex/settings/usage for up-to-date
information on rate limits and credits
Model:                gpt-5.5 (reasoning medium, summaries auto)
Directory:            ~/projects/example-app
Permissions:          Workspace (Approve for me)
Agents.md:            ~/.codex/AGENTS.md, AGENTS.md
Account:              user@example.com (Enterprise)
Collaboration mode:   Default
Session:              <session-uuid>
Context window:       73% left (77.6K used / 258K)
5h limit:             [█████████████████░░░] 87% left (resets 23:05)
Weekly limit:         [██████████████░░░░░░] 72% left (resets 08:54 on 11 Jun)

诊断行是 Context window,它同时给出剩余百分比和已用与总 Token 数的绝对值。/status 不会按类别细分总量:表 7-1 中的按类别数据是各消费者预期贡献的数值,而非 /status 会打印的内容。要衡量特定消费者的成本,请在启用或禁用它前后运行 /status,并从 Context window 行读取增量。

5h limit 和 Weekly limit 条是速率限制预算,而非上下文预算。它们对长周期工作很重要,因为触及任一限制都会结束会话,无论上下文窗口还剩多少。要随时查看前缀的按组件细分,请使用 codex debug prompt-input,如前文所述。

对于脚本化用法,可通过 -c 每次调用覆盖自动压缩阈值。会话跨越限制时代码会自动压缩:

codex -c model_auto_compact_token_limit=50000 "audit the authentication module for hardcoded credentials"

养成在阶段边界检查 /status 的习惯:大规模文件调查前、长序列编辑后,或可能产生冗长输出的操作前。尽早发现上下文压力能给你更多选择。

如果 Shell 命令可能产生冗长输出(完整测试套件运行、带大量警告的构建、日志转储),请在返回代理前通过过滤器管道传输输出。例如,make 2>&1 | tail -50 将构建命令的上下文成本限制在最后 50 行(通常包含相关错误),而非完整输出。

上下文监控也为委托决策提供依据。在 60% 容量且剩余大量工作时,委托给子代理可能值得额外开销。在 30% 容量且剩余工作适度时,继续内联更简单。

L1-Bullet 实际工作原理

前文介绍的 /compact 命令和自动压缩阈值是 Codex CLI 的用户可见控制。其下是压缩机制本身,值得理解,因为它解释了阈值触发后运行时的某些行为。

Codex CLI 的压缩主要是 服务端操作,而非客户端可见的文本摘要。[⁵⁵] 当达到自动压缩阈值时,运行时遵循双层策略:

  1. 会话内存 L1-Bullet。系统首先检查会话内存中已存储的结构化信息(任务状态、文件编辑历史、关键决策)是否可替代完整的 LLM 摘要调用。大多数自动压缩走此路径,完全避免 LLM 调用。

  2. 通过 Responses API 的服务端 L1-Bullet。若会话内存不足,Codex 发起 Responses API 调用,返回会话继续所需的压缩产物。该产物对客户端不透明,这使其防篡改,能保留比纯文本摘要更多状态,并给服务端留出空间在无需客户端变更的情况下演进压缩算法。[⁵⁶]

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

图 7.1 - 压缩工作原理

决策流程:达到自动压缩阈值时,运行时检查会话内存是否足够。若足够,运行会话内存 L1-Bullet,无 LLM 调用;若不足,通过 Responses API 进行服务端压缩,返回不透明的压缩产物。任一路径均导致会话继续前重读最多五个最近编辑的文件。若未达阈值,会话直接继续。

确切的端点和载荷形状未在公开 Codex CLI 参考中记录;本节引用来自社区逆向工程,而非供应商规范。

自动压缩阈值按以下公式计算:

effective_window = model_context_window - min(max_output_tokens, 20000)
threshold = effective_window - 13000

对于拥有 200,000 Token 上下文窗口的 GPT-5.3-Codex,该公式得出约 167,000 Token。根据 OpenAI 发布的模型文档,GPT-5.4 和 GPT-5.5 均提供 1,000,000 Token 上下文窗口,而 GPT-5.4-mini 上限为 400,000 Token。[⁵⁷] 实际效果是 GPT-5.4 和 GPT-5.5 会话在触发压缩前可维持更多轮次,但本章开头所述的二次增长问题依然适用:即使 1M Token 最终也会耗尽。在 config.toml 中覆盖自动压缩阈值:

model_auto_compact_token_limit = 150000

压缩后,Codex CLI 注入一条引导消息并自动重读最多五个最近编辑的文件,总预算 50,000 Token(每文件 5,000 Token)。这能保持代码状态最新,但引入成本:重读周期每次压缩增加最多 50,000 Token,在反复压缩的会话中会累积。

当前 Codex 模型直接针对该工作流训练,因此在 60% 至 70% 利用率时主动压缩是可靠模式,而非权宜之计。

实用要点:将压缩视为工具而非安全网。在任务边界而非容量极限时 L1-Bullet。在 AGENTS.md 中前置持久上下文。使用子代理处理隔离工作,避免在单一会话中累积所有内容。要使运行时倾向于更早压缩,请在 config.toml 中降低阈值:

### config.toml: 在较低 Token 计数时主动压缩
model_auto_compact_token_limit = 90000

计划模式与全新上下文实现

规划在编写任何代码前就会消耗上下文。计划模式通过将规划与实现分离到不同的上下文生命周期中来解决此问题。规划阶段结束时,计划模式显示上下文使用百分比。若规划消耗 30%,在同一会话继续;若消耗 70%,则为实现启动全新会话。

#### 阶段 1:在专用会话中规划,然后在 TUI 内运行 /plan
codex "refactor the payment module to use the new gateway abstraction"
#### /plan
# 计划模式输出包含上下文使用情况:
# Plan complete. Context usage: 67% (85,760 / 128,000 tokens)
# Recommendation: start a fresh session for implementation.
#### 阶段 2:在全新上下文中实现
codex "implement the following plan: [paste or reference the plan]"

计划本身(改什么、按什么顺序、为什么)是跨越上下文边界的唯一产物。其他一切留在原处。这与子代理委托配合良好:实现会话按模块派发子代理,每个子代理从干净上下文启动,仅接收其对应的计划部分。

计划模式的上下文使用情况显示

即使您打算继续在同一个会话中工作,计划模式的上下文使用情况显示也很有用。在继续之前检查百分比,可以让您做出明智的选择,而不是在实施过程中发现上下文几乎耗尽。

提示词缓存:长会话的经济账

二次增长的问题真实存在,但这并不直接等同于成本的二次增长。Codex CLI 在 Responses API 上使用前缀缓存,这改变了经济模型。

首次发送特定的提示词前缀时,模型会为该前缀计算并缓存其内部的键值状态。后续共享相同前缀的轮次将跳过对缓存部分的重新计算,且缓存令牌的价格比未缓存令牌便宜得多。

被缓存的内容(高命中率)

  1. 系统提示词(同一会话中每一轮的内容均相同)

  2. 绝大部分对话历史(轮次之间不变的部分)

不被缓存的内容

  1. 最近的用户消息(始终是新的)

  2. 最近的工具调用结果(始终是新的)

设计启示

在会话期间保持 AGENTS.md 稳定。在会话中途更改它会导致系统提示词缓存失效,而这通常是最大的缓存前缀,也是负责最大成本节约的部分。如果您想为未来的会话更新 AGENTS.md,请在会话之间进行,而不是在长时间运行的中途。

结构化提示词以最大化缓存命中

Codex CLI 的提示词架构经过精心设计,旨在最大化可缓存的前缀。提示词布局将最稳定的内容放在最前面:系统指令、工具定义、沙盒配置、环境上下文、来自配置的开发者指令、聚合的 AGENTS.md 内容,随后是用户查询,最后是不断增长的对话历史。因为每一轮仅追加新消息而不修改早期消息,从系统指令到上一轮输出的整个前缀保持逐字节相同,从而符合缓存复用条件。[⁵⁸]

对于当前的 GPT-5 系列,缓存输入令牌的计费价格约为标准输入令牌价格的 10%,即打 9 折优惠。[⁵⁹] 在系统提示词稳定、工具配置一致且结构良好的会话中,首轮之后 80% 到 90% 的输入令牌将命中缓存。实际节省约为典型会话总输入令牌成本的 70% 到 80%,具体取决于会话长度以及静态前缀与每轮新增内容的比率。

以下几种模式有助于您保持在高缓存命中率区间内:

  1. 在 config.toml 中显式设置 project_root_markers,以便 AGENTS.md 发现遍历在会话之间停止在已知目录。例如,project_root_markers = [".git", "pyproject.toml", "package.json"] 将遍历固定到项目实际拥有的那些标记之一。这使得组装后的前缀在会话之间保持逐字节相同,并有资格复用缓存。

  2. 预先声明所有 MCP 服务器,而不是在会话中途添加。禁用的服务器占用前缀空间但保持缓存稳定;建议禁用而非移除。

  3. 避免在系统指令中使用动态内容。 config.toml 指令中的时间戳、构建编号或生成的内容会导致每次会话都使缓存失效。

  4. 尽可能延迟压缩,因为每次压缩都会重置前缀,并强制后续轮次发生缓存未命中。

持久化上下文:双阶段记忆管线

迄今为止讨论的一切都关乎会话内上下文:在单次对话中累积并在会话结束时消失的令牌。但 Codex CLI 还维护着一个持久化记忆层,可跨会话传递知识,减少每天重复解释约定、偏好和项目特定事实的需求。会话开始时注入的记忆消耗与其他提示词内容相同的令牌空间,因此了解管线原理对上下文预算至关重要。[⁶⁰]

管线分两阶段运行。阶段 1(提取) 在会话空闲足够长的时间后生成轻量级代理,处理已完成的会话回放,将事实、决策和偏好提取为结构化的 Markdown 以及紧凑的会话摘要。阶段 2(巩固) 定期将阶段 1 的输出合并到 ~/.codex/memories/ 下的全局记忆文件中。每个 codex_home 同一时间仅运行一个巩固进程,使用增量水印以避免全量重建。[⁶¹]

~/.codex/memories/ 下的存储布局反映了这两个层级:

  • memory_summary.md 是启动时的主要注入内容(上限约 5,000 个令牌)

  • MEMORY.md 存放长格式的合并记忆,可通过 grep 搜索

  • raw_memories.md 包含巩固前的提取内容

Markdown 文件旁的 SQLite 数据库是记录系统;Markdown 文件是为提示词注入而生成的读取优化视图。

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

图 7.2 - 双阶段记忆管线

阶段 1 提取在其边界处进行秘密清理,将阶段 1 的事实和摘要送入阶段 2 巩固,后者将它们合并到 SQLite 记录系统数据库中。基于差异的遗忘标记陈旧事实,使用感知选择驱逐极少引用的事实,均在同一数据库中完成,随后生成读取优化视图,用于 memory_summary.md、MEMORY.md 和 raw_memories.md 文件。

三种行为保持记忆的紧凑与最新:

  1. 秘密清理 在阶段 1 边界运行,在提取输出到达磁盘前清除会话中输入的凭据。将其视为纵深防御,而非唯一防线;尽可能避免将密钥粘贴到会话中。

  2. 基于差异的遗忘 防止记忆变成纯追加模式。当新提取与现有事实矛盾时(例如项目从 Jest 切换到 Vitest),巩固代理将旧事实标记为陈旧;经过宽限期后从 SQLite 中删除。

  3. 使用感知选择 以牺牲极少引用的记忆为代价保留高频引用的记忆:这是 LRU 缓存驱逐策略的语义类比。

[memories] 表中的三个键控制管线运行频率:

[memories]
min_rollout_idle_hours = 6     # 线程变得符合条件前的空闲小时数(默认 6,范围 1–48)
max_rollout_age_days = 30      # 考虑的最大线程天数(默认 30,范围 0–90)
max_rollouts_per_startup = 16  # 每次启动处理的候选数(默认 16,最大 128)

管线在会话启动时运行;始终运行的持久化代理可能需要重启 Codex CLI 以使更改生效。较低的空闲小时数可更早浮现记忆;较长的年龄窗口可重访较旧线程;较小的批次可减少每次启动的工作量,但会导致队列变长。

使用 /memories 在不离开 TUI 的情况下切换记忆注入和生成。定期检查 ~/.codex/memories/MEMORY.md 值得花时间:验证巩固代理的摘要是否准确,并编辑任何已漂移的内容(它是纯 Markdown)。

MCP 记忆服务器:持久化跨会话上下文

内置记忆系统存储事实并在会话开始时加载,但它使用简单的基于文件的加载,而非语义搜索。当您的项目跨越数月工作、包含数千个决策时,平铺加载就会失效。MCP 记忆服务器通过模型上下文协议提供可搜索、语义索引的持久化记忆,填补了这一空白。

两类记忆服务器

有两类记忆服务器值得了解。

情景记忆服务器(例如 Basic Memory[1])将决策、交接和自由文本笔记存储为可搜索的 Markdown,通常具有跨客户端互操作性,因此你在 Codex CLI 中编写的笔记也可在其他支持 MCP 的工具中使用。

结构记忆服务器(例如 codebase-memory-mcp[2])通过静态分析对代码本身进行索引,为代理提供按需的结构化理解,这种理解在会话重启后依然保留。

这两类服务器是互补而非替代关系。

记忆服务器会向系统提示词添加 MCP 工具模式,从而消耗 Token。典型的情景记忆服务器消耗几百到几千个模式 Token,与其节省的文件读取和重新发现工作量相比,这点开销微不足道。请将上述名称视为示例而非背书,并查阅各服务器自身的文档以了解当前状态。

团队记忆:超越个人回忆的鸿沟

当多名开发者及其代理在同一代码库上工作时,每个代理都会积累知识(架构决策、调试见解、 API 怪癖),但这些知识会随会话结束而消失,或被锁定在某个开发者的 ~/.codex/memories/ 中。下一位开发者只能冷启动。

跨开发者的团队记忆仍是 Codex CLI 原生记忆系统的一个短板。内置的两阶段管道在设计上仅支持单用户:~/.codex/memories/ 仅限于单一主目录,且整合代理没有跨开发者合并知识的机制。在 Codex CLI 推出原生团队记忆功能前,最实用的变通方案是:通过 Hook 接入一个由 Git 托管的共享知识仓库——SessionStart Hook 拉取最新的团队知识,并将近期决策作为 additionalContext 注入;Stop Hook 将结构化的会话摘要写回共享仓库。第 9 章 详细介绍了 Hook。

上下文失效模式:分类学

实际会话中会反复出现四种上下文失效模式,每种都有其特征标识和相应的缓解措施。

1. 上下文中毒

过时信息导致模型产出看似自信实则错误的输出。常见来源:引用已重命名命令的陈旧 AGENTS.md、重构前的缓存文件内容、或不再匹配服务器 API 的 MCP 工具描述。

缓解措施: 定期修剪 AGENTS.md(第 4 章)、重大重构后重启会话、记忆管道中的基于差异的遗忘机制。

2. 上下文干扰

无关信息稀释了注意力。模型大致均等地分配对所有 Token 的关注;无关内容会与核心内容争夺注意力。

缓解措施: 减少上下文覆盖面。针对窄任务设置 project_doc_max_bytes = 0。仅保留所需的 MCP 服务器。应用 第 4 章 的修剪清单。

3. 上下文混淆

相互矛盾的信息迫使模型解决冲突,往往导致错误结论。最常见的来源是分层的 AGENTS.md 文件,其中父级和子级给出相互冲突的指令。

缓解措施: 使用 AGENTS.md 诊断命令(第 4 章)识别冲突。应用 AGENTS.override.md 使优先级显式化。

4. 上下文冲突

在提示词或 AGENTS.md 中提供参考实现,会导致模型过度锚定于示例,而非从第一性原理推理。对推理模型的评估显示:即使参考解法在技术上是正确的,只要它们出现在上下文中,就会导致准确率显著下降。

缓解措施: 不要在 AGENTS.md 中包含示例解法或参考实现。如必须提供示例,请明确标注为“仅供参考,请勿复制”,并将其与任务描述隔离。

工具悖论作为上下文管理的发现

一个相关的观察:裁剪可用工具集可显著提升代理准确性。工具悖论既是工具选择问题,也是上下文管理问题;MCP 服务器描述是最大的固定成本上下文消费者之一,每个不必要的服务器都会降低信噪比。

在平台层面,Codex CLI 会过滤延迟工具:已注册但尚未加载的 MCP 工具不会出现在模型的工具列表中,因此模型不会浪费 Token 去调用一个尚未就绪的工具。

查找过往会话

Codex CLI 提供对话历史搜索:跨所有本地会话的不区分大小写内容匹配,并带有结果预览。你无需凭记忆大致回忆对话发生的时间,或在按日期排序的会话列表中翻找,而是可以按讨论内容搜索。

搜索会对会话内容(提示词、响应和工具输出)建立索引,并返回匹配的会话,同时提供上下文片段显示匹配发生的位置。这对于跨天任务尤为宝贵——你知道上周解决过某个问题,却记不清是哪个会话包含了解决方案。

Codex CLI 将记忆运行时状态保存在专用的 SQLite 数据库中。这支撑了对话历史搜索的查询性能,并为本章前文所述的记忆管道提供了坚实基础。

现状总结

上下文窗口是会话对自身所知的全部。第 8 章 转向一个相关问题:代理还能与什么对话。MCP(模型上下文协议)让 Codex CLI 能够消费外部工具并将自身暴露为工具。你添加的每个服务器都带来新能力和新的上下文成本,而你刚在本章学会管理的这种权衡,正是你用来决定哪些 MCP 值得保留的杠杆之一。

总结

  1. 上下文成本呈二次增长。 每一轮都会重发全部历史;由于 Token 持续流入,仅通过 /compact 或委托流出,窗口呈不对称填满。

  2. Resume 和 Fork 保留上下文。 codex resume --last 延续中断的会话;codex fork 在不丢失原会话的情况下创建分支。冒险操作前先 Fork;中断工作后用 Resume 继续。

  3. /compact 是有损压缩。 它无法保留完整文件内容或精确错误文本,且未保存的更改必须先写入磁盘。内部机制上,Codex CLI 分两级压缩(优先会话记忆,次之服务端),并预热早期稳定轮次的摘要。PreL1-Bullet/PostL1-Bullet Hook(第 9 章)可自动化跨压缩边界的状态保留。

  4. 子代理委托是一种上下文策略。 Token 密集型子任务在独立窗口运行并返回简洁摘要,保持父会话精简。

  5. 计划模式将规划与实现分离。 拥有独立的上下文生命周期;规划后显示的上下文使用率百分比,提示你是继续在同一会话中,还是另起炉灶。

  6. 提示词缓存降低输入成本。 对于 GPT-5 系列,缓存输入 Token 按标准费率的约 10% 计费(即 9 折优惠)。结构化提示(稳定的系统指令、确定性的工具顺序、延迟压缩)可在典型会话中实现 80% 至 90% 的缓存命中率,并节省 70% 至 80% 的输入成本。

  7. 大型代码库的纪律。 针对性文件包含、阶段边界的滚动摘要、维护良好的 AGENTS.md、选择性加载 MCP 服务器。针对窄脚本任务设置 project_doc_max_bytes = 0;使用 /status 和 -c model_auto_compact_token_limit=<N> 进行可观测性监控和阈值控制。

第十章:MCP:消费与服务

8.1 双阶段记忆管线

双阶段记忆管线将知识跨会话传递。先提取,后巩固;密钥清洗、基于差异的遗忘和感知使用的选择,使 ~5,000 个 token 的注入预算保持最新。当扁平文件模型不足时,MCP 记忆服务器增加了语义搜索和跨客户端共享。开发者间的团队记忆仍是一个空白;在 Codex CLI 发布原生团队记忆之前,基于 git 的 SessionStart/Stop 钩子模式是最实用的变通方案。

练习

练习 7-1. 在你常用的项目上运行一个全新的 Codex CLI 会话。在发布任何任务指令前,使用 /status 记录基线 上下文窗口 使用量。在 config.toml 中禁用一个 MCP 服务器或连接器,重启 Codex CLI,再次运行 /status。差值告诉你该单个消费者的成本;由此,你可以决定哪些其他连接器和 MCP 服务器值得占据前缀位置。

练习 7-2. 阅读与一项非平凡任务相关的 5 到 10 个源文件,然后执行 /compact。尝试在不重读的情况下使用其中某个文件的信息。记录模型保留了什么,以及什么需要重新阅读。这说明了何时应提前读取文件,何时应在使用前立即读取?

练习 7-3. 选择一个涉及三个或更多模块的任务。一次作为单会话实现,一次每个模块使用一个子代理实现。通过 /status 对比峰值上下文使用量。在何种任务规模下,委派会变得明显有利?

练习 7-4. 选择一个同时需要规划和实现的任务。先作为单会话运行,记录规划/实现边界处的上下文使用量。然后再次运行,在一个会话中使用规划模式 (/plan),在一个全新的会话中进行实现。对比两种方法在实现阶段可用的剩余空间。

8.2 60 秒读懂 MCP

MCP 是一个开放标准,允许任何 AI 代理通过单一的共享接口与任何外部工具对话。你无需为 GitHub 编写自定义集成代码,再为数据库写不同的代码,再为文档服务器写另一套代码,只需将每个服务配置一次为 MCP 服务器,代理即可通过同一协议消费所有服务。可将其视为代理能力的 USB-C:一个接口,多种设备。

以下是将 Codex CLI 连接到 GitHub MCP 服务器的示例:

步骤 1. 导出 GitHub MCP 服务器读取的环境变量:

export GITHUB_PERSONAL_ACCESS_TOKEN="ghp_your_token_here"

从同一个 Shell 启动 Codex CLI,或从一个能透传此环境变量的启动器启动。避免将 token 直接放入 config.toml;MCP 服务器进程应在启动时继承它。

步骤 2. 将服务器添加到你的 ~/.codex/config.toml:

[mcp_servers.github]
enabled = true
command = "npx"
args = ["-y", "@modelcontextprotocol/server-github"]
enabled_tools = ["create_issue", "list_issues", "get_file_contents", "search_code"]

步骤 3. 重启 Codex CLI 并让它使用 GitHub:

codex "List the open issues labelled 'bug' in our main repo and summarise them"

代理调用 GitHub MCP 服务器的 list_issues 工具,接收结构化数据,并对其进行推理。相同的模式适用于数据库、浏览器自动化、文档服务以及任何拥有 MCP 服务器实现的系统。

8.3 MCP 是什么(以及不是什么)

MCP 是一个开放协议,定义了代理到工具通信的标准接口。MCP 服务器通过通用线路协议暴露工具、资源和提示词;任何兼容的客户端(Codex CLI、Claude Code、Cursor)均可在无需自定义集成代码的情况下消费它们。

MCP 是一个供应商中立的标准,已捐赠给 Linux 基金会的智能体 AI 基金会 (AAIF),并通过开放规范流程进行治理。

MCP 提供的内容:

  1. 一种跨语言和平台边界发现和调用工具的标准方式

  2. 工具输入和输出的类型化接口

  3. 除工具外,还包含资源(文件、数据)和提示词(模板)的协议

  4. 基于工具注解和能力作用域的安全模型

MCP 不提供的内容:

  1. 它不是代理间通信协议。关于代理间协调,请参阅 Google 的 Agent-to-Agent (A2A) 协议。MCP 连接代理与工具;A2A 连接代理与代理。

  2. 它不是模型服务协议。MCP 服务器提供能力(工具、数据);它们不提供模型推理服务。

8.4 架构:主机、客户端与服务器

MCP 采用三层架构:

  1. 主机: 用户交互的应用程序(Codex CLI、IDE、Web 应用)。一个主机管理一个或多个客户端。

  2. 客户端: 主机内部的协议级连接器,维护与恰好一台服务器的会话。Codex CLI 为每个配置的服务器创建一个客户端。

  3. 服务器: 一个通过 MCP 协议暴露工具、资源和提示词的进程。服务器作为子进程运行(stdio 传输)或作为网络服务运行(HTTP 传输)。

主机决定连接哪些服务器、向模型暴露哪些工具,以及如何处理工具调用的审批。服务器无法看到其他服务器或模型的推理过程。

MCP 主机包含 Codex CLI,后者管理每个已连接服务器的一个客户端。每个客户端维护与恰好一台服务器的会话:一个作为子进程连接到 stdio 传输服务器,另一个作为网络服务连接到 HTTP 传输服务器。

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

图 8.1 - MCP 主机、客户端和服务器

8.5 工具注解:风险词汇表

MCP 工具注解将机器可读的风险元数据附加到每个工具上,使主机能做出明智的审批决策:

{
  "name": "delete_file",
  "annotations": {
    "readOnlyHint": false,
    "destructiveHint": true,
    "idempotentHint": false,
    "openWorldHint": true
  }
}

| 注解 | 含义 |

| :--- | :--- |

| readOnlyHint | 工具仅读取数据;无副作用 |

| destructiveHint | 工具可能删除或覆盖数据 |

| idempotentHint | 重复调用产生相同结果 |

| openWorldHint | 工具与代理沙箱外的系统交互 |

Codex CLI 使用这些注解通过适当的审批路径路由工具调用。在 on-request 模式下,readOnlyHint: true 的工具可能会自动批准;无论何种模式,destructiveHint: true 的工具始终需要显式批准。

构建自己的 MCP 服务器时,请诚实地标注每个工具。保守的注解(将仅具写能力的工具标记为破坏性)比乐观的注解更安全。操作员可以放宽审批要求;但无法撤销因工具声称是只读而被自动批准的破坏性操作。

连接常用服务器

GitHub

规范的第一个 MCP 服务器。配置见开篇示例。大多数工作流有用的 enabled_tools:

  1. list_issues、create_issue、get_issue:问题分类与管理

  2. get_file_contents、search_code:跨仓库代码搜索

  3. create_pull_request、list_pull_requests:PR 自动化

使用 enabled_tools 仅将工作流所需的工具列入白名单。不受限制的 GitHub MCP 服务器会暴露数十个工具,即使代理从未使用它们,每个工具也会消耗上下文窗口空间。

使用 Context7 进行文档查找

@upstash/context7-mcp 是 Upstash 的 MCP 服务器,用于获取代理遇到的任何库的当前、特定版本的文档。没有它,代理会从训练数据中回答库相关问题,这些数据可能已过时数月。有了它,代理会在您正在使用的版本中查找实际文档。

[mcp_servers.context7]
enabled = true
command = "npx"
args = ["-y", "@upstash/context7-mcp"]
enabled_tools = ["resolve-library-id", "query-docs"]

resolve-library-id 将自由形式的名称(“Next.js”、“MongoDB”)转换为 Context7 的规范 ID;query-docs 检索与特定问题匹配的文档片段。本章作者几乎在每个会话中都运行 Context7。

使用 Playwright 进行浏览器自动化

@playwright/mcp 是微软官方的 Playwright MCP 服务器。它驱动真实浏览器,无需视觉模型:服务器暴露结构化的无障碍快照,代理可对其进行推理,这使其非常适合 CI 中的表单填写、导航和 DOM 级断言:

[mcp_servers.playwright]
enabled = true
command = "npx"
args = ["-y", "@playwright/mcp@latest"]

默认工具集涵盖导航、点击、输入、快照和标签页管理。其他功能(网络模拟、存储操作、视频录制、PDF 生成、基于坐标的鼠标操作、测试断言)可通过命令行上的 --caps=... 选择加入。仅列出工作流所需的内容,因为每个功能组都会向上下文窗口添加自己的工具。

数据库访问

数据库服务器允许代理查询并(可选地)写入关系存储。规范的 Postgres 示例是 @modelcontextprotocol/server-postgres:

[mcp_servers.postgres]
enabled = true
command = "npx"
args = ["-y", "@modelcontextprotocol/server-postgres", "${DATABASE_URL}"]
enabled_tools = ["query"]

对于生产数据库,请使用只读连接字符串并仅暴露 query 工具。通过 MCP 服务器对生产数据的写入访问应要求与其他生产数据库变更相同的变更管理流程。

MCP 的上下文成本

每个连接的 MCP 服务器都会向模型的上下文窗口添加工具定义。暴露 20 个带有详细描述的工具的服务器,在代理处理单个提示前就可能消耗数千个 token;大型服务器(如拥有 60 多个工具的完全填充的 GitHub MCP)可能消耗数万个 token。第 7 章 的测量工作流(codex debug prompt-input 加上分词器)是查看每个连接的服务器在您机器上确切成本的方法。

最廉价的工具是已在上下文中的工具。Codex CLI 的 Shell 执行是内置的,因此 PATH 上安装的任何 CLI 都隐式地可供代理使用,而无需添加任何工具模式。对于列出问题、打开拉取请求、读取特定提交的文件或过滤搜索结果等常规 GitHub 操作,gh 通常比 GitHub MCP 更好。模型知道如何调用 gh,因为它在训练数据中见过大量 gh 用法,且 Shell 命令可以通过 grep、jq 或 head 管道预过滤输出,然后再进入上下文。

该模式推广到本章前面介绍的各服务器:

  1. GitHub 可使用 gh 处理问题、PR、文件读取和代码搜索。当您想要由审批注解保护的更丰富工作流时,MCP 服务器胜出。

  2. 文档查找 可在求助于 Context7 前使用 curl、gh api 或项目自己的 CLI。当您需要模型在训练中未见过的当前特定版本文档时,Context7 胜出;对于模型已熟知的流行库,它则落后。

  3. 浏览器自动化 有三条路径。对于只读获取和轻量级抓取,curl 足矣。对于编码代理中的程序化浏览器自动化,微软现在明确推荐 playwright-cli 工作流而非 MCP 服务器,因为 CLI 调用更节省 token。^(⁶⁴) Playwright MCP 服务器仍是探索性自动化、自愈测试和长期运行的自主工作流的正确选择,因为持久浏览器状态值得其模式成本。

  4. 数据库访问 可使用 psql、mysql 或项目 ORM 的 CLI。当您想要由运行时而非约定强制执行结构化工具注解(readOnlyHint、审批门控)时,MCP 服务器胜出。

决策规则:当您需要结构化工具模式、类型化输入输出或运行时强制的安全注解时,请选择 MCP 服务器。当操作在模型训练数据中已被充分涵盖,且模式成本无法带来 Shell 无法提供的任何收益时,请选择 CLI。

当 MCP 服务器是正确选择时,四个杠杆管理其上下文成本。三个位于 ~/.codex/config.toml;第四个位于服务器自身的源代码中。

  1. enabled_tools 白名单,在每个 [mcp_servers.<name>] 块上设置。将服务器暴露的工具集限制为工作流所需的工具。本章前面的 GitHub 示例使用 enabled_tools = ["create_issue", "list_issues", "get_file_contents", "search_code"],而非服务器默认暴露的数十个工具。未列出的工具不会被加载到上下文中。

  2. 服务器级启用开关,在同一 [mcp_servers.<name>] 块上设置。enabled = false 保留配置但跳过启动时加载服务器。适用于偶尔使用的服务器;翻转标志即可恢复,无需重写配置。

  3. 作用域配置文件:在配置文件 ~/.codex/<name>.config.toml 内声明服务器为 [mcp_servers.<id>];仅当您使用 codex --profile <name> 激活该配置文件时才加载,因此不需要它的会话无需支付其上下文成本。第 6 章 详细介绍了配置文件机制。

  4. 工具描述简洁性,在服务器自身的源代码中设置,作为每个工具定义上的 description 参数。构建自定义服务器时,请保持描述简洁。冗长的描述与其他文本一样被分词,并存在于每轮的静态前缀中。

企业级 MCP:认证、作用域与限制

企业部署需要控制可用的 MCP 服务器、谁可以使用它们以及它们携带的凭据。

服务器作用域。 平台团队分发的托管 config.toml 使用文档化的 mcp_servers.<id>.enabled 键声明允许的服务器。团队未审核的服务器被显式禁用或完全省略:

托管配置:仅这些服务器运行;其余默认禁用。

[mcp_servers.github]
enabled = true
# ... command, args, env, enabled_tools 按需填写
[mcp_servers.context7]
enabled = true
# ...
[mcp_servers.unvetted_server]
enabled = false

Codex CLI 的配置层意味着开发者本地的 config.toml 可以覆盖单个键,但平台团队的托管配置是基线;在敏感服务器上显式设置 enabled = false 可以防止在团队级别意外重新启用。第 17 章涵盖了完整的企业部署拓扑,包括托管配置如何分发,以及项目范围的覆盖如何与团队策略交互。

凭证隔离。 MCP 服务器凭证(API 令牌、数据库 URL)应遵循与其他凭证相同的密钥管理实践:在运行时将它们注入服务器进程,切勿提交到配置文件。建议在启动 Codex CLI 之前设置服务器期望的确切环境变量,或使用启动器/包装器从操作系统的密钥存储中读取,然后 exec MCP 服务器。除非您的 Codex CLI 版本明确记录了该行为,否则不要假设 Shell 风格的占位符(如 ${GITHUB_TOKEN})会在 config.toml 内部展开。

引诱/交互式提问。 MCP 引诱允许服务器在会话中期向用户请求额外信息:提示缺失参数、在破坏性操作前请求确认,或就模糊查询请求澄清。引诱由细粒度审批策略中的 mcp_elicitations 审批类别控制。

构建简单的 MCP 服务器

MCP 服务器构建简单。使用 pip install "mcp[cli]" 安装 SDK,然后使用 FastMCP 装饰器 API 编写最小服务器:

#### server.py: 使用 FastMCP 的最小 MCP 服务器
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("example-server")
@mcp.tool()
def word_count(text: str) -> int:
    """Count words in the given text."""
    return len(text.split())
if __name__ == "__main__":
    mcp.run()

FastMCP 自动将函数签名转换为工具模式:text: str 成为必需的字符串输入,文档字符串成为工具描述,返回类型映射到工具的输出类型。幕后 SDK 仍使用低级 Server API 暴露的相同 JSON-RPC 协议;FastMCP 是新服务器的推荐入口点。

通过向 ~/.codex/config.toml 添加 [mcp_servers.<name>] 块将其接入 Codex CLI(如果仅希望在特定配置文件中使用该服务器,也可以作为 [mcp_servers.<name>] 放入配置文件 ~/.codex/<name>.config.toml 内):

[mcp_servers.wordcount]
enabled = true
command = "python"
args = ["/absolute/path/to/server.py"]

在 args 中使用绝对路径,以便无论从哪个目录启动 Codex 都能启动服务器。替代方案是设置 cwd 并保持 args 为相对路径:

[mcp_servers.wordcount]
enabled = true
command = "python"
args = ["server.py"]
cwd = "/path/to/server/directory"

同样的模式可扩展到更大的服务器:使用 @mcp.tool() 添加更多装饰函数,并保持每个工具的输入和返回类型显式;MCP 协议处理发现、调用和结果编组。

测试 MCP 服务器

在连接到 Codex CLI 之前,在隔离环境中测试 MCP 服务器:

##### 如果您已使用 pip 将 SDK 安装到当前环境中
mcp dev server.py
##### 或者,如果您通过 uv 运行 Python 工具
uv run mcp dev server.py

这将启动 MCP Inspector,一个可视化调试工具,它启动 FastMCP 服务器,执行协议握手,并允许您交互式地列出和调用工具。使用它确认 word_count 带有必需的 text 参数出现,然后用示例文本调用它,然后再将服务器接入 Codex CLI。

MCP 协议两端的 Codex CLI

到目前为止,本章将 Codex CLI 视为 MCP 客户端:它消费服务器。但该协议是对称的,Codex CLI 也可以作为服务器暴露自身,使其工具(文件操作、Shell 执行和代码分析)可供任何连接的代理使用:

codex mcp-server

该服务器通过 MCP 协议暴露两个工具:

  1. codex 启动一个新的 Codex 会话。必需参数是 prompt;可选参数覆盖会话的 model、approval-policy、sandbox、cwd、base-instructions 和 developer-instructions。它返回 { threadId, content }。

  2. codex-reply 通过传递 codex 返回的 threadId 以及下一个用户提示,继续现有会话。

第二个 Codex CLI 实例可以将第一个实例作为另一个 MCP 服务器消费。将其添加到编排器的 ~/.codex/config.toml 中:

[mcp_servers.codex_worker]
command = "codex"
args = ["mcp-server"]
enabled_tools = ["codex", "codex-reply"]

重启编排器并要求其委派:

codex "Use the codex_worker MCP server to read CHANGELOG.md and summarise the last release. Return only the summary."

编排器使用提示调用 codex_worker.codex;生成的子 Codex 会话读取文件、总结它,并带着线程 ID 返回结果。后续调用使用该线程 ID 调用 codex_worker.codex-reply,而不是每次都启动新会话,从而保留工作进程的累积上下文。

在此基础上构建三种模式:

  1. 跨代理委派。 编排器代理将编码任务委派给 Codex CLI,同时自行处理规划和协调。

  2. 工具组合。 管道代理将 Codex CLI 与其他 MCP 服务器(数据库、部署、监控)链接,以执行多步骤工作流。

  3. IDE 集成。 IDE 扩展连接到 Codex CLI 作为 MCP 服务器,在编辑器中提供代理能力。

客户端不必是另一个 Codex CLI;Claude Code、自定义 Python 编排器或 IDE 扩展都通过相同的 MCP 协议与相同的 codex mcp-server stdio 进程对话。此后的操作关注点与消费第三方服务器相同,这也是本章剩余部分的内容。

在生产环境中运行 MCP

迄今为止的内容让您实现了连接。在生产环境中运行 MCP 会增加四个累积而非重叠的关注点:写回操作比读操作风险更大,需要显式控制;服务器可能失败,需要弹性配置;部署可能移至场外,需要不同的传输;出现问题时,您需要诊断信息来解释原因。本章其余部分按此顺序处理它们。

写回安全

只读 MCP 集成默认安全:代理查询数据并对其进行推理。写回集成则不同。创建工单、发布消息和更新数据库都引入了代理采取用户未批准的操作或重复操作的风险。四个安全不变量应管控启用写入的 MCP 集成:

  1. 显式工具标注。 写入工具必须标注 readOnlyHint: false,并在适用时标注 destructiveHint: true。这确保审批系统正确路由它们。

  2. 审批门控调用。 除最高可信环境外,写入工具应在所有环境中要求显式审批。配置审批策略,将写入型 MCP 工具视同文件系统写入。

  3. 可能时的幂等性。 设计写入工具使其可安全重试。如果代理会话被中断并恢复,重新调用的写入工具不应创建重复记录。

  4. 审计日志。 每次写入工具调用都应在审计追踪中记录完整参数和结果。使用 PostToolUse 钩子(第 9 章)将 MCP 写入操作与文件系统操作一起记录。

实践中反复出现的三种模式

工单系统 (Jira, Linear):根据代码分析创建工单,根据 CI 结果更新工单状态,并将 PR 关联到工单;将 Agent 的写入范围限制在最小,仅允许创建和评论,不允许重新分配或关闭。

即时通讯 (Slack, Teams):发布摘要、警报和状态更新;使用限定频道作用域的令牌,将 Agent 限制在特定频道,而非组织范围内的访问。

数据库:编写迁移脚本,插入测试固件,更新配置表;始终使用具有最小必要权限的专用服务账户,切勿使用开发者的个人数据库凭据。

故障恢复能力

写入安全管控 Agent 被允许做什么;恢复能力管控其所依赖的服务器 出现异常时会发生什么。MCP 服务器是外部进程,可能会失败、挂起或返回意外结果,配置不应假设它们总能正常工作。

优雅降级

如果 MCP 服务器不可用,Agent 应继续使用其内置能力,而不是导致整个任务失败。Codex CLI 通常能容忍 MCP 服务器启动失败,但在依赖它用于生产环境前,请针对您的版本验证该行为,并编写不假设 MCP 工具始终可用的提示词。

超时配置

tool_timeout_sec 限制任何单次工具调用的运行时长。对于确实需要缓慢工作的服务器(大型仓库搜索、浏览器导航或大表数据库查询),请调高该值;对于其他所有情况保持较短,以防挂起的服务器无限期阻塞 Agent。配套键 startup_timeout_sec 管控初始握手过程,详见调试章节。

用于测试的虚拟工具

开发依赖 MCP 服务器的工作流时,请创建返回预设响应的轻量级虚拟服务器。这使您能在不依赖外部服务的情况下测试提示词工程和审批流程。

远程与容器化服务器

当服务器运行在非本地主机上时,故障表现形式会有所不同。MCP 支持 HTTP 传输,用于作为远程基础设施运行而非本地子进程的服务器:

[mcp_servers.remote_tools]
enabled = true
url = "https://mcp.internal.company.com/tools"
bearer_token_env_var = "MCP_REMOTE_TOKEN"

远程传输适用于企业部署,此时 MCP 作为由平台团队管理的共享基础设施运行:一套规范的 GitHub 服务器实例配合一组凭据,而非每个开发者笔记本一套。

为实现更严格的隔离,Docker MCP Toolkit 将每个 MCP 服务器运行在各自的容器内,仅赋予其所需的网络访问权限和文件系统挂载。对于企业部署,这是第三方 MCP 服务器可用的最强隔离模型,且容器级的凭据边界使密钥管理变得简单直接。

故障排查指南

无论服务器运行在本地、远程还是容器中,相同的四个诊断步骤可覆盖大多数故障:

  1. 启用详细日志。 设置 RUST_LOG=debug(或 RUST_LOG=codex=debug 仅针对 codex crate)以查看完整的 MCP 协议交换。

  2. 检查服务器健康。 在 Codex CLI REPL 中使用 /mcp 命令列出已连接的服务器及其状态。

  3. 隔离测试。 通过 stdio 直接调用服务器(或通过 MCP Inspector 进行更丰富的调试),验证其在 Codex CLI 之外是否能正确响应。

  4. 检查工具定义。 审查服务器返回的工具模式,确保其与 Agent 预期的一致。

四种最常见的故障模式直接对应上述步骤:

  1. 服务器启动失败。 检查配置中的 command 和 args,验证可执行文件是否在 PATH 中。如果 TUI 显示 MCP client for '<name>' timed out after 30 seconds,说明握手超过了默认的 startup_timeout_sec。这在 npx -y 首次运行下载包时很常见,因为在服务器能响应初始 tools/list 请求前,需要先获取并链接包。两种修复方法:在受影响的 [mcp_servers.<name>] 块中调高 startup_timeout_sec(60 到 120 秒覆盖大多数包下载),或全局安装服务器以便后续启动即时完成,例如为本章前文提到的 GitHub 示例运行 npm i -g @modelcontextprotocol/server-github。

  2. 工具未显示。 验证 enabled_tools 是否与服务器暴露的确切工具名称匹配。

  3. 凭据错误。 确认 MCP 服务器进程能看到其期望的令牌变量。如果令牌在 gh api 中有效但通过 MCP 失败,请重启 Codex CLI 并检查 MCP 配置是否未传递字面量占位符,如 ${GITHUB_TOKEN}。

  4. 上下文占用过高。 暴露的工具过多;使用 enabled_tools 仅将所需工具加入白名单。

现状总结

MCP 赋予了 Agent 触达外部系统的能力;本章展示了如何在不失控上下文成本或写入风险的前提下增加这种触达能力。第 9 章 从相反的角度切入:不是给 Agent 更多能力,而是拦截 Agent 已有的能力。Hooks 让您能在 Agent 生命周期的十个明确定义点(工具调用运行前、运行后、会话开始时、压缩触发时)触发脚本,从而在不修改 AGENTS.md 的任何一行代码下,实现验证、记录、转换或否决 Agent 的行为。结合本章的杠杆和下一章的生命周期 Hooks,您已拥有大部分用于生产级 Codex CLI 部署的构建模块。

概要

  1. MCP 是标准协议。 它将 Agent 连接到外部工具和服务,无需为每个集成编写专用接线。

  2. 三层架构。 Host、Client 和 Server 将 Agent 逻辑与工具实现分离。

  3. 工具注解提供风险词汇。 审批系统利用它们做路由决策。

  4. 按需白名单。 enabled_tools 管理上下文成本并减少攻击面。

  5. 构建服务器很直观。 定义工具并实现其逻辑;协议处理其余事务。

  6. 写回集成需要自律。 显式注解、审批闸控、幂等性和审计日志。

  7. 企业级 MCP 增加控制。 服务器准入列表、凭据隔离和引导控制。

  8. Codex CLI 作为 MCP Server。 支持跨 Agent 委托和工具组合。

  9. 恢复模式。 优雅降级、超时和虚拟工具防止 MCP 故障阻塞工作流。

练习

练习 8-1. 配置带有受限 enabled_tools 列表的 GitHub MCP 服务器。运行一个需要 GitHub 访问权限的任务并观察 Agent 调用了哪些工具。然后移除 enabled_tools 并对比上下文窗口使用情况。

练习 8-2. 用 Python 构建一个自定义 MCP 服务器,暴露两个工具:一个只读(查询)和一个可写(创建)。用适当的风险元数据标注它们。连接到 Codex CLI 并验证写工具需要审批而读工具不需要。

练习 8-3. 将 Codex CLI 设置为 MCP 服务器,并将第二个 Codex CLI 实例作为 Client 连接到它。从 Client 向 Server 委派一个编码任务,观察协议如何处理工具发现、调用和结果传递。

第九章:Hooks:拦截 Agent 生命周期

MCP 向外延伸 Agent 的触达;Hooks 则让您掌控 Agent 自身的生命周期。在十个定义好的节点上,从会话开启到子 Agent 停止,您可以运行 Shell 命令来注入上下文、强制执行策略、记录行为或发送通知,且无需更改 AGENTS.md 的任何一行。

学习目标

您将能够:

  1. 在 .codex/hooks.json 中配置所有十个 Hook 事件

  2. 编写 Shell 和 Python Hook 脚本,以注入上下文、强制提示词策略、运行测试套件和扫描密钥

3. 推理钩子超时、错误处理以及同步执行的权衡

钩子系统:概述与事件

钩子是在代理生命周期的特定节点触发的 Shell 命令回调。它们以同步方式运行,并通过 stdout、stderr 和退出代码来传递结果。当在活动配置层中发现 hooks.json 文件,或在 config.toml 中存在内联钩子定义时,钩子默认启用。托管环境可通过 requirements.toml 集中禁用钩子。

钩子在 Windows 上受全面支持。相同的 hooks.json 配置无需平台特定的更改即可在 macOS、Linux 和 Windows 上工作。

Codex CLI 从两个来源读取钩子配置:项目根目录下的 .codex/hooks.json,以及 config.toml 中内联的 [[hooks]] 定义。两者均为热读取;编辑后无需重启即可生效。表 9-1 将十个生命周期事件映射到其主要用例。

表 9-1. 钩子事件及其特性

| 事件 | 触发时机 | Stdout 效果 | 退出码 2 效果 | 可阻塞? |

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

| SessionStart | 会话打开 | 注入模型上下文 | 记录错误;会话继续 | 否 |

| PreToolUse | 每次工具调用执行前 | 记录或忽略 | 拒绝工具调用;显示错误 | 是 |

| PermissionRequest | 扩展或工具请求提升权限 | 由审批系统读取决策 JSON | 拒绝请求;回退到 TUI | 是 |

| UserPromptSubmit | 提交提示词,模型见到它之前 | 追加到提示词 | 拒绝提示词;用户看到 stderr | 是 |

| PostToolUse | 每次工具调用完成后 | 记录或忽略 | 记录错误;执行继续 | 否 |

| PreL1-Bullet | 上下文压缩开始前 | 记录或忽略 | 记录错误;压缩继续 | 否 |

| PostL1-Bullet | 上下文压缩完成后 | 注入压缩后上下文 | 记录错误;会话继续 | 否 |

| SubagentStart | 创建子代理,首次模型请求前 | 前置到子代理上下文 | 记录错误;子代理继续 | 否 |

| SubagentStop | 子代理轮次接近完成 | 记录或忽略 | 阻止完成(强制继续) | 是 |

| Stop | 会话关闭 | 无 | 记录错误;清理继续 | 否 |

hooks.json 对象以事件名称为键。每个事件映射到一个钩子组数组;每个组包含一个由单个钩子对象组成的 hooks 数组:

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash ~/.codex/scripts/inject-context.sh",
            "statusMessage": "Loading project context…",
            "timeout": 15
          }
        ]
      }
    ]
  }
}

每个组内部嵌套的 hooks 数组并非错误。外层数组存放钩子组;每个组存放单个钩子。此结构支持有条件的组激活。

config.toml 中的内联配置

钩子也可在 config.toml 中内联定义,将双文件设置合并为单一配置界面:

#### .codex/config.toml
[[hooks.SessionStart]]
[[hooks.SessionStart.hooks]]
type = "command"
command = "bash ~/.codex/scripts/inject-context.sh"
statusMessage = "Loading project context..."
timeout = 15
[[hooks.PostToolUse]]
[[hooks.PostToolUse.hooks]]
type = "command"
command = "bash ~/.codex/scripts/auto-test.sh"
statusMessage = "Running tests..."
timeout = 60

hooks.json 和内联 config.toml 定义可同时受支持。当两者都存在时,来自所有来源的钩子按发现顺序触发。内联格式在托管的 requirements.toml 中也受支持,适用于集中强制执行钩子策略的企业环境。

MCP 与 apply_patch 观测

PreToolUse 和 PostToolUse 同样观测 MCP 工具调用(以 mcp__<server>__<tool> 为前缀)以及 apply_patch 和 Bash。带有类似 "mcp__docs__search" 匹配器的 PreToolUse 钩子可在 MCP 工具调用执行前将其拦截、审计或阻止。

这支持一个 MCP 审计钩子,它记录每个 MCP 工具调用及其完整的请求和响应负载,以供合规审查:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "mcp__*",
        "hooks": [
          {
            "type": "command",
            "command": "bash ~/.codex/scripts/mcp-audit.sh",
            "statusMessage": "Auditing MCP call...",
            "timeout": 5
          }
        ]
      }
    ]
  }
}

生命周期事件

这十个事件大致按代理会话遍历它们的顺序触发:会话打开、提示词到达、工具执行、上下文压缩、子代理生成与返回、会话关闭。本节将按该顺序逐一介绍每个事件,涵盖配置、契约及实战示例。

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

图 9.1 - 钩子生命周期事件

一张流程图,追踪钩子生命周期事件的有序触发,始于 SessionStart 和 UserPromptSubmit,继而 PreToolUse。它在权限检查处分支,提升权限时会在 PostToolUse 前插入 PermissionRequest。它在子代理检查处再次分支,生成的子代理运行 SubagentStart 和 SubagentStop。最后在上下文已满时分支,发生压缩时触发 PreL1-Bullet 和 PostL1-Bullet。所有路径最终汇聚于 Stop。

SessionStart:配置环境

SessionStart 在模型处理任何用户输入前执行一次。这是放置会在 AGENTS.md 中过时的动态上下文的正确位置。退出码为 0 时,Codex CLI 会在第一轮对话前将脚本的 stdout 注入模型的上下文。请保持此输出结构化且简短;每个 Token 都会占用你的上下文窗口(见第 7 章)。

#!/usr/bin/env bash
##### ~/.codex/scripts/inject-context.sh
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
TICKET=$(echo "$BRANCH" | grep -oE '[A-Z]+-[0-9]+' | head -1)
DIRTY=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
echo "## Session Context"
echo "- Branch: $BRANCH"
echo "- Jira ticket: ${TICKET:-none detected}"
echo "- Uncommitted files: $DIRTY"
if [ -f ".codex/project-notes.md" ]; then
  echo ""
  cat .codex/project-notes.md
fi

将此脚本接入你的 hooks.json:

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash ~/.codex/scripts/inject-context.sh",
            "statusMessage": "Loading branch context…",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

退出码 2 会记录错误但会话继续(不同于 UserPromptSubmit,后者退出码 2 会阻止提示词)。如果上下文注入失败,会话将在没有注入上下文的情况下打开。

SessionStart 在 stdin 上包含一个 source 字段,其值为 startup、resume、clear 或 compact 之一。如果你的脚本在全新启动与恢复或 /clear 后重新打开时应表现不同,可根据此字段分支处理。

UserPromptSubmit:在代理行动前塑造输入

UserPromptSubmit 在你按下回车后、模型见到文本前、提示词进入会话历史前触发。用它用样板内容增强提示词,或拒绝违反策略的提示词。

增强提示词

退出码为 0 时,Codex CLI 将钩子的 stdout 追加到你的提示词中:

#!/usr/bin/env bash
###### ~/.codex/scripts/append-test-status.sh
###### 追加当前测试套件状态,以便代理始终知道什么在失败
FAILING=$(python -m pytest --tb=no -q 2>&1 | tail -5)
if [ -n "$FAILING" ]; then
  echo ""
  echo "### Current test status"
  echo "$FAILING"
fi

用于复杂增强逻辑的 Python 等效实现:

#!/usr/bin/env python3
###### ~/.codex/scripts/append-context.py
import subprocess, sys, os
result = subprocess.run(
  ["python", "-m", "pytest", "--tb=no", "-q"],
  capture_output=True, text=True
)
if result.returncode != 0:
  lines = result.stdout.strip().splitlines()[-5:]
  print("\n### Current test status")
  print("\n".join(lines))
阻止提示词

退出代码 2 将直接拒绝该提示词;它永远不会到达模型,也不会进入历史记录:

#!/usr/bin/env bash
###### ~/.codex/scripts/prompt-policy.sh
###### 阻止直接推送到 main 分支的请求
set -euo pipefail
###### UserPromptSubmit 通过 stdin 传递一个 JSON 对象;文本位于 `.prompt` 字段中。
PROMPT="$(jq -r '.prompt // empty')"
if printf '%s' "$PROMPT" | grep -qi 'push.*main\|force.push\|--force'; then
 echo "提示词已阻止:禁止直接推送到 main 分支。请改为开启 PR。" >&2
 exit 2
fi

UserPromptSubmit 钩子通过 stdin 接收以 JSON 对象形式提交的提示词,文本位于 prompt 字段中(以及 session_id、cwd 和 hook_event_name 等通用字段旁)。如本例所示,请使用 jq -r '.prompt' 来提取它,而不是尝试从环境变量获取:stdin JSON 是文档规定的标准通道。

请为 UserPromptSubmit 钩子设置严格的 timeout。因为该钩子会阻止提示词继续执行,缓慢的脚本会让代理显得故障。五秒通常足以完成策略检查;当你需要在模型行动前获得测试反馈时,三十秒对测试运行器来说是合理的。

PreToolUse:拦截 Shell 命令

PreToolUse 在每次工具调用执行前触发,是唯一能阻止工具调用运行的钩子。它适用于 Bash 工具调用、MCP 工具调用(前缀为 mcp__<server>__<tool>)以及 apply_patch。配置 matcher 以定位相关工具类型:

{
 "hooks": {
 "PreToolUse": [
 {
 "matcher": "Bash",
 "hooks": [
 {
 "type": "command",
 "command": "bash ~/.codex/scripts/pre-tool-policy.sh",
 "statusMessage": "正在检查命令策略……",
 "timeout": 5
 }
 ]
 }
 ]
 }
}

退出代码 2 拒绝工具调用。退出代码 0 允许其继续执行。

若要阻止对特定路径的文件写入操作,请使用 Codex CLI 的 sandbox_mode 或 AGENTS.md 指令,而非 PreToolUse。

PermissionRequest:可编程审批

PermissionRequest 允许外部脚本以编程方式批准或拒绝权限请求,当钩子未返回决策时,将回退到标准的 TUI 审批流程。Shell 命令、统一执行和网络审批提示均会触发此事件。

钩子接收完整的请求上下文(命令、沙箱状态和自动审查结果),并返回三种裁决之一:

{"hookSpecificOutput": {"decision": {"behavior": "allow"}}}
{"hookSpecificOutput": {"decision": {"behavior": "deny", "message": "该代理角色不允许写入 /etc"}}}
{"hookSpecificOutput": {"decision": null}}

当 decision 为 null(或钩子退出时未写入决策)时,标准 TUI 提示将作为后备方案出现。PermissionRequest 钩子扩展了审批系统,而非取代它。

四种常见模式:

  1. CI/CD 自动审批。 钩子检查命令是否匹配已知安全的允许列表(npm test、make lint)并自动批准;未知命令升级为人工审核。

  2. 策略即代码。 钩子调用 OPA 或 Cedar 策略引擎,根据项目、分支和角色约束评估请求。

  3. 审计追踪记录。 每个审批决策连同完整上下文被转发至 SIEM 或可观测性平台,独立于裁决结果。

  4. 多代理委托作用域。 编排器钩子仅在子代理的行为落在父代理委托权限范围内时才予以批准。

审批系统由此成为可编程的安全层,而非仅是用户界面功能。第 5 章 在审批模式架构中全面介绍了 PermissionRequest 钩子。

PostToolUse:观察而不阻塞

PostToolUse 在每次工具调用完成后触发。工具已经运行完毕;你观察的是事后状态,而非把守动作。常见用途:

  1. 每次文件写入后运行测试套件并将结果注回上下文

  2. 文件写入后立即扫描修改过的文件以查找密钥或凭据^(⁶⁵)

  3. 为每个执行的 shell 命令向审计追踪追加结构化日志条目

  4. 代理运行敏感操作时发送通知

Python 编写的测试保存钩子:

#!/usr/bin/env python3
##### ~/.codex/scripts/post-tool-test-runner.py
import subprocess, sys, json, os
##### 如果提供,从 stdin 读取工具调用详情
tool_info = {}
try:
 import json
 tool_info = json.load(sys.stdin)
except Exception:
 pass
tool_name = tool_info.get("tool_name", "")
##### Codex 规范的文件写入工具是 apply_patch
if tool_name not in ("apply_patch", ""):
 sys.exit(0)
result = subprocess.run(
 ["python", "-m", "pytest", "--tb=short", "-q", "--no-header"],
 capture_output=True, text=True, timeout=60
)
if result.returncode != 0:
 # 退出码 0 但向 stdout 写入输出,以便结果作为上下文可用
 print("### 写入后测试结果")
 print(result.stdout[-2000:]) # 裁剪以避免上下文泛滥
sys.exit(0)

PostToolUse 的 stdout 处理遵循与其他钩子事件相同的协议:退出码 0 并向 stdout 写入结构化输出。

PreL1-Bullet 和 PostL1-Bullet:上下文 L1-Bullet 钩子

PreL1-Bullet 和 PostL1-Bullet 在上下文压缩前后触发。当 /compact 运行时(手动或通过自动压缩触发器),会话历史会被总结,详细的工具调用轨迹将丢失。PreL1-Bullet 在压缩开始前触发;PostL1-Bullet 在压缩完成后触发。两个钩子均通过 stdin 接收 JSON 对象(与每个命令钩子一样)。压缩专用字段如下:

| 字段 | 描述 |

| --- | --- |

| session_id | 当前会话标识符 |

| turn_id | 触发压缩时的轮次 |

| trigger | 压缩的发起方式:manual(用户输入 /compact)或 auto(达到上下文窗口阈值) |

这些钩子仅用于审计。它们无法阻止压缩,也无法访问完整的压缩前上下文内容。PreL1-Bullet 作为通知触发;PostL1-Bullet 的 stdout 会被注入到压缩后的上下文中,这使其成为重新注入压缩本会丢弃的关键信息的机制。

规范模式是压缩记忆:压缩前保存关键上下文,压缩后重新注入。

#!/usr/bin/env bash
##### ~/.codex/scripts/pre-compact.sh — PreL1-Bullet 钩子
##### 在压缩丢弃详细历史前保存关键状态
INPUT="$(cat)"
SESSION_ID="$(printf '%s' "$INPUT" | jq -r '.session_id // "unknown"')"
TURN_ID="$(printf '%s' "$INPUT" | jq -r '.turn_id // 0')"
TRIGGER="$(printf '%s' "$INPUT" | jq -r '.trigger // "unknown"')"
COMPACT_DIR="$HOME/.codex/compact-state"
mkdir -p "$COMPACT_DIR"
##### 捕获代理应记住的当前状态
cat > "$COMPACT_DIR/session-${SESSION_ID}.json" << EOF
{
 "session_id": "${SESSION_ID}",
 "turn": ${TURN_ID},
 "trigger": "${TRIGGER}",
 "branch": "$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown)",
 "uncommitted_files": $(git status --porcelain 2>/dev/null | wc -l | tr -d ' '),
 "test_status": "$(python -m pytest --tb=no -q 2>&1 | tail -1)"
}
EOF
#!/usr/bin/env bash
##### ~/.codex/scripts/post-compact.sh — PostL1-Bullet 钩子
##### 压缩后重新注入关键状态
INPUT="$(cat)"
SESSION_ID="$(printf '%s' "$INPUT" | jq -r '.session_id // "unknown"')"
TURN_ID="$(printf '%s' "$INPUT" | jq -r '.turn_id // 0')"
TRIGGER="$(printf '%s' "$INPUT" | jq -r '.trigger // "unknown"')"
COMPACT_DIR="$HOME/.codex/compact-state"
STATE_FILE="$COMPACT_DIR/session-${SESSION_ID}.json"
if [ -f "$STATE_FILE" ]; then
 echo "## 压缩后上下文恢复"
 echo "会话于第 ${TURN_ID} 轮被压缩(触发方式:${TRIGGER})。"
 echo "压缩前保存的状态:"
 cat "$STATE_FILE"
 echo ""
 echo "从此状态恢复工作。详细的工具调用历史不再可用。"
fi

将两者接入 hooks.json:

对于运行长周期智能体会话(数小时的自主工作)的团队,压缩钩子能让关键上下文在压缩边界间存活下来:正在处理哪些文件、测试状态如何、已做出哪些架构决策。第 7 章涵盖了互补的上下文保留策略。

SubagentStart 和 SubagentStop:子智能体边界钩子

SubagentStart 和 SubagentStop 在子智能体边界触发。它们填补了纯顶层生命周期钩子留下的多智能体可观测性空白。

当 Codex CLI 创建一个线程生成的子智能体时,SubagentStart 会在子智能体发送其首个模型请求前触发一次。这是一个上下文注入钩子:处理程序可返回 hookSpecificOutput.additionalContext,该内容会在子智能体的初始模型调用前被前置到其对话中。该钩子接收子智能体的 agent_id(线程标识符)和 agent_type(从 spawn_agent 调用解析而来)。

当子智能体轮次即将完成时,SubagentStop 会触发,取代常规根智能体 Stop 钩子对子智能体的作用。它接收相同的 agent_id 和 agent_type 字段,外加 agent_transcript_path(子智能体的完整对话日志)和 last_assistant_message(子智能体的最终输出)。SubagentStop 还支持完成控制:处理程序可携带理由阻塞以强制子智能体继续,或提供自定义停止理由。

两个钩子均通过 agent_type 匹配器使用基于类型的路由。你可以在编排工作流中为 researcher、implementer 和 reviewer 子智能体类型连接不同的钩子,每种类型拥有自己的上下文注入和质量把关逻辑。

{
  "hooks": {
    "SubagentStart": [
      {
        "matcher": "reviewer",
        "hooks": [
          {
            "type": "command",
            "command": "bash ~/.codex/scripts/inject-review-context.sh",
            "statusMessage": "Injecting reviewer context...",
            "timeout": 5
          }
        ]
      }
    ],
    "SubagentStop": [
      {
        "matcher": "implementer",
        "hooks": [
          {
            "type": "command",
            "command": "bash ~/.codex/scripts/check-implementation-quality.sh",
            "statusMessage": "Checking implementation quality...",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

这些钩子衍生出三种治理模式:

  1. 生成时注入上下文。 SubagentStart 钩子在创建时将团队特定约束、合规指令或特定角色的 AGENTS.md 内容注入每个子智能体,无需将所有内容烘焙进父智能体的提示词中。

  2. 子智能体输出的质量把关。 SubagentStop 钩子检查子智能体的最终消息和记录,实现审查/拒绝/继续模式。若输出未通过策略检查,钩子阻塞完成,子智能体继续工作。

  3. 逐子智能体审计追踪。 SubagentStop 上的 agent_transcript_path 字段指向每个子智能体的完整对话,支持多智能体运行的事后分析和逐子智能体的 Token 计数以进行成本归因。

结合线程产物持久化,这些钩子形成了完整的子智能体生命周期:启动时注入上下文、运行中产出产物、停止时检查结果。第 11 章涵盖了这些钩子所管辖的子智能体架构。

Stop:拆卸与清理

Stop 在会话结束时触发。它不向模型上下文注入内容;退出码 2 会记录错误但不阻止拆卸:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bash ~/.codex/scripts/on-stop.sh",
            "statusMessage": "Wrapping up...",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

典型的 on-stop.sh:

#!/usr/bin/env bash
##### ~/.codex/scripts/on-stop.sh
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
#### 审计日志
echo "$TIMESTAMP branch=$BRANCH user=$USER" >> ~/.codex/session-audit.log
##### 桌面通知(特定平台)
if command -v notify-send &>/dev/null; then
  notify-send "Codex" "Session complete on $BRANCH"
elif command -v osascript &>/dev/null; then
  osascript -e "display notification \"Session complete on $BRANCH\" with title \"Codex\""
fi
##### Slack Webhook(若已配置)
if [ -n "$CODEX_SLACK_WEBHOOK" ]; then
  curl -s -X POST "$CODEX_SLACK_WEBHOOK" \
    -H 'Content-type: application/json' \
    --data "{\"text\":\"Codex session ended on \`$BRANCH\` at $TIMESTAMP\"}" \
    > /dev/null
fi

编写健壮的钩子

积极设置超时。 30 秒的 UserPromptSubmit 钩子意味着每次提示词提交前模型都要等待 30 秒。典型值:策略检查 5 秒,上下文注入 10 到 15 秒,测试运行器 30 到 60 秒。

防范 stdout 体量过大。 SessionStart 和 UserPromptSubmit 将 stdout 注入模型上下文。无界限的日志倾倒会无声地消耗数千 Token。裁剪至模型所需的内容。

优雅处理缺失依赖。 依赖失败(缺少 venv、无 Slack token)应静默退出 0,而非退出 2 并阻塞会话。保留退出码 2 给刻意的策略拒绝。

理解钩子状态渲染。 钩子状态消息在 TUI 的专用实时区域渲染。显示在 300ms 延迟后出现(避免快速钩子闪烁),完成后停留 600ms 以便阅读结果,并在多个钩子同时触发时将并行钩子折叠为紧凑的状态行。设计 statusMessage 字符串使其一目了然;它们可能共享屏幕空间。

隔离测试钩子。 在添加到 hooks.json 前,从终端运行你的钩子脚本。验证退出码、stdout 和 stderr 是否符合预期。

开发阶段在 bash 钩子中添加 set -euo pipefail 以尽早捕获错误,生产环境则替换为更有选择性的错误处理。静默失败比喧闹失败更难调试,尤其是在你迭代时。

模式:执行、审计与通知

下述钩子配方构建于上一节的事件之上。每个都足够小,可放入 .codex/scripts/ 并用三四行 JSON 接入。

每次提交提示词时进行风格检查

将 linter 检查接入 UserPromptSubmit,这样模型在编辑代码前始终知晓当前的 lint 状态:

#!/usr/bin/env bash
##### 将 ruff 输出追加到每个提示词
OUTPUT=$(ruff check . --output-format=concise 2>&1 | head -20)
if [ -n "$OUTPUT" ]; then
  echo ""
  echo "### Linter state (ruff)"
  echo "$OUTPUT"
fi

每次文件写入时进行密钥扫描

使用 PostToolUse 在智能体写入后立即扫描密钥:^(⁶⁶)

#!/usr/bin/env bash
##### ~/.codex/scripts/secret-scan.sh
##### 需要:gitleaks (https://github.com/gitleaks/gitleaks)
if ! command -v gitleaks &>/dev/null; then
  exit 0
fi
OUTPUT=$(gitleaks detect --source . --no-git -q 2>&1)
if [ $? -ne 0 ]; then
  echo "### Secret scan warning"
  echo "$OUTPUT" | head -20
fi

网络操作上的中间人攻击检测

安全意识强的部署可使用 PostToolUse 钩子在智能体网络操作期间检测中间人攻击。该模式针对网络相关工具(curl、wget、API 调用)并检查 SSL 证书链中的异常,这些异常暗示拦截或 DNS 操纵。若钩子检测到可疑证书(非预期颁发者、缩短的链、不匹配的 SAN),它会向 stderr 写入警告并以退出码 2 退出以标记会话:

!/usr/bin/env bash

~/.codex/scripts/mitm-detect.sh
PostToolUse 钩子:检查证书链异常

TOOL_CMD="$(jq -r '.tool_input.command // empty')"

仅检查网络相关的工具调用

echo "$TOOL_CMD" | grep -qiE 'curl|wget|http|fetch|requests' || exit 0

从命令中提取最近的目标主机

HOST=\((echo "\)TOOL_CMD" | perl -pe 's#.https?😕/##; s#[😕 ].##' | head -1)

[ -z "$HOST" ] && exit 0

验证证书链;标记意外的颁发者或过短的链

CERT_INFO=\((echo | openssl s_client -connect "\)HOST:443" -servername "$HOST" 2>/dev/null)

ISSUER=\((echo "\)CERT_INFO" | openssl x509 -noout -issuer 2>/dev/null)

DEPTH=\((echo "\)CERT_INFO" | grep -c "^ [0-9]")

if echo "$ISSUER" | grep -qiE 'mitmproxy|burp|charles|fiddler|zscaler'; then

echo "### MITM 警告" >&2

echo "为 $HOST 检测到可疑的证书颁发者: $ISSUER" >&2

exit 2

fi

if [ "$DEPTH" -lt 2 ]; then

echo "### MITM 警告" >&2

echo "\(HOST 的证书链意外地短 (depth=\)DEPTH)" >&2

exit 2

fi

将其接入 PostToolUse 并使用 Bash 匹配器,以便它检查代理执行的每一个 shell 命令。将其与秘钥扫描钩子搭配使用,以同时覆盖凭证泄露和传输层拦截。

审计日志

!/usr/bin/env bash

ENTRY=$(python3 -c "

import json, os, time, subprocess

branch = subprocess.getoutput('git rev-parse --abbrev-ref HEAD')

print(json.dumps({

'ts': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),

'user': os.environ.get('USER', 'unknown'),

'branch': branch,

'cwd': os.getcwd(),

}))

")

echo "$ENTRY" >> ~/.codex/audit.jsonl

会话结束时的 Slack 通知

!/usr/bin/env bash

[ -z "$CODEX_SLACK_WEBHOOK" ] && exit 0

BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")

MSG="Codex 会话结束 | 分支: `$BRANCH` | 用户: $USER"

curl -s -X POST "$CODEX_SLACK_WEBHOOK" \

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

--data "{"text":"$MSG"}" > /dev/null

结合 PreToolUse 和 PostToolUse 的企业生命周期治理

对于企业部署,钩子成为治理层而非便利功能。PreToolUse 和 PostToolUse 形成了一个前后拦截对,可在每次工具执行时强制执行组织策略。^(⁶⁷)

PreToolUse 治理模式:
  1. 危险工具拦截。 将工具名称与拒绝列表匹配,并返回带有解释的拒绝判决。无论审批模式如何,这都能防止代理执行破坏性命令(数据库删除、强制推送、生产部署):

    {
     "hooks": {
     "PreToolUse": [{
     "matcher": "^Bash$",
     "hooks": [{
     "type": "command",
     "command": "/usr/local/bin/enterprise-tool-policy.sh",
     "timeout": 10
     }]
     }]
     }
    }
    
  2. 合规性检查。 在任何文件编辑前,验证目标路径是否在代理的授权范围内。钩子可强制要求在功能分支上工作的代理绝不修改共享基础设施文件、/etc/ 中的配置,或其分配模块以外的文件。

  3. 执行前审计日志。 在工具调用执行前,将每个拟议的工具调用写入不可变的审计日志,捕获工具名称、输入参数、时间戳和会话上下文。这将创建一条独立于工具调用成败的取证追踪。

PostToolUse 治理模式:
  1. 合规性扫描。 在每次文件写入后,运行自动化检查(许可证头验证、导入限制、命名规范强制执行),并通过 stdout 将纠正指导注入代理的上下文中。

  2. 变更归因。 使用代理会话 ID 和任务标识符标记每次文件修改,实现从变更行回溯到产生该变更的特定代理运行的可追溯性。

  3. 指标收集。 汇总工具执行结果(成功/失败率、持续时间分布、资源消耗)到团队级仪表板,用于容量规划。

所有配置层级的所有匹配钩子并发执行。企业平台团队可以通过 requirements.toml 强制要求治理钩子(个人开发者无法覆盖),而项目团队在仓库层级添加自己的钩子。这两组钩子是组合而非冲突关系。

自进化代理重训练循环

一个更具雄心的应用是映射自 OpenAI Cookbook 重训练循环的自进化代理模式。^(⁶⁸) 该模式使用闭环反馈:PostToolUse 钩子在每次工具执行后运行综合评分,当分数低于阈值时,元提示词代理生成改进的指令来替换当前的 AGENTS.md。

综合评分管道使用四个互补的评分器以防止单一指标优化:

| 评分器 | 类型 | 目的 |

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

| 实体保留 | 基于规则 | 确保关键术语在转换中幸存 |

| 长度约束 | 确定性 | 将目标输出大小保持在容差范围内 |

| 语义一致性 | 余弦相似度 | 防止内容偏移 |

| 整体质量 | LLM 作评判者 | 捕捉规则遗漏的细微信号 |

钩子在带有 Bash 匹配器的 PostToolUse 上触发。当平均分低于阈值(通常为 0.85)时,系统触发元提示词优化过程来修订 AGENTS.md。改进版本会被提交到 git,在提交信息中包含评分分数,从而创建提示词演进的可审计历史。

该模式需要在评估基础设施上进行大量前期投资,仅适用于高风险、大批量的工作流,在这些工作流中评估成本(每次基准运行约 74,000 tokens)是合理的。对于大多数团队,前文描述的简单钩子模式才是正确的起点。

工具

一旦钩子脚本存在,下一个问题是如何运维它们:观察其运行时性能、在不编辑文件的情况下启用/禁用它们、跨团队分发它们,以及让委托决策在 TUI 中可见。

/hooks 浏览器

在会话中输入 /hooks 会打开一个运行时浏览器,列出每个事件、针对它安装的钩子数量以及当前活跃的钩子数量:

钩子
来自配置和已启用插件的生命周期钩子。
事件                  已安装      活跃        描述
PreToolUse            1           1           工具执行前
PermissionRequest     0           0           请求权限时
PostToolUse           3           2           工具执行后
PreL1-Bullet            1           1           上下文压缩前
PostL1-Bullet           1           1           上下文压缩后
SessionStart          1           1           新会话开始时
UserPromptSubmit      2           1           用户提交提示词时
SubagentStart         1           1           创建子代理时
SubagentStop          1           0           子代理结束其轮次前
Stop                  1           1           Codex 结束其轮次前

两列最重要。已安装 统计在所有配置源中找到的钩子定义数量:项目 hooks.json、 config.toml 中的内联 [[hooks]] 块、托管的 requirements.toml 和插件包。活跃 统计其中当前启用了多少个。在上图中,开发者安装了三个 PostToolUse 钩子(测试运行器、秘钥扫描、MITM 检查),但在调试会话期间禁用了测试运行器,并完全关闭了一个缓慢的 SubagentStop 质量门控。

从浏览器中,您可以在不从配置中移除钩子的情况下单独切换各个钩子的开启或关闭状态,这在本地开发时需要运行速度较慢的 PostToolUse 钩子,但在无头 codex exec 运行中希望跳过该钩子时非常有用。切换状态是持久化的:启用/禁用决策存储在 $CODEX_HOME 下,并且可以在会话重启、沙箱重置和机器重启后保留。托管的 requirements.toml 策略仍会覆盖持久化状态,因此如果管理员强制要求某个钩子,开发者无法持久化地禁用它。

关于每次调用的统计信息(计数、时长、失败率),请使用下文所述的 OpenTelemetry 管道,而不是浏览器。

钩子运行的 OpenTelemetry 指标

钩子会发出结构化的 OpenTelemetry Span。每次调用都会生成一个带有事件类型、钩子命令、退出代码和挂钟时长标签的 Span。这些 Span 会流经任何兼容 OTEL 的 Collector,因此使用 Datadog、Grafana、Honeycomb 或类似技术栈的团队无需自定义管道即可监控钩子延迟、失败率和调用分布。

一个实用的起点:在 [otel] 下配置 OTLP 导出器。钩子 Span 与其他所有内容通过同一个导出器流动;没有单独的钩子开关。

#### .codex/config.toml
[otel]
trace_exporter = { kind = "otlp-http", endpoint = "http://localhost:4317", protocol = "grpc" }
metrics_exporter = { kind = "otlp-http", endpoint = "http://localhost:4317", protocol = "grpc" }

当 Span 开始流动时,您可以构建仪表板来回答诸如“哪些 PreToolUse 钩子增加了最多延迟?”或“多少百分比的 PostToolUse 调用以非零代码退出?”等问题。对于在多种事件类型中运行数十个钩子的团队,这将钩子性能从一个黑盒变成了一个一流的可观测子系统。将 OpenTelemetry 指标与审计日志模式结合使用,可同时获得实时仪表板和持久的离线记录。

在团队环境中部署钩子时,请固定 Codex CLI 版本,并在升级前查看变更日志。

插件捆绑钩子

钩子可以作为插件的一部分分发,而非独立脚本。当插件包含带有钩子定义的 hooks/ 目录时,安装该插件会注册这些钩子;无需编辑 hooks.json。注册市场(codex plugin marketplace add owner/repo)并安装其插件之一,可以在一次操作中接入 PostToolUse 密钥扫描器和 PreToolUse 命令策略检查器。

钩子继承插件的生命周期:卸载插件会移除其钩子,更新插件会更新其钩子。托管的 requirements.toml 策略可以强制要求特定插件(及其捆绑的钩子),为企业管理员提供了一条清晰的执行路径:要求一个插件,即可获得其钩子。

委托补丁审批渲染

当补丁审批被委托时(例如委托给自动审查器 auto_review 或自定义的 PermissionRequest 钩子),TUI 会渲染委托详情:审查者身份、风险级别,以及审批是自动进行还是升级处理。对于使用组合审批模式的团队(低风险变更由钩子处理,高风险变更由人工审查),这种视觉区分使得信任边界一目了然。

总结

  1. 十个生命周期事件,十个钩子。 SessionStart、UserPromptSubmit、PreToolUse、PostToolUse、PreL1-Bullet、PostL1-Bullet、SubagentStart、SubagentStop、PermissionRequest 和 Stop。每个都在定义点以定义契约触发。

  2. 上下文注入钩子。 SessionStart 在第一轮之前注入新鲜上下文;UserPromptSubmit 可在模型看到提示词之前对其进行增强或拒绝。

  3. 工具调用钩子。 PreToolUse 在每次工具调用(Bash、MCP 或 apply_patch)之前触发并可阻止它;PostToolUse 观察每次已完成的调用,准备运行测试、扫描密钥或追加观察结果。

  4. L1-Bullet 记忆模式。 PreL1-Bullet 和 PostL1-Bullet 在上下文压缩前后触发;在压缩前保存关键状态并在压缩后重新注入,可解决长周期会话的信息丢失问题。

  5. 子代理边界钩子。 SubagentStart 和 SubagentStop 支持在子代理首次模型请求前注入上下文,以及在其输出返回父代理前进行质量把关。通过 agent_type 进行基于类型的路由,可为每个子代理角色连接不同的治理逻辑。

  6. 共享退出代码协议。 退出代码 0 并输出 stdout 表示成功;退出代码 2 并输出 stderr 表示阻塞;其他代码表示静默失败。钩子同步运行,因此超时很重要;注入上下文的输出计入上下文窗口。

  7. 钩子塑造生命周期,AGENTS.md 设定规则。 AGENTS.md 是静态的,钩子是动态的;沙箱审批模式在执行前控制工具调用,而 PostToolUse 在执行后观察它们;MCP 向外扩展能力,而钩子塑造生命周期。

  8. 权限控制与运行时管理。 PermissionRequest 钩子允许外部脚本批准或拒绝权限请求,并可回退到 TUI。/hooks 浏览器可在运行时列出和切换钩子;切换状态持久化在 $CODEX_HOME 下。插件捆绑钩子让团队能通过插件市场分发可复用的质量门禁。

另请参阅: 第 19 章 (测试与评估)。 PostToolUse 测试保存模式是代理测试循环的一半。第 19 章 介绍了另一半:设计一个密闭的、确定性的、足够快的测试套件,以便代理在每次文件写入后都能可靠地运行。

练习

  1. 分支感知上下文注入。 编写一个 SessionStart 钩子,读取当前 Git 分支,从分支名称中提取 Jira 或 GitHub Issue 编号(例如 feat/GH-1234-add-login),并通过调用 gh issue view 注入单段摘要。在有无 Issue 编号的分支上分别测试。

  2. 保存即测试观察者。 实现一个 PostToolUse 钩子,在每次文件写入后运行 pytest -q --tb=line 并将摘要行写入 stdout。通过在代理写入文件后立即询问“当前测试状态是什么?”,验证摘要是否出现在上下文中。

  3. 完整生命周期集成。 将 SessionStart、UserPromptSubmit、PreToolUse、PostToolUse 和 Stop 组合到单个 hooks.json 中。设置保守的超时,在每个 Bash 脚本中添加 set -euo pipefail,并在 .codex/scripts/ 内的 README 中记录每个钩子。运行三个会话并查看审计日志。找出至少一处钩子增加意外延迟的情况,并解释如何减少它。

第三部分:扩展应用

第十章:技能生态系统:使用与编写技能

第 9 章 展示了钩子如何拦截代理的生命周期:在会话开始时注入上下文、对完成做出反应、阻止违反策略的提示词。钩子表达的是控制逻辑,而非可复用知识。一旦您连接了一个强制执行团队提交信息约定的钩子,如果不手动复制脚本和配置文件,就无法将该工作流共享给不同项目上的同事。这种摩擦指向了另一种基元:一种设计用于不拦截代理做什么,而是教会它如何做新事情的基元。技能就是这种基元。

本章涵盖技能生态系统的两方面:消费者侧(查找、安装和组合社区技能)和生产者侧(编写自己的技能、编写有效的描述、控制调用策略,以及发布它们以便他人也能安装)。

学习目标

您将能够:

  1. 使用 $skill-installer 和 npx skills 安装、配置和禁用技能

  2. 编写包含正确前置元数据和分层支持材料的完整、有效的 SKILL.md 文件

  3. 使用参考验证器构建并端到端测试一个真实的技能

配置高影响工作流

  1. 为必须永不自动触发的高影响工作流配置 allow_implicit_invocation: false。

  2. 通过 Codex 插件市场创建并分发插件,包括带有安装策略的本地市场配置。

  3. 将技能和插件组合成可复用的 Agent 工作流。

消费者视角:使用与浏览生态系统

什么是技能及其工作原理

技能是一个包含 SKILL.md 文件的目录。该文件使用 YAML 前置元数据声明名称和描述,随后是自由格式的 Markdown 说明。Agent 将这些说明作为加载的上下文进行读取,并在说明指示时调用存储在 SKILL.md 旁边的脚本或参考文档。没有任何内容会自动执行;Agent 读取说明并根据你的提示词决定如何对其采取行动。

设计理念是渐进式披露。启动时,Codex CLI 仅从每个可用技能加载 name 和 description 字段,每个技能约 100 个 token。完整主体仅在 Agent 判定技能相关时加载,要么是因为你的提示词隐式匹配了描述,要么是因为你按名称引用了该技能。scripts/、references/ 和 assets/ 子目录中的文件仅在 SKILL.md 说明引用它们时才加载。

一个提交了二十个技能的团队在启动时大约消耗 2,000 个 token,这完全在任何现代模型的上下文预算范围内,并且仅为与当前任务相关的技能加载详细说明。

最小目录结构如下:

incident-triage/
├── SKILL.md        # 必需;下面的每个条目均为可选
├── scripts/        # 运行手册命令、告警查询、呼叫助手
├── references/     # 事后分析模板、严重程度剧本
└── assets/         # 事件 JSON Schema、消息模板

agentskills.io 标准

Agent Skills 规范由 agentskills.io^(69) 管理,是打包 Agent 知识的可移植格式。Anthropic 开发了该格式,现已成为开放标准。^(70) 采用范围跨越数十个 Agent 产品,包括 CLI Agent、IDE Agent、桌面和云产品以及特定领域平台;agentskills.io 客户端目录记录了当前列表。^(71) agentskills.io 注册表是技能发现和分发的中心枢纽。

跨平台采用是该标准最具影响力的特性:提交到你仓库的单个 SKILL.md 无论开发者使用哪种 Agent 都能完全一致地工作。为 Codex CLI 编写的技能无需修改即可移植到 Claude Code、Cursor、Roo Code、Gemini CLI 以及其他每个采用该标准的产品。^(72) 无需移植步骤,无需工具特定方言,无需每个 Agent 的配置文件。这是该规范刻意极简设计的实际回报:足够严格以确保互操作性,足够简单以便每个 Agent 运行时都能实现它。

与 AGENTS.md(第 4 章)一样,SKILL.md 是开放标准而非 Codex 专有文件格式。两者均由 Linux 基金会的 Agentic AI Foundation 托管,因此使 AGENTS.md 安全可靠的相同治理保证也适用于技能。这两种格式是互补的:AGENTS.md 承载项目的常规规则和约束,而 SKILL.md 承载特定工作流的程序性知识。它们共同形成一个一次编写、到处部署的层,可在实现任一标准的任何 Agent 运行时上运行。

安全研究人员已识别出滥用 scripts/ 目录或在 SKILL.md 中嵌入提示词注入载荷的恶意技能。请像对待第三方代码一样对待已安装的技能:在激活前审查每个文件,并避免从不可信来源安装。

目录与安装

规范目录是 OpenAI 官方仓库 github.com/openai/skills^(73)。它包含两个可见层级:系统技能内置于 Codex CLI($skill-creator、$skill-installer、由 codex-auto-review 模型支持的内置代码审查技能)和一个经过 OpenAI 内部审查的精选目录可安装技能。精选示例包括 gh-address-comments、gh-fix-ci、多个 Notion 集成技能以及 $define-goal,后者可在编写任何代码前将模糊意图转化为具体、可衡量的目标。系统技能已可用(带 $ 前缀);精选技能需要单一安装步骤。

内置的 $skill-installer 是从官方目录获取的标准方式:

$skill-installer gh-fix-ci

你也可以将安装器指向显式 URL 或本地路径,这对于跨机器共享开发中技能很有用:

$skill-installer install https://github.com/openai/skills/tree/main/skills/.curated/gh-fix-ci

除了官方目录,社区技能还托管在第三方注册表中,如 agentskills.io([74]())、skills.sh(75) 和 Tessl 注册表。^(76) skills.sh 提供的跨平台 npx skills CLI 可通过单个命令在各 Agent 产品中安装社区技能:

npx skills add vercel-labs/agent-skills --skill frontend-design -a codex

安装、管理与版本控制技能

范围与目录解析

Codex CLI 按以下优先级顺序(从高到低)扫描技能目录:

$CWD/.agents/skills       # 项目级
$CWD/../.agents/skills    # 父目录
$REPO_ROOT/.agents/skills # 仓库根目录
$HOME/.agents/skills      # 用户级
/etc/codex/skills         # 系统/管理员
内置系统技能              # 最低优先级

同名的项目级技能会覆盖用户级技能,用户级技能覆盖系统级技能。

| 范围 | 目录 | 控制者 |

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

| 项目 | $REPO_ROOT/.agents/skills/ | 代码仓库(提交至源码控制) |

| 用户 | $HOME/.agents/skills/ | 个人开发者 |

| 管理员 | /etc/codex/skills/ | 组织平台团队 |

| 系统 | 内置 | OpenAI |

项目级是团队最实用的层级。将团队的项目专用技能提交到 $REPO_ROOT/.agents/skills/,克隆仓库的每位开发者都能自动获得一致的 Agent 行为,无需手动安装步骤。

禁用而非删除

如果你想阻止技能加载而不删除其目录,请在 ~/.codex/config.toml 中添加条目:

[[skills.config]]
name = "gh-address-comments"
enabled = false

修改 config.toml 后重启 Codex CLI。当技能导致意外的隐式激活时,此方法效果很好:禁用它,调查描述,调整后重新启用。

调用策略

默认情况下,当你的提示词匹配描述时,Codex CLI 会隐式激活技能。通过在技能目录内添加 agents/openai.yaml 文件可覆盖此行为:

policy:
  allow_implicit_invocation: false

禁用隐式调用后,技能仅在你按名称引用时激活。对敏感或昂贵的技能使用此设置,此时你需要明确意图而非自动模式匹配。

选择合适的基元

技能是 Codex CLI 提供的六个配置接口之一。每个接口解决不同的问题:

  1. AGENTS.md 承载静态项目上下文:约定、命令、文件边界(第 4 章)。

  2. Hooks 强制执行生命周期护栏:控制 Agent 是否运行(第 9 章)。

  3. 技能 编码程序性知识:如何做 X。

  4. MCP 服务器 暴露实时集成:授予 Agent 对 Y 的实时访问权限(第 8 章)。

  5. 子代理 委派执行:在隔离的上下文窗口中并行工作(第 11 章)。

  6. 插件 将其余部分打包为可分发的单元(本章后文介绍)。

这些层级是累加的。典型的成熟配置会使用 AGENTS.md 管理项目上下文,使用两三个技能处理团队工作流,使用一两个 MCP 服务器进行外部集成,使用子代理进行编排,并使用插件进行分发。从能解决问题的最底层开始;只有当下层力有不逮时,才向上迁移。

其中,最棘手的选择在于技能、MCP 和钩子之间:这三种原语往往看起来像是完成同一项工作的替代方案。表 10-1 对它们进行了直接对比。

表 10-1. 技能 vs. MCP vs. 钩子 对比矩阵

| 维度 | 技能 | MCP 服务器 | 钩子 |

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

| 定义 | 加载到代理窗口中的基于文本的上下文 | 暴露类型化工具端点的实时服务器 | 生命周期事件触发的 Shell 回调 |

| 加载模式 | 渐进式:启动时加载名称和描述,激活时加载主体 | 预加载式:启动时加载所有工具架构 | 事件驱动式:在 SessionStart、Stop 或提交提示词时运行 |

| 执行模式 | 代理读取指令并使用自己的工具执行 | 代理调用结构化端点,针对运行中的进程执行 | 运行您的脚本;输出/退出码反馈给代理 |

| 最适用场景 | 程序性知识、团队约定、工作流模式 | 实时数据访问、认证 API、有状态操作 | 上下文注入、提示词拦截、会话边界副作用 |

| 分发方式 | 提交到仓库的文件或从注册表安装 | 作为守护进程运行;配置引用主机和端口 | 提交到 .codex/ 的脚本 |

| 跨平台 | 一个 SKILL.md 可在多个代理产品间运行(Codex CLI、Claude Code、Cursor、Roo Code、Gemini CLI 等) | 除非平台支持 MCP,否则为平台专用 | 仅限 Codex |

| 何时使用 | “教会代理如何做 X” | “给代理提供对 Y 的实时访问” | “控制代理何时、是否运行” |

如果您正从变得难以驾驭的 AGENTS.md 文件迁移,技能通常是正确的重构目标。详见第 4 章 的完整膨胀分析。因为这两种格式都受 AAIF 管辖,迁移路径有标准支撑:项目上下文保留在 AGENTS.md,可复用的程序性知识迁移到可移植的技能中。

生产者视角:编写您自己的技能

SKILL.md 格式

技能是一个目录,而非单个文件。它必须包含一个 SKILL.md 文件;其他皆为可选。^(⁷⁷)

my-skill/
├── SKILL.md ← 必需:前置元数据 + 指令
├── agents/
│ └── openai.yaml ← Codex 专用:调用策略、MCP 依赖
├── scripts/ ← 可选:可执行辅助工具
├── references/ ← 可选:参考文档,按需加载
└── assets/ ← 可选:模板、数据文件

SKILL.md 由 YAML 前置元数据分隔符分隔为两个部分。开始 --- 和结束 --- 之间的所有内容为结构化元数据。之后的所有内容为 Markdown 散文,即代理在技能激活后作为指令读取的内容。

下面是一个完整的带注释示例:

---
name: deploy-to-staging
description: |
 将当前服务部署到暂存环境。当被要求部署、推送到暂存、
 切割发布候选版本或更新正在运行的暂存服务时使用。
 请勿用于生产环境部署或本地测试环境。
license: Apache-2.0
compatibility: Requires Docker, AWS CLI ≥ 2.x
allowed-tools: Bash Read
metadata:
 author: platform-team
 version: "2.1"
---
#### Instructions
1. 运行 `scripts/preflight.sh` 以验证环境变量和 Docker 守护进程的可达性。
2. 构建并标记 Docker 镜像:`docker build -t $SERVICE:$SHA .`
3. 推送到 ECR:`scripts/push-ecr.sh`
4. 更新 ECS 任务定义并注册新版本。
5. 等待服务稳定;报告成功或失败。

前置元数据受代理技能规范管辖。两个字段为必填(name 和 description);其他四个为选填。表 10-2 列出了每个字段及其约束。请遵守这些约束,否则参考验证器将拒绝该技能。

表 10-2. SKILL.md 前置元数据字段参考

| 字段 | 必填 | 约束 |

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

| name | 是 | 1 至 64 字符。仅限小写字母数字和连字符。必须与父目录名称完全一致。不能有前导/尾随连字符,不能有连续连字符。 |

| description | 是 | 1 至 1,024 字符。每次会话开始时加载。充当路由规则:描述技能做什么、何时使用,包含有助于代理识别相关任务的关键词。 |

| license | 否 | 许可证名称或捆绑许可证文件的路径。 |

| compatibility | 否 | 最多 500 字符。仅指定真实需求(Python 版本、所需二进制文件、网络访问等)。 |

| metadata | 否 | 任意键值对(字符串键、字符串值)。用于版本、作者、团队、变更日志指针等。 |

| allowed-tools | 否 | 撰写时为实验性。空格分隔的预批准工具名称,使代理跳过逐操作权限提示。 |

name 约束是大多数初次作者的绊脚石。参考验证器会将失败显示为单行错误,但提前识别这些模式会更快。表 10-3 将有效名称与四个最常见的拒绝原因进行了对比。

表 10-3. 常见 name 字段陷阱

| 名称 | 有效 | 原因 |

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

| deploy-to-staging | 有效 | 不适用 |

| pr-review | 有效 | 不适用 |

| CodeReview | 无效 | 包含大写字母 |

| pr_summarizer | 无效 | 包含下划线 |

| -deploy | 无效 | 以连字符开头 |

| deploy--v2 | 无效 | 包含连续连字符 |

三层加载模型

Codex 通过三个加载层级实现渐进式披露:^(⁷⁸)

会话开始时,Codex CLI 扫描每个配置的技能位置,仅读取 YAML 前置元数据。您可以安装 100 个技能,发现开销约为 8,000 个令牌,相当于一个冗长的 MCP 服务器工具清单。请将 SKILL.md 正文控制在 500 行以内(约 5,000 个令牌);如果指令超出该限制,请将内容拆分到单独的文件中。按主题聚焦的 Markdown 文件(每主题一个,例如 REFERENCE.md、FORMS.md 或特定领域文件如 legal.md)放在 references/ 下。可执行脚本(Python、Bash 或 JavaScript)放在 scripts/ 下。代理仅在 SKILL.md 指令指示时按需加载它们。

这是技能与 MCP 的根本架构差异。MCP 在会话开始时注入所有工具架构;一个包含五个服务器的生产级 MCP 配置,在第一条用户消息到达前可能消耗 55,000 个令牌。渐进式披露模型颠倒了这一点:按需付费。

编写 description 字段

description 字段一份双职:既是人类文档,也是隐式调用的路由规则。模糊的描述会导致漏调;过宽的描述会导致误触。

##### 太模糊 — 会漏掉调用
description: Manages deployments.
##### 太宽泛 — 会在无关任务上触发
description: Handles any infrastructure or cloud task.
##### 写得好 — 具体触发条件,明确排除项
description: |
 将当前服务部署到暂存环境。当被要求部署、推送到暂存、
 切割发布候选版本或更新正在运行的暂存服务时使用。
 请勿用于生产环境部署或本地 Docker Compose 测试环境。

写得好的示例包含三个结构要素:

核心设计原则

  1. 清晰的主要操作:明确界定技能的目的。

  2. 触发同义词:覆盖用户表达同一意图的常用词汇。

  3. 显式排除项:防止危险的误触发。

该字段限制为 1,024 字符:足以容纳同义词和排除项,而无需写成长篇大论。

先写排除项。当你明确阐述技能不应做什么时,包含标准几乎会自行成型。

agents/openai.yaml 文件:策略与 MCP 依赖

agents/openai.yaml 文件(可选)用于配置 UI 表现、调用策略和 MCP 服务器依赖。[⁷⁹]

interface:
 display_name: "Staging Deploy"
 short_description: "Deploy containerised services to the staging environment"
 brand_color: "#FF9900"
 default_prompt: "Deploy the current service to staging"
policy:
 allow_implicit_invocation: false
dependencies:
 tools:
 - type: "mcp"
 value: "awsInfrastructure"
 description: "AWS infrastructure MCP server for ECS and ECR access"
transport: "streamable_http"
 url: "https://mcp.internal.example.com/aws"

allow_implicit_invocation 默认为 true。对于涉及生产基础设施、运行状态修改测试、删除文件或调用影响计费 API 的技能,请将其设置为 false。

包含“deploy”一词的杂散提示绝不应静默触发部署工作流。对于高影响力技能,务必设置 allow_implicit_invocation: false。

dependencies.tools 数组连接技能与 MCP:技能编码如何运行工作流;MCP 服务器提供对外部系统的实时访问。当 Codex CLI 加载带有 MCP 依赖的技能时,会提示用户安装任何缺失的服务器。

构建与发布技能

本地验证

在 Codex 中测试前,请运行参考验证器。skills-ref 是一个位于规范仓库中的 Python 工具,从源码安装:[⁸⁰]

git clone https://github.com/agentskills/agentskills.git
cd agentskills/skills-ref
pip install -e .
skills-ref validate ~/code/your-repo/.agents/skills/deploy-to-staging/

如果你使用 uv,uv sync 是 pip install -e . 的替代方案。验证器会检查前置元数据约束,确认 name 与目录名匹配,并报告行级错误。

在 Codex 中测试

在技能所在的仓库中打开 Codex CLI 会话。输入使用你描述中同义词的提示,确认技能被激活。然后验证 allow_implicit_invocation: false 能阻止隐式激活。

发布与分享技能

agentskills.io 是一个规范和客户端目录,而非托管注册表;没有“发布”按钮能将技能变为单名安装。以分发任何其他开源制品的相同方式分发技能:推送到公共 Git 仓库并告知他人 URL。

  1. 将技能目录推送到公共 GitHub 仓库(约定路径:仓库根目录下的 skills/<skill-name>/)。

  2. 在技能目录中添加 README.md,包含简短描述、前置条件和一个示例调用。

  3. (可选)在社区目录(如 agentskills.io 或 skills.sh)上列出你的技能;两者都索引公开托管的技能仓库。

有三种模式供读者安装你发布的内容。选择与你的分发渠道匹配的一种:

#### 直接从 Git URL 安装 — 适用于任何主机
$skill-installer install https://github.com/<owner>/<repo>/tree/main/skills/<skill-name>
#### 跨平台社区 CLI,支持多种代理
npx skills add <owner>/<repo> --skill <skill-name> -a codex
#### 通过团队注册的市场安装
codex plugin marketplace add <owner>/<repo>

裸名称查找(例如 $skill-installer gh-fix-ci)保留给 OpenAI 在 github.com/openai/skills 处策展的官方目录中的技能。社区技能始终需要 URL 或 owner/repo 命名空间。

技能规模:五层级谱系

并非每个工作流都需要包含脚本、参考资料和 CI 验证的完整生产级技能。技能复杂度落在五层级谱系上,大多数团队因直接跳到上层而过度投入。

第 1 级:10 字提示。 AGENTS.md 中的单句或直接输入。无文件,无 YAML。适合运行一两次的一次性工作流。示例:“对所有已更改文件运行 ESLint 并修复可自动修复的问题。”

第 2 级:可复用提示。 存储在文本文件中或仅含描述的简短 SKILL.md,无支持文件。适合每周重复的工作流。示例:包含描述和五行生成变更日志指令的 SKILL.md。

第 3 级:团队约定。 包含描述、触发同义词、排除项及含有一两个辅助脚本的 scripts/ 目录的 SKILL.md。适合跨团队共享的工作流。大多数项目专用技能应止步于此。

第 4 级:库技能。 完全规范的 SKILL.md,包含 references/、scripts/、assets/、agents/openai.yaml、MCP 依赖及 CI 验证。适合作为社区目录条目发布的跨团队或跨组织技能。

第 5 级:生产制品。 具有版本锁定、自动化基准测试、回归测试和正式评审流程的技能。适合把关生产部署或强制执行合规策略的技能。

陷阱在于在第 2 或 3 级足矣时却伸手去够第 4 或 5 级。解决今日问题的 10 字提示,胜过为解决明天假设问题而投入三天打磨的生产级技能。

如果编写 SKILL.md 耗时超过了它自动化任务本身的耗时,说明你在过度设计。先写提示。用三次后,提取为第 2 级技能。团队采纳后,晋升为第 3 级。让使用量驱动复杂度。

创作工作流:手动构建,再编码化

一种新兴社区实践,由 Nate Herk 的 Codex CLI 课程推广,即先手动构建工作流,再将其编码为技能,而非预先设计技能稍后再发现缺口。[⁸¹] 五步流程如下:

  1. 在标准会话中手动构建。 端到端完成任务。识别步骤、错误处理逻辑、输出格式和 API 端点。

  2. 编写 SKILL.md。 用清晰的描述(路由规则)和分步指令编码流程。

  3. 验证激活。 确认技能出现在 TUI 选择器中并响应其 $ 调用。

  4. 关联 AGENTS.md。 如果技能依赖项目特定路径、基础设施或约定,确保项目的 AGENTS.md 在会话开始时提供该上下文。

  5. 迭代优化。 面对真实输入运行。当失效模式出现时,收紧指令。

这自然产出第 2 或 3 级技能,避免了跳到第 4 级的过早复杂性。围绕同一工作流衍生出三种相邻实践:[⁸²]

全局与项目本地。 如果技能引用特定文件路径、基础设施端点或项目特定配置,使其成为项目本地(提交至 $REPO_ROOT/.agents/skills/)。如果其通用运行(如变更日志生成器、PR 总结技能),全局安装在 $HOME/.agents/skills/。

作为技能步骤的质量门禁。 部署技能可包含浏览器验证步骤,在未发现严重 Bug 前阻止推送。这将质量把关直接嵌入技能流程,而非依赖单独的 CI 检查,符合第 19 章中的“源头质量”原则。

创作时的推理力度。 Medium 或 High 推理力度是迭代优化的最佳平衡点:足够快以便迭代,足够准确以正确编码流程。为调试复杂故障保留 Extra High。

[⁷⁹]: 脚注 79 内容

[⁸⁰]: 脚注 80 内容

[⁸¹]: 脚注 81 内容

[⁸²]: 脚注 82 内容

评估技能

使用 $skill-creator 脚手架新技能,而不是从头编写 SKILL.md。

评估技能

skills-ref 可以捕捉语法错误,但无法捕捉行为失败。要回答“技能是否真正有效?”,你需要一个评估流水线:一种结构化、可重复的方式,用于根据预期结果衡量技能行为。[3]

三层评估架构

第一层:手动冒烟测试。 通过所有三种调用路径触发技能:TUI 选择器、$ 前缀,以及通过描述匹配的隐式路由。验证“何时使用”和“何时不使用”的指导原则是否成立。

第二层:确定性 JSONL 评分。 运行 codex exec --json 以生成结构化事件流(thread.started、turn.started/turn.completed、item.started/item.completed,类型包括 command_execution、file_change、mcp_tool_call)。以编程方式解析事件,以断言特定行为(文件创建、命令执行、调用正确的工具)。从包含 10 到 20 个代表性用例的 CSV 测试集中提供提示词。

第三层:模型辅助量表评分。 使用 codex exec --output-schema 配合 Zod 或 JSON Schema 定义来收集结构化评估输出。一个单独的模型实例根据量表(正确性、完整性、指令遵循度)对每个响应进行评分。这可以捕捉确定性检查遗漏的定性失败。

描述字段是评估的一部分。 如果描述错误,技能会在不该触发时触发,或在该触发时失败。通过创建触发/非触发提示词对,单独评估描述,确认隐式路由符合预期。一个内部完美但描述误导的技能在实践中仍会失败。

社区评估工具

在官方工具成熟之前,社区评估工具正在填补空白。有两个值得了解:

  1. Skillgrade[4] 将所有三层封装为单个命令:

    skillgrade run --skill .agents/skills/deploy --tests tests/deploy.csv
    

    它运行 CSV 测试用例,执行确定性断言,可选调用模型辅助评分,并为每个用例写入机器可读的通过/失败报告。

  2. Skill Creator V2(Nathan Onn)采用不同角度:针对固定测试用例集进行并行变体基准测试,根据输出质量、一致性和指令遵循度对每个变体评分。[5] 它用定量比较取代了“感觉更好”的判断。基准测试运行消耗大量 Token;请参阅 第 14 章 了解成本影响。对于第 4 层或第 5 层技能,定量评分基础设施可以证明投资的合理性。

CI 集成

运行 skills-ref 进行语法验证,运行评估流水线进行行为验证:

- name: Validate skills
  run: |
    pip install git+https://github.com/agentskills/agentskills.git#subdirectory=skills-ref
    for skill_dir in .agents/skills/*/; do
      skills-ref validate "$skill_dir"
    done
- name: Eval skills
  uses: openai/codex-action@v1
  with:
    codex-args: "--json"
    eval-config: .codex/eval/skills.yaml

根据评估通过率设置合并门禁:低于阈值的技能回归将阻止 PR。这对技能应用了与应用代码相同的规范。

托管生命周期平台

想要版本控制、分发、评估和治理,又不想自己构建流水线的团队,可以使用托管平台(如 Tessl[6]),它提供注册表、治理控制和全生命周期工具。托管平台以较少的控制权换取更少的维护和更快的企业级治理路径;当手动管理一切的开销开始占主导地位时(第 4 层或第 5 层),这种权衡最合理。

插件:打包与分发

技能编码程序性知识。插件将这些知识连同应用连接器和 MCP 服务器配置打包成可安装、可分发的单元。Codex 插件市场是分发渠道:技能、应用集成和 MCP 服务器配置的托管安装和更新工作流。[7]

插件架构

插件是一个包含 .codex-plugin/plugin.json 清单和可选组件包的目录。[8] 清单最多声明三种组件类型:

  1. 技能:skills/ 子目录中的 SKILL.md 文件,通过前文所述的渐进式披露模型按上下文加载

  2. 应用:外部服务连接器(GitHub、Slack、Gmail、Google Drive),通过 OAuth 或 API Token 提供经过认证的数据访问

  3. MCP 服务器:在 .mcp.json 文件中声明的模型上下文协议服务,暴露来自外部系统的工具和共享上下文

任意组合均有效。插件将 AGENTS.md 片段、config.toml MCP 部分、OAuth 设置和支持资产打包成单个可安装单元。

my-review-tools/
├── .codex-plugin/
│ └── plugin.json # 必需清单
├── skills/
│ └── security-review/
│ └── SKILL.md # 技能指令
│ └── perf-review/
│ └── SKILL.md
├── .mcp.json # 可选 MCP 配置
├── .app.json # 可选应用连接器
└── assets/
 └── logo.png

最小 plugin.json 示例:

{
 "name": "my-review-tools",
 "version": "1.0.0",
 "description": "Code review patterns for the platform team",
 "skills": "./skills/",
 "mcpServers": "./.mcp.json",
 "interface": {
 "displayName": "Review Tools",
 "shortDescription": "Automated code review against team standards",
 "category": "Code Quality"
 }
}

所有清单路径必须以 ./ 为前缀,并相对于插件根目录。name 字段必须是稳定的 kebab-case;Codex CLI 在市场操作中将其用作插件标识符。

市场发现与分发

Codex CLI 从三个来源读取插件目录,按优先级顺序解析:

  1. openai/skills 精选目录:官方经过审核的插件目录,启动时获取并本地缓存。这些显示在 /plugins TUI 浏览器的“精选”下。

  2. 仓库级市场:$REPO_ROOT/.codex/plugins/marketplace.json 将插件限定在特定仓库。提交此文件,每个协作者自动获得相同的插件集。

  3. 个人市场:~/.codex/plugins/marketplace.json 保存针对单个开发者的插件,适用于个人工作流自动化或开发中的插件。

当插件名称出现在多个市场中时,优先级最高的来源生效,允许组织在需要时用仓库级分支覆盖精选插件。[9]

插件通过两种调用机制呈现。隐式调用:描述你想要的结果(例如“总结我未读的 Gmail 线程”),Codex CLI 自动选择合适的插件工具。显式调用:输入 @ 后跟插件或技能名称,直接针对特定能力。在脚本化工作流和 AGENTS.md 指令中,显式调用更佳,因为确定性很重要。

安装策略与企业治理

marketplace.json 中的每个插件条目都带有一个 installation 策略,直接映射到企业 IT 治理模式:[10]

| 策略 | 行为 | 企业用例 |

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

| INSTALLED_BY_DEFAULT | 对打开仓库的每个团队成员立即激活 | 标准工具推广 |

| AVAILABLE | 在 /plugins 中列出但需要手动安装 | 批准的可选工具 |

| NOT_AVAILABLE | 完全从目录中隐藏 | 未批准、已弃用或受限工具 |

插件认证策略

独立的 authentication 字段控制何时与第三方服务交换凭据:

  • ON_INSTALL:安装期间立即触发认证

  • ON_FIRST_USE:延迟到插件被调用时才认证

插件市场配置示例

{
  "name": "acme-team-plugins",
  "interface": { "displayName": "Acme Engineering Plugins" },
  "plugins": [
    {
      "name": "internal-review-bot",
      "source": { "source": "local", "path": "./plugins/review-bot" },
      "policy": { "installation": "INSTALLED_BY_DEFAULT", "authentication": "ON_INSTALL" },
      "category": "Code Quality"
    },
    {
      "name": "slack-integration",
      "source": { "source": "local", "path": "./plugins/slack" },
      "policy": { "installation": "AVAILABLE", "authentication": "ON_FIRST_USE" },
      "category": "Communication"
    },
    {
      "name": "untrusted-community-tool",
      "source": { "source": "local", "path": "./plugins/community-x" },
      "policy": { "installation": "NOT_AVAILABLE" },
      "category": "Experimental"
    }
  ]
}

这反映了移动设备管理(MDM)和 IDE 扩展治理中的允许列表模式。将 marketplace.json 提交到仓库根目录,可将插件策略随其所管辖的代码一起纳入版本控制。

谨慎使用 INSTALLED_BY_DEFAULT。默认安装的插件会增加每个会话的上下文负担。仅将其保留给真正通用的团队工作流(代码审查标准、安全策略),而非可有可无的功能。

通过 app-server RPC 进行安装和移除生命周期管理

插件系统支持完整的安装、浏览、移除和更新生命周期:^(⁹¹)

| 生命周期阶段 | 能力 | CLI 界面 |

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

| Install | 注册市场并从 Git、本地路径或 URL 安装插件 | codex plugin marketplace add, /plugin install |

| Browse | 带标签页的市场 TUI、内联切换、跨仓库插件读取(含生成的描述) | /plugins |

| Remove | 彻底卸载市场源及其缓存的插件 | codex plugin marketplace remove |

| Update | 自动将配置的市场升级到最新上游版本 | codex plugin marketplace upgrade |

移除逻辑位于 codex_core::plugins::remove_marketplace(),这是一个可从 CLI 和 app-server RPC 层调用的共享函数。^(⁹²) CLI 是对该核心函数的薄封装,因此任何客户端界面(VS Code 扩展、桌面应用、JetBrains 插件,或基于 app-server 协议构建的自定义集成)都可以通过相同的代码路径安装、浏览和移除插件。

来自远程市场的未安装插件会显示生成的描述,包含源 Git URL、路径、引用和 SHA 详情,以便团队在决定安装前评估插件。^(⁹³)

可复用工作流模式:组合技能与插件

当技能和插件组合成可复用的 Agent 工作流时效果最佳:即技能激活、插件调用和 MCP 工具调用的序列,编码了团队的标准操作程序。^(⁹⁴)

模式 1:审查-修复循环

代码审查技能识别差异中的问题。修复技能(或 Agent 原生编码能力)解决每个发现。第二次审查确认修复。该循环可编码为单个复合技能(其指令编排序列),或作为三个独立技能由父级 AGENTS.md 指令串联。

模式 2:脚手架-测试-部署流水线

脚手架技能生成样板代码(新服务、新迁移、新 API 端点)。测试技能运行项目测试套件并报告失败。部署技能在测试通过后推送到暂存环境。每个技能独立有用;组合后,它们编码了完整的功能交付工作流。

模式 3:插件即工作流包

当工作流不仅需要指令,还需认证 API 访问和外部工具时,插件打包整个技术栈。一个插件可能包含:用于编排 Terraform 漂移修复工作流的技能、用于读取云提供商状态的 MCP 服务器、用于将结果发送到 Slack 的应用连接器。开发者安装一个插件即获得完整能力。

通用原则:从独立技能开始用于仅含指令的工作流。当工作流需要 MCP 服务器、应用连接器或团队级治理控制时,提升为插件。当提交的 SKILL.md 即可满足时,抵制将所有内容打包进插件的冲动。

设计组合工作流时,保持每个技能专注于单一职责。审查代码的技能不应同时部署代码。组合发生在编排层(AGENTS.md 指令、父技能或插件清单),而非单个技能内部。

插件捆绑钩子

插件可随技能和 MCP 服务器一起携带自己的生命周期钩子,扩展 第 9 章 所述的核心钩子系统。^(⁹⁵) 插件捆绑钩子在与用户定义钩子相同的生命周期点触发(PreToolUse、PostToolUse、PreL1-Bullet、PostL1-Bullet),但作用域限定在插件上下文中。安全插件可捆绑 PreToolUse 钩子以阻止危险的 Shell 命令,无需用户编写自定义钩子脚本。钩子启用状态按插件跟踪,因此切换插件开/关会同时切换其捆绑钩子。

扩展优先架构

Codex CLI 采用扩展优先架构:核心能力作为可插拔扩展而非硬连线的运行时组件。自动审查器(检查建议变更的安全性和正确性的模型)本身就是可插拔扩展,团队可替换、定制或补充:面向受监管环境的更严格策略引擎,或面向内部工具项目的轻量级检查。插件 ID 规范化保持共享工作区整洁(my-org/linter 和 My-Org/Linter 注册为同一插件而非两个),插件可发现性分离让管理员能独立控制市场浏览器中可见的插件与可安装的插件。对技能和插件作者的实用启示:治理、审查和策略执行技能插入的是核心功能使用的相同扩展点。

案例研究

这些模式在生态系统中普遍存在。四个示例展示了不同层级、不同来源的技能:构建系统集成、OpenAI 团队官方的一级项目技能、编排完整工作流的社区元技能,以及主要平台供应商的精选目录。

Nx 单体仓库 Agent 技能

Nx 构建系统的官方 Codex CLI 集成展示了技能与 MCP 分离的实践。@nx/codex 包提供六个核心技能(nx-project-details、nx-affected、nx-generators、nx-dependency-graph、nx-migrate 和 nx-ci-pipeline),每个编码为带有调用 Nx CLI 命令的辅助脚本的 SKILL.md。除了技能,一个 MCP 服务器(nx-mcp)暴露实时工作区元数据:项目图、任务流水线和生成器模式。

技能编码程序性知识(“添加新库时,按以下约定运行 nx g @nx/js:library”),而 MCP 服务器提供实时数据(“这是工作区中的 47 个项目及其依赖关系”)。同时使用两者的 Agent 可推理单体仓库结构(通过 MCP)并正确操作(通过技能),无需开发者在每次提示中解释 Nx 约定。对于大型 Nx 单体仓库,这将 AGENTS.md 的篇幅从数页说明压缩为指向已安装技能的几行指向。

codex-pr-body:OpenAI 团队官方编写的首个项目级技能

codex-pr-body 技能:第三层参考实现

codex-pr-body 技能是 OpenAI 团队编写的首个正式的项目级技能,直接提交到了 Codex CLI 仓库本身。与 $skill-installer 或 $skill-creator 等作为内置原语随产品发布的系统技能不同,codex-pr-body 位于 .agents/skills/codex-pr-body/ 并遵循本章提倡的相同约定。其用途:生成考虑了项目特定工作流模式的拉取请求描述。

三个设计选择使其成为一个有用的参考实现:

随代码库一同发布的技能

该技能被提交到仓库中,而非外部发布。克隆仓库的每位贡献者都能自动获得一致的 PR 描述行为:无需安装步骤,无版本漂移。这验证了第三层团队约定模式:复杂到足以保证结构化说明,简单到足以跳过外部发布基础设施。

仅读取 Sapling CLI 以保障安全

该技能将 sl 限制为只读操作(sl log、sl status、sl diff)并显式禁止写入。技能需要仓库历史来生成准确的描述,但作为副作用绝不能修改工作树。此模式可推广至任何在不冒险导致意外变更的情况下检查版本控制状态的技能。

感知堆叠 PR 的描述

Codex CLI 团队使用堆叠拉取请求(通过 Sapling 和 Graphite),其中单个功能可能跨越多个依赖的 PR。该技能检查特定于当前堆叠层的提交范围,而非简单地对比 main 分支,从而生成反映每个 PR 增量变更的描述。使用堆叠差异工作流的团队面临相同问题;codex-pr-body 演示了如何将堆叠感知编码为可移植的技能格式。

更广泛的意义在于:构建智能体的团队已采用本章提倡的模式。当你在权衡是否投资团队专用技能时,codex-pr-body 既是参考实现,也是该模式可扩展的证据。

aigorahub/elves:编排工作流的元技能

迄今讨论的技能为单一任务编码知识:迁移流程、PR 描述格式、审查清单。Elves(aigorahub/elves)则完全不同:一种编排完整的多批次自主开发工作流的技能,拥有自己的记忆管理、治理模型和战略性遗忘。^(⁹⁶)

Elves 通过两次调用交接模式运行:一个 Stage(暂存)调用用于准备工作计划(解析需求、生成批次定义、创建治理结构),随后是一个 Launch(启动)调用开始自主执行。这种分离是刻意设计的,在智能体投入可能长达数小时的自主工作前,给人类一个审查检查点。

四项架构创新使 Elves 对技能设计者意义重大:

分层记忆

Elves 跨批次边界维护四个不同的知识层:一个计划(整体工作规范)、一个生存指南(来自先前批次的惨痛教训,必须持久化)、一个心得(可能被丢弃的软性观察)和一个执行日志(已采取行动的持续记录)。每层有不同的保留规则:计划不可变,生存指南仅追加,心得在批次间审查和修剪,执行日志在每批次完成后归档。

宪法式判决

每批次结束后,Elves 根据一组人工编写的“底线”规则评估自身输出,这些是硬性约束,一旦违反即中止工作流并升级给人类。这是技能层面的治理而非钩子层面:规则是技能知识的一部分,而非外部执行基础设施。

战略性遗忘

Elves 不让上下文窗口填满直到压缩降低质量,而是主动归档已完成的批次历史,仅将持久化知识提升到其 .ai-docs/ 目录。工作集在任意长的工作流中保持精简。此模式是第七章讨论的模型级压缩策略在应用层面的补充。

生产级自主比率

该框架专为多批次开发任务上的高智能体-人类时间比而设计:人类在暂存和审查上投入几分钟,而智能体自主工作数小时。这些比率之所以可行,是因为技能的记忆和治理层处理了原本需要人类干预的故障模式。

Elves 证明技能不仅仅是指令集。它们可以是完整的运营框架:记忆管理、治理、自主性和自我修剪,打包为可移植的 SKILL.md。对于构建雄心勃勃的自主工作流的团队,Elves 是第五层生产级技能外观的当前参考实现。

Google Agent Skills:平台维护的知识包

从个人作者向平台维护包的转变,最好由 Google Agent Skills(google/skills)来说明,这是一个涵盖测试、代码审查、API 设计、性能、安全等领域的官方目录。^(⁹⁷) 每个技能均可通过标准分发机制安装:

npx skills add google/skills

其意义不在于内容(涵盖的是公认的工程实践),而在于分发模式:一家主要平台厂商维护并版本化技能包,可安装到任何支持 Agent Skills 标准的智能体中。这印证了生态系统的可移植性承诺:个人社区作者使用的相同 SKILL.md 格式,也适用于官方平台分发。

对企业团队而言,Google Agent Skills 可作为基线。安装官方包以广泛覆盖工程最佳实践,再在其上叠加团队专用技能以适配组织特定约定。这种组合同时提供了广度(平台维护、定期更新)和深度(团队编写、上下文相关)。

总结

  1. 技能是一个目录。 SKILL.md(包含 name 和 description 的 YAML 前置元数据,随后是 Markdown 说明),可选地由 scripts/、references/ 和 assets/ 支持。智能体将说明作为加载的上下文读取,并在说明指示时调用支持文件;无任何内容自动执行。

  2. 可移植的开放标准。 Agent Skills(agentskills.io)在 Linux 基金会的 Agentic AI Foundation 治理下,与 AGENTS.md 并列,可在兼容的智能体产品中原样工作。

  3. 三层架构,一套工作流。 技能用三层渐进式披露编码程序性知识;MCP 服务器通过有状态连接暴露实时集成;钩子在生命周期事件强制执行运行时策略。成熟的智能体工作流同时使用三者,技能承载之前存在于臃肿 AGENTS.md 文件中的“如何做 X”知识。

  4. 描述即路由规则。 它既是人类文档,也是智能体决定何时加载技能的触发器。编写时需包含明确的主要动作、触发同义词和显式排除项。

  5. agents/openai.yaml 桥接。 声明外部 MCP 服务器依赖,并对永不应在偶然匹配的提示词上触发的技能强制执行 allow_implicit_invocation: false。

  6. 解析顺序为项目、用户、管理员、再系统。 将团队技能提交到 .agents/skills/ 以确保团队间行为一致。

  7. 五层复杂度谱系。 从 10 个单词的提示词到带有正式评估的生产制品。从起作用的最低层开始,仅当具体痛点要求时才提升层级。

8. 以构建为先的工作流

手动完成任务,将其编码为 SKILL.md,针对失败模式进行迭代。这自然会产出第 2 层到第 3 层的技能,且不会过早引入复杂性。

9. Codex 插件市场

分发将技能、应用连接器和 MCP 服务器配置捆绑为单个可安装单元的插件。marketplace.json 中的安装策略(INSTALLED_BY_DEFAULT、AVAILABLE、NOT_AVAILABLE)提供了企业级治理,其生命周期涵盖通过 codex plugin marketplace 子命令和应用服务器 RPC 层进行安装、浏览、移除和升级。

另请参阅: 第 4 章(AGENTS.md)。当 AGENTS.md 文件变得难以驾驭时,技能是正确的重构目标。第 4 章介绍了表明该将程序性知识提取为可移植技能的膨胀模式。

练习

  1. 从零编写一个团队约定技能。 找出团队 AGENTS.md 中一个足够专业、适合归入技能的章节(运行数据库迁移的说明、构建集成测试的结构或生成发布变更日志)。将其提取到 .agents/skills/ 中的一个技能里,编写包含至少两个触发短语和一个显式排除项的描述,使用 skills-ref 进行验证,并确认该章节可从 AGENTS.md 中移除而不影响代理行为。

  2. 调整过度触发的描述。 安装一个与你工作相关的社区技能类别。发送五个应激活它的提示词和五个不应激活它的提示词。如果观察到误触发(假阳性),编辑描述以缩小触发条件。记录修改前后的描述,并解释为何修订版本校准得更好。

  3. 创建一个本地插件市场。 构建一个将两个相关技能(例如,一个审查技能和一个修复技能)捆绑为单个可安装单元的插件。在仓库根目录创建一个带有 AVAILABLE 安装策略的 .codex/plugins/marketplace.json。验证该插件出现在 /plugins 浏览器中,安装它,并测试这两个技能。然后将策略改为 INSTALLED_BY_DEFAULT,并确认全新的 Codex CLI 会话会在无需手动安装的情况下自动激活该插件。

第十一章:子代理与并行执行

第 10 章将子代理委托作为重置上下文窗口时钟的一种策略引入:启动一个拥有自己全新窗口的新线程,以处理会淹没父代理的工作。本章将充分展开该策略。

当任务天然具有并行性(对一百个文件进行 lint 检查、翻译十几个模块、总结一周的拉取请求描述)时,单线程会成为瓶颈。当任务超出一个上下文窗口时,你需要一种移交工作的方式。当某个子任务的失败不应导致整个作业中止时,你需要隔离性。Codex CLI 通过子代理系统同时解决了这三个问题:一组共享会话但拥有各自独立的上下文、模型、沙箱和工具权限的代理线程。

学习目标

你将能够:

  1. 描述三个内置代理,并判断何时值得定义自定义代理。

  2. 编写包含正确必需字段和可选字段的 TOML 代理定义。

  3. 使用 spawn_agents_on_csv 将工作分配到 CSV 输入中并聚合结果文件。

  4. 根据作业的规模和形状,在进程内子代理、带工作树的本地 Shell 扇出和 Codex 云任务之间进行选择。

  5. 识别使并行性成为糟糕默认选项的资源和速率限制成本。

子代理模型

内置代理

Codex CLI 自带三个代理:default、worker 和 explorer。你进行输入交互的会话即为 default 代理。另外两个是专用线程,无需编写任何配置即可将工作移交给它们:

  1. worker 专注于执行。用于你想要一个未被规划对话污染的全新上下文的实现任务。

  2. explorer 侧重于读取。用于代码库探索,此时父代理不应吸收探索者读取的每个文件。

Codex 仅在你要求时才会生成子代理。你通过自然语言告诉父代理将工作移交给内置代理:“生成一个 explorer 来梳理仓库中对 payment_service 的所有引用”,或“使用 worker 应用补丁并运行测试”。父代理决定哪个内置代理合适并启动线程。没有自动路由器会检查任务并为你在 worker 和 explorer 之间做选择。

内置代理涵盖了大多数一次性委托场景。只有当你想要一组特定的指令集、不同的模型、更严格的沙箱或受限的工具列表以保持一致应用时,才需要自定义代理。

在线程间切换

在 REPL 中使用 /agent 可在活动线程间切换并检查正在进行的线程。如果有一个父代理和一个生成的 worker,选择器如下所示:

Subagents
Select an agent to watch.  + ← previous,  + → next.
' 1. • Main [default] (current)  019eb02d-9b91-7370-b503-7179f1ed731d
  2. • Boyle [worker]            019eb02e-b7a1-7dc0-8b38-f7ff725e00d3

每行显示三项内容:昵称(Main、Boyle)、方括号中的代理角色(default、worker)以及线程的 UUID。Main 是保留给会话自身线程的标签;其他每个线程都以科学、数学或哲学领域的著名历史人物命名(Boyle、Euclid、Hypatia、Lovelace、Turing 等)。Codex 从内置的约一百个名字列表中抽取,因此三个 worker 的扇出会显示为三个不同的行,而不是三个相同的 worker 条目。

当非活动线程需要批准(例如需要确认的工具调用)时,Codex 会带上来源标签呈现请求,以便你知道是哪个线程在请求。按 o 可在批准前打开该线程,以便查看请求来源的上下文。

应用于父代理的权限和沙箱覆盖会传播给从它生成的子代理。切换到只读模式的父代理不会悄悄地将一个具备写入能力的全新沙箱交给子代理。

自定义代理定义

自定义代理是单表 TOML 文件。将其放置在 ~/.codex/agents/<name>.toml 以供个人在所有项目中使用,或放在 .codex/agents/<name>.toml 以随项目分发。

有三个必需字段:

| 字段 | 描述 |

| --- | --- |

| name | 用于调用代理和标记其输出的稳定标识符 |

| description | 简短摘要;显示在代理选择器中 |

| developer_instructions | 代理的系统指令,以多行字符串形式编写 |

五个可选字段可覆盖代理从父会话继承的内容:

| 字段 | 描述 |

| --- | --- |

| model | 模型标识符(gpt-5.5、gpt-5.4、gpt-5.4-mini) |

| model_reasoning_effort | low、medium、high 或 xhigh |

| sandbox_mode | read-only、workspace-write 或 danger-full-access |

| mcp_servers | 该代理可使用的 MCP 服务器名称数组 |

| nickname_candidates | 并发运行多个实例时使用的显示标签 |

一个在数据库迁移脚本登陆主分支前对其进行审计的代理的完整定义:

迁移审计代理配置

name = "migration-auditor"
description = "审查 SQL 迁移脚本的不可逆操作、重锁操作和不安全排序。"
model = "gpt-5.4"
model_reasoning_effort = "high"
sandbox_mode = "read-only"
nickname_candidates = ["Codd", "Stonebraker", "Hellerstein", "DeWitt", "Widom"]
developer_instructions = """
你在 SQL 迁移脚本应用到生产数据库之前对其进行审计。
对于你读取的每个文件,请在以下三个标题下报告发现:
- 不可逆:DROP、RENAME、NOT NULL 添加、或无回滚路径的列类型收窄。
- 重锁:在大表上持有排他锁足够长时间以阻塞写入的语句(DROP COLUMN、Postgres < 11 上带默认值的 ADD COLUMN、某些索引操作)。
- 顺序敏感:其结果取决于迁移是从头到尾运行,还是在部分失败后重新运行的操作。
引用每个发现的文件和行号。不要提出修复方案;拥有该迁移的团队决定如何补救。
"""

不存在表数组格式。每个文件一个代理,按其功能命名。migration-auditor 位于 .codex/agents/migration-auditor.toml,无论父代理生成多少副本,都作为 migration-auditor 调用。

nickname_candidates 为多个此代理实例同时运行时使用的显示标签设定主题。默认情况下,Codex 从内置的约一百位科学、数学和哲学历史人物列表(欧几里得、希帕提亚、洛夫莱斯、图灵……)中挑选,因此三个审查代理的扇出会在 /agent 选择器中显示为三个不同的行,而不是三个相同的 reviewer 条目。提供 nickname_candidates 将仅为此代理覆盖该列表,因此 migration-auditor 将显示为 Codd、Stonebraker、Hellerstein 等,按数组顺序依次选取。列表必须包含唯一条目,且这些值仅用于展示:Codex 仍通过其 name 标识和生成代理,因此提示“生成 migration-auditor”无论实例最终获得哪个昵称都能工作。

要触发代理,请在发给父代理的提示中命名它。例如:

对 db/migrations/2026-06/ 下的每个文件生成 migration-auditor,并返回一个按文件分组、严重程度按不可逆优先排序的 Markdown 报告。

父代理将 migration-auditor 读取为对自定义定义的引用,使用 TOML 文件中的模型、沙箱和指令生成线程,并在完成时将结果聚合回对话。相同的提示结构对 worker 和 explorer 也有效,无需磁盘上的任何配置。

为子代理选择模型

对于执行机械工作(静态检查、格式化、提取结构化数据)的工作代理,使用更便宜或更快的模型;将更强大的模型留给父代理用于规划和聚合。gpt-5.4-mini 通常比旗舰模型更适合子代理工作角色;当工作代理本身进行大量推理时,gpt-5.5 更合理。省略 model 以从父代理继承。

会话级限制

config.toml 中 [agents] 下的三个设置管辖会话中的每个子代理:

| 键 | 默认值 | 控制内容 |

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

| max_threads | 6 | 最大并发打开代理线程数 |

| max_depth | 1 | 嵌套深度:1 允许父代理的直接子代理,不允许孙代理 |

| job_max_runtime_seconds | — | spawn_agents_on_csv 的默认每工作代理超时 |

这些是会话级的,而非按代理。默认值特意保守。六个并发线程足以应对大多数扇出而不触及速率限制,将嵌套限制在一层可防止广泛的委派指令演变为重复扇出,这会复合令牌使用量、延迟和本地资源消耗。当你有明确理由并了解你的速率限制和令牌预算能承受什么时,再提高任一限制。

每个子代理都计入其所用模型的账户级速率限制。六个 gpt-5.5 工作代理的扇出会比六个 gpt-5.4-mini 更早触及限制。将模型选择与槽位数量匹配。

任务分解


  1. 62 ↩︎

  2. 63 ↩︎

  3. 83 ↩︎

  4. 84 ↩︎

  5. 85 ↩︎

  6. 86 ↩︎

  7. 87 ↩︎

  8. 88 ↩︎

  9. 89 ↩︎

  10. 90 ↩︎

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