Navigation Menu

Claude Code 的 harness engineering 实战指南

79 分钟阅读

为什么 Claude Code 用户绕不开 harness engineering

周一早上,你打开 Claude Code,接着上周五没干完的活,给一个支付回调加重试逻辑。你以为只是接着上次的进度跑,结果 agent 把上周已经写过的 retry 工具函数又重新发明了一遍,顺手把别人写好的幂等校验逻辑改了名字;停下时它信心满满地说"已完成,所有测试通过",你跑 pnpm typecheck 发现三个文件报红。你怒了

,Opus 4.6 都发布有阵子了,怎么连"别动别人写的代码"这种事都做不到?

不是模型不行,是模型外面那层 harness 没搭好。所谓 harness,就是围绕 Claude Code 这个模型加 CLI 做的那一整套外部约束:CLAUDE.md 里那几行项目知识、.claude/settings.json 里的权限白名单、PreToolUse/PostToolUse/Stop 钩子、skills、sub-agent、用 --output-format stream-json 看 agent 在干嘛的那套可观测性配置。这套东西的形式化公式叫 Agent = Model + Harness,2026 年 2 月被 LangChain 推广开,2026 年 4 月由 Birgitta Böckeler 在 Martin Fowler 站点上正式定义(来源)。换句话说,你以为自己在用 Claude Code,实际上你用的是 Claude Code 自带的那套默认 harness。它能不能干好你那个具体项目里的活,跟你自己有没有动手改它直接相关。

差距有多大?HumanLayer 团队引用过 Terminal Bench 2.0 的一组数据

Opus 4.6 模型,跑在 Claude Code 自带 harness 里排第 33 名,把它放进另一套训练时没见过的 harness 里能跑到第 5 名,误差约正负 4 名(来源)。模型一行参数没动,排名差 28 名。这就是为什么很多人换上更贵的模型反而没明显感觉。你升级的是分子,分母那层 harness 还是默认的。

在 Claude Code 这个具体工具里,普通用户能动的那几个旋钮(AGENTS.mdCLAUDE.md 怎么写、hooks 怎么挂、permissions/skills/sub-agent 怎么搭),怎么组合成一份能跑起来的最小 harness 蓝图,以及 Claude Code 模型每过几个月升级一次时,你的 harness 里该删什么、加什么。如果你想回到 harness engineering 总览,先弄清楚 harness 是什么再动手,那篇讲概念溯源、五层组件全貌和跨工具比较。等你的个人 harness 跑稳了,想往长任务和三主体架构方向走,可以再进入进阶篇看 harness engineering for long running 怎么搭

Claude Code 的 harness 由哪五层构成

把 Claude Code 拆开看,外面那一圈"约束"其实分成五层,每层各管一摊事,缺一不可。shipwithai 的整理把它落成最常被引用的版本:Memory(CLAUDE.md / MEMORY.md)、Tools(MCP)、Permissions(settings.json)、Hooks(PreToolUse / PostToolUse / Stop)、Observability(会话日志 / stream-json 事件流)。

解决什么Claude Code 里的载体典型例子谁来动它
Memory模型跨 session 失忆、不知道本仓库的约定CLAUDE.mdAGENTS.md~/.claude/MEMORY.md"本项目禁止用 next/image""数据库变更必须人来执行"人写,agent 读;偶尔由 agent 起草初稿
Tools光会聊天不会动手,要让 agent 能调外部能力MCP server、内置工具、自定义 skill接 Playwright MCP 让 agent 自己点页面、接数据库 MCP 查 schema人挑选并接入,agent 在会话里调用
Permissions给工具划红线,决定 agent 能跑哪些命令、能改哪些目录.claude/settings.jsonpermissions 字段允许 pnpm test、禁止 rm -rf、reviewer 角色不给 Write/Edit人配,agent 受约束
Hooks把"必须做 / 不准做"变成机械执行的硬约束.claude/settings.json 里的 PreToolUse / PostToolUse / Stop 脚本工具调用前拦截改禁止目录、改完文件自动跑 typecheck、Stop 时跑 lint 失败就把 agent 召回人写脚本,agent 触发后必须经过
Observability让人看见 agent 到底在干什么,不靠猜会话日志、--output-format stream-json 事件流把每次工具调用 / 思考 / 文本输出落成 JSON 单事件,事后复盘哪条 RULE 被绕过人开关、人复盘;agent 不感知

这五层为什么不能省。前三层(写在 CLAUDE.md 里的项目规则、settings.json 里的权限)本质都是软约束:模型在某次推理里只要"觉得"这条规则不适用,就可能绕过去。Hooks 不一样,它在工具触发前就跑,退出码非零就直接拦下这次工具调用,模型没有"绕过它"的入口。它是 Claude Code 里唯一无条件阻塞的环节,硬约束就靠它兜底。Observability 不阻止任何事,但没有它你根本不知道前四层的哪一层在第几步被绕过去了,它是让前四层可以迭代的前提。

"绕过去"这句话太抽象,给两个具体场景。场景一是上下文压缩(context compaction):长会话跑到一定长度,Claude Code 会自动压缩历史,CLAUDE.md 里的规则虽然会在压缩后被重新注入,但中间几轮工具调用产生的"刚才为什么这样做"的上下文已经丢了。agent 这时候只剩下规则文本本身,没有"上次因为忘了 X 规则被拦下来过"的记忆,相当于一个对项目约定一知半解的新人接手,碰到边界情况就照常理推断,于是 RULE 被绕过。场景二是规则和当前任务在模型眼里冲突:CLAUDE.md 写"禁止删测试",但当前 task 要求"修好这个 CI",模型把这两条放上推理天平时,会判定"任务优先级更高、规则可能不适用于这个具体情况",把测试删掉一段重写。它不是没读到规则,是主动判定不适用。这两种场景对应同一件事:CLAUDE.md 和 settings.json 的 permissions 都要先过模型推理这关,模型有空间把它们重新解释(来源)。hook 没有这个空间,它是 shell 脚本,退出码 2 就是退出码 2,模型连"解释"它的机会都没有。

如果你在动手前还想回到 harness engineering 总览,先弄清楚 harness 是什么再动手。等你把个人 harness 跑稳、想进入长任务和三主体架构的进阶篇再回来加层也来得及。

AGENTS.md 和 CLAUDE.md:先把关系厘清

AGENTS.md 是 2026 年成形的跨工具公约,Codex、Cursor、Claude Code 都会读这个文件。CLAUDE.md 是 Claude Code 专属的同质文件,分项目级(仓库根目录)和用户级(~/.claude/CLAUDE.md)两种。两者用法一致:都是会话开始时被自动注入到模型上下文里的"项目说明书"。如果你只为 Claude Code 服务,写 CLAUDE.md 就够了;如果想让团队里 Codex 用户、Cursor 用户也共享同一套规则,就写 AGENTS.md,让 Claude Code 通过软链接或 import 指令一并读到。它们是同一个文件在不同工具生态里的两个名字,选一个写好,另一个用一行 import 指过去。

写多长:60 到 100 行的"目录式入口"

行数本身不是目标,目标是"agent 每次会话都会扫一遍这份文件,所以它必须短到不耗 token、又要全到能覆盖最常踩的坑"。两个参考点:HumanLayer 团队的 CLAUDE.md 控制在 60 行以下OpenAI 仓库的 AGENTS.md 约 100 行,并且只当目录使用,指向 docs/ 目录里更深的真实信息来源。两边数字差了 40%,但思路一样:主文件极短,详细规则放子文档,需要时让 agent 用 Read 工具按需加载。

为什么不能往里塞?ETH Zurich 测了 138 个 agentfile:LLM 自己生成的 agentfile 反而损害性能、token 多花 20% 以上;人工写的 agentfile 也只带来约 4% 的提升;agent 还要多花 14% 到 22% 的推理 token 去消化这些上下文文件指令。把 AGENTS.md 写成"超级 system prompt",是负收益的常见姿势。它既挤占了 agent 真正用来思考问题的 token,又因为内容太多让模型自己挑哪条规则该执行,结果哪条都没执行好。

写什么:WHAT / HOW / RULES 三段

把这份 60 到 100 行拆成三段,覆盖率就够了:

AGENTS.md 三段骨架结构图,分别展示 WHAT、HOW、RULES 三段各自承担的内容和条目示例 三段不是死规矩,但 60-100 行写到位的 AGENTS.md 基本都长这个样子。
  • WHAT:项目是什么、技术栈是什么、关键目录的语义是什么。让 agent 第一次见到仓库时不用瞎猜。
  • HOW:常用命令(怎么装、怎么跑、怎么跑测试)、本仓库特有的开发流程、提交前必须跑过的验证命令。让 agent 知道"完工"长什么样。
  • RULES:必须遵守的规范(命名、目录约束、依赖红线)、禁止改的目录、禁止用的 API。让 agent 知道哪些坑碰一下就出血。

每一段都直接列出"事实和动作",不写愿望、不写"请尽量"。需要展开的细节(比如完整的代码风格指南、完整的测试策略)放到 docs/code-style.mddocs/testing.md,AGENTS.md 里写一行索引:"详细命名规则见 docs/code-style.md"。这样既保证 agent 知道存在哪些规则,又不让规则正文在每次会话里都吃 token。

用什么心态写:失败日志,不是愿望清单

Mitchell Hashimoto 的做法是把 AGENTS.md 当失败日志:任何时候发现 agent 犯错,就工程化一个解决方案让它再也不犯同类错误,并把这个解决方案沉淀进 AGENTS.md。Addy Osmani 把这条原则更进一步浓缩为 "Earn each line":每一行规则都应能追溯到一次具体的过去失败、或一条硬性外部约束(合规、性能预算、数据库授权流程之类);追溯不到的就是噪音,删掉。

这个写法和"想到什么就写进去"的差别非常大。前者每一行都对应一次真实事故,写完之后 agent 不会再犯同样的错;后者堆出来的规则模型读完就忘,因为它们没有足够强的语义信号让模型识别"这条该被遵守"。如果你正在为"agent 又把生产配置改了一遍""agent 又把测试删了重写"这种问题反复擦屁股,先别急着加新规则,先把上次踩的坑写成一行精确的 RULE,再看看下次会不会重演。把规则当成"补丁",AGENTS.md 才会越长越值钱。

OpenAI 团队的 AGENTS.md 本身也是 Codex 编写的,仓库从第一次提交起就由 agent 塑造。换句话说,"让 agent 帮你写 AGENTS.md 初稿"是可行的,但产出的初稿仍然要按"失败日志"原则人工裁剪,不能整段保留。

一份可拷贝的骨架

# AGENTS.md
# 直接放进仓库根目录;Claude Code 用户改文件名为 CLAUDE.md。
# 三段结构是死的,里面的具体内容按项目实际填,不要照抄。

本文件是 agent 会话开始时自动加载的项目说明。
保持简短,详细规则放 docs/,这里只做索引。

## WHAT

- 项目:一个面向 B 端的订单履约后台,Next.js 16 + Postgres + Stripe。
- 主要目录:
  - app/        — 路由和页面(App Router)
  - lib/        — 业务逻辑,按领域分包
  - db/         — Drizzle schema 和迁移脚本
  - tests/      — Vitest 测试,与 lib/ 同结构
- 部署:Vercel;数据库由人工执行迁移,agent 不直连。

## HOW

- 装依赖:`pnpm install`
- 跑开发服:`pnpm dev`
- 跑全部测试:`pnpm test`
- 类型检查:`pnpm typecheck`
- 提交前必须跑过:`pnpm typecheck && pnpm test`
- 写完一个功能 → 跑 `pnpm test --filter <name>` → 再跑全量
- 详细测试策略见 docs/testing.md

## RULES

- 禁止直接执行任何数据库写入/迁移命令;准备好 SQL 让人类执行。
- 禁止修改 db/migrations/ 下已生成的迁移文件,新增改动写新迁移。
- 禁止删除或重写已有测试以让 CI 通过;测试失败说明实现错了。
- 禁止在 app/ 里直接 import drizzle 客户端,必须经 lib/db/。
- 命名:服务端文件用 kebab-case,组件用 PascalCase。
- 详细命名规则见 docs/code-style.md
- 详细架构分层见 docs/architecture.md

这份骨架只是起点。真正决定它价值的,是接下来一周里你每次发现 agent 犯错时都回来加一条 RULE、并把对应的失败原因写进 git commit message。AGENTS.md 是 harness engineering 总览里 Memory 这一层的承重件,它写不写得到位,决定了后面几层的硬约束有没有靠山。

hooks 是 Claude Code 里唯一不能被绕过的硬约束

AGENTS.md 写得再周全,模型也可能在某次推理里把某条 RULE 绕过去。CLAUDE.md 里的指令、settings.json 里的 permissions 条目,都可能被上下文或模型推理覆盖;只有 hooks 在工具触发前运行,且无法被绕过。其中 PreToolUse hook 以退出码 2 退出,是 Claude Code 中无条件阻塞工具调用的唯一机制(Claude Code Harness Engineering: The Complete Guide)。OpenAI 团队把这条总结成一句更狠的话:If it cannot be enforced mechanically, agents will deviate,只要约束没法机械执行,agent 早晚会偏离。所以如果你还没搞清楚 harness 由哪几层构成,先回到 harness engineering 总览,先弄清楚 harness 是什么再动手,再回来动 hooks。

跨工具看一眼这套机制在 Claude Code 之外有没有等价物。Cursor 的 rules 系统和 CLAUDE.md 大致对等,一份 markdown 写规则,会话开始时塞进上下文;但 Cursor 没有任何类似 hooks 的工具调用拦截机制,所以它只有 Claude Code 五层里的第一层(Memory),缺少机械化阻塞这道关。Codex 走的是 AGENTS.md 公约,从 2026 年起 OpenAI 把它当作主要的规则承载,机械执行靠的是自定义 lint 注入修复指令,本质上是 PostToolUse 路径而不是 PreToolUse 拦截。OpenCode 这类开源仿制者正在补齐 hook 类原语,但和 Claude Code 当前实现还有差距,后者支持 command、prompt、agent 三种 hook handler 类型,Cursor 和 Copilot 只支持 command 类型(来源)。

这对正在用 Cursor 的人意味着:本节的设计哲学(成功静默、失败冗长、错误信息携带修复指令)可以直接迁移,但具体落地形式得换。Cursor 里你能做的是把这些约束塞进 .cursorrules、CI 流水线和 pre-commit hook,而不是依赖工具调用阻塞。换句话说,"硬约束兜底"的位置从 agent 工具调用前挪到了 git push 前,时间上晚一拍,反馈环长一些,但思路是一样的。

Claude Code 三类 hook 的触发时机流程图,从 agent 决定调工具到 Stop hook 收尾共六个节点 Pre 拦在工具前、Post 跟在工具后、Stop 守在出口,位置不同决定了它们的角色不同。

三类常用 hook 给可执行示例

PreToolUse:把"禁止改 X"变成机械拦截。.claude/settings.json 里给 Edit/Write 类工具挂一个脚本,命中禁止路径就以退出码 2 退出,工具调用直接被阻塞,agent 不会看到"权限不足"以外的解释空间:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          { "type": "command", "command": ".claude/hooks/guard-paths.sh" }
        ]
      }
    ]
  }
}
#!/usr/bin/env bash
# .claude/hooks/guard-paths.sh
input=$(cat)
path=$(echo "$input" | jq -r '.tool_input.file_path // empty')

case "$path" in
  *"/migrations/"*|*"/.env"*|*"/secrets/"*)
    echo "禁止改 $path:迁移文件/密钥必须人工 PR 审过。如需修改请在 PR 里说明原因。" >&2
    exit 2
    ;;
esac
exit 0

PostToolUse:agent 改完代码立刻跑 typecheck/lint。 让反馈回到 agent 自己的上下文,而不是堆到人类身上。这一类 hook 的设计原则是"成功静默、失败冗长"。typecheck 通过 agent 什么都听不到,失败时错误文本注入回路,agent 自我修正(Addy Osmani 在 Agent Harness Engineering 里把它确立成原则)。

Stop:agent 自认完工时再验一遍。 HumanLayer 团队的做法是在 Claude 结束时跑 biome 格式化加 TypeScript 类型检查,成功时静默退出,失败时以 exit code 2 让 harness 重新激活 agent 修复错误(Skill Issue: Harness Engineering for Coding Agents)。一个最小可拷贝的 Stop hook 长这样:

#!/usr/bin/env bash
# .claude/hooks/stop-verify.sh
set -o pipefail

errors=""

if ! out=$(pnpm biome check --write . 2>&1); then
  errors+="biome 格式化/lint 失败:\n$out\n\n"
fi

if ! out=$(pnpm tsc --noEmit 2>&1); then
  errors+="TypeScript 类型检查失败:\n$out\n\n"
fi

if [ -n "$errors" ]; then
  printf "%b" "$errors" >&2
  printf "请修复上述报错后再结束。修复完成后重新运行验证。\n" >&2
  exit 2
fi

exit 0
{
  "hooks": {
    "Stop": [
      { "hooks": [ { "type": "command", "command": ".claude/hooks/stop-verify.sh" } ] }
    ]
  }
}

成功路径完全没输出,失败路径把全部错误文本和一句行动指令一起塞回 stderr,agent 拿到这段文本会继续工作,直到 Stop hook 再次被触发并以 0 退出。

高阶模式:让报错本身携带修复指令

只把 lint 报"error: invalid layering"塞回去,agent 大概率会瞎改一通。OpenAI 团队的做法值得抄:他们用自定义 linter 强制架构分层 Types→Config→Repo→Service→Runtime→UI,违反规则时直接在 lint 错误信息里写明该怎么改、改到哪一层、用哪个 Provider 入口。错误信息本身就是注入到 agent 上下文里的修复指令(OpenAI Harness Engineering)。这其实是把"lint = 文档 + 强制 + 修复指令"三件事压成一个文件。

工具调用循环也可以靠 hook 拆。LangChain 在自家 Deep Agents 里用一个 LoopDetectionMiddleware,通过工具调用 hook 跟踪每个文件的编辑次数,同一文件被编辑 N 次后注入一句"…consider reconsidering your approach",把 agent 从"再改一次就好了"的死胡同里拉出来。同一篇还有个 PreCompletionChecklistMiddleware,在 agent 准备退出前拦下来,提醒它再跑一遍针对任务规格的验证;另一个 LocalContextMiddleware 在 agent 启动时映射 cwd 和上下层目录、跑 bash 找 Python 等工具,把上下文提前注入进来,减少 agent 自己摸索时的错误面(Improving Deep Agents with harness engineering)。这三个 middleware 对应 hook 的三种正向用法:循环里拉一把、退出前补一刀、启动时垫一垫。

生产规模的承重位置:秒级反馈 + 后台 agent

Stripe Minions 的工程化做到了一个值得记住的比例:pre-push hook 秒级修 lint,推送之后最多 2 轮 CI 覆盖 300 万以上的测试。hook 的承重位置在"秒级反馈",CI 只兜底。把 lint 留到 CI 才报,agent 在等待循环里早就把上下文用光了。

文档腐烂也可以用 hook + 定时 agent 解决。OpenAI 团队最初每周五花 20% 时间手动清理 AI 残渣,发现不可扩展,改为后台 Codex 定期扫描偏差、更新质量等级、发起重构 PR,多数 1 分钟内审完自动合并;同样的思路还派生出一个定期跑的 doc-gardening agent,专门扫过时文档发修复 PR。这是 hook 的另一种形态,不挂在工具调用上,挂在时间上。

边界:hook 不是越严越好

把 hook 拧到最紧,看起来稳,但会把 agent 卡死在一些根本不重要的偶发问题上。OpenAI 团队的判断是把测试偶发失败通过后续重跑解决,而不是无限期阻塞。在 agent 吞吐量远超人类注意力的系统里,纠错成本低、等待成本高。所以 hook 的设计标准不是"能不能拦",而是"拦下来之后,让 agent 多花的几分钟值不值得"。判断标准很朴素:拦下来如果是真错(typecheck 报错、跑了禁止命令),值得;拦下来只是网络抖一下、CI 节点慢一下,不值得。

个人 hook 跑稳了,下一步往往是多 agent 流水线和长任务编排。那时候要操心的就不再是单个 hook 怎么写,而是 generator/reviewer 怎么分、上下文怎么交接,这部分单独成篇,个人 harness 跑稳了想上长任务和三主体架构,进入进阶篇 里有完整路线。

Permissions、Skills、MCP、Sub-agent 怎么组合成完整 harness

Hooks 解决了"规则真正卡住 agent"的问题,但 agent 还需要工具、记忆和分工。Claude Code 给的另外四个原语(Permissions、Skills、MCP、Sub-agent)本质上都是在帮 agent 省上下文预算。这事不抽象:Dex Horthy 观察到 168K token 的上下文窗口用到约 40% 时,agent 输出质量就开始明显下降,他称之为 Smart Zone 到 Dumb Zone 的临界点。也就是说 agent 真正能用得"聪明"的有效空间只有约 6 万 7 千 token,省得越早越好。如果你想退一步把这五层组件放回 harness engineering 总览,先弄清楚 harness 是什么再动手,本节关注的是四个工具/权限/记忆原语怎么配合。

Permissions:别只把它当软约束

.claude/settings.json 里的 permissions 第一层作用是"限定 agent 能跑哪些工具",这层确实是软约束,模型推理时可能找到绕过去的路径。它真正承重的是第二层:作为机制隔离不同 agent 的职责。腾讯云开发者社区一篇梳理多 agent 流水线的文章给出的做法是:Code Reviewer 和 Security Reviewer 不给 Write 和 Edit 权限,确保评审者只能读、不能改。这样 reviewer 没有"修复完就通过"的路可以走,它的唯一出口是写一份判定报告。这条比 prompt 里写一百遍"请保持客观"都管用。

reviewer 没了 Write/Edit,怎么和 generator 协作?整套交接靠结构化报告 + 主协调 agent 派发走通,没有共享文件、没有 reviewer 直接给 generator 留言这种隐式通道。落地形式是:reviewer 的工具集只配 Read/Grep/Glob/Bash,跑完一轮检查后输出一份带严重度标注(CRITICAL / HIGH / MEDIUM / LOW)的结构化报告,每条问题挂一个文件路径加行号;这份报告作为 sub-agent 的最终返回值回给父 agent。父 agent 拿到报告决定下一步:有 CRITICAL 就派一个 fixer sub-agent,prompt 里把 reviewer 报告原文加一句"按下列条目修复"贴进去;fixer 是另一套权限,给 Edit/Write,不给 Read 之外的诊断工具,避免它跑偏自己又开始改方向。修完之后父 agent 再派 reviewer 跑一轮,循环到 reviewer 输出 APPROVED 才出口。整个过程 reviewer 和 fixer 永远不在同一个上下文窗口里,reviewer 看不到 fixer 怎么改的、fixer 看不到 reviewer 上一轮的中间推理,两边都只读对方的最终交付物(来源)。这种"评审 → 报告 → 父调度 → 修复 → 再评审"的回路才是 reviewer 不能改代码的工程化兑现,光靠 prompt 写"请客观"做不到。

Skills:先用渐进式披露压住上下文注入量

Skill 解决的是上下文超载,agent 只在决定(或被指定)需要时才获得特定指令、知识或工具的访问权(HumanLayer 的 Skill Issue 一文把这个机制叫渐进式披露)。具体到 Claude Code:skill 激活时 SKILL.md 才作为用户消息加载到 agent 的上下文窗口,同时 agent 被告知 skill 文件所在目录,可以按需读子文件。没激活之前,skill 不占预算。

先把 Skills 用起来的意义在于:你的项目里那些偶尔才用到的专家流程(合规自查、性能审计、特定迁移脚本),与其堆进 AGENTS.md 让模型每次都吃一遍 token,不如做成 skill 按需调用。这一层管的是 agent 注入了多少自己的指令。

MCP:Skills 压住注入量之后,接外部世界

Skills 把"agent 自带的指令"按需化之后,下一个问题是"agent 怎么动外面"。MCP 是同样的渐进式披露逻辑反过来用。MCP 服务器的工具描述会被注入到 coding agent 的系统 prompt 里,意味着每接一个 MCP server,系统 prompt 都变长一点。Anthropic 已经发布了实验性的 MCP tool search,用于在用户连接了大量 MCP 工具时向 Claude 渐进式披露工具,本质就是承认"全量注入扛不住"。

MCP 的另一条红线是安全:HumanLayer 那篇文章直接给警告,永远不要连不信任的 MCP server,工具描述本身就是 prompt 注入向量;通过 stdio 或 npx/uvx 在客户端运行的 server 还能直接执行宿主机代码。这条不是"建议",是"装上就等于把家门钥匙交出去"。

Sub-agent:Skills 和 MCP 都压不住时,把会话整段隔离

Skills 减少注入量、MCP 接外部世界,这两层都是在同一个上下文窗口里抠预算。当任务本身就会产生大量中间噪音(探索整个仓库、跑一长串 bash、做一次独立评审),单一窗口怎么省都不够,这时候才轮到 Sub-agent 上场。

Sub-agent 是四个原语里最直接的"上下文防火墙"。HumanLayer 的解释很干净:sub-agent 把一整次会话的工作打包,父 agent 只看到自己写给子 agent 的 prompt 和子 agent 的最终结果,中间的工具调用、工具结果和其他消息不会进入父 agent 的上下文窗口。Claude Code 内置了两个任务专属的 sub-agent 就是干这个用的:Explore(代码库探索,避免父 agent 自己 grep 一遍就把 40% 预算用掉)和 Bash(执行冗长 bash 命令并提炼结果返回)。

把 sub-agent 用到极致的是 Anthropic 自己的生成-评估模式:生成代码的 agent 和评判代码的 agent 必须分开。Anthropic Engineering 的原话是"让评估者变得更怀疑,远比让生成者变得更自我批判要容易得多"。模型分工的常见配方(腾讯云开发者社区那篇文章总结):Planner 和 Reviewer 用 Opus,推理强;Generator 和 QA 用 Sonnet,执行快。一个 evaluator 可以配得多狠?Anthropic 内部的 evaluator 接了 Playwright MCP,打分前自己导航页面、截图、研究实现,再写详细 critique。评审的"独立性"通过权限隔离保证(接 Playwright 不接 Edit),评审的"深度"通过工具配置堆出来。

Claude Code 四个原语层级图:Permissions、Skills、MCP、Sub-agent 各自从一个角度帮 agent 省上下文预算 四个原语其实在做同一件事,把 agent 推到 40% 临界点的那一刻往后拖。

四个原语放一起对比

原语解决什么什么时候用踩坑点
Permissions职责隔离(reviewer 不能改、generator 不能跨目录)多 agent 流水线里给每个 agent 单独配别只当软约束写,把它当作机制隔离用
Skills上下文超载(指令/知识按需加载)知识库或专家流程偶尔才用到SKILL.md 别写成第二份 AGENTS.md,要克制
MCP接外部世界(DB/浏览器/CI)确实需要 agent 动外部系统时不信任的 server 不连;装多了用 tool search
Sub-agent上下文防火墙(整段工作不污染父上下文)探索/批量命令/评审/独立子任务父→子的 prompt 要自包含,子→父的输出要结构化

极端规模参考

四个原语用对了能撑多大?Nicholas Carlini 用大约两周时间、16 个并行 Claude Opus 实例、约 2000 个 Claude Code 会话,做出了一个 GCC torture test 通过率 99% 的 C 编译器,产出约 10 万行 Rust 代码,API 成本约 2 万美元(截至 2026 年 6 月公开的复盘数据)。这种规模能跑得动的关键,就是他在 harness 里把上下文压力压到极致:日志全部写进文件而不打到控制台,并使用 grep 友好的单行格式(如 ERROR: [reason])主动减少上下文污染;测试也不全跑,每个 agent 只跑随机 1-10% 的测试子集,对单 agent 是确定性子采样、跨 VM 又是随机的。每个会话都只看到自己该看到的那点东西。

个人 harness 跑稳了想上长任务和三主体架构,可以进入进阶篇看 generator-planner-evaluator 怎么真正落到生产规模。

从零搭一个 Claude Code 个人 harness 的最小步骤

把 AGENTS.md、hooks、permissions/skills 三层原语装配成一个真能跑的 harness,骨架沿用 Anthropic 给出的两段式方案:先跑一个初始化 agent布置环境,之后每次会话跑编码 agent做增量进度并留下交接物(参见 Anthropic 在 Claude 4 prompting guide 里给出的多上下文窗口 harness 结构,第一个上下文窗口用单独 prompt)。

从零搭一个 Claude Code 个人 harness 的六步流程图,依次列出 AGENTS.md、hooks、初始化产出、冒烟测试、强语气 prompt、stream-json 观察 第六步不是终点,是第一次失败日志循环的入口,失败回流到第一步,harness 才长出价值。

按下面六步走,每步指明该回到哪节取素材。

  1. 写 60-100 行 AGENTS.md。WHAT/HOW/RULES 三段(项目和技术栈、常用命令和验证方式、必须遵守的规范),骨架细节回前面的 AGENTS.md 节。这一步不要追求完美,先写最关键的三类信息让 agent 读得懂系统。

  2. 加 PostToolUse + Stop hook。让 agent 改完代码立刻跑 typecheck/lint,会话结束时再验一遍,失败时 exit code 2 让 harness 重新激活 agent 修复。脚本回前面 hooks 节取。

  3. 让初始化 agent 生成四件产出init.sh 脚本、claude-progress.txt 进度文件、初始 git commit、feature_list.json。feature list 用 JSON 而不是 Markdown,Anthropic 反复试验后选了 JSON,因为模型对 JSON 文件做非预期修改的概率更低。Anthropic 在 claude.ai clone 示例里写出了超过 200 项功能,全部初始标记为 failing,让后续编码 agent 有一份"完整功能长什么样"的清单。

  4. init.sh 里加端到端冒烟测试。Anthropic 的做法是让编码 agent 总是先启动本地开发服务器,然后用 Puppeteer MCP 新建对话、发送消息、接收响应,确保 agent 能在动手前发现应用是否处于损坏状态。OpenAI 团队走得更远:把 Chrome DevTools 协议接入 agent 运行时,为 DOM 快照、屏幕截图、导航做成 skill,让 Codex 直接复现 bug、验证修复、推理 UI 行为。

    最小冒烟测试到底长什么样?给一段可以拷进 init.sh 的骨架,先 curl 一个 health check 跑通"dev server 起来了",再确认根路由能返回 200,跑过就清场退出,失败就 exit 1 让编码 agent 看见报错:

    #!/usr/bin/env bash
    # init.sh —— 最小冒烟测试,跑过才算环境就绪
    set -euo pipefail
    
    PORT=3000
    BASE="http://127.0.0.1:${PORT}"
    
    pnpm install --frozen-lockfile
    
    # 后台起 dev server,记 PID 方便清场
    pnpm dev > .dev.log 2>&1 &
    DEV_PID=$!
    trap 'kill "$DEV_PID" 2>/dev/null || true' EXIT
    
    # 最多等 30 秒让 server 起来
    for i in $(seq 1 30); do
      if curl -fsS "${BASE}/api/health" >/dev/null 2>&1; then
        break
      fi
      sleep 1
    done
    
    # health check 必须 200
    curl -fsS "${BASE}/api/health" > /dev/null || {
      echo "smoke: /api/health 未通过,看 .dev.log" >&2; exit 1;
    }
    
    # 根路由必须 200
    code=$(curl -s -o /dev/null -w "%{http_code}" "${BASE}/")
    [ "$code" = "200" ] || { echo "smoke: 根路由返回 $code" >&2; exit 1; }
    
    echo "smoke ok"
    

    起步阶段就这么简单:确认 dev server 能起、health 能通、根路由能响应,三件事跑通你就知道环境没坏。等任务复杂到需要登录态、表单提交、跨页跳转再升级到 Playwright/Puppeteer MCP 走完整用户流;步子先小再大,第一版不要追求完整 UI 验证。

  5. prompt 里用强语气指令。光说"不要"不够。Anthropic 的原话样本:"It is unacceptable to remove or edit tests because this could lead to missing or buggy functionality." 软措辞 agent 会把它当建议无视,硬措辞才进得了它的决策权重。

  6. --output-format stream-json 观察一次。Claude Code CLI 的这个模式把每次工具调用、思考过程、文本输出作为独立 JSON 事件流式输出(据腾讯云开发者社区的实践总结),是观察自己 harness 在干什么的标配。每个编码 agent 会话开始时还要执行固定三步:跑 pwd 确认目录、读 git 日志和进度文件、读 feature list 选优先级最高的未完成功能。

跑完一次任务,把这次 agent 无视掉的 RULE 工程化成一个 hook 或写得更具体的 RULES 条目,回到第一步循环,这就是失败日志循环。Anthropic 维护 harness 的常态是"先用最简单的方案,需要时再增加复杂度",起步阶段不要往里堆功能。

个人 harness 跑稳了,再想上长任务和三主体架构,可以进入长运行 agent 的 harness 进阶篇;如果还想退回去把 harness 究竟是什么搞清楚,回到 harness engineering 总览再走一遍。

升级 Claude Code 模型时,harness 该删什么、加什么

harness 不是写完归档的东西。Anthropic 的总结很直白:harness 里每个组件都编码了一个"模型自己做不到什么"的假设,这些假设值得压力测试,因为它们可能本来就不对,也可能随着模型升级悄悄过时(Harness design for long-running application development)。你给 Sonnet 4.5 写的那条"每次结束前 reset 一次上下文"的逻辑,到较新版本的 Opus 模型上可能正在拖后腿。

最具体的参照是 Anthropic 自己升级 Opus 时做的三处删减:完全移除 Sprint 分解构造,因为 Opus 4.6 已经能自己把工作切块;evaluator 从"每个 Sprint 检查一次"改成"最后只检查一次";以及完全去掉 context resets。Sonnet 4.5 有上下文焦虑(context anxiety)、接近上限时会过早收工,所以需要清空上下文加结构化交接文档把 agent 重启一遍;Opus 4.5 已经在很大程度上自己消除了这种焦虑,那套重启脚本反而成了多余的中断。

升级流程别学第一次的 Anthropic。他们最初想一次性大规模简化,结果跑不出原来的性能,也分不清是哪块组件在承重,最后改成方法论方式:每次只移除一个组件,跑 baseline 任务对比一次,再决定下一步动哪里。直接照搬到你这边,升级 Claude Code 模型后,不要一口气把 AGENTS.md 三段砍掉、Stop hook 删掉、init.sh 重启脚本去掉,而是一次只动一处。

每次升级后值得问三个问题。一是哪些 hooks 现在没事干了:如果新模型的 typecheck 错误率比上一代低一个数量级,每次 Stop 都全套跑可能就是浪费时间,可以改成抽样或只在改了某些目录时触发。二是 AGENTS.md 里哪些 RULES 是专门给老模型挡坑的:新模型自己能避开的写法,留在文档里只会占预算、增加 14%-22% 推理 token 开销。三是哪些上下文压缩措施可以拆。context reset 是最典型的一条,新模型不焦虑就别人为打断它。

反过来,旧模型做不到、新模型一上手就能做的事,是给 harness 加"更难的活"的时机。前面提到 Anthropic 让 evaluator 直接接 Playwright MCP 跟运行中的页面交互,这种"评审者亲自操作 UI"的设计,模型推理能力不够时根本跑不动。新模型上线,把 evaluator 从纸面打分升级成端到端验证,往往比再加一条防御性 RULES 收益高得多。

Claude Code 模型升级时 harness 删项与加项对照表,覆盖 hooks、AGENTS.md RULES、上下文压缩、Sprint 分块四个维度 升级清单:删项找'老模型的拐杖',加项找'新模型才扛得动的活'。

落到一份每次升级都跑一遍的动作清单:先列三条候选删项(hook / RULE / 包装层),再列一条候选加项(更难的端到端验证、更大的任务粒度),按方法论一次只动一个,跑同一个 baseline 任务对比。如果你正在从个人项目往长任务、三主体架构那边迈,个人 harness 跑稳了想上长任务和三主体架构,进入进阶篇再说;如果回头发现自己对 harness 这层是什么、为什么需要还没完全捋清楚,回到 harness engineering 总览,先弄清楚 harness 是什么再动手,比急着改组件更值。

常见问题

Claude Code 内置的 Explore 和 Bash sub-agent 各自管什么?

两个内置 sub-agent 都是为了把"大量中间噪音、最终只需要一个结论"的工作打包出去。Explore 专门用于代码库探索,避免父 agent 自己 grep 一遍就把大量上下文预算耗掉;Bash 用于执行冗长 bash 命令并把结果提炼后返回,不把原始命令输出塞满父 agent 的上下文。一般规律是:你觉得某个步骤会产生大量中间噪音但最终只需要一个结论,就值得用 sub-agent 来做。

Stripe Minions 每周 1300 个 PR 全自动合并,普通团队能借鉴哪一部分?

规模上不用对标,但 hook 的设计哲学值得直接用。Stripe 的做法核心是 pre-push hook 秒级修 lint,推送后最多 2 轮 CI 覆盖 300 万以上测试,hook 在最近的位置(本地)拦掉最多的错误,CI 只兜底。普通团队最容易落地的部分:把 lint 和 typecheck 挂到 PostToolUse 或 Stop hook,让 agent 自己在本地就把格式问题修掉,不把脏东西推到远端再等 CI 反馈。这一条不需要任何额外基础设施,今天就能加。

harness engineering 翻译成中文叫什么?为什么不直接叫"agent 工程"?

没有一个官方的中文译法,但从词义上说"约束层工程"或"外层约束工程"比"agent 工程"准确得多。"agent 工程"容易被误解为"开发 agent 的工程",而 harness engineering 特指围绕模型外面搭那一圈约束(AGENTS.md/CLAUDE.md、hooks、permissions、skills、可观测性配置),让模型在特定任务里表现得更可预期。之所以用 harness(马具/约束装置)这个词,是因为它强调的是"装在模型外面的东西",而不是模型本身。目前中文社区里"harness 工程"这个词也在直接使用,还没有形成统一的中文翻译。

harness engineering、prompt engineering、context engineering 三者怎么划界?

三者是分层关系,不是平行概念。prompt engineering 在最里层,管的是"怎么问",一句话怎么措辞、用什么角色、给不给样例;context engineering 包住它一层,管"模型推理那一刻看到的上下文窗口里装了什么",RAG 检索出哪些片段、要不要带上仓库结构、把哪些工具描述塞进 system prompt;harness engineering 是最外那层,管"整套运行时",工具循环、重试、权限、hooks、可观测性、人工闸门。一个常用的类比:模型是 CPU、context 是 RAM、harness 是操作系统,每一层都假设里面那层已经装好(来源)。context engineering 是 harness engineering 的子集,prompt engineering 又是 context engineering 的子集,三者不冲突,只是关心的尺度不同。

AGENTS.md 超过 100 行会发生什么?有具体的硬阈值吗?

没有官方公布的硬阈值,目前的公开数据只能给量级判断:ETH Zurich 测 138 个 agentfile 看到 agent 多花 14%-22% 推理 token 处理上下文文件指令;社区经验则建议 AGENTS.md 超过 150-200 行就拆子目录、按需让 agent Read,否则每次会话都把所有规则一次性装上下文,token 浪费和上下文污染就会同时出现(来源)。这两条都不是"超过 X 行就掉性能 Y%"那种硬规则,更接近"再往后写就是负收益"。实操上 60-100 行是公开案例里的甜区,写到 100 行还止不住就该考虑把详细规则挪到 docs/ 而不是继续往主文件里塞。

Planner/Reviewer 用 Opus、Generator/QA 用 Sonnet 这个分工的成本差多少?什么时候应该全用 Opus?

按 2026 年 6 月的官方 API 价格,Opus 4.7 是输入 $5 / 输出 $25 每百万 token,Sonnet 4.6 是 $3 / $15,Sonnet 在两个维度上都比 Opus 便宜 40%(来源)。一条多 agent 流水线里,Generator/QA 这类高吞吐量调用占总 token 的大头,混用相对于全 Opus 通常能省 30%-40% 的账单;推理强的 Planner/Reviewer 用 Opus 又不会拖垮成本,因为它们的调用次数少、单次 token 也少。什么时候全用 Opus 反而合理:任务以推理为主(架构设计、安全审计、长上下文复盘),Generator 路径很短、QA 几乎不跑,这时候省下来的钱有限,全 Opus 把推理质量拉满更划算。一句话判断:链路里"动手干活"的那段长就混用,"想清楚再说"的那段长就全 Opus。

升级模型后怎么挑 baseline 任务来判断"删掉某个 hook 后性能没退步"?

公开 benchmark(Terminal Bench 2.0 这种)可以借用,但更可靠的做法是用自家项目里 2-3 个真实 PR 当回归基线。选那种时间跨度长、工具依赖多、状态变化丰富的任务,因为这三类特征会把 harness 设计差异放大成可量化的成功率差异,Terminal Bench 2.0 被研究者选为 meta-harness 评估基准就是这个原因(来源)。普通团队没标准化测评集时,可以这样起步:留三个有代表性的历史任务,每次升级或改 harness 跑一遍,记录通过率、用时、token 量;只要新版本在这三个任务上不退步,就接着上线。一次只动一个组件、拿同一组任务复跑,Anthropic 自己也是从"一次性大改"翻车后退回这套方法论的。