7 个 Harness 文件、5 个 Loop 步骤:Agent 工程全拆解
导读
很多人在做 agent loop 时,会把问题归到模型、prompt、token,甚至归到“智能体还不够聪明”。这篇文章给出的判断更扎实:loop 经常失灵,根子往往在底层工程结构没搭好。作者把 .claude/ 目录拆开来看,指出真正承担工作的只有几类文件,而且每一类都对应明确职责。
这篇文章值得读的地方,在于它把“harness”和“loop”这两个常被混用的概念彻底分层。你会看到,权限、hooks、subagents、skills、MCP、memory 这些配置,决定了每一轮迭代能做什么、能验证什么、会在哪些地方开始漂移。很多故障的定位,从这一步开始才真正清楚。
文章还给出了一套很实用的判断框架:七个 harness 文件,五个 loop 步骤,三种最常见失败模式。这让“搭 agent 工程”从一堆模糊经验,变成一套可以检查、可以补齐、可以持续运行的系统。
如果你已经有一套 Claude Code 或类似 agent 工作流,这篇文章的价值更高。它会让你重新审视:当前卡住的地方,到底是 loop 设计问题,还是 harness 基础设施问题。
大多数开发者都在和 loop 较劲。其实 loop 本身没问题。真正没搭好的是底下那层文件夹。打开任何一个正在工作的 Claude Code 项目的
.claude/,你会看到大约七样东西在承担真正的工作:CLAUDE.md,

大多数开发者都在和 loop 较劲。其实 loop 本身没问题。真正没搭好的是底下那层文件夹。
打开任何一个正在工作的 Claude Code 项目的 .claude/,你会看到大约七样东西在承担真正的工作:CLAUDE.md、settings.json、hooks/、agents/、skills/、.mcp.json,以及一个类似 MEMORY.md 的状态文件。
大多数开发者只打开过其中一个文件。
也许两个。
这就是他们的 loop 往往在第三轮迭代卡住的原因。
读完这篇文章,你会知道每个文件各自负责什么;上层五个 loop 步骤如何运转;三种最常见、最容易毁掉第一次尝试的失败模式是什么;以及今晚最值得补上的下一个文件是哪一个。
没有框架包装。
没有订阅服务。
就是一篇 walkthrough,给你精确路径,给你精确内容。 harness 是地基。先把地基浇好。
7 个 Harness 文件、5 个 Loop 步骤:Agent 工程全拆解
harness 就是 .claude/ 这个文件夹。
它在多次运行之间保持稳定。
loop 则运行在这个 harness 之上:一个目标、一次行动、一次验证、一次 memory 写入,再加上继续还是停止的判断。
harness 像厨房。
loop 像菜谱。
两者缺一,系统都跑不起来。
有厨房没菜谱,空间只会闲着。
有菜谱没厨房,最后只是空想。
很多开发者把整套东西视作一个模糊整体,统一叫“我的 agent 配置”。
这样看,问题就很容易看丢,因为失败点分布在不同层。
token 爆炸、prompt 疲劳、权限丢失,这些属于 harness 问题。
loop 长时间无法收敛、验证步骤放过垃圾结果、定时任务逐渐漂移,这些属于 loop 问题。
当你能准确叫出所属层级,诊断就会快很多。
你不会再去重写 prompt,真正的 bug 可能只是少了一条权限配置。
我一开始以为,先把 loop 搭出来,就能反过来知道自己需要哪些 harness 文件。
实际顺序刚好相反。
harness 决定了每一轮迭代被允许做什么。
权限决定 loop 能否写入磁盘。
subagents 决定验证是否在干净上下文里运行。
skills 决定 loop 有没有办法专门化。
hooks 决定 loop 能否在你希望的触发点真正启动。
这些决策没锁定之前,loop 只能靠猜。
loop 一旦靠猜,幻觉就会开始出现:捏造文件、捏造命令、测试看起来通过,实际什么都没验证。
harness 的作用,就是把“猜”这件事关掉。
所以顺序应该永远是:先 harness,后 loop。
Harness:逐个文件拆开看
CLAUDE.md
这是 Claude Code 每次启动时最先读取的文件。
它的内容会进入整场 session 的常驻上下文。
这里应该放项目的基本形状:目录结构、语言与框架、真正可用的命令、agent 必须遵守的约定,以及一份明确的禁止事项清单。
这个文件应该放在 repo 根目录。
别把它埋进 docs 目录里。
一个最小可用版本可以长这样:
# 项目:my-app
技术栈:Next.js 14、TypeScript、Postgres、Tailwind。
目录布局:`app/`(路由)、`lib/`(辅助函数)、`db/migrations/`。
## 命令
- `pnpm dev` - 本地开发
- `pnpm test` - vitest
- `pnpm db:migrate` - 应用迁移
## 永远别做
- 合并后不要再编辑 `db/migrations/*`
- 没有在 PR 正文里给出理由前,不要新增依赖
- 访问用户数据时,不要绕过 `lib/auth/`
这里最大的陷阱是膨胀。
论文 Less Context, Better Agents(arXiv 2606.10209)测到一个很扎眼的结果:仅仅因为常驻上下文过大,任务完成率就会从 91.6% 掉到 71%。
把它控制在 300 行以内。
每周修剪一次。
每多加一段,就等于给未来每一次 turn 加一笔税。
一个很标准的参考仓库是 centminmod/my-claude-code-setup。
它并排给出了三种能工作的 CLAUDE.md 结构。

settings.json
这里放的是工具 allowlist、环境变量,以及 hook 注册。
日常工作里,有两个位置最关键:.claude/settings.json,位于 repo 根目录,用于 repo 级规则。~/.claude/settings.json,用于你的个人默认配置。
作用域优先级是 managed > project > local > user。
所以项目级配置总会覆盖个人配置。
最值得你今天就做的一步,是给只读 Bash 和 MCP 调用写一个 allow 数组:
{
"permissions": {
"allow": [
"Bash(ls:*)",
"Bash(git status:*)",
"Bash(git diff:*)",
"Bash(cat:*)",
"Read(*)"
],
"deny": [
"Bash(rm -rf:*)",
"Bash(git push --force:*)"
]
}
}
这样一来,agent 就不会每执行一次 ls、git status、cat 都停下来弹权限确认。
破坏性操作依然会被拦住。
完整的键位参考可以看 Claude Code 文档里的 Settings 一节。
secret 放到 .claude/settings.local.json 里,并把它加入 .gitignore。
hooks
hooks 是一组确定性脚本,会在工具事件发生时触发。PreToolUse 在工具运行前触发。PostToolUse 在工具运行后触发。Stop 在 agent 结束一轮 turn 时触发。
它们在 settings.json 里注册,方式是写一个 matcher 模式,再指定对应 shell 命令。
一个很经典的起步 hook,是匹配 Edit|Write 的 PostToolUse,把文件内容送进 prettier:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{"type": "command", "command": "npx prettier --write \"$CLAUDE_FILE_PATH\""}
]
}
]
}
}
这样每次编辑结束时,文件都会落到一个确定状态。
这就是你的策略地板。
没有 hooks,每次运行都像凭感觉开车。
一个实用原则是:成功时保持安静,失败时大声报错。
参考资料可以看 Claude Code 文档里的 Hooks。
subagents
它们位于 .claude/agents/ 目录下,以带 YAML frontmatter 的 markdown 文件形式存在。
主 agent 通过 Task 工具调用它们。
它们在一个全新的 context window 里运行。
一个最小 verifier subagent 可以写成这样:
---
name: verifier
description: 对照目标规格审查 diff。每次代码变更后都要调用。
model: haiku
tools: [Read, Grep, Bash]
---
你是一个 verifier。读取 `PROMPT.md` 里的目标规格。读取 diff。
返回 JSON 结论:{passes: bool, failures: [{line, reason}]}。
不要给修复建议。不要运行代码。不要客套。
如果 reviewer 和 maker 共用同一个上下文,reviewer 往往会认同自己刚写出来的东西。
把 review 拉到一个全新上下文里,就是在关闭最响亮的一种失败模式。
参考仓库可以看 wshobson/agents,有 37K stars,里面提供了 194 个现成模板。
如果你想要一个更具对抗性的 verifier,内置 11 个具名 shortcut 检查项,比如放松测试、吞掉错误、伪造重命名,可以看 moonrunnerkc/swarm-orchestrator。

skills
它们位于 .claude/skills/ 下,每个 skill 都是一个文件夹,里面放一个带 YAML frontmatter 的 SKILL.md。
它们采用渐进式加载。
session 启动时,只有名称和描述进入上下文。
只有当 agent 判断触发条件匹配,完整主体才会被加载进来。
---
name: db-migration-writer
description: 为当前 repo 编写 Postgres migration 文件。用户要求新增或修改表、列、索引、约束时使用。
when_to_use: 请求 schema 变更、新功能需要新增列、热路径查询缺少索引
---
# 步骤
1. 读取 `db/schema.sql`,确认当前状态。
2. 把 migration 写入 `db/migrations/NNN__.sql`。
3. 同时包含 up 和 down。用 `pnpm db:migrate --dry` 测试。
4. 永远不要碰现有 migration 文件。
这样的纪律很关键。
否则你一旦积累出五十个 skills,每次 prompt 都要背上五十个 skills 的 token 成本。
一个很标准的模式参考是 anthropics/skills,有 155K stars。
如果你想看一个非常重型的预制套件,可以看 affaan-m/ECC,有 222K stars。

真正有价值的做法,是当同一类任务第三次出现时,立刻写出三个 skill。
这比看教程后预先堆五十个 skill 更有效。
MCP
server 声明放在 repo 根目录的 .mcp.json 中。
Model Context Protocol,也就是 MCP,是一套规范,让 loop 可以调用外部的实时工具。
这里有三条规则:
只安装当前工作真会用到的 servers。
涉及凭证的工具,优先选择官方实现。
不要抱着“也许以后会用”的心态一次装五个。
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {"GITHUB_TOKEN": "${GITHUB_TOKEN}"}
},
"context7": {
"command": "npx",
"args": ["-y", "@upstash/context7-mcp"]
}
}
}
Anthropic 维护的一组 server 可以看 modelcontextprotocol/servers,87K stars。
代码托管集成可以看 github/github-mcp-server,31K stars。
实时库文档服务,也就是专治陈旧 API 问题的,可以看 upstash/context7,58K stars。
发现索引可以看 punkpeye/awesome-mcp-servers,89K stars。
这里最早出现的错误,往往是你给某个 server 开了写权限,却还没加上记录每次调用的 hook。
state 与 memory
第七块,也是大多数人会一直跳过,直到第三个项目开始失控,才终于补上的那块。
一种常见结构是:在一个固定路径放 MEMORY.md 作为索引文件,再加一个 vault 目录保存项目 canon。
~/.claude/memory/
MEMORY.md # 索引,链接到下面的主题文件
user-prefs.md # 偏好:简洁还是详细、语气风格
project-decisions.md # “我们在 2026-03-12 选了 Postgres,没选 Mongo,原因在此”
feedback-recent.md # 你反复在应用的修正
~/vault/ # 项目 canon(跨 session 保持稳定)
architecture.md
api-spec.md
post-mortems/
memory 存的是跨 session 还会变化的东西。
vault 存的是跨 session 保持稳定的东西。
如果你想做生产级 session 压缩,把一段 200K token transcript 压成 4K token recap,同时保住关键事实,可以看 thedotmack/claude-mem,84K stars。
为什么这一步重要,背后的理论可以看 Anthropic 工程团队关于 context engineering 的讨论。
他们给这个失败模式起了个名字:context rot。
很多人这里的第一个错误,是把 memory 当成只增不减的日志。
每个 session 都要修剪。
否则它自己就会变成 rot 的来源。
Loop:运行在 Harness 之上
1. Goal spec
这是定义“完成态”长什么样的外部契约。
它落在磁盘上。
它没有只存在于 agent 脑子里。
loop 每轮迭代都会重新读取它。
文件名可以叫 PROMPT.md、AGENTS.md,或者 AGENT_SPEC.md。
关键点在于“每轮重读”。
# 目标
把整个代码库里的 `users.password` 从 bcrypt 迁移到 argon2id。
# 完成条件
- 所有新的密码写入都使用 argon2id(`lib/auth/hash.ts`)。
- 现有 bcrypt hash 会在用户下一次成功登录时完成 rehash。
- 测试套件全绿:`pnpm test auth`。
# 永远别碰
- 已经合并的 `db/migrations/*`
- `legacy/` 下的任何内容
- session cookie 格式
# 遇到以下情况就停止
- 需要编辑 `lib/auth/` 之外超过 3 个文件
- 一个原本通过的测试开始失败
如果没有这个文件,agent 大概在三轮迭代后就会开始漂移。
最小的参考案例可以看 ghuntley/how-to-ralph-wiggum,1.7K stars。
它用了一个 PROMPT.md,再配一个 loop 会原地更新的 IMPLEMENTATION_PLAN.md 状态文件。
goal spec 缺失时,失败看起来很像进展。
代码在写。
测试在过。
可它解决的目标,并非你的目标。
2. Plan、Act、Verify
最小可用 loop 只有三步。
agent 对照 goal spec 制定计划、执行动作,然后用一个独立验证步骤检查结果;只有验证过关,下一轮迭代才允许启动。
Ralph 模式的核心,是每轮都用一个新上下文。
状态放在磁盘上,通常是规格文件加上一份持续累积的日志。
#!/usr/bin/env bash
# 最小 loop runner:每个 turn 都是新上下文,状态写在磁盘上
set -euo pipefail
while true; do
# 在新上下文里 plan + act
claude -p "Read PROMPT.md, IMPLEMENTATION_PLAN.md. Do the next step. Commit on green."
# 在新上下文里 verify(不同 subagent)
if claude -p "/verify"; then
echo "iter ok"
else
echo "verify failed, will retry"
fi
# 当 spec 写明 done 时退出
grep -q "^STATUS: done$" IMPLEMENTATION_PLAN.md && break
sleep 5
done
规范模式和 CLI 起步范例可以看 cobusgreyling/loop-engineering,3K stars。
一个生产级 TypeScript 参考实现,可以看 vercel-labs/ralph-loop-agent,里面有 verifyCompletion,805 stars。
如果你想看一整套可安装的 Plan-to-Work-to-Review-to-Release 周期,可以看 Chachamaru127/claude-code-harness,2.9K stars。
一旦删掉 verify 这一步,高自信垃圾就会层层累积。
每一个错误输出,都会变成下一轮迭代的输入。
3. Sub-agent fan-out
当一个目标自然分叉成许多互相独立的子任务时,比如分析 10 篇文章、修 5 个文件、搜索 8 个来源,loop 就应该并行拉起多个 subagent。
最后由 orchestrator 做综合。
一个臃肿的上下文做不了这件事。
十个小上下文可以。
# claude-agent-sdk-python 风格的 fan-out
from claude_agent_sdk import Agent, run_parallel
orchestrator = Agent.load(".claude/agents/orchestrator.md")
workers = [Agent.load(".claude/agents/researcher.md") for _ in range(8)]
results = run_parallel([
w.run(source=src) for w, src in zip(workers, sources)
])
synthesis = orchestrator.run(inputs=results)
Anthropic 工程团队在多智能体研究上的一次内部 eval 显示,相对于单智能体基线,这种方法带来了 +90.2% 的提升。
官方 SDK 是 anthropics/claude-agent-sdk-python,7.4K stars。
一个更重型的公开 fan-out 套件是 ruvnet/ruflo,有 60 多种 agent 类型和 314 个 MCP 工具,61K stars。
如果省掉 fan-out,orchestrator 很快就会溺水。
一个上下文里塞进十份任务的源材料,本身就是触发 context rot 的典型形状。
4. Scheduler 与持久化
当你离开座位后,是什么在触发 loop?
可能是 cron、launchctl、systemd,也可能是某个 queue runner。
scheduler 应该刻意比 agent 更“笨”。
如果 scheduler 自己也想思考,比如按状态分支、判断这次该不该跳过,它往往会静悄悄地连错几天。
# crontab:每 30 分钟跑一次 loop,日志写入磁盘
*/30 * * * * cd ~/my-loop && ./run.sh >> logs/$(date +\%Y-\%m-\%d).log 2>&1
在 macOS 上,你也可以写成一个 launchd plist:
StartCalendarInterval
Minute0
WorkingDirectory/Users/me/my-loop
ProgramArguments
/bin/bashrun.sh
持久化是另一半关键。
每一轮迭代都必须把自己做了什么、试过什么、下一步是什么,序列化到磁盘上。
否则 scheduler 唤醒的,只会是一个已经把目标忘掉的 agent。
如果你想把临时 session 升级成可调度的长期运行,可以参考 Kanevry/session-orchestrator。
5. Failure modes
几乎所有第一次尝试,最后都死在这三种失败模式里:
(a) 高自信垃圾。verify 步骤缺失,或者 verify 太弱。错误输出会一路放行,并在后续迭代里不断叠加。
(b) context rot。整个 loop 挤在一条很长的单一上下文中,模型在某个阈值之后开始退化。Anthropic 就是这么命名这个现象的。准确率会在累积历史接近 200K token 时明显崩塌。
(c) Ralph Wiggum loops。同一轮迭代反复重来,因为磁盘上的状态没有记住已经发生的进展。agent 会重新规划自己其实已经完成的那一步。
论文 Less Context, Better Agents(arXiv 2606.10209)给出了一组非常直接的数据:
保留完整历史时,任务完成率是 71%。
采用 prune-and-summarize 后,任务完成率是 91.6%。
而且消耗的 token 还更少。

之前:单上下文 loop,1.48M tokens,71% 完成率,每次运行里藏着三个没被发现的 hallucination
之后:prune-and-summarize loop + verifier subagent,553K tokens,91.6% 完成率,每个数字都有 trace
moonrunnerkc/swarm-orchestrator 还整理出了 agent 伪装“完成”时常走的 11 种 shortcut:放松测试、吞掉错误、伪造重命名、返回 stub、把注释删掉当修复。
把这些名字记住。
你很快就会在自己的日志里认出它们。
一个完整的最小配置,会把全部七个 harness 文件接成一个可运行的 loop。
整个项目目录通常长这样:
my-loop/
├── .claude/
│ ├── CLAUDE.md # 每个 session 的常驻上下文
│ ├── settings.json # allow 数组 + PostToolUse prettier hook
│ ├── agents/
│ │ └── verifier.md # Haiku,在新上下文里审查 diff
│ └── skills/
│ └── db-migration-writer/
│ └── SKILL.md # 一个已被使用三次以上的 skill
├── .mcp.json # github MCP、context7 MCP
├── PROMPT.md # goal spec(每轮 loop 都会读取)
├── IMPLEMENTATION_PLAN.md # 状态文件(每轮 loop 都会写入)
├── MEMORY.md # 跨 session 偏好
├── run.sh # loop runner(Plan -> Act -> Verify)
└── logs/ # 持久化,每次 cron tick 一份日志
这套接线关系是单向的。
harness 定义规则。
loop 在这些规则里运行。
状态文件把第 N 次迭代和第 N+1 次迭代连起来。
一轮完整迭代,会按这个顺序走过七个 harness 文件和五个 loop 组件:cron 触发 run.sh。run.sh 调用 claude -p。
Claude Code 读取 CLAUDE.md 和 settings.json(harness 1、2)。
每次编辑时应用 PostToolUse hook(harness 3)。
读取 PROMPT.md 和 IMPLEMENTATION_PLAN.md(loop 第 1 步)。
执行 plan 和 act(loop 第 2 步)。
在新上下文里派发 verifier subagent(harness 4 + loop 第 2 步中的 verify)。
把结果写回 IMPLEMENTATION_PLAN.md(loop 第 3 步)。
如果学到了新的偏好,就更新 MEMORY.md(harness 7)。
随后退出。cron 等待下一次 tick(loop 第 4 步)。
只要七个 harness 文件里少一个,某个具体 loop 步骤就会退化。
没有 CLAUDE.md,planner 每轮都得重新推断项目形状。
没有 verifier subagent,verify 步骤就会退回主上下文里,自我审查几乎总是通过。
没有 MEMORY.md,同一条修正意见每个星期二都会再来一遍。
把这七个 harness 文件搭好一次。
loop 就能一直跑下去。
今晚该做什么
打开你的 .claude/ 文件夹。
运行:
ls -la .claude/
数一数里面有多少文件。
如果你什么都没看到,或者只有
settings.json,那就从CLAUDE.md开始。控制在 300 行以内。可以从centminmod/my-claude-code-setup抄一个结构出来。
如果你已经有CLAUDE.md和settings.json,但还没有agents/,下一步就加一个 verifier subagent。把 review 从主上下文里拉出去。结构可以参考wshobson/agents。
如果你已经有agents/,但还没有skills/,就把一个高频任务升级成 skill。选那个你这周已经复制粘贴了三次的 prompt。自己写第一个之前,先去anthropics/skills读三个SKILL.md。
如果七个 harness 文件你都有了,但 loop 还没跑起来,那就挑一个重复任务,写出它的 goal spec,再在上面铺一个 Plan-Act-Verify loop。离“装上就能跑”最近的起点是Chachamaru127/claude-code-harness。
选定之后,只做一件事:
在新标签页里打开对应 repo,然后 clone 下来。 harness 是地基。
地基没铺好,每一个 loop 底下都会是个洞。
更多推荐




所有评论(0)