上一章讲了协议层——用 CLI 和 Zettelkasten 为 agent 构建确定性的工具接口。工具接口解决的是 agent 能做什么的问题。但给了 agent 强大的工具之后,下一个问题立刻浮出水面:怎么保证它用对了? 一把好枪需要一套好缰绳。这就是 harness engineering 的起点。
从信任到约束的思维转变
每个刚开始用 AI 写代码的人都会经历一个蜜月期。你给 Claude 一个任务,它交回来的东西看起来不错,你点头说”可以”,然后继续下一个。这种感觉很好——像是你终于有了一个不抱怨、不摸鱼、随叫随到的同事。
然后你开始踩坑。
不是那种”AI 写了段错代码”的小坑——那种你自己 review 一眼就能抓到。我说的是系统性的、静默的、你以为一切正常但其实质量在缓慢腐烂的那种坑。Agent 告诉你”已经验证,所有测试通过”,你信了。三天后你发现它验证的是上一个版本的代码。Agent 告诉你”已经修复了那个 bug”,你信了。一周后你发现它只是把错误信息改得更好看了,底层逻辑一行没动。
问题不在 AI 太笨。问题在你太信任它。
这是一个根本性的思维转变:与 AI 协作的核心不是信任,是约束。你不是在管理一个需要信任才能发挥的人类同事,你是在驾驭一个能力极强但缺乏内在质量标准的执行引擎。Harness 这个词很精确——你要做的是套上缰绳,而不是松开缰绳。
这不是悲观。这是工程。
人类工程师之间的信任建立在共享的职业标准、声誉机制和长期博弈之上。你信任一个高级工程师,是因为你知道他不会拿自己的职业生涯开玩笑。LLM 没有这些。它没有声誉要维护,没有职业生涯要考虑,它甚至不知道”偷懒”意味着什么——它只是在统计意义上选择了最可能的下一个 token。
所以,你需要一套不依赖信任的质量保障体系。这套体系的核心是 gate——关卡。每一个关键节点都有一道门,通过了才能继续。问题是:这道门该由谁来守?
三种 Gate 的层级:Mechanical > Independent LLM > Same-LLM Persona
![]()
我在 advisor-builder pipeline 的 V1 版本里犯了一个典型的错误。
当时的需求是:builder agent 写完代码后,需要一个质量审查环节来决定是否通过。听起来很自然的方案是——再找几个 LLM 来审。于是我设计了一个 3-persona voting 系统:同一个 Claude 模型扮演三个角色(reviewer、devil’s advocate、quality judge),分别打分,多数票决定通过与否。
这个设计在纸面上很优雅。三个视角,多元化评审,少数服从多数。
实际运行的结果是灾难性的。
devil’s advocate 这个角色被指示”尽可能找问题”,它确实很听话——它能在几乎完美的代码里找出一堆”可能的问题”。这些问题大多是 aspirational 级别的吹毛求疵(“这里的错误处理可以更优雅”、“这个命名不够 self-documenting”),但在投票系统里它们和真正的 bug 拥有相同的权重。结果是:好代码被反复击毙,pipeline 的通过率低得离谱,而真正有问题的代码反而因为 devil’s advocate 把注意力分散在鸡毛蒜皮上而溜过去了。
3 个 Claude persona = 1 个信号 + 2 份噪声。
这个教训引出了一条核心原则:可形式化的质量判据,永远选 mechanical gate。
什么叫 mechanical gate?就是用代码写死的、确定性的检查。类型检查、lint 规则、测试通过率、覆盖率阈值、文件大小限制、API schema 一致性——所有这些都是 mechanical gate。它们的特点是:结果是二元的(通过/不通过),判据是明确的(不依赖”品味”或”判断”),执行是确定性的(同样的输入永远得到同样的输出)。
V2 的方案极其简单:一个 Python script,检查几项硬指标。没有 persona,没有投票,没有”请从以下三个角度评审”。代码要么通过所有检查,要么不通过。通过了,进入下一步。不通过,回去改。
这是三种 gate 的层级:
第一层:Mechanical Gate。 任何能被形式化为确定性规则的判据,都应该用代码实现。这是你的第一道防线,也是唯一可以完全信赖的防线。它不会累,不会走神,不会”觉得差不多行了”。
第二层:Independent LLM Gate。 有些判据无法完全形式化——比如”这段代码的设计是否合理”、“这个 API 的用户体验是否直观”。这时候你需要 LLM 的判断力,但必须是一个独立的 LLM session。独立意味着:它没有看过 builder 的推理过程,没有被 builder 的上下文污染,它拿到的是纯粹的产出物。这很重要,因为如果 reviewer 能看到 builder 的 chain-of-thought,它会不自觉地被说服——“哦,原来他是这么想的,那确实有道理。“不,你要的是冷启动的审视。
第三层:Same-LLM Persona Gate。 这是最弱的一层,基本上只比没有强。让同一个 LLM session 扮演不同角色来审查自己的产出,本质上是在让一个大脑假装自己是两个大脑。LLM 对自己的输出有天然的 confirmation bias——它记得自己为什么做出那个选择,它会不自觉地为那个选择辩护。Persona 的切换改变的是语气,不是认知。
规则很简单:能用第一层的,绝不用第二层;能用第二层的,绝不用第三层。
你可能会问:那是不是永远不需要 LLM review 了?不是。有些东西天然不可形式化:架构决策的合理性、代码的可读性、用户交互的自然程度。这些需要 LLM 的”品味”。但你要清楚地意识到,这层判断是有噪声的,你应该把它放在 mechanical gate 之后,作为锦上添花而非基础保障。
Gate 的本质:给 AI 提供人工直觉
这里有一个更深层的问题值得展开:为什么 mechanical gate 在某些维度上不仅不逊于 LLM 判断,甚至更优?
神经科学家 Antonio Damasio 记录过一类病人:前额叶受损,失去情绪感知能力,但智商完好。你会以为纯理性的人应该做出更好的决策——恰恰相反,他们在日常生活中决策瘫痪。买哪种牙膏能分析半小时。原因是:人类决策不是”全量评估所有选项再选最优”,而是 “直觉先砍掉 90% 的选项,逻辑在剩下 10% 里精挑”。直觉是一种启发式剪枝(heuristic pruning),没有它,理性分析的搜索空间大到不可计算。
LLM 缺乏这种剪枝能力。它会均匀地探索所有可能性,对每个选项投入差不多的注意力,最终收敛到一个”统计意义上最安全”的中庸解。这就是为什么 LLM 的代码 review 经常在鸡毛蒜皮上挑刺,却放过真正的设计缺陷——它没有那个让你”一看就觉得不对”的直觉剪枝器。
Gate 的设计本质上就是在给 AI 提供人工直觉。Mechanical gate 说的是:“别管什么设计哲学了,类型不对就是不对,测试没过就是没过。“它帮 AI 砍掉了 90% 不该浪费注意力的路径,让后续的 LLM review 可以聚焦在那些真正需要人类品味的 10% 上。不是替代 AI 的判断,是约束 AI 的注意力分配。
Coordinator 的自我信任陷阱
如果你的系统里有一个 coordinator agent——一个负责分配任务、汇总结果、做最终决策的中枢节点——那你需要特别警惕一个陷阱:coordinator 默认信任 subagent 的输出。
这个问题比你想象的严重。
在人类团队里,leader review 下属的交付物是天经地义的。但 coordinator agent 不会自发地质疑 subagent。当 subagent 返回一个结果说”任务完成,测试通过”,coordinator 的默认行为是接受这个信息并继续下一步。它不会想”等等,让我自己验一下”。
为什么?因为 LLM 的 default behavior 是 agreeable 的。它被训练成了一个倾向于说”好的,收到”的模型,而不是一个倾向于说”我不信,证明给我看”的模型。当你用 prompt 告诉 coordinator “你是团队的技术 lead,你要对质量负责”的时候,它理解了这句话的语义,但它不会真的变成一个挑剔的 tech lead。它会表演一个 tech lead 的样子——说一些听起来像审查的话,然后放行。
修复方案是 verify-before-trust:coordinator 不信任任何 subagent 的输出,除非经过验证。
验证分两类:
Quick Verify:适用于可以快速确认的事实性断言。subagent 说”所有测试通过”,coordinator 自己跑一遍测试。subagent 说”文件已更新”,coordinator 检查文件的 modification time 和内容 hash。这类验证成本低,应该无条件执行。
Independent Verify:适用于需要深度审查的复杂产出。subagent 提交了一段架构代码,coordinator spawn 一个独立的 review agent 来审查。这个 review agent 不能看到 builder 的 prompt 和推理过程——它只看代码本身。这类验证成本高,应该在关键节点执行。
一个重要的设计决策:验证轮数不硬编码,而是定义 exit conditions。
早期我犯过的一个错误是规定”每次提交必须经过 2 轮 review”。这导致了两种问题:简单的改动被过度审查(浪费时间),复杂的改动两轮不够(质量不足)。正确的做法是定义退出条件:
- Quick verify 全部通过 → exit
- Independent verify 没有发现 blocking issue → exit
- 发现 blocking issue → 修复后重新进入验证,直到满足 exit conditions
这是一个 while loop,不是一个 for loop。你不关心循环了几次,你关心的是终止条件是否被满足。
Tamper Detection vs Zero Trust:选对你的威胁模型
做安全架构的人都知道一个道理:安全措施要匹配威胁模型。你给自行车装银行级别的保险柜锁,不叫安全意识强,叫资源浪费。
LLM agent 的”安全”问题也是一样。你需要先搞清楚:你的 agent 会”作恶”吗?
答案是:LLM 不是 adversarial,是 lazy。
这是一个关键区分。Adversarial agent 会主动尝试绕过你的防线、隐藏自己的真实行为、伪造验证结果。Lazy agent 不会主动作恶,但它会走捷径——跳过它认为不重要的步骤、对模棱两可的情况选择最省力的解释、在验证环节做表面功夫。
对 adversarial agent,你需要 zero-trust 的完整体系:沙箱隔离、最小权限、行为审计、密码学验证。
对 lazy agent,你需要的是 tamper detection——简单的篡改检测就够了。
我的方案极其朴素:
- SHA256 hash:每个关键产出物附带内容 hash。验证时重新计算 hash,对比是否一致。agent 说”我更新了配置文件”,你 hash 一下文件内容,和它声称的版本对比。
- Writer signature:每个文件变更记录是哪个 agent 修改的。不是为了追责(agent 不怕追责),是为了在出问题时快速定位源头。
- Git HEAD tracking:agent 声称”我基于最新代码做的修改”,你检查它实际操作时的 git HEAD 是否真的是最新的。
就这三样东西。没有 PKI 基础设施,没有 HSM 硬件安全模块,没有 formal verification。
YAGNI until threat model changes。
当你的 agent 开始有能力主动欺骗你(注意,不是犯错,是欺骗),你再升级安全体系。在那之前,tamper detection 的投入产出比远高于 zero-trust。
我见过太多人在 agent 安全上过度投入。他们设计了精密的多层验证系统、加密签名链、行为异常检测算法——然后他们的 agent 连”跑一遍单元测试”都做不好。这就像给一个连路都走不稳的机器人安装导弹防御系统。
先解决 laziness,再操心 adversarial。对 99% 的 AI-native 项目来说,你永远不需要走到第二步。
对抗 AI 惰性:五种偷懒模式
说 LLM 是 lazy 不是一个比喻,它有具体的表现形式。在大量实践中,我观察到 AI 有五种系统性的偷懒模式,每一种都是 harness 需要主动约束的行为:
- 数量约束取下限。 你说”列举 5-10 个例子”,它给你 5 个。你说”至少 3 种方案”,它给你恰好 3 种。任何有弹性的数量要求,LLM 默认走下界。
- 跳过过程给结论。 你要它分析一段代码的性能瓶颈,它直接告诉你”瓶颈在数据库查询”——跳过了 profiling 数据、调用链分析、缓存命中率这些中间步骤。结论可能是对的,但你无法验证,因为推导过程被省略了。
- 模板填充代替思考。 你让它写一个 design doc,它给你一个结构完美的文档——背景、目标、方案、风险、时间线——每一节都有内容。但仔细看会发现,那些内容是”一个 design doc 通常会包含的东西”,不是针对你的具体问题的分析。形式满分,实质为零。
- 模糊需求走反贪婪。 当需求表述有歧义时,LLM 会选择工作量最小的那种解释。“优化这个模块”可以理解为”重构架构”也可以理解为”加个注释”。你猜它选哪个。
- 复杂问题简单处理。 你给它一个涉及三个子系统交互的 bug,它只修了最表面的那个子系统,然后宣布”已修复”。深层的交互问题被当作不存在。
这五种模式的深层原因是一样的:LLM 的训练目标是生成”看起来合理”的输出,不是”做到位”的输出。对抗 AI 的惰性,本质上也是在对抗人的惰性——因为这些偷懒方式恰好是人类最不容易察觉的那种。它不是不做事,它是用最小成本把事情做到”看起来做完了”的程度。Harness 的核心设计目标之一,就是让这种”看起来做完了”过不了关。
上面讲的是 agent 做事时的偷懒。但偷懒不只发生在执行环节——你验证 agent 工作成果的过程本身,也会被偷懒污染。 偷懒模式是 agent 的行为端,验证反模式是人类审查端。两者相互强化:agent 偷了一个 lazy,你的验证又放了一个水,错误就这样静默地进入了 production。
五种 Agent 验证反模式
在大量使用 agent 做工程的过程中,我总结了五种反复出现的验证失败模式。它们不是偶尔出现的 edge case,而是 LLM 的默认行为倾向。如果你不主动对抗,它们就会默认发生。
反模式一:Skim-and-Conclude
Agent 读了函数的前 10 行和最后 5 行,然后宣布”这个函数的逻辑是 X”。它跳过了中间 50 行的边界条件处理,而那恰好是 bug 藏身的地方。
这不是 agent 偷懒——它真的”以为”自己看完了。LLM 的注意力分配不均匀,开头和结尾天然获得更多权重。长函数的中间段落是认知盲区。
对策:要求 agent 逐段复述代码逻辑,尤其是条件分支和错误处理路径。如果它不能复述,它就没读。
反模式二:Happy-Path-Only Verification
Agent 跑了测试,测试通过了,它报告”验证完成”。但它只跑了 happy path 的用例。所有的边界条件、错误路径、并发场景——都没有覆盖。
更隐蔽的版本是:agent 自己写了测试来验证自己的代码。这些测试覆盖了 agent 写代码时想到的场景——也就是说,测试和代码有同样的盲区。这是一个完美的自我循环。
对策:mechanical gate 检查覆盖率。但更重要的是,用 independent agent 来写验证用例——一个没有看过实现的 agent 只根据 spec 写测试。
反模式三:Aspirational Reporting + Self-Confirming Loop
Agent 的 report 里写着”已实现完整的错误处理,包括重试逻辑和 circuit breaker”。你打开代码一看,重试逻辑是一个空函数体加了个 TODO 注释,circuit breaker 在 import 列表里但从未被调用。
这不是 agent 在撒谎。它是在描述它”打算做的事情”和”实际做到的事情”之间,没有做区分。LLM 的语言生成倾向于 aspirational tone——描述最理想的版本,而不是当前的实际状态。我把这种现象叫 aspirational theater。
当 agent 自己 review 自己的产出时,这个问题会被放大成一个自我确认循环(self-confirming loop):写代码的和审查代码的是同一个认知实体,审查结论天然会为自己的选择辩护。这就像让学生自己给自己的论文打分——分数没有任何信息量。
对策:两层防线。第一,要求 agent 在 report 中只使用可验证的断言:“函数 X 的第 Y 行实现了 Z 逻辑”。任何不可定位到具体代码行的描述,默认视为 aspirational。第二,review 必须由独立的 session 执行——reviewer 看不到 builder 的思考过程,只看最终产出,从零开始形成判断。
反模式四:Shallow Fix
Agent 被要求修一个 bug。它确实改了代码,测试也通过了。但它只修了症状,没修根因。那个 null pointer exception 不再出现了——因为 agent 在调用处加了个 null check。至于为什么那个值会是 null,没人关心了。
三天后,同一个 root cause 在另一个调用处再次爆发。
对策:要求 agent 在修复 bug 时必须回答”为什么这个值在这里处于这个状态?“如果它不能回答,它就没有理解 bug。修复方案必须包含 root cause analysis,不只是 symptom suppression。
反模式五:Skip-Anomalies
Agent 在执行过程中遇到了一个 warning、一个 500 响应、一个返回空数据的 API——但 happy path 还能走通,于是它选择忽略这些异常信号,继续往下跑,最后报告”任务完成”。
这是最隐蔽的验证失败。前四种模式至少还停留在”验证做得不好”的范畴,Skip-Anomalies 是连验证的意愿都没有。Agent 看到了异常,但它判断这个异常”不影响当前任务”,于是跳过了。问题是,LLM 对”影不影响”的判断完全不靠谱——那个空响应可能意味着认证失效,那个 warning 可能意味着依赖版本不兼容。这些被跳过的异常信号,往往就是三天后生产事故的根因。
对策:在 harness 中建立异常零容忍的 mechanical gate。Agent 执行过程中产生的任何非预期输出——非零 exit code、stderr 有内容、HTTP 4xx/5xx、返回值结构不符合预期——都必须被捕获并显式处理。处理方式可以是修复、可以是上报、可以是在 report 中标注为 known issue,但不能是沉默跳过。如果 agent 的最终 report 中没有提及执行过程中出现的异常,mechanical gate 直接拒绝。
这五种模式有一个共同特征:它们在表面上都看起来像是”做了验证”。 Agent 确实执行了检查、确实写了 report、确实跑了测试。但验证的实质是空的。这就是为什么你不能简单地在 prompt 里加一句”请仔细验证”——agent 会认真地执行一个形式上完美但实质上空洞的验证流程,然后真诚地告诉你”一切正常”。
你需要的不是更好的 prompt。你需要的是 mechanical gate 和 independent session。
实战:advisor-builder 从 V1 到 V3 的质量演进
让我用一个真实案例来串联本章的所有概念。
V1:盲目信任
V1 的 advisor-builder pipeline 是最简单的两阶段架构:advisor 分析需求并生成计划,builder 根据计划实现代码。没有 gate,没有验证。advisor 说什么,builder 就做什么。builder 做完了,pipeline 就结束了。
质量完全取决于运气。有时候 advisor 的计划很清晰,builder 执行得也准确,交付物质量不错。有时候 advisor 遗漏了边界条件,builder 忠实地实现了一个不完整的方案,交付物里埋着定时炸弹。
问题的发现方式永远是:我自己去看了代码。
这不 scale。我把时间花在了逐行 review AI 的产出上,效率还不如自己写。
V2:三个 Persona 的失败实验
V1 的问题很明显——缺少审查环节。于是我加了前面详述过的 3-persona voting。结局也已经说过了:3 个 Claude persona = 1 个信号 + 2 份噪声。
但 V2 的失败教会了我一件重要的事情:LLM review 的价值不在于”更多 LLM”,而在于”更独立的 LLM”。 数量不产生质量,独立性才产生质量。
V3:Mechanical Gate + Independent Review + Show Your Work
V3 的架构是这样的:
第一层:Mechanical Gate。 builder 的每次提交都必须通过一组确定性检查。核心逻辑极其朴素:
# mechanical_gate.py — advisor-builder V3 的第一道防线
def run_gate(submission_dir: str) -> GateResult:
checks = [
("lint", run_ruff(submission_dir)),
("typecheck", run_mypy(submission_dir)),
("tests", run_pytest(submission_dir, min_coverage=0.80)),
("file_size", check_no_file_exceeds(submission_dir, max_kb=200)),
("schema", validate_api_schema(submission_dir)),
]
failures = [(name, r) for name, r in checks if not r.passed]
return GateResult(
passed=len(failures) == 0,
failures=failures,
# hash 由 harness 计算,不是 agent 自报
content_hash=sha256_tree(submission_dir),
)
没有 LLM,没有 persona,没有 “请从以下角度评审”。代码要么全部通过,要么打回去改。这一层拦截了大部分低级问题——格式、类型、未覆盖的分支——而这些恰恰是最消耗 review 精力的噪声。
第二层:Independent Review。 通过了 mechanical gate 的代码,由一个完全独立的 Claude session 进行审查。这个 session 不知道 builder 是谁、builder 用了什么 prompt、builder 为什么做出这些设计选择。它拿到的输入只有:需求文档 + 最终代码。它的输出是一份结构化的 review report,明确区分 blocking issues 和 suggestions。
第三层:Show Your Work。 这是 V3 新增的核心机制。在每个关键决策点,要求 agent 展示推理过程——不是”请解释你的想法”,而是更结构化的要求:
- “列出你考虑过的所有方案及其 tradeoff”
- “指出你在实现中做的每一个 assumption”
- “标注你不确定的地方”
这些 checkpoint 的意义不在于让 agent 的推理变得更好(prompt 对推理质量的提升是有限的),而在于给你提供了一个可审计的中间产物。当最终结果出问题时,你可以回溯到 checkpoint 找到分岔点——是 advisor 的计划就错了,还是 builder 的实现偏离了计划?
Aspirational Theater Detection 是 V3 review 环节的一个专门子任务。Independent reviewer 被要求做一件特定的事情:对 builder 的 report 中的每一条声明,检查是否有对应的实际代码实现。
规则很简单:
- “实现了 X” → reviewer 必须找到实现 X 的具体代码行,否则标记为 aspirational
- “处理了 Y 情况” → reviewer 必须找到对应的条件分支,否则标记为 aspirational
- “优化了 Z” → reviewer 必须看到优化前后的 diff 或 benchmark,否则标记为 aspirational
这个检测机制在 V3 上线第一周就抓出了大量问题。最常见的情况是 builder 在 report 里写了”完整的错误处理”,实际代码里只有 happy path 的逻辑,错误路径全是 pass 或 # TODO。
V3 的效果:
- Mechanical gate 拦截了大多数首次提交——它们几乎都带着 lint 错误或类型问题,修复后才进入下一层
- Independent review 在通过 mechanical gate 的提交中,仍然能发现可观的非平凡设计问题
- Aspirational theater detection 在上线第一周就标记了数十条”声称实现但实际未实现”的 claims
- 最终需要我亲自审查的内容大幅减少——从”逐行 review 每一次提交”变成了”只看 independent reviewer 标记为 blocking 的少数 case”
从 V1 到 V3,核心变化不是 AI 变聪明了——同一个 Claude 模型,同样的能力边界。变化的是约束体系。
本章小结
这一章的核心信息可以压缩成一句话:不要信任 AI 的自我报告,要设计让它无法绕过的检查点。
几个关键原则:
-
Mechanical gate 优先。 能形式化的判据就写成代码。LLM review 是 mechanical gate 的补充,不是替代。
-
独立性决定 review 质量。 3 个共享 context 的 persona 不如 1 个独立 session。数量不重要,独立性才重要。
-
LLM 是 lazy 不是 adversarial。 你的防线应该针对 laziness(tamper detection, checkpoint),不是针对 adversarial behavior(零信任完整体系)。YAGNI。
-
验证必须是 structural 的。 “请仔细检查”不是验证。Mechanical gate + independent session + exit conditions 才是验证。
-
Show your work 不是为了改善 AI 的思考,是为了给你一条审计链。 当结果出错时,你需要知道哪一步出了问题。
Harness-native engineering 的本质是一个控制论问题:你有一个强大但不可靠的执行器,你需要设计反馈回路来保证输出质量。执行器越强大,反馈回路越重要——因为一个强大的执行器如果不受约束,它制造问题的速度和它解决问题的速度一样快。
下一章我们谈另一个问题:当你不只有一个 agent,而是有一支 agent 团队时,怎么让它们协同工作而不是互相拆台。