在我的上一篇文章中
Harness Engineering:让 AI Agent 从 Demo 走向生产级可靠性的关键
提出了一个论点:Agent = Model + Harness——而到了一定阶段,真正的瓶颈不是模型的智能,而是 harness,也就是你围绕模型构建的一切。上一篇文章停留在概念层面:重新框定问题、分层架构,以及为什么能力很强的 agent 仍会偏离方向的理论。
这是接下来的深度剖析——而且它刻意朝一个方向保持鲜明立场。这里的每一种技术都存在于提交到 repo 的普通 Markdown 文件中:不 fork 某个 coding-agent framework,不做代码级编排,也不触碰 Claude 或 Codex 的内部机制。(你会注意到两篇文章之间有一些细微的框架差异,原因正是如此——这是 可构建 的版本,而不是概念版本。)
👾 重点就是要证明,harness 不必是硬核工程。任何人 今天都可以把这些文件放进 repo,并立刻感受到差异。
这篇文章讲的是这个构建的一半:如何让一个 repository 成为 agent 真正能够 阅读 的东西。 续篇会讲 agent 开始在其中工作后,如何让它保持诚实——但我们一步一步来。在下一部分中,我还会分享一些模板,你可以直接放进 Claude Cowork、Claude Claude 或其他 agent 工作的文件夹里,立即试用这个 harness。
Dogfooding 的经验
我最近用 Python 重新创建了一个更简单版本的 Claude Code。在这个过程中,我做了深入的互联网调研,想弄清楚如何构建长期运行且可靠的 agentic applications。新加坡人喜欢说 “Eat our own dog food”。在这里,它的意思是把这些概念应用到我正在构建的 Claude Code 中,并亲自使用。话不多说,下面是应用 harness 后的发现:
当我说 “harness” 时,我指的并不是什么玄妙的东西。做过足够多这类项目后,我开始把它看成五个相互连接的子系统。缺少任何一个,agent 用起来都会显得 别扭——就像一辆车少了仪表盘或刹车一样。
Instruction——一个入口页(
AGENTS.md或CLAUDE.md),包含项目概览、技术栈及版本、首先要运行的命令,以及少数绝不能违反的硬性规则。Tools——足够的访问权限,能真正完成工作。出于“安全”禁用 shell,导致 agent 连
pip install都不能运行,就像雇了一名工程师却没收了他的键盘。原则是 least privilege,而不是 no privilege。Environment——一个能自我描述的环境:锁定的依赖、固定的 runtime 版本、可复现的容器。agent 如果在和破损环境搏斗,就会把注意力浪费在错误的战场上。
State——一个记录已完成、进行中和被阻塞事项的地方,让工作可以跨 session 存续。
Feedback——明确的命令,用来告诉 agent 它的工作是否真的正确。
如果我只能给一个 repo 添加其中一个,我每次都会添加最后一个。Feedback 是 ROI 最高的子系统——投入最低,回报最大——因为它决定了 agent 是在 猜测 自己完成了,还是能够 检查 自己完成了。
两个大实验室都收敛到了这一点,这也让我不再认为这只是我个人的偏好。
OpenAI 将工程师的工作浓缩为三件事——designing environments, expressing intent, building feedback loops.
Anthropic 甚至直接把他们的 Claude Agent SDK 称为 “general-purpose agent harness”。词汇不同,思想相同。
🧑🏻 💻 给工程师。 我放进任何 repo 里回报最高的一件事,就是在入口文件(AGENTS.md 或 CLAUDE.md)中加入一个 verification block。十分钟的工作,却能改变一切:
## Verification Commands (MUST Adapt to your app / project)
- Tests: pytest tests/ -x
- Type check: mypy src/ --strict
- Lint: ruff check src/
- Full check: make check # runs all of the above
我有意把所有东西都通过 make targets 路由。这样一来,agent 永远不需要猜项目使用的是 npm、yarn、poetry 还是 pipenv——harness 提供了一个统一入口。
repository 就是 specification
如果要把整篇文章压缩成一句话,那就是:
如果它不在 repository 里,那么对 agent 来说它就不存在。
一个处在非结构化 repository 中的 AI agent,就像一个没有任何 onboarding 的天才新人:它把精力浪费在弄清自己身处何处上。
但如果你给它一张清晰的地图——一个入口路由器、与代码共置的文档,以及精确的 verification commands——同一个模型就会从 20% 的成功率跃升到接近完美。
停止追逐更好的模型,或过度优化 prompts;去修复 workspace。
这听起来显而易见,直到你开始统计一个真实项目中有多少知识存在于 repo 之外——Slack 线程里、两个季度前就已经过时的 Confluence 页面里、零散的代码注释里,更多时候则存在于两三个资深人员的脑子里。所有这些对 agent 都是不可见的。每一个缺口都会迫使它猜测。
⚠️ 陷阱在于: agent 无法知道自己不知道什么。它无法判断“这条路已经封了”只存在于 Jack 的脑子里。因此它会基于自己的理解行动,然后径直踩进陷阱——而且是 自信满满地。我读到过一个团队的案例:70% 的 agent 任务需要人工救援,几乎每一次失败都是因为 agent 违反了某条大家都知道、但没人写下来的隐性规则。
所以现在我会对任何项目做一个测试。它极其简单粗暴。打开一个全新的 agent session,除了 repository 什么都不给,然后问五个问题:
这个系统是什么?→ README.md + AGENTS.md(或 CLAUDE.md)
它是如何组织的?→ 每个 module 下的 ARCHITECTURE.md
我如何运行它?→ Makefile / init.sh / package scripts
我如何验证它?→ test、lint、check commands(在 GENTS.md(或 CLAUDE.md)中)
我们现在进展到哪里了?→ PROGRESS.md / feature_list.md / git history
注:第 5 点会在本系列第 2 部分中介绍。
如果 agent 仅凭文件就能回答全部五个问题,它就可以在无人值守的情况下开始真正工作。
如果有一个问题回答不了,那就是地图上的空白区域——而地图空白的地方,agent 就会猜。错误的猜测会变成 bug。过度猜测会消耗 agent 有限的注意力。并且最残酷的是:每一个新 session 都必须重新做同样的猜测。 猜测的成本永远高于一次性把地图画好的成本。
信息越隐藏,发现成本就越高,留给实际任务的 context budget 就越少,因此质量也越低。
如何画出一张好地图
通过 fresh-session test 并不是要写 更多 文档,而是要在 正确 的位置写 正确 的文档。四条原则就能帮我完成大部分工作。
为什么一张面向 agent 的好地图至关重要:
Discovery Cost——agent 的注意力预算中,仅仅用于 寻找 某个关键事实的那一部分。把某个东西埋在十层目录深处,agent 每个 session 都要为此消耗 context。把它放在第一眼就能看到的位置,成本就会显著下降。
Knowledge Decay——你的文档在单位时间内变得陈旧的比例。这是安静的杀手,而这个结论我花了太久才学会:
1️⃣ 知识要和代码 放在一起。 关于 API authentication 的规则应该放在 API 代码旁边,而不是埋在一个 4,000 行的全局文档中。目录本身就成为索引——当 agent 到达代码时,它也到达了约束条件,无需搜索。
2️⃣ 一个标准化入口文件。
AGENTS.md或CLAUDE.md是 landing page,不是百科全书。50–100 行就足够,200 行是上限。 它唯一的职责,是让 agent 回答 这是什么、如何运行、如何验证——并指向其他所有内容。3️⃣ 最少但完整。如果删掉一条规则不会改变 agent 做出的任何一个决定,那这条规则就不应该存在。 但每个 fresh-session question 仍然必须有答案。这是一个你需要持续调优的平衡:不多不少。不要在 instruction files 中重复类型定义、interface 注释或配置说明。
4️⃣ 随代码一起更新。 将文档变更和代码变更绑定在同一个 commit 中。否则就是慢性毒药(稍后详述)。
给工程师。 让地图保持可导航的布局:
我还借鉴了一条纪律,而且它非常值得:把 agent state 当作数据库事务来处理——ACID。
• Atomicity:一个逻辑变更 = 一个 commit;如果中途出问题,就
git stash并回滚,不留下半成品状态。
• Consistency:每次操作后都运行 verification;永远不要 commit 一个破损的中间状态。
• Isolation:并发 agent 使用独立 branches 或 progress files,避免相互覆盖。针对每个 feature 的开发,要求 Claude 使用
git worktree来进行开发。只有在 feature 完全测试通过后,才 merge 回工作branch或main。
• Durability:任何必须跨 session 保留下来的东西,都要进入 git-tracked files。chat history 里的内容不算数——只有写下来的内容才算数。 要求 agent 将 plan、todo、thinking models 导出到本地 markdown files。
⚠️ 补充:**过时的文档比没有文档 更糟糕**。缺失的文档会让 agent 提问。陈旧的文档会让它自信地走错方向。_
为什么一个巨大的 instruction file 会失败
当我最初接受“把所有东西放进 single source of truth”这个想法时,我把 一切 都塞进了 AGENTS.md——deployment runbooks、edge-case histories、我能想到的每一条规则。它膨胀到超过一千行。然后 agent 反而变得 更差。
⚠️ 常见错误:把 AGENTS.md 或 CLAUDE.md 当成百科全书,而不是路由器。
它为什么会适得其反,可以分为三点:
Signal-to-noise 崩塌。 在修一个一行 bug 时,强迫 agent 读 50 行 deployment 琐事,会把相关 instruction 淹没在噪声中。低 signal-to-noise ratio 和缺失信息一样致命。
“lost in the middle” 效应。 Language models 往往会最关注长文档的 开头 和 结尾,而略读中间部分。把一条不可协商的约束埋在第 40 段,它几乎就等于不存在。如果一条规则真的关键,它就应该放在最顶部或最底部——绝不要放在中间。
agent 无法判断什么重要。 当硬性约束和软性建议在同一个位置使用同样的格式时,agent 无法区分 “MUST NOT” 和 “you might consider”。
解决方法是把 AGENTS.md 和 CLAUDE.md 当作路由器,而不是手册——“按需展示”,就像优秀 UI 设计一样。先给 overview;细节只隔一次点击,需要时再读。
🧑🏻 💻 给工程师。 将 A
GENTS.md控制在 50–200 行,并把所有 domain-specific 内容放到一行链接之后:
# AGENTS.md
## Run it
- make setup && make test
## Hard constraints (non-negotiable)
- Always use parameterized queries for DB access.
- All component files use PascalCase.
## Where to look (read on demand)
- API request/response patterns → docs/api-patterns.md
- Database rules & migrations → docs/database-rules.md
- Auth & session model → src/auth/ARCHITECTURE.md
🔥🔥🔥 要像对待 technical debt 一样对待 instruction debt:
你添加的每一条规则都应该说明 它为什么存在 以及 什么时候可以删除。
没有 expiry condition 的规则会变成永久噪声。也不要把类型定义或配置说明重复写进文档里——让 agent 从 source 中读取它们。
目标不是提供 最多 context,而是提供 最少 但仍能回答每个 fresh-session question 的 context。约束它;不要微观管理它。 强制执行 invariants,让 agent 自己搞清楚实现。
一个实用的旁注:地图也能省钱
有一件事确实让我惊讶,而且值得短暂绕开主线讲一下,因为它既影响工程,也影响预算。
📒 Note:本节来自关于 large models 如何计费和缓存的一般实践,而不是 harness field notes 本身。
模型会复用对话中稳定的 prefix——system prompt、project map、早期读取的文件——通过缓存提供,而不是重新计算。一个读取 同一组 canonical files 的新 session,会复用这个 warm prefix。效果是双向的:每一次交互都会降低 latency 并 降低 cost。正如一位实践者所说,prompt caching 是“整个产品围绕其构建的架构约束”。
反过来,在我理解之前也曾被它坑过。cache 的 key 依赖于这个 prefix 不发生变化。只要早期内容发生任何编辑——system prompt、entry file,甚至是你读取文件的 顺序——都会使其后的所有缓存失效,并需要支付全价重新计算。
所以我养成了一个习惯:把稳定的地图放在前面并保持不变;把易变内容(progress file、live task state)放到最后。 一个整洁、稳定的 repository map 不仅是良好的工程实践。它还会悄悄为自己省钱。
核心结论
所有这一切中最让人感到解放的认识是:它并不需要一个更聪明的模型。它需要把 周围环境 当作真正的产品——文件、命令、记录、检查。
一个能力很强的 agent 在空白 repository 中的表现,和一个没有 onboarding 的天才新人完全一样:它把精力花在弄清东西在哪里,而不是完成工作。
给它一张地图——一个用于路由的短入口文件、与其所描述代码共置的知识,以及能证明工作结果的精确命令——你已经拥有的同一个模型,就会悄然从五次成功一次提升到几乎每次成功。
修复 workspace。 repository 就是 specification——如果它不在 repo 里,它就不存在。
但构建 workspace 只是工作的一半。一个自描述的 repo 能让 agent 开始;但它无法阻止 agent 在 session 之间忘掉一切,无法阻止它在破损代码上宣告胜利,也无法阻止它随着时间推移悄悄腐蚀 codebase。这是更难、也更有意思的另一半——也是 Part 2 要讨论的内容。

