【学习笔记】Martin Fowler 的 Harness 分类法 —— 两个维度,看懂所有 Agent 工具-03/15
前两篇我堆了一大堆词:工具层、记忆层、上下文工程、护栏、Skills、子 Agent、LLM Judge、Linter、Feature List、Ralph Loop……
我自己写的时候都觉得这玩意儿有点像 2010 年那会儿的「大数据」——什么都能往里塞,什么都说得上。
直到我读到 Martin Fowler 今年 4 月那篇文章。

老爷子还是老爷子。一辈子干的事就是把混沌的工程实践抽象成几个清晰的概念——重构、企业架构、微服务,全是他给行业「立的规矩」。这次他对着一锅 Harness 杂烩切了一刀,刀口干净利落:两个维度,四个象限。

读完那篇文章,我又翻回头看自己的笔记,才发现之前混在一起的概念,其实在两个完全独立的轴上。看不清这两个轴,建 Harness 就跟我去年装修房子选灯具一样——逛了八个市场,每盏灯单独看都好看,搬回家才发现风格全打架。
这一篇我们就来用 Fowler 的尺子量一遍。
一、第一个维度:行动前还是行动后?
Fowler 把所有 Harness 控制点分成两类:
|
类型 |
英文 |
时机 |
典型例子 |
|---|---|---|---|
|
向导 |
Guide(Feedforward) |
行动前 |
系统提示、架构规则、编码标准、CLAUDE.md |
|
传感器 |
Sensor(Feedback) |
行动后 |
测试、Linter、类型检查、LLM Judge |
Guide 是给 Agent 一份「行动前手册」。在它写代码之前就告诉它:项目用 TypeScript,函数命名 camelCase,错误处理要用 Result 类型,禁止直接 import lodash。
Sensor 是事后的检测器。Agent 写完代码,跑测试看通不通过,跑 Linter 看格式对不对,再拉一个模型来当代码审查员看逻辑是否合理。
这个区分听起来简单,但很多人建 Harness 的时候是混着用的,没意识到两边的设计逻辑完全不同。
Guide 的核心是「预防」——Agent 没跑偏之前先把它拉回来。优势是便宜、即时;劣势是 Agent 可能根本没读那条规则,或者读了但选择性忽略。
Sensor 的核心是「兜底」——Agent 跑偏了再抓回来。优势是确定性强、不会被忽略;劣势是已经做了无用功,还得回头返工。
现实里你不能只用一种。只用 Guide?Agent 会偷偷绕过你的规则,没人事后检查它就一路跑到底。只用 Sensor?Agent 写出一坨垃圾你才发现,光是回滚的成本就比预防贵 10 倍。
我自己用 Claude Code 的时候踩过一次坑。给它一个项目,CLAUDE.md 里写了一条「禁止使用 console.log,统一用 logger」。它写代码的时候老老实实用了 logger。但中途调试时它自己加了几个 console.log,调完就忘了删。
那条规则是 Guide。我没配 Sensor(比如 ESLint 的 no-console),结果就是规则形同虚设。后来我加了一条 pre-commit hook 跑 ESLint,Sensor 一上线,console.log 再也漏不出去。
Guide 加 Sensor,前后夹击,才是稳的。
二、第二个维度:确定性还是概率性?
第二刀切的是「执行方式」:
|
模式 |
英文 |
性质 |
典型代表 |
|---|---|---|---|
|
计算型 |
Computational |
确定性 |
测试、Linter、类型检查、依赖检查 |
|
推理型 |
Inferential |
概率性 |
LLM Judge、代码审查 Agent、语义检查 |
计算型的特点是同一个输入永远产生同一个输出。你跑 100 次单元测试,结果一样。Linter 报红的代码,永远报红。Type Checker 抓到的类型错误,下次还在那。
推理型的特点是输出有不确定性。你让另一个模型来审查这段代码,它今天可能给 8 分,明天可能给 7.5 分。说这函数命名「不够 idiomatic」,到底是扣分项还是建议项,看模型的脾气。
为什么 Fowler 要单独拎出这一刀?因为这两种模式的工程实践完全不同。
计算型工具便宜、快、可靠。你可以在每次保存代码时就跑 Linter,每次提交时跑测试,CI 上跑类型检查。代价基本忽略不计,反馈秒级返回。
推理型工具贵、慢、不稳定。让 LLM 审查 1000 行代码,单次成本可能就是几毛钱,延迟十几秒,结果还需要做共识聚合(多次跑取多数)。
更微妙的是:推理型工具能看到计算型工具看不到的东西。
举个例子。你有一段代码:
def calculate_discount(price, user):
if user.is_vip:
return price * 0.5
return price * 0.95
类型检查、Linter、单元测试,全部通过。但拉一个 LLM 来看,它可能告诉你:「这个折扣逻辑硬编码了 50% 和 5%,没有从配置读取,未来运营改活动规则时只能改代码。」
这不是 bug,是设计气味。计算型工具抓不到,因为它没有「设计」的概念。推理型工具能抓到,因为它能理解「未来扩展性」这种语义层面的东西。
但反过来,你让 LLM 跑「这段代码有没有语法错误」,它一万次里会有一两次告诉你「有错误」(其实没有),或者反过来。这件事 Linter 一次都不会出错。
所以两类工具不是互相替代,是各管一段。计算型管「形」,推理型管「神」。
三、把两刀合起来:2×2 矩阵
把两个维度交叉,就是 Fowler 框架的核心:
Computational Inferential
┌──────────────────┬──────────────────┐
│ │ │
Guide │ 系统提示中的 │ CLAUDE.md │
(行动前) │ 硬规则 │ 人话规则 │
│ │ │
│ 例:必须用 │ 例:「优先使用 │
│ TypeScript │ 函数式风格」 │
│ Schema 校验 │ 「错误信息要 │
│ │ 清晰」 │
├──────────────────┼──────────────────┤
│ │ │
Sensor │ Linter、测试、 │ LLM Judge、 │
(行动后) │ 类型检查、 │ 代码审查 Agent │
│ CI 流水线 │ 语义对比 │
│ │ │
│ 例:ESLint │ 例:「这个改动 │
│ pytest │ 是否符合架构?」 │
│ mypy │ 「文档是否完整?」│
│ │ │
└──────────────────┴──────────────────┘
四个象限,各管一摊:
Guide × Computational:行动前的硬约束。在系统提示里直接写「必须用 TypeScript 严格模式,禁止 any」,配合 JSON Schema 强制工具调用参数。Agent 一出口就被卡住。
Guide × Inferential:行动前的软约束。CLAUDE.md 里写「这个项目崇尚函数式风格,能不用 class 就不用」。Agent 自己理解、自己权衡。它可能 90% 的时候做到,剩下 10% 看心情。
Sensor × Computational:事后确定性检查。代码写完跑 ESLint、跑 pytest、跑 mypy。返回结果二值——通过 / 不通过,没有第三种。
Sensor × Inferential:事后概率性检查。让另一个 LLM 当审查员,看代码是否符合架构、文档是否完整、变量名是否清晰。返回的是评分加建议,不是 yes/no。
读到这里你可能会想:那我四个象限都得有?
是的。少了任何一个象限,你的 Harness 就有盲区。
只有 Guide × Computational:Agent 老老实实按规则跑,但所有超出规则的细节没人管。你定义不到的东西全是黑洞。
只有 Sensor × Computational:Agent 想干嘛干嘛,写完了你的测试给它评分。一旦它写了一堆能通过测试但烂得不行的代码,你束手无策。
只有 Inferential 那一列:成本上天,速度感人。一千行代码改动光做一次审查就十几块钱,CI 跑得跟蜗牛似的。
四、一个真实例子:Claude Code 是怎么把四象限摆齐的?
把 Claude Code 的 Harness 拆开看,四个象限正好都齐了:
|
象限 |
Claude Code 里对应的东西 |
|---|---|
|
Guide × Computational |
工具的 JSON Schema —— 调 Edit 必须传 file_path、old_string、new_string,少一个直接报错 |
|
Guide × Inferential |
CLAUDE.md —— 「优先编辑现有文件」「不要主动写文档」「只在必要时加注释」这些软规则 |
|
Sensor × Computational |
Hook 系统 —— pre-tool-use / post-tool-use 钩子可以跑 Linter、跑测试,结果写回 Agent |
|
Sensor × Inferential |
子 Agent 派遣 —— 派一个独立 Agent 去 review 代码、检查文档完整性 |
四个象限不是抽象出来的形而上学,是工程上必然要凑齐的拼图。Anthropic 的设计是先用 Guide 把 Agent 引到大致方向(系统提示 + Schema),然后用 Computational Sensor 抓硬错(Hook + 测试),最后用 Inferential Sensor 兜软错(子 Agent review)。
每一层的成本递增、覆盖范围递宽。

五、三种 Harness 类别:成熟度天差地别
Fowler 还提了第二个分类——按照「Harness 控制的对象」分,他切了三类:
|
类别 |
英文 |
成熟度 |
代表工具 |
|---|---|---|---|
|
可维护性 Harness |
Maintainability Harness |
最成熟 |
ESLint、Prettier、Black、TypeScript |
|
架构适应性 Harness |
Architecture Fitness Harness |
新兴 |
ArchUnit、Dependency Cruiser、依赖图工具 |
|
行为 Harness |
Behavior Harness |
最不成熟 |
E2E 测试、LLM Judge、视觉回归测试 |
可维护性 Harness 管的是代码长得对不对——格式、命名、类型、未使用的变量。这一类工具行业积累了 30 年,工具链极度成熟,几乎是免费的基础设施。
架构适应性 Harness 管的是模块之间的关系对不对——A 能不能依赖 B,UI 层能不能直接调数据库。这一类工具最近几年才在大型代码库里普及,Agent 时代会变得越来越重要——因为 Agent 倾向于「就近取材」,不知不觉就把架构搞乱。
行为 Harness 管的是程序行为对不对——用户点这个按钮真的能登录吗,这段代码真的修了那个 bug 吗。这一类是最难的,也是 Agent 最容易在这里翻车的。
为什么行为 Harness 最不成熟?
因为它要回答的问题太复杂。「这段代码符合 ESLint 规则吗」是个二值问题,但「这段代码真的实现了用户想要的功能吗」需要对业务、对用户意图、对边界情况都有理解。E2E 测试是目前最接近的工具,但写一套覆盖完整的 E2E 测试本身就比写功能贵。
LLM Judge 给行为 Harness 打开了一个新口子——你可以让模型来判断「这个改动是否满足需求」。但 LLM Judge 自己就有概率性,所以工程化部署需要做共识、做基线、做漂移监测。
Anthropic 在 Claude Code 的实践里有一条很狠的经验:E2E 浏览器测试优于单元测试。
为什么?因为单元测试是 Agent 自己也能改的——「这个测试不通过?我改测试让它过。」E2E 测试模拟真实用户操作,Agent 没法作弊。这是行为 Harness 设计的一个核心心法:让验证标准不可被验证者篡改。
六、核心原则:Keep Quality Left
Fowler 在文章里反复强调一个原则:Keep Quality Left。
「Left」指的是开发流程的左边——越早越好。
类比传统 DevOps 里的「Shift Left Testing」(左移测试)。Fowler 把它推到了 Agent 时代:
|
检查阶段 |
应该放什么 |
为什么 |
|---|---|---|
|
编码时(IDE) |
Linter、Type Checker |
即时反馈,零成本 |
|
Pre-commit |
快速测试、格式化 |
阻止脏东西进仓库 |
|
CI 流水线 |
完整测试、构建验证 |
集成层面的质量门 |
|
Pre-deploy |
E2E 测试、性能测试 |
上线前最后一道关 |
|
生产环境 |
监控、LLM Judge |
真实用户场景的兜底 |
这个梯度的核心逻辑是——便宜的检查放前面,昂贵的检查放后面。
为什么?因为 Agent 出错的成本是非线性的。在 IDE 里抓住一个错误,几秒钟改完。让它跑到 CI 上才被抓住,要等 5 分钟流水线再跑一遍。让它跑到生产环境才被抓住,可能要回滚、要数据修复、要发故障公告。
每一层往后,成本指数级上升。
所以好的 Harness 设计应该是个金字塔:
┌────────┐
│ 生产 │ 最贵,事故级
│ 监控 │
┌──┴────────┴──┐
│ Pre-deploy │ E2E、性能
│ 测试 │
┌──┴───────────────┴──┐
│ CI 流水线 │ 完整测试集
│ │
┌──┴─────────────────────┴──┐
│ Pre-commit hooks │ 快速测试 + 格式
│ │
┌──┴───────────────────────────┴──┐
│ IDE / 编辑时 │ Linter + 类型
│ │
└─────────────────────────────────┘
最便宜,最快,覆盖面最大

底座越宽,顶上的事故越少。
但实际工程里我见过很多反过来的——底座没几样东西,全靠生产监控兜底。结果就是事故频发,团队天天救火。
我自己的 Agent 项目踩过这个坑。早期为了快,只在 CI 上跑测试。Agent 经常本地写完一段代码,自己跑了一遍 happy path 觉得挺好,提交上去 CI 才发现 30% 的测试挂了。光是 Agent 等 CI 反馈、再修复、再等 CI 的循环,就吃掉一半算力预算。
后来加了 pre-commit hook 跑快速测试集,加了 IDE 实时类型检查,循环时间从 8 分钟压到 30 秒。同样的算力预算,能多跑 3 倍的任务。
把质量左移,不是软件工程的玄学,是真金白银的成本账。
七、In the Loop vs On the Loop:人类的位置在哪里?
Fowler 框架最后一刀,切的是人类在 Agent 工作流里的位置。
|
模式 |
含义 |
可扩展性 |
|---|---|---|
|
In the Loop |
人类直接修复 Agent 的产出 |
不可扩展 |
|
On the Loop |
人类改进生产产出的 Harness |
可扩展 |
In the Loop:Agent 写了段有 bug 的代码,你打开 IDE,把 bug 改了。
简单直接。但你的时间是瓶颈——Agent 出 100 个 bug 你修 100 次,第 101 次它还会犯一样的错。
On the Loop:Agent 写了段有 bug 的代码,你不去改这段代码。你去改 Agent 的系统提示、改 CLAUDE.md、改测试套件、改 Linter 规则——让它下次不会再犯这种错。
第一次见这个区分的时候我没太当回事,觉得不就是「治标 vs 治本」吗?
后来真到了 Agent 大规模运行的场景,才明白这两个模式的差别有多大。
我有个项目让 Claude Code 帮忙做代码迁移——把一个老的 React 类组件全部改成函数组件加 Hooks。任务量大概 200 个文件。
第一周我是 In the Loop 跑的。Agent 改完 10 个文件,我 review,发现它老是把 componentDidMount 简单粗暴地映射到 useEffect(..., []),但其实有些情况下这种映射会改变行为(比如 ref 处理、StrictMode 下的双重调用)。
我手动改了三十多个文件之后,意识到不对劲——这速度还不如我自己重构。
于是切换到 On the Loop。我没再去改代码,而是在 CLAUDE.md 里加了一段(大约 200 字):「componentDidMount 映射到 useEffect 时需要考虑以下三种情况:1)ref 操作场景下需要……;2)StrictMode 下需要……;3)涉及订阅清理时需要……」
加完这段之后,剩下 160 多个文件,Agent 自己处理得 95% 都对。
时间从「每个文件改 5 分钟」变成「一次性写 200 字然后看结果」。
这就是 On the Loop 的力量。你不是在解决一个问题,你是在解决「这类问题再也不会出现」。
但 On the Loop 也有它的代价:你得对 Harness 本身有深刻的理解。
In the Loop 简单——会写代码就行。On the Loop 需要你知道:哪条规则该写在系统提示里?哪条该写在 CLAUDE.md?哪条应该用 Linter 实现?哪条应该用 LLM Judge?什么时候 Guide 够用,什么时候必须上 Sensor?
这些问题,回到了 Fowler 的 2×2 矩阵——你必须懂这个矩阵,才能设计出可演化的 Harness。
这也是为什么 Fowler 反复强调:工程师的核心技能,正在从「写代码」变成「设计 Harness」。
八、Fowler 的尺子怎么用?三个实战检查清单
理论说了一堆,落地时其实就三个问题:
8.1 问题 1:每次 Agent 翻车之后,问自己——是哪个象限漏了?
Agent 写了个有问题的东西。
│
├── 是行动前没引导清楚? → Guide 漏了
│ └── 软规则缺失? → Inferential Guide(CLAUDE.md / 系统提示)
│ └── 硬约束缺失? → Computational Guide(Schema / 工具签名)
│
└── 是事后没检测到? → Sensor 漏了
└── 形式错误? → Computational Sensor(Linter / 测试)
└── 语义/设计错误? → Inferential Sensor(LLM Judge / Review Agent)
这个流程跑过几次以后,你会发现自己 Harness 的盲区集中在某一两个象限。补上去。
8.2 问题 2:每个新增的规则,问自己——它在哪个象限?
如果一条规则你写在 CLAUDE.md 里(Inferential Guide),但其实用 Linter 一行配置就能实现(Computational Sensor),那就把它升级。规则越往「Computational」迁移,效果越稳定,成本越低。
8.3 问题 3:每次救火之后,问自己——是 In 还是 On?
如果你只是修了那个具体的 bug,下次 Agent 还会犯。如果你改了系统提示、加了规则、调了验证标准,未来就不再犯。
前者是体力活,后者才是工程。
九、站到巨人的肩膀上
Fowler 这套分类法的好处是它给了你一把尺子。
之前我建 Harness 的时候,全靠经验和直觉——遇到问题就堆个东西上去,半年下来 Harness 变成了一锅杂烩,谁也说不清哪条规则在哪一层、谁覆盖谁、谁会冲突。
有了这把尺子,每一个组件先问两个问题:是 Guide 还是 Sensor?是 Computational 还是 Inferential?想清楚了再放进去,整个系统的边界就清晰了。
更妙的是,这把尺子还能用来诊断别人的 Harness。看一个 Agent 系统的设计,先扫一眼它的四个象限有没有齐——一眼就能看出它的盲区在哪里、哪里会翻车、哪里成本失控。
理论框架的价值就在这——不是它告诉你新东西,是它给了你看待旧东西的新角度。
下一篇我们进入第二阶段——核心技术篇。先聊上下文工程,因为它是 Harness 里最容易卡住整体性能的那一块。压缩、缓存、卸载、渐进披露,这四板斧没用好,模型再聪明也救不回来。
参考文献:
更多推荐


所有评论(0)