案例研究:Codex —— 把代理式编码变成一套可审计的工作面#
Codex 的重点不只是"会写代码的模型",而是模型周围那一圈可配置、可回滚、可审计的工作环境。
OpenAI 对 Codex 的公开叙述,已经把注意力从单次补全转向了更完整的代理式编码工作面:AGENTS.md、权限规则、hooks、记忆、技能与 worktree,一起构成了一个团队可以调校的马具 [OpenAI Codex Team, 2026]。本章把 Codex 当作一具厂商托管的闭源马具来读:不试图复刻产品内部实现,只抽取那些团队能够在自己仓库中明确持有的表面。
备注
边界。 本章只分析 Codex 的公开可见配置面与可迁移实践。凡涉及运行时内部策略、模型选择、调度器、远端执行环境的判断,都按"不可见"处理;读者不应把本章当作产品内部实现说明。
14.1 —— Codex 的五个可持有表面#
Codex 对团队真正有用的地方,是它把"让智能体怎么工作"这件事拆成几类可以被版本化的材料:
项目手册。
AGENTS.md让仓库拥有一份给智能体看的 onboarding 文件。它不是百科全书,而是入口、边界、验证命令与团队约定。权限规则。 规则文件把"哪些命令可以静默运行、哪些必须问人、哪些默认拒绝"做成显式策略。
hooks。 会话开始、工具调用之后、任务结束时的固定动作,让智能体形成可重复的工作节奏。
记忆。 长期偏好与一次性任务上下文分开;稳定、低敏、长期有效的偏好才进入记忆。
任务提示模板。 人类把需求写成小型 work ticket,而不是模糊愿望。
这五个表面分别对应三大护法:AGENTS.md 和任务模板主要是 SDD × 缰绳;权限规则与 hooks 是 TDD × 护栏;记忆与结束自检则落在 MDD × 梳理。
14.2 —— 十二格亮点图#
格子 |
得分 |
证据 |
|---|---|---|
SDD × 缰绳 |
5 |
|
SDD × 护栏 |
4 |
项目手册可以写明目录边界、公开 API、隐私规则与验证矩阵;坏规则仍需靠 hooks 或 CI 执行。 |
SDD × 牧场 |
3 |
worktree 让任务有独立工作区,但验收标准仍主要由团队在 PR 与 CI 中定义。 |
SDD × 梳理 |
3 |
|
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 工具应该怎么查 |
通过标准 |
|---|---|---|
|
查找根目录与子目录 |
根文件少于约 100 行,提供项目地图、上下文指针、工作约定、验证矩阵。 |
模块局部规则 |
在将要修改的路径向上查找最近的 |
局部规则能解释该模块的测试、边界、生成文件、禁止改动区。 |
权限规则 |
查找 |
读操作可自由执行;验证命令可执行;破坏性 git、部署、secret 读取必须 ask/deny。 |
Hooks 廉价验证 |
查找 |
编辑后能触发格式化、lint、typecheck 或窄测试;长测试留给显式验证阶段。 |
Worktree 隔离 |
运行或记录 |
任务开始前知道自己在哪个分支 / worktree;不会覆盖用户未提交改动。 |
Memory 卫生 |
查看 Codex memory 或团队长期偏好文件;检查是否含 token、host、ticket、一次性上下文。 |
memory 只含稳定、低敏、长期偏好;一时任务上下文留在线程或 issue。 |
Prompt 工单化 |
检查用户请求或 issue 是否有 Goal、Context Pointers、Constraints、Done When。 |
非平凡任务先研究 / 计划,再执行;完成标准可验证。 |
验证矩阵 |
从 |
每个被改栈至少有一个明确检查;没有测试时必须说清缺口。 |
隐私与日志 |
搜索日志规则、secret scanning、 |
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) |
|
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 公开实践 [OpenAI Codex Team, 2026] —— 本章的主要厂商来源。
AGENTS.md 约定 [Agentic AI Foundation, 2025] —— 项目级智能体手册的生态背景。
CAR 分解 [CAR Research Collective, 2025] —— HarnessCard 所使用的三层框架。
Fowler 的 Harness Engineering 词汇 [Fowler, 2026] —— 本章把 Codex 映射到"三大护法 × 四区域"的语言来源之一。
动手环节#
在 source/_handson/14-codex/ 下,住着五份可直接改造的制品:
AGENTS.md.template—— 一份少于 100 行的项目手册模板。rules.toml—— 一份命令权限边界的形状示例。hooks.json—— 会话开始、编辑后廉价检查、结束自检的 hooks 示例。memory.md—— 长期记忆的内容边界。prompt-template.md—— 把一次性需求写成小 work ticket 的提示模板。