案例研究:Codex —— 把代理式编码变成一套可审计的工作面#

Codex 的重点不只是"会写代码的模型",而是模型周围那一圈可配置、可回滚、可审计的工作环境。

OpenAI 对 Codex 的公开叙述,已经把注意力从单次补全转向了更完整的代理式编码工作面:AGENTS.md、权限规则、hooks、记忆、技能与 worktree,一起构成了一个团队可以调校的马具 [OpenAI Codex Team, 2026]。本章把 Codex 当作一具厂商托管的闭源马具来读:不试图复刻产品内部实现,只抽取那些团队能够在自己仓库中明确持有的表面。

备注

边界。 本章只分析 Codex 的公开可见配置面与可迁移实践。凡涉及运行时内部策略、模型选择、调度器、远端执行环境的判断,都按"不可见"处理;读者不应把本章当作产品内部实现说明。

14.1 —— Codex 的五个可持有表面#

Codex 对团队真正有用的地方,是它把"让智能体怎么工作"这件事拆成几类可以被版本化的材料:

  1. 项目手册。 AGENTS.md 让仓库拥有一份给智能体看的 onboarding 文件。它不是百科全书,而是入口、边界、验证命令与团队约定。

  2. 权限规则。 规则文件把"哪些命令可以静默运行、哪些必须问人、哪些默认拒绝"做成显式策略。

  3. hooks。 会话开始、工具调用之后、任务结束时的固定动作,让智能体形成可重复的工作节奏。

  4. 记忆。 长期偏好与一次性任务上下文分开;稳定、低敏、长期有效的偏好才进入记忆。

  5. 任务提示模板。 人类把需求写成小型 work ticket,而不是模糊愿望。

这五个表面分别对应三大护法:AGENTS.md 和任务模板主要是 SDD × 缰绳;权限规则与 hooks 是 TDD × 护栏;记忆与结束自检则落在 MDD × 梳理

14.2 —— 十二格亮点图#

格子

得分

证据

SDD × 缰绳

5

AGENTS.md、任务提示模板、技能入口共同塑造每回合上下文;这是 Codex 最强的可迁移表面。

SDD × 护栏

4

项目手册可以写明目录边界、公开 API、隐私规则与验证矩阵;坏规则仍需靠 hooks 或 CI 执行。

SDD × 牧场

3

worktree 让任务有独立工作区,但验收标准仍主要由团队在 PR 与 CI 中定义。

SDD × 梳理

3

AGENTS.md 可被版本化评审;但若没有固定审计节奏,它也会像普通文档一样漂移。

TDD × 缰绳

3

任务提示模板把"Done When"前置,能引导智能体找测试,但不等于测试先行。

TDD × 护栏

5

权限规则与 hooks 能把危险命令、廉价检查、结束自检变成机器执行的边界。

TDD × 牧场

3

独立 worktree 降低相互踩踏;真正的发布关卡仍要靠仓库自己的 CI/CD。

TDD × 梳理

3

hooks 可以记录检查结果;是否把失败模式沉淀回测试套件,是团队责任。

MDD × 缰绳

3

结束自检可以要求列出命令、结果、风险;成本和吞吐仍需要外部度量承接。

MDD × 护栏

4

权限规则能把高风险命令转成人类确认点,是成本与爆炸半径的实际上限。

MDD × 牧场

2

Codex 客户端本身不是团队发布系统;生产 SLI 仍要在业务侧定义。

MDD × 梳理

4

记忆、会话结束摘要、工作区隔离,给复盘提供了稳定抓手。

这张图的形状很鲜明:Codex 强在"把智能体的工作面做成可配置产品",弱在"替团队定义业务验收与产出度量"。这正是本书一直强调的分界:厂商可以提供马具表面,团队必须持有马具责任。

14.3 —— AGENTS.md 是入口,不是杂物间#

Codex 的 AGENTS.md 最容易被误用成"什么都往里塞"的长文档。更稳的做法是让它保持短小,只回答四类问题:项目地图、上下文指针、工作约定、验证矩阵。

<!-- verified: 2026-05-21 · Ch.14 hands-on · AGENTS.md template (full-stack repo) -->

# AGENTS.md — {{PROJECT_NAME}}

<!--
Keep this file under ~100 lines. AGENTS.md is the project manual the agent
reads on every session; it is NOT a junk drawer. Long docs belong under
docs/ and are referenced by Context Pointers below. For polyglot monorepos,
push module-specific rules into <module>/AGENTS.md (or AGENTS.override.md).
-->

## Project Map

- `backend-java/` — Spring services
- `backend-go/` — Go services and background jobs
- `scripts/` — Python automation
- `native/` — C++ libraries and bindings
- `crates/` — Rust components
- `web/` — Vue or React frontend

## Context Pointers

- Architecture overview: `docs/architecture.md`
- API contracts: `docs/api/`
- Database schema: `db/schema/`
- Frontend routing: `web/src/router/`
- Test guide: `docs/testing.md`

## Working Agreements

- Read relevant code before editing.
- Prefer small, reviewable changes.
- Do not reformat unrelated files.
- Preserve public APIs unless the task explicitly asks for breaking changes.
- Ask before running destructive commands or touching generated files.
- For non-trivial changes, produce a plan first; only then edit files.
- If context is insufficient, ask. Do not guess.

## Verification Matrix

Replace placeholder commands with the repo's actual task runner. Be honest
about what does not exist yet — that is more useful than pretending.

| Stack | Risk to look for | Verification |
| --- | --- | --- |
| Java | layering, transaction boundary | `./mvnw test` (or `./gradlew test`) |
| Go | context propagation, goroutine / channel leaks | `go test ./...`, `go test -race ./...` for concurrency |
| Python | argv / path / env handling, exception leaks | `pytest`, `ruff check` if configured |
| C++ | ownership, lifetime, thread safety, ABI | existing CMake / Bazel build + `ctest`, sanitizer if available |
| Rust | error types, public API stability | `cargo fmt --check && cargo clippy && cargo test` |
| Vue | reactivity, props/emits boundary | `npm run lint && npm run typecheck` |
| React | hooks deps, state lifting, memo necessity | `npm run lint && npm run typecheck` |

When the repo lacks a check, say so explicitly:

> Frontend: no component test suite yet; run `npm run typecheck` and
> manually verify affected pages.

## Security And Privacy

- Never print secrets, tokens, cookies, request bodies, phone numbers,
  emails, or payment data — to terminal, logs, or final summary.
- Do not commit `.env`, credentials, local databases, or generated private
  reports.
- Use parameterised APIs for SQL and shell-safe argument handling for
  commands.
- Treat user files as authoritative; do not overwrite without confirmation.

## Done Means

- The change is explained.
- Relevant tests or checks were run, or the reason they were not run is
  stated.
- Remaining risk is called out explicitly.
- The final answer lists: files changed · commands run · results · risks.

<!-- last_updated: {{DATE}} -->

这份模板刻意要求把长材料留在 docs/db/schema/web/src/router/ 之类的真实位置。AGENTS.md 的作用是指路,而不是替代整个知识库。

14.4 —— 规则与 hooks:把"请小心"变成边界#

自然语言提醒很有用,但它不是边界。边界必须能在工具调用之前或之后执行。Chapter 14 的 hands-on 里给出两层:

# verified: 2026-05-21 · Ch.14 hands-on · Codex rules (command-permission boundary)
#
# Rules in Codex govern command permissions and safety boundaries — NOT
# writing style. Coding standards and naming conventions belong in
# AGENTS.md or skills; rules belong here only when they decide whether a
# command runs at all.
#
# Each rule answers one question: "Can the agent execute this command
# without asking?" The values are:
#   - "allow"    — run silently
#   - "ask"      — pause and ask the human
#   - "deny"     — refuse, even if the human says yes (use sparingly)
#
# Ordering matters: earlier rules are checked first; the first match wins.

[[rule]]
# Read-only inspection is free.
match  = ["ls *", "cat *", "rg *", "git status", "git log *", "git diff *"]
action = "allow"
reason = "Read-only inspection has no side effects."

[[rule]]
# Local build / test loops are part of normal agent work.
match  = [
  "./mvnw test*", "./gradlew test*",
  "go test *",
  "pytest*", "ruff check*", "ruff format*",
  "cargo fmt --check", "cargo clippy*", "cargo test*",
  "npm run lint", "npm run typecheck", "npm test*",
]
action = "allow"
reason = "Local verification loop; safe to run unattended."

[[rule]]
# Destructive Git operations are the #1 source of two-AM regret.
match  = [
  "git push --force*",
  "git push -f*",
  "git push --force-with-lease*",
  "git reset --hard*",
  "git clean -fdx*",
  "git branch -D *",
  "git tag -d *",
  "git update-ref -d *",
]
action = "ask"
reason = "History-rewriting Git ops are nearly impossible to undo if pushed."

[[rule]]
# Recursive deletes and overwrites of generated artefacts.
match  = [
  "rm -rf *",
  "rm -fr *",
  "find * -delete",
  "find * -exec rm *",
]
action = "ask"
reason = "Bulk delete; require human confirmation regardless of target."

[[rule]]
# Production / deployment / database mutation.
match  = [
  "kubectl apply *",
  "kubectl delete *",
  "helm upgrade *",
  "terraform apply*",
  "flyway migrate*",
  "alembic upgrade *",
  "psql -h *prod*",
  "mysql -h *prod*",
]
action = "ask"
reason = "Touches production or shared state; always require explicit approval."

[[rule]]
# Lockfile and dependency updates have supply-chain implications.
match  = [
  "npm install --save*",
  "npm update*",
  "pnpm add *",
  "yarn add *",
  "poetry add *",
  "uv add *",
  "cargo add *",
  "go get -u *",
]
action = "ask"
reason = "New dependency or lockfile change deserves a human glance."

[[rule]]
# Sudo and privilege escalation.
match  = ["sudo *", "doas *"]
action = "deny"
reason = "Agent sessions never need root inside a project sandbox."

[[rule]]
# Secrets and credential surfaces.
match  = [
  "cat .env*", "cat *.pem", "cat *.key", "cat *credentials*",
  "aws sso login*", "aws configure*", "gcloud auth*", "az login*",
]
action = "deny"
reason = "Default-deny on anything that reads or rotates secrets."

[[rule]]
# Overwriting the harness itself is meta-dangerous.
match  = [
  "* > AGENTS.md", "* > .codex/rules.toml", "* > .codex/hooks.json",
  "rm AGENTS.md", "rm .codex/*",
]
action = "ask"
reason = "Changing the harness should be a deliberate, reviewed act."

# -- shape only ---------------------------------------------------------------
#
# The exact key names ("rule", "match", "action", "reason") and file format
# above are illustrative — the Codex CLI's actual schema may differ across
# versions. Treat this file as the SHAPE of a permission boundary, not as
# a copy-paste-ready config. Wrap whatever permission API your version of
# Codex exposes in something with this shape, so the boundary survives
# vendor changes.
{
  "_comment_verified": "2026-05-21 · Ch.14 hands-on · Codex hooks (SessionStart + PostToolUse + Stop)",
  "_comment_purpose": [
    "Two lightweight hooks (SessionStart, Stop) plus one PostToolUse per file-type.",
    "Hooks here are about DISCIPLINE, not intelligence — they make sure the",
    "agent always knows where it stands at the start, runs cheap checks after",
    "edits, and produces a self-review at the end. Heavy checks (full test",
    "matrix, integration tests) belong in CI, not in hooks — otherwise the",
    "team will quietly disable them.",
    "Schema below is illustrative; wrap whatever your Codex version exposes",
    "in this shape so the discipline survives schema changes."
  ],

  "hooks": [
    {
      "event": "SessionStart",
      "name": "orient-the-agent",
      "run": [
        "echo 'Branch:'      && git rev-parse --abbrev-ref HEAD",
        "echo 'Dirty files:' && git status --short",
        "echo 'Last commit:' && git log -1 --oneline",
        "echo 'Verification:' && grep -A 20 '^## Verification' AGENTS.md 2>/dev/null || true",
        "echo 'Module manuals:' && find . -mindepth 2 -name 'AGENTS.md' -not -path './node_modules/*' -not -path './.venv/*'"
      ],
      "reason": "An AI agent that does not know which branch it is on, which files are dirty, or which verification commands the repo uses, will reinvent or destroy something."
    },

    {
      "event": "PostToolUse",
      "match": { "tool": "edit", "paths": ["**/*.go"] },
      "name": "go-cheap-checks",
      "run": ["gofmt -l ${path}", "go vet ./..."],
      "blocking": false,
      "reason": "Format + vet are seconds; full test goes through agent-check.sh."
    },

    {
      "event": "PostToolUse",
      "match": { "tool": "edit", "paths": ["**/*.py"] },
      "name": "python-cheap-checks",
      "run": ["ruff format --check ${path}", "ruff check ${path}"],
      "blocking": false
    },

    {
      "event": "PostToolUse",
      "match": { "tool": "edit", "paths": ["**/*.rs"] },
      "name": "rust-cheap-checks",
      "run": ["cargo fmt --check"],
      "blocking": false
    },

    {
      "event": "PostToolUse",
      "match": { "tool": "edit", "paths": ["**/*.ts", "**/*.tsx", "**/*.vue"] },
      "name": "frontend-cheap-checks",
      "run": ["npm run -s lint --silent || true"],
      "blocking": false,
      "reason": "Lint is cheap; typecheck is heavier and lives in agent-check.sh."
    },

    {
      "event": "Stop",
      "name": "session-self-review",
      "instruction": [
        "Before exiting, emit a YAML block with these fields and nothing else:",
        "  files_changed: [<relative paths>]",
        "  commands_run:  [<exact commands>]",
        "  checks_passed: [<lint/test/typecheck names>]",
        "  checks_skipped: [<name>: <reason>]",
        "  new_dependencies: [<package>: <reason>]",
        "  secrets_or_pii_in_output: false  # MUST be false; if true, redact and re-emit",
        "  remaining_risks: [<short bullet>]",
        "  suggested_next_step: <one sentence>"
      ],
      "reason": "Closes the loop. Avoids 'I assumed you ran the tests.'"
    }
  ]
}

这里的要点不是具体 schema,而是形状:读操作自由、验证操作便宜、破坏性操作问人、秘密相关操作默认拒绝。团队把这个形状包到自己当前版本的 Codex 配置里,就能避免被某个版本的字段名绑死。

14.5 —— 记忆只放长期偏好#

记忆是最容易污染的表面。把一次性 bug 背景、票号、生产主机、客户样本写进长期记忆,会让后续任务背上隐私与幻觉双重债务。更好的边界是:只保存三个月后仍可能正确、且可以对新同事公开说出的偏好。

<!-- verified: 2026-05-21 · Ch.14 hands-on · Codex memory (long-term preferences only) -->

# Codex Memory — long-term preferences

Memory is a notebook, not a vault.

Anything you put in here is injected into every Codex session forever. So
it should only contain long-term, stable, low-sensitivity preferences —
the kind of thing you would happily say out loud to a new teammate on day
one. Never put secrets, tokens, production hostnames, sample user data,
one-off task details, or unverified guesses in memory.

## Working Preference

- For any non-trivial change, do `research → plan → execute`. Do not edit
  files in the same turn as receiving the goal.
- Prefer small diffs and focused commits. Refactors that touch many files
  must be proposed as a plan first.
- Never run destructive Git commands (force push, hard reset, branch
  delete, history rewrite) without explicit human approval in the same
  turn.
- When unsure, ask. "Asking" is preferred over "guessing", even if it
  costs a round-trip.
- The final answer must include: files changed, commands run, results,
  remaining risks. No "done" without evidence.

## Verification Preference (by stack)

- **Go** concurrency changes must mention race-test coverage
  (`go test -race ./...`). If race-test was not run, say why.
- **Rust** changes must run or recommend `cargo fmt --check`,
  `cargo clippy`, and `cargo test`. Public-API changes must call out the
  semver impact.
- **Java** changes that touch transactional boundaries must call out the
  before/after isolation level or the absence of a transaction.
- **Python** scripts that take user input must validate paths, env vars,
  and argv before use. Log lines that include arbitrary input must be
  treated as untrusted.
- **C++** changes that move or store pointers must call out ownership and
  lifetime in the summary.
- **Frontend** changes must mention lint, typecheck, and (when present)
  affected component or e2e test status.

## Privacy Preference

- Do not include secrets, tokens, cookies, request bodies, phone numbers,
  emails, payment data, or any production hostname in prompts, logs, or
  final summaries.
- If the user pastes a value that looks like a secret, refuse to echo it
  back; ask them to revoke and rotate, then continue with a placeholder.
- Treat sample data labelled `prod`, `prd`, `live`, `customer`, or
  similar as sensitive by default.

## What NOT to put in memory

- Tokens, cookies, passwords, certificate paths.
- Production hostnames, internal URLs, sample customer IDs.
- One-off task context (bug ticket numbers, sprint goals, this week's
  feature flag).
- Anything you are not at least 80% sure is still true in three months.
- A summary of a specific user's bug report.

If you find yourself reaching for memory to remember a single task, you
probably want a scratchpad or an `AGENTS.md` change, not a memory write.

同样的原则也适用于提示。一次性任务应该留在当前线程或 issue 里,而不是写进长期记忆;真正可复用的是"如何写好任务"的模板:

<!-- verified: 2026-05-21 · Ch.14 hands-on · Codex prompt template (Goal / Context / Constraints / Done When) -->

# Codex prompt template — the small work ticket

The point of this template is to stop treating prompts as wishes and start
treating them as small work tickets. Four fields cover every non-trivial
change. Copy, fill in, send.

```text
Please do not edit files yet. Do research first, then plan.

Goal:
  <what you want to achieve, and why>

Context Pointers:
  - <entry file or function you suspect is involved>
  - <similar existing implementation to copy from>
  - <relevant tests>
  - <docs or interface contracts>

Constraints:
  - <compatibility that must not break>
  - <files or directories that are off-limits>
  - <language / framework conventions that must be honoured>
  - <security, privacy, logging rules>

Done When:
  - <tests or checks that must pass>
  - <scenarios that need manual verification>
  - <what the final answer must include>

Please output:
  1. Relevant code paths you found.
  2. Your reading of the current implementation.
  3. 2–3 candidate approaches with trade-offs.
  4. Recommended minimum-change plan.
  5. Verification steps and remaining risks.
```

Once the plan looks sound, follow up with this one-liner instead of
re-explaining the goal:

```text
Execute the plan. Keep the diff small; do not reformat unrelated code.
After each phase, report: what changed, what is left, how to verify.
```

## Why these four fields

- **Goal** stops the agent from turning a bug fix into a half-system
  refactor.
- **Context Pointers** stops the agent from re-discovering the codebase
  every turn.
- **Constraints** stops the agent from "improving" things that must not
  change — REST URLs consumed by old mobile clients, public Java APIs,
  React state libraries the team has standardised on, and so on.
- **Done When** turns "looks done" into "verified done".

If a field is genuinely empty, write `none` rather than deleting the field.
That is itself useful signal: an explicit "no compatibility constraints"
is very different from a forgotten one.

## Bad-vs-good example

A bad request:

```text
Optimise this endpoint and update the frontend too.
```

A good request, in the same shape as the template above:

```text
Goal:
  Reduce load time for the order detail page. It currently fans out to 6
  HTTP calls on entry; /api/orders/{id}/items and /api/orders/{id}/summary
  can be aggregated server-side.

Context Pointers:
  - services/order/.../OrderController.java
  - services/account/.../AccountOverviewService.java   (aggregation precedent)
  - web/src/pages/order/OrderDetail.tsx
  - web/src/api/httpClient.ts

Constraints:
  - REST URLs must not change (legacy mobile clients).
  - Java code follows the existing Spring service/controller layering.
  - React side must not introduce a new state-management library.
  - Plan first; do not edit files.

Done When:
  - Backend has new or updated unit tests.
  - Frontend typecheck passes.
  - Final answer lists which endpoints are kept vs merged.
```

The agent now has somewhere to land. The diff is much more likely to be
the diff you wanted.

14.6 —— 给 AI 工具跑的 Codex Harness Checklist#

Codex 的可持有表面比 Claude Code 更偏"项目工作面":AGENTS.md、rules、hooks、memory、worktree、任务提示模板。下面这份 checklist 的目标,是让一个审计型 Codex 会话进入任意仓库后,能判断这具马具是否足够让它安全地开始工作。

检查项

AI 工具应该怎么查

通过标准

AGENTS.md 入口

查找根目录与子目录 AGENTS.md;记录最近层级如何覆盖全局规则。

根文件少于约 100 行,提供项目地图、上下文指针、工作约定、验证矩阵。

模块局部规则

在将要修改的路径向上查找最近的 AGENTS.md 或同类局部说明。

局部规则能解释该模块的测试、边界、生成文件、禁止改动区。

权限规则

查找 .codex/rules.toml 或团队等价文件;枚举 allowaskdeny

读操作可自由执行;验证命令可执行;破坏性 git、部署、secret 读取必须 ask/deny。

Hooks 廉价验证

查找 .codex/hooks.json 或等价 hook 配置;按文件类型映射验证命令。

编辑后能触发格式化、lint、typecheck 或窄测试;长测试留给显式验证阶段。

Worktree 隔离

运行或记录 git status --short、当前分支、是否在独立 worktree;检查未提交改动。

任务开始前知道自己在哪个分支 / worktree;不会覆盖用户未提交改动。

Memory 卫生

查看 Codex memory 或团队长期偏好文件;检查是否含 token、host、ticket、一次性上下文。

memory 只含稳定、低敏、长期偏好;一时任务上下文留在线程或 issue。

Prompt 工单化

检查用户请求或 issue 是否有 Goal、Context Pointers、Constraints、Done When。

非平凡任务先研究 / 计划,再执行;完成标准可验证。

验证矩阵

AGENTS.md、package files、Makefile、CI 中提取受影响栈的验证命令。

每个被改栈至少有一个明确检查;没有测试时必须说清缺口。

隐私与日志

搜索日志规则、secret scanning、.env 处理、PII 禁止输出说明。

final summary 不回显秘密、cookies、请求体、客户数据;日志新增语句有敏感级别判断。

结束自检

查 hook 或 final response 约定是否要求列文件、命令、结果、风险、跳过项。

任务结束能留下可审计证据,而不是只说"done"。

梳理节奏

查找 harness review、AGENTS.md 更新记录、rules/hooks 变更记录。

项目有固定节奏清理过期规则、失败 hook、无用 memory 和失效命令。

Codex 可以把这份检查作为任务前的"起飞前检查"。一份合格输出不需要长,但必须有证据:

tool: codex
repo: "<path>"
readiness: ready | ready-with-warnings | blocked
checks:
  - id: agents-entry
    status: pass | warn | fail
    evidence:
      - "<path>:<line>"
    note: "<one sentence>"
blocked_by:
  - "<only if readiness=blocked>"
minimum_next_step: "<single concrete action>"

这份 checklist 也可以反向用来写 AGENTS.md:如果某一项无法检查,就说明这具马具在那个面上还没有落地。比如没有验证矩阵,不要让智能体猜测试命令;没有权限规则,不要假装"请小心"能挡住一次 git reset --hard

HarnessCard#

字段

HarnessCard schema 版本

CAR-HarnessCard v0.2 [CAR Research Collective, 2025]

对象

Codex 公开配置面,2026-05 观察窗口 [OpenAI Codex Team, 2026]

许可证

Codex 产品本身为闭源;本章 hands-on 示例以 MIT 协议发布

Control 层(CAR)

AGENTS.md、提示模板、记忆共同塑造任务上下文。

Agency 层(CAR)

工具与 shell 行动通过权限规则、hooks、人类确认共同约束。

Runtime 层(CAR)

本地仓库、worktree、远端模型与客户端运行时的组合;业务验收仍归团队 CI/CD。

SDD(0–5)

3.75

TDD(0–5)

3.5

MDD(0–5)

3.25

主要引用

[OpenAI Codex Team, 2026]

研究脉络#

动手环节#

source/_handson/14-codex/ 下,住着五份可直接改造的制品:

  • AGENTS.md.template —— 一份少于 100 行的项目手册模板。

  • rules.toml —— 一份命令权限边界的形状示例。

  • hooks.json —— 会话开始、编辑后廉价检查、结束自检的 hooks 示例。

  • memory.md —— 长期记忆的内容边界。

  • prompt-template.md —— 把一次性需求写成小 work ticket 的提示模板。