大数跨境

Harness Engineering:用 Markdown 为 AI Agent 构建可读、可验证的仓库

Harness Engineering:用 Markdown 为 AI Agent 构建可读、可验证的仓库 AI大模型观察站
2026-07-03
7
导读:本文解释如何用 AGENTS.md、验证命令、共置文档和可复现环境,把 repository 变成 AI agent 可阅读、可执行、可验证的 specification。

在我的上一篇文章中

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 用起来都会显得 别扭——就像一辆车少了仪表盘或刹车一样。

  1. Instruction——一个入口页(AGENTS.md 或 CLAUDE.md),包含项目概览、技术栈及版本、首先要运行的命令,以及少数绝不能违反的硬性规则。

  2. Tools——足够的访问权限,能真正完成工作。出于“安全”禁用 shell,导致 agent 连 pip install 都不能运行,就像雇了一名工程师却没收了他的键盘。原则是 least privilege,而不是 no privilege。

  3. Environment——一个能自我描述的环境:锁定的依赖、固定的 runtime 版本、可复现的容器。agent 如果在和破损环境搏斗,就会把注意力浪费在错误的战场上。

  4. State——一个记录已完成、进行中和被阻塞事项的地方,让工作可以跨 session 存续。

  5. 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 永远不需要猜项目使用的是 npmyarnpoetry 还是 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 什么都不给,然后问五个问题:

  1. 这个系统是什么?→ README.md + AGENTS.md(或 CLAUDE.md)

  2. 它是如何组织的?→ 每个 module 下的 ARCHITECTURE.md

  3. 我如何运行它?→ Makefile / init.sh / package scripts

  4. 我如何验证它?→ test、lint、check commands(在 GENTS.md(或 CLAUDE.md)中)

  5. 我们现在进展到哪里了?→ 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;细节只隔一次点击,需要时再读。

🧑🏻 ‍💻  给工程师。 将 AGENTS.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 要讨论的内容。



【声明】内容源于网络
0
0
AI大模型观察站
专注于人工智能大模型的最新进展,涵盖Transformer架构、LLM训练优化、推理加速、多模态应用等核心技术领域。通过深度解析论文、开源项目和行业动态,揭示大模型技术的演进趋势,助力开发者、研究者和AI爱好者把握前沿创新。
内容 388
粉丝 0
AI大模型观察站 专注于人工智能大模型的最新进展,涵盖Transformer架构、LLM训练优化、推理加速、多模态应用等核心技术领域。通过深度解析论文、开源项目和行业动态,揭示大模型技术的演进趋势,助力开发者、研究者和AI爱好者把握前沿创新。
总阅读7.7k
粉丝0
内容388