别信“已经完成”:把验证塞进代理的工作流
测试、lint、类型检查、冒烟测试、端到端验证和可观测性,怎样组成真正的反馈系统?这一期把“我觉得没问题”换成证据。


Harness 从 0 到 1|第 6 期
没有验证命令,代理的完成只是陈述;有了完整流水线,完成才有证据。
这套系列面向正在用 Claude Code 、 Codex 、 Cursor 、 OpenCode 或其他编程代理的中国开发者、产品经理和小团队。你不需要先学会复杂的 Agent 框架,先把自己的项目整理成代理能读、能执行、能复查的工作区。
这一期解决什么
测试、 lint 、类型检查、冒烟测试、端到端验证和可观测性,怎样组成真正的反馈系统?这一期把“我觉得没问题”换成证据。
先记住系列路线
- 第 1 期:看见模型能力和执行可靠性之间的差距。
- 第 2 期:建立 Harness 的五子系统地图。
- 第 3~6 期:把规则、状态、范围和验证逐层放回仓库。
- 第 7 期:把一次次手动提示升级成自动循环。
- 第 8 期:当循环长成图,学习路由、并行、回退和人工审批。
下面进入本期正文。建议你打开一个自己正在维护的项目,对照着做,不要只收藏。
Agent 有一个系统性倾向:过早宣称任务完成。举例来说,你让它实现”密码重置”功能,它改了数据库 schema 、写了 API 端点、加了邮件模板,跑了单元测试全部通过,然后告诉你”做完了”。但实际一跑才发现:密码重置链接发不出去,邮件服务配置缺失;数据库迁移半途失败,schema 不一致;端到端流程根本没走过一遍。
Guo 等人 2017 年在 ICML 上的经典论文证明:现代神经网络系统性地过度自信,模型自报的置信度显著高于实际准确率。 AI 编码 agent 也一样,它”觉得”做完了,但实际上差得远。 harness 必须用外部化的、基于执行的验证来替代 agent 的”感觉”。
滑坡效应
过早完成声明几乎总是一样的套路:代码看着还行,语法正确、逻辑似乎合理,静态检查没有明显错误。但 harness 没有强制要求全面执行验证,agent 跳过了实际运行或只跑了部分测试。跑了单元测试但跳过集成测试,跑了测试但没检查覆盖率。最后”代码看起来没问题”就被当作了”功能已完成”的证据。
每一步都在丢失信息。从任务规范到代码实现,再到运行时行为,每次转换都可能引入偏差,每次跳过的验证都加剧了信息不对称。
三层终止检查
flowchart LR
Claim["agent 说:做完了"] --> L1["先跑<br/>lint / typecheck"]
L1 --> L2["再跑<br/>测试和启动检查"]
L2 --> L3["最后跑<br/>完整用户流程"]
L3 --> Done["三层都过才算完成"]
flowchart LR
A["代码写完了<br/>单测也绿了"] --> B["但应用没真正启动<br/>完整流程也没跑"]
B --> C["配置、数据库、外部服务问题<br/>还都藏着"]
C --> D["于是 agent 太早宣告完成"]
核心概念
- 过早完成声明:agent 断言任务完成,但实际上存在未满足的正确性规范。问题出在 agent 依据代码层面的局部信心做判断,而系统级正确性需要全局验证。
- 置信度校准偏差:agent 自报的完成信心与实际完成质量之间存在系统性差距。复杂多文件任务中,这个偏差显著为正,agent 总是比实际做得更自信。
- 终止标准:一组明确的、可执行的判定条件,定义在 harness 里。 agent 必须满足所有条件才能声明完成,“完成”从主观判断变成了客观判定。
- 验证-确认双闸门:第一层验证检查代码是否正确实现了指定行为,第二层确认检查系统级行为是否满足端到端需求,两层都通过才算完成。
- 运行时反馈信号:来自程序执行的日志、进程状态、健康检查,这是 harness 判定完成质量的客观基础。
- 完成优先级约束:先验证功能正确性,再处理性能,最后管风格。核心功能没验证通过之前,不许做重构。
单元测试通过 ≠ 任务完成
这是最常见的陷阱,也是最危险的一个。 agent 写了代码,跑了单元测试,全部绿色,然后说”做完了”。但单元测试的设计哲学是隔离被测单元、模拟依赖,这恰好使其无法检测跨组件问题:
接口不匹配:渲染进程传给预加载脚本的文件路径是相对路径,但预加载脚本期望绝对路径。各自的单元测试都用了 mock,都通过了,只有端到端跑通时才发现问题。
状态传播错误:数据库迁移改了表结构,但 ORM 的缓存层还持有旧结构的缓存条目。单元测试每次都是全新的 mock 环境,不会暴露这种跨层状态不一致。
环境依赖性:代码在测试环境(一切 mock)行为正确,在真实环境因配置差异、网络延迟、服务不可用而失败。
“顺便重构”是完成判定的毒药
Claude Code 有一个常见行为模式:在核心功能还没验证通过时就开始重构代码、优化性能、改进风格。 Knuth 说的”过早优化是万恶之源”在 agent 场景中有了新含义,重构会改变已完成验证和未完成验证之间的边界,可能破坏之前隐式正确的代码路径。
自我评价的系统性偏差
Anthropic 在 2026 年的研究中发现了一个更深层的失败模式**:当 agent 被要求评估自己的工作时,它系统性地过度正面评价,即使人类观察者认为质量明显不达标。**
这个问题在主观任务(如设计审美)上尤其严重,“布局是否精致”是一个判断题,agent 可靠地偏向正面。即使在有可验证结果的任务上,agent 也会因为判断失误而影响表现。
解决方案不是让 agent “更客观”。同一个模型既生成又评估,内在地倾向对自己慷慨**。解决方案是把”干活的人”和”检查的人”分开。**
一个独立的评估 agent,经过专门调校为”挑剔”之后,比让生成 agent 自我评估有效得多。 Anthropic 的实验数据如下:
| 架构 | 运行时长 | 成本 | 核心功能是否可用 |
|---|---|---|---|
| 单 agent 裸跑 | 20 分钟 | $9 | 否(游戏实体无法响应输入) |
| 三 agent(planner + generator + evaluator) | 6 小时 | $200 | 是(游戏可以正常游玩) |
这是同一个模型(Opus 4.5),同一段提示词(“做一个 2D 复古游戏编辑器”)。区别只在 harness:从”裸奔”到”planner 扩展需求 → generator 逐功能实现 → evaluator 用 Playwright 实际点击测试”。
预防过早完成的方法
1. 外部化终止判定
完成判定不应该由 agent 自己做。 harness 独立执行终止校验,输入是运行时信号,不是 agent 的置信度。在 CLAUDE.md 里可以写清楚:
## 完成定义
- 功能完成 = 端到端验证通过,不是"代码写完了"
- 必须运行的验证层级:
1. 单元测试通过
2. 集成测试通过
3. 端到端流程验证通过
- 在第 1 层没通过时,不许进入第 2 层
- 在第 2 层没通过时,不许进入第 3 层
2. 构建三层终止校验
- 第一层:语法与静态分析。成本最低,信息量最小,但必须通过。这是最低限度的检查,字都没写错才能往下看。
- 第二层:运行时行为验证。测试执行、应用启动检查、关键路径验证。这是核心完成证据,不仅要写了,还要能跑。
- 第三层:系统级确认。端到端测试、集成验证、用户场景模拟。这是防止过早声明的最后一道防线,不仅要能跑,还要跑对。
3. 给 agent 提供可操作的错误反馈
OpenAI 在 Codex 实践中提出了一个特别有效的模式**:给 agent 写的错误消息要包含修复指导**。要明确指出哪里错了、应该怎么改,不要只说”错了”。 例如不要用 "Test failed",而用 "Test failed: POST /api/reset-password returned 500. Check that the email service config exists in environment variables. The template file should be at templates/reset-email.html." 这种具体的、可操作的反馈让 agent 能自我修正,不需要人类介入。
4. 捕获运行时信号
有效的运行时信号包括:
- 应用是否成功启动并达到就绪状态?
- 关键功能路径在运行时是否执行成功?
- 数据库写入、文件操作等副作用是否正确?
- 临时资源是否被清理?
实际案例
任务:实现用户密码重置功能。涉及数据库操作、邮件发送和 API 端点修改。
提前交卷路径:agent 修改数据库 schema 、编写 API 端点、添加邮件模板、跑单元测试(通过)、声明完成。看起来做了很多,但关键环节都跳过了。
实际遗漏项:(1) 端到端流程未测试,重置链接的实际发送和验证未确认。(2) 数据库迁移在部分执行后失败,导致 schema 不一致。(3) 邮件服务配置在目标环境中缺失。
harness 介入:终止校验强制执行,(1) 启动完整应用验证重置端点可访问;(2) 执行完整重置流程;(3) 验证数据库状态一致性。所有缺陷在会话内被发现,节省了 5-10 倍的后续修复成本。
核心要点
- agent 系统性地过度自信,置信度校准偏差是客观存在的。代码写完了不代表做对了。
- 完成判定必须外部化,harness 独立验证,不信任 agent 的”感觉”。
- 三层校验缺一不可:语法通过、行为通过、系统通过,层层递进。
- 错误消息要包含具体修复步骤,让 agent 能自我修正,只说”错了”不够。
- 核心功能验证通过之前不许重构,完成优先级约束是防止过早优化的关键。
延伸阅读
- On Calibration of Modern Neural Networks - Guo et al. — 证明现代深度网络系统性地过度自信
- Building Effective Agents - Anthropic — 运行时证据在完成判定中的关键作用
- Harness Engineering - OpenAI — 过早完成声明是 agent 的主要失败模式之一
- The Art of Software Testing - Myers — 测试方法层次和有效性的经典参考
练习
-
终止校验函数设计:为一个涉及数据库迁移和 API 修改的任务设计完整的终止校验。列出需要的运行时信号和每个信号的通过/失败标准。在一个实际任务上运行,记录它发现了哪些隐藏问题。
-
校准偏差测量:选 10 个不同类型的编码任务,记录 agent 的自报完成信心和实际完成质量。计算偏差值,分析它和任务复杂度的关系。
-
多层防御实验:对同一组任务跑三种配置:(a) 仅静态分析,(b) 加单元测试,(c) 完整三层校验。比较过早完成声明的比例和未捕获缺陷的数量。
单元测试通过后,agent 经常会说”做完了”,但端到端运行时才会暴露真正的问题。举例来说,让 agent 给 Electron 应用加一个文件导出功能,它写了渲染进程组件、预加载脚本、服务层逻辑,每个组件的单元测试都通过了。 agent 说”做完了”。实际点击导出按钮时:文件路径格式不对、进度条没反应、大文件导出时内存泄漏。 5 个组件边界缺陷,单元测试一个都没发现。
每个部分单独看都”对”了,但拼在一起就出了问题。 Google 的测试金字塔告诉我们,大量单元测试是基础,但如果你止步于此,就会系统性地漏掉组件交互问题。对于 AI 编码 agent 来说,这个问题更严重,因为 agent 倾向于只跑最快的测试然后宣告完成**。只有端到端测试能证明系统级缺陷不存在**。
单元测试的盲区
单元测试的设计哲学是隔离:模拟依赖,专注被测单元。这个哲学使单元测试快速且精确,但也制造了系统性的盲区。每个模块在隔离环境中表现完美,但真正拼在一起运行时才会暴露以下几类问题:
接口不匹配:渲染进程传给预加载脚本的文件路径是相对路径,但预加载脚本期望绝对路径。各自的单元测试都用了 mock,都通过了。只有端到端跑通时才发现问题。
状态传播错误:数据库迁移改了表结构,但 ORM 的缓存层还持有旧结构的缓存条目。单元测试每次都是全新的 mock 环境,不会暴露这种跨层状态不一致。
资源生命周期问题:文件句柄、数据库连接、网络套接字的获取和释放跨越多个组件。单元测试为每个测试创建和销毁独立资源,不会暴露资源竞争或泄漏。
环境依赖性:代码在测试环境(一切 mock)行为正确,在真实环境因配置差异、网络延迟、服务不可用而失败。
端到端测试同时影响结果与行为
这是很多人没意识到的一点:当 agent 知道它的工作要过端到端测试时,它的编码行为会改变。
- 考虑组件交互:写代码时会想”这个接口和上游怎么对接”,不只关注单个函数。
- 尊重架构边界:有架构约束的系统里,端到端测试迫使 agent 遵守边界规则。
- 处理错误路径:端到端测试通常包含故障场景,迫使 agent 考虑异常处理。
测试金字塔与审查反馈提升
flowchart TB
subgraph Unit["单元测试只看孤立部件"]
U1["渲染层测试"]
U2["Preload 测试"]
U3["服务层测试"]
end
subgraph E2E["端到端运行会穿过真实系统"]
R["点击渲染层按钮"] --> P["Preload 桥"]
P --> S["服务层"]
S --> F["文件系统 / 操作系统"]
F --> Result["真实导出文件"]
end
flowchart LR
Review["审查意见:<br/>renderer 不能直接 import fs"] --> Rule["加一条 direct fs import 检查"]
Rule --> Message["报错里直接告诉 agent<br/>把文件访问移到 preload"]
Message --> Harness["把这条检查加入 harness"]
Harness --> Stronger["以后再犯会第一时间报错"]
OpenAI 在 Codex 工程实践中强调**:为 agent 写的错误消息必须包含修复指导**。不写 "Direct filesystem access in renderer",而写 "Direct filesystem access in renderer. All file operations must go through the preload bridge. Move this call to preload/file-ops.ts and invoke it via window.api." 这把架构规则变成了自动修正的闭环。错误消息不只是告诉你”出了什么问题”,还要告诉你”该怎么改”,让 agent 能够自主完成修正。
核心概念
- 组件边界缺陷:组件 A 和 B 各自单元测试通过,但它们的交互产生了不正确的行为。这是端到端测试最擅长捕获的问题类型。
- 测试充分性梯度:单元测试能检测的缺陷 <= 集成测试能检测的缺陷 <= 端到端测试能检测的缺陷。每往上一层,检测能力增强。
- 架构边界执行规则:把架构文档里的规则(如”渲染进程不能直接访问文件系统”)变成可执行的自动化检查,从”写在纸上”变成”跑在 CI 里”。
- 审查反馈提升:把重复出现的代码审查意见转化为自动化测试。每次发现重复问题就加一条规则,harness 会自动变强。
- 面向 agent 的错误消息:失败信息不只是说”出了什么问题”,还要告诉 agent 具体怎么修,把测试失败变成自我修正的反馈循环。
实施方法
0. 先定好架构边界,再写端到端测试
端到端测试的前提是系统有清晰的边界。如果架构是一团面条,端到端测试只会证明”这团面条整体能跑”,不会告诉你哪里违反了设计意图。
OpenAI 的经验**:对 agent 生成的代码库,架构约束必须是第一天就建立的早期前置条件,不是等团队规模大了再考虑的事。** 原因很直接:agent 会复制仓库中已有的模式,即使那些模式是不均匀的或次优的。没有架构约束,agent 会在每次会话中引入更多偏差。
OpenAI 采用了”分层领域架构”,每个业务领域被分成固定的层:Types → Config → Repo → Service → Runtime → UI 。依赖方向严格向前,跨领域关注点通过显式的 Providers 接口进入。任何其他依赖都是禁止的,并且通过自定义 lint 机械执行。
关键原则**:执行不变量,不微管实现。** 比如要求”数据在边界解析”,但不规定用哪个库。错误消息要包含修复指导,要告诉 agent 具体怎么改,不只说”违规了”。
1. harness 必须包含端到端层
在你的验证流程里明确:对于涉及跨组件修改的任务,端到端测试通过是完成的前置条件:
## 验证层级
- 层级 1: 单元测试 (必须通过)
- 层级 2: 集成测试 (必须通过)
- 层级 3: 端到端测试 (涉及跨组件修改时必须通过)
- 跳过任何必须层级的任务 = 未完成
2. 把架构规则变成可执行检查
每条架构约束都应该有对应的测试或 lint 规则:
# 检查渲染进程是否直接调用 Node.js API
grep -r "require('fs')" src/renderer/ && exit 1 || echo "OK: no direct fs access in renderer"
3. 设计面向 agent 的错误消息
失败信息要包含三要素:什么出了问题、为什么、怎么修:
ERROR: Found direct import of 'fs' in src/renderer/App.tsx:12
WHY: Renderer process has no access to Node.js APIs for security
FIX: Move file operations to src/preload/file-ops.ts and call via window.api.readFile()
4. 建立审查反馈提升流程
每次在代码审查中发现新类型的 agent 错误,就把它变成自动化检查。一个月后你的 harness 会比月初强得多。
实际案例
任务:在 Electron 应用中实现文件导出功能。涉及渲染进程 UI 、预加载脚本文件系统代理、服务层数据转换。
单元测试阶段:渲染组件测试(通过,mock 文件操作)、预加载脚本测试(通过,mock 文件系统)、服务层测试(通过,mock 数据源)。 agent 声明完成。
端到端测试揭示的缺陷:
| 缺陷 | 描述 | 单元测试 | 端到端 |
|---|---|---|---|
| 接口不匹配 | 文件路径格式不一致 | 未检测 | 检测 |
| 状态传播 | 导出进度未通过 IPC 传回 UI | 未检测 | 检测 |
| 资源泄漏 | 大文件导出句柄未释放 | 未检测 | 检测 |
| 权限问题 | 打包环境权限不同 | 未检测 | 检测 |
| 错误传播 | 服务层异常未到 UI 层 | 未检测 | 检测 |
5 个缺陷全部被端到端测试捕获,单元测试一个都没发现。代价是测试时间从 2 秒增加到 15 秒,在 agent 工作流里完全可以接受。
核心要点
- 单元测试对组件边界缺陷系统性盲视:它们的隔离设计恰好使其无法检测交互问题。
- 端到端测试不仅检测缺陷,还改变 agent 的编码行为:让它更关注集成和边界。
- 架构规则必须可执行:每次提交自动检查,不能只写在文档里等人来看。
- 错误消息要面向 agent 设计:包含”怎么修”的具体步骤,形成自我修正闭环。
- 审查反馈提升让 harness 自动变强:每个被捕获的缺陷类别都变成永久防线。
延伸阅读
- How Google Tests Software - Whittaker et al. — 测试金字塔模型的经典来源
- Harness Engineering - OpenAI — 架构约束自动化执行的工程实践
- Chaos Engineering - Netflix (Basiri et al.) — 主动注入故障验证系统弹性
- QuickCheck - Claessen & Hughes — 属性测试方法,介于示例测试和形式化验证之间
练习
-
跨组件缺陷检测:选一个涉及至少三个组件的修改任务。先只跑单元测试记录结果,再跑端到端测试。分析每个额外发现的缺陷属于哪种跨层交互问题。
-
架构规则自动化:选项目里的一条架构约束,把它变成可执行检查(含面向 agent 的错误消息)。集成到 harness 里,用基准任务验证效果。
-
审查反馈提升:从代码审查历史中找一个重复出现的意见类型,按五步流程转化为自动化检查。比较提升前后该类问题的出现频率。
Agent 执行任务时常常像一个黑盒:它跑了 20 分钟,改了一堆文件,然后告诉你”做完了但有两个测试失败”。你问它为什么失败,“不太确定,可能是时序问题”。你问它改了哪些关键路径,“让我看看代码……”。
这种情况的根源在 harness 缺乏可观测性。 agent 执行任务时,如果看不到运行时的实际状态,就只能凭猜测做决策。
没有可观测性,agent 在不确定状态中做决策,评估变成主观判断,重试变成盲目摸索。 OpenAI 和 Anthropic 都将可靠性定义为证据问题,harness 必须以可指导下一步决策的形式暴露运行时行为和评估信号。
可观测性缺失的影响
当 harness 缺乏可观测性时,四类问题会系统性出现。
无法区分”正确”和”看似正确”:一个函数在代码审查时看起来完全正确,语法对、逻辑通。但运行时因为边界条件处理错误,在特定输入下产生了不正确结果。只有运行时追踪能揭示实际执行路径偏离了预期。代码审查看的是”写了什么”,运行时追踪看的是”实际跑了什么”,两者缺一不可。
评估变成玄学:没有评分标准和验收条件时,评估者(人或 agent)只能依赖隐式假设。同一个输出,不同评估者可能给出截然不同的评价,质量评估不可复现。
重试变成盲猜:agent 不知道为什么失败时,重试方向是随机的。它可能在错误的方向上反复尝试,修复了不相关的代码路径而忽略真正的故障根源。每次盲重试都消耗 token 和时间。
会话交接信息断崖:当未完成的工作移交给下一个会话时,缺乏可观测性意味着新会话必须从零诊断系统状态。 Anthropic 的长期运行 agent 观察表明,这种重复诊断可能占会话总时间的 30-50% 。
实际运行示例
来看一个使用”计划者-生成者-评估者”三角色工作流的 harness,执行”为应用添加暗色模式”任务。
没有可观测性:计划者输出模糊描述,生成者根据模糊描述实现暗色模式,但和计划者的隐式预期不一致。评估者基于自己的隐式标准拒绝,但说不出具体哪里不对,只有一句”感觉不太对”。生成者基于模糊拒绝理由盲重试,循环 3-4 次,总耗时约 45 分钟,最终勉强产出。
有完整可观测性:计划者输出冲刺合同,列明要改哪些组件、每个组件的验证标准、排除项(不处理打印样式)。生成者按合同实现,运行时可观测性记录每个组件的样式加载和应用过程。评估者用评分标准逐维度评估,附具体证据引用:“按钮颜色对比度不足(WCAG AA 标准 4.5:1,实测 2.1:1)“。一次迭代出高质量结果,总耗时约 15 分钟。
效率差 3 倍,区别只在可观测性。
双层可观测性
可观测性不是”多打点日志”那么简单。它分两层,缺一不可。
flowchart LR
Contract["先把这次任务写清楚<br/>改哪些文件 / 不改哪些部分 / 怎么算通过"] --> Generator["生成器"]
Generator --> Signals["运行时收集<br/>日志 / 追踪 / 健康检查"]
Contract --> Review["按检查表逐项看<br/>功能 / 测试 / 边界"]
Signals --> Review
Review --> Verdict["指出哪一项没过<br/>以及应该去改哪里"]
Verdict --> Generator
运行时可观测性:系统层的信号,包括日志、追踪、进程事件、健康检查,回答”系统做了什么”。
过程可观测性:harness 决策工件的可见性,包括计划、评分标准、验收条件,回答”为什么这个变更应该被接受”。
核心概念
- 运行时可观测性:系统层的信号,包括日志、追踪、进程事件、健康检查,回答”系统做了什么”。
- 过程可观测性:harness 决策工件的可见性,包括计划、评分标准、验收条件,回答”为什么这个变更应该被接受”。
- 任务轨迹:一个任务从开始到完成的完整决策路径记录,类似分布式系统中的请求追踪。 agent 的每一步操作及其上下文都被记录,出了问题可以回放完整过程。
- 冲刺合同:编码开始前协商的短期协议,明确任务范围、验证标准、排除项。是过程可观测性的核心工具。
- 评估评分标准:把质量评估从主观判断变成基于证据的结构化评分,使不同评估者对同一输出产生相似结论。
- 双层可观测性:系统层和过程层同时设计、相互增强。运行时信号解释行为,过程工件解释意图。
Agent 自行处理可观测性的局限
你可能在想:“agent 不能自己打日志吗?” 问题在于:
- agent 不知道它不知道什么:它不会主动记录自己没意识到需要的信号。没有 harness 层面的约束,agent 只会记录它认为重要的东西,而它认为重要的东西往往不够。
- 日志格式不统一:不同会话用不同的日志格式,无法做系统化分析。
- 过程可观测性不是日志能解决的:冲刺合同和评分标准是结构化的工件,需要 harness 层面的支持,不是多 print 几行就能搞定的。
搭建可观测性的方法
1. 在 harness 里内置运行时信号采集
不要依赖 agent 自己打日志。 harness 应该自动采集以下信号:
- 应用生命周期:启动、就绪、运行、关闭各阶段状态
- 功能路径执行:关键路径的执行记录,包括入口、检查点和出口
- 数据流:数据在组件间的流转记录
- 资源利用:异常的资源使用模式(如内存持续增长)
- 错误和异常:完整的错误上下文,不只是错误消息
2. 实施冲刺合同
在每个任务开始前,生成者和评估者(可能是同一个 agent 的不同调用)协商一份合同,明确这次要做什么、怎么做算通过:
# 冲刺合同: 暗色模式支持
## 范围
- 修改主题切换组件
- 更新全局 CSS 变量
- 添加暗色模式测试
## 验证标准
- 每个组件的视觉回归测试通过
- 主流程端到端测试通过
- 无样式闪烁 (FOUC)
## 排除项
- 不处理打印样式
- 不处理第三方组件暗色模式
3. 建立评估评分标准
把”好不好”变成可量化的评分:
# 评分标准
| 维度 | A | B | C | D |
|------|---|---|---|---|
| 代码正确性 | 所有测试通过 | 主流程通过 | 部分通过 | 编译失败 |
| 架构合规 | 完全合规 | 轻微偏离 | 明显偏离 | 严重违反 |
| 测试覆盖 | 主流程+边缘 | 仅主流程 | 仅有骨架 | 无测试 |
4. 用 OpenTelemetry 标准化
为每个 harness 会话创建一个 trace,每个任务创建一个 span,每个验证步骤创建子 span 。使用标准属性标注关键信息。这样可观测性数据可以和标准工具链(Jaeger 、 Zipkin)集成。
Anthropic 的三 agent 架构实验
Anthropic 在 2026 年 3 月发布了一项系统性的 harness 实验。他们用三种架构跑同一个任务(“用 Web Audio API 做一个浏览器端 DAW”),记录了详细的阶段数据:
| Agent 和阶段 | 时长 | 成本 |
|---|---|---|
| Planner(规划者) | 4.7 分钟 | $0.46 |
| Build 第 1 轮 | 2 小时 7 分钟 | $71.08 |
| QA 第 1 轮 | 8.8 分钟 | $3.24 |
| Build 第 2 轮 | 1 小时 2 分钟 | $36.89 |
| QA 第 2 轮 | 6.8 分钟 | $3.09 |
| Build 第 3 轮 | 10.9 分钟 | $5.88 |
| QA 第 3 轮 | 9.6 分钟 | $4.06 |
| 总计 | 3 小时 50 分钟 | $124.70 |
三个 agent 各司其职,每个都有明确的可观测性角色:
Planner(规划者):接收一段 1-4 句话的用户需求,扩展成完整产品规格。被要求”大胆设定范围”并且”专注于产品上下文和高层技术设计,而不深入详细的技术实现”。原因是:如果 planner 过早指定了粒度技术细节且搞错了,错误会级联到下游实现。更好的做法是约束交付物,让 agent 在执行中自己找到路径。
Generator(生成者):按 sprint 逐个功能实现。每个 sprint 前和 evaluator 协商一份 sprint 合同,约定这个功能块”做完”的标准。然后按合同实现,自评后交给 QA 。
Evaluator(评估者):用 Playwright MCP 像用户一样点击运行中的应用,测试 UI 功能、 API 端点和数据库状态。对每个 sprint 按四个维度评分:产品深度、功能性、视觉设计和代码质量。每个维度有硬性阈值,任一不达标则 sprint 失败,generator 收到详细反馈后修复。
QA 第 1 轮反馈的示例:“这是一个视觉上令人印象深刻的应用,AI 集成工作良好,但核心 DAW 功能有几个是展示性的,没有交互深度:剪辑不能拖拽/移动,没有乐器 UI 面板(合成器旋钮、鼓垫),没有视觉效果编辑器(EQ 曲线、压缩器仪表)“。这些不是边缘情况,它们是让 DAW 可用的核心交互。具体的、有证据的反馈,不是”感觉不对”。
Evaluator 不是一开始就这么强。早期版本会识别出合理的问题,然后说服自己这些问题不严重,最终批准工作。调校方式是:读 evaluator 的日志,找到它的判断和人类判断分叉的地方,更新 QA 的 prompt 解决那些问题。经过几轮这种开发循环,evaluator 的评分才变得合理。
核心要点
- 可观测性是 harness 的架构属性:它是设计时必须考虑的核心能力,不应只作为事后添加的功能。
- 双层可观测性缺一不可:运行时信号解释”发生了什么”,过程工件解释”为什么这样做”。
- 冲刺合同前置对齐工作:防止”生成者做了评估者因可预见原因立即拒绝的东西”。
- 评分标准让评估可复现:不同评估者对同一输出产生相似评分。
- 可观测性缺失导致 30-50% 的会话时间浪费在重复诊断上。
延伸阅读
- Observability Engineering - Charity Majors — 现代可观测性工程的理论和实践框架
- Dapper - Google (Sigelman et al.) — 大规模分布式追踪的开创性实践
- Harness Design - Anthropic — 引入冲刺合同和评估评分标准
- Site Reliability Engineering - Google — 可观测性在生产系统中的系统化应用
练习
-
可观测性差距分析:审查你当前的 harness,评估系统层和过程层可观测性。找出无法从现有信号区分的系统状态,提出补充方案。
-
冲刺合同实践:为一个真实任务写冲刺合同。让 agent 按合同执行,对比没有合同时的效率和质量差异。
-
任务轨迹构建:记录一个完整编码任务中 agent 的每一步操作。用 OpenTelemetry 语义约定标注。分析轨迹中的信息瓶颈——哪些步骤的决策缺乏足够的信号支持。
在使用 AI 编码 agent 进行持续开发时,一个常见的问题是:每个 agent 会话结束时,如果没有刻意做清理,代码库的状态会越来越混乱。举例来说,一个 agent 会话修改了 20 个文件并提交代码后退出,下一个会话启动时,可能发现构建失败、测试变红、临时调试文件散落各处,功能清单和进度记录也没有更新。新会话需要花费大量时间诊断上一个会话做了什么,才能继续工作。
OpenAI 和 Anthropic 都明确指出,长期可靠性取决于操作纪律,单次运行成功并不够。每个会话结束时的状态质量,直接决定下一个会话的效率。这一讲要讨论的,就是如何让每个会话在结束时留下一个干净的状态,让下一个会话可以立刻开始工作。
熵增是默认状态
Lehman 的软件演化定律告诉我们:一个持续变更的系统,如果没有人主动管理,它的复杂性一定会增加。这对 AI 编码 agent 来说尤其成立——agent 每次会话都会引入变更,如果不在退出时清理,技术债务会指数级累积。
OpenAI 在 5 个月的 Codex 实验中观察到一个现象:agent 会复制仓库中已有的模式,哪怕那些模式是不一致或次优的。随着时间推移,这种复制必然导致整体质量漂移。打个比方:第一个人在公共区域放了一个杯子,第二个人路过时心想”反正已经乱了”,自己也放了一个。一周后,桌上就堆满了。代码库的退化也是同样的过程。
OpenAI 团队最初每周五花 20% 的工作时间手动清理 agent 留下的烂摊子,但这种做法显然不可持续。他们最终找到了系统性的解决方案:
- 把好习惯写进仓库规则:比如”优先使用共享工具包,不要手写 ad-hoc 辅助函数”、“不要瞎猜数据结构,查类型定义或用类型安全 SDK”。这些规则是具体的、机械的、可以自动检查的。
- 建立周期性清理流程:一组后台任务定期扫描偏离规则的代码,更新质量评分,自动开重构 PR 。大多数 PR 可以在一分钟内审查并自动合并。
- 人类经验捕获一次,持续执行:每次代码审查意见、重构 PR 、用户报的 bug,都转化为文档更新,或直接编码到检查工具中。文档还不够时,就把规则提升为自动检查的代码。
一句话总结:技术债是高息贷款,持续小额还款比攒到一次性爆雷好得多。
清洁状态:不只是”代码能编译”
清洁状态的要求远比”代码能编译”要多。构建通过是最基本的前提——下一个会话不应该一上来就先修别人的构建错误。所有测试也必须通过,包括会话开始前就存在的旧测试,你这次改动不能破坏已有的功能。而且验证必须在 CI 环境里跑,不是”在我机器上能过就行”。
flowchart LR
Work["功能工作已完成"] --> Build{"构建通过?"}
Build -->|是| Test{"测试通过?"}
Build -->|否| Fix["先修好再退出"]
Test -->|是| Record["更新功能清单 + 进度"]
Test -->|否| Fix
Record --> Cleanup["清理临时工件 / 调试代码"]
Cleanup --> Startup{"标准启动路径可用?"}
Startup -->|是| Clean["干净交接"]
Startup -->|否| Fix
Fix --> Build
构建和测试只是底线,还有三条容易被忽略的要求。
第一,当前进度必须记录在机器可读的工件中。具体来说包括三类:已完成的子任务和它的通过标准、正在做但还没做完的子任务和它当前卡在哪、还没开始的子任务。好的进度记录可以减少 60% 到 80% 的会话启动诊断时间。
第二,临时调试产物必须清理干净。调试日志、临时文件、注释掉的代码、 TODO 标记,这些东西都会增加下一个会话的认知负担。新会话看到一堆 console.log('debug') 和 // 临时方案,回头改,根本分不清哪些是有意的、哪些是垃圾。
第三,标准启动路径必须可用。下一个会话能不能不靠人工干预就直接开始工作?环境初始化、代码库加载、上下文获取、任务选择,这些路径中的任何一个被破坏,新会话就无法自行启动工作。
flowchart LR
Dirty["这次退出时<br/>测试红的、临时文件没删、进度没写"] --> Diagnose["下个会话先花时间<br/>搞清楚发生了什么"]
Diagnose --> Fragile["接着在一个很乱的仓库上继续改"]
Fragile --> More["更多调试文件、更多坏检查、<br/>更多说不清的进度"]
More --> Dirty
Clean["这次退出前<br/>测试绿的、进度写了、临时文件删了"] --> Fast["下个会话打开仓库就能继续写代码"]
Fast --> Stable["不用先修复问题"]
Stable --> Clean
总结一下,一个干净的会话退出需要满足五个条件:构建通过、测试通过、进度已记录、临时工件已清理、启动路径可用。缺一个都不算”做完了”。
核心概念
- 清洁状态:会话退出时必须满足五个条件——构建通过、测试通过、进度已记录、无过时工件、启动路径可用。这五个条件共同构成”做完”的真正定义。
- 会话完整性:可以类比数据库事务。一个会话的工作要么全部完成并留下清洁状态,要么回滚到上一个一致状态,不存在”做了一半但还行”的中间地带。
- 质量文档:对代码库中每个模块持续记录质量评分的文件。它是持续更新的,追踪每个模块到底是变强了还是变弱了。
- 清理循环:定期执行的维护会话,目标是从代码库中系统性地清除积攒的问题。它属于常规保养,不属于紧急修复。就像汽车定期换机油,不等发动机报警才去修。
- Harness 简化:随着模型能力提升,定期移除不再必要的 Harness 组件。今天必须有的约束条件,三个月后用更强的模型可能就成了多余开销。
- 幂等清理:清理脚本无论执行多少次,结果都一样。这意味着清理失败时重跑一遍也安全,不会因为重复执行产生新问题。
“以后再清理”是永远不清理
最常见的心理陷阱就是”这次来不及清理了,下次再弄”。但下次的 agent 根本不知道你上次留下了什么,它看到的只是一堆混乱的代码和不确定的状态。它得花大量时间推断”这段代码里哪些是有意的,哪些是临时的”。
更糟的是,每个会话都有自己的任务目标。新会话来的时候是为了做新功能,不是为了清理上一个会话遗留问题的。它会直接忽略混乱,在混乱的基础上开始新工作,然后引入更多混乱。这是一个熵增的正反馈循环——越乱越不管,越不管越乱。
数据最能说明问题。下面是一个使用 agent 持续开发 12 周的项目的实际对比:
没有清洁策略:
| 时间 | 构建通过率 | 测试通过率 | 新会话启动时间 |
|---|---|---|---|
| 第 1 周 | 100% | 100% | 5 分钟 |
| 第 4 周 | 95% | 92% | 15 分钟 |
| 第 8 周 | 82% | 78% | 35 分钟 |
| 第 12 周 | 68% | 61% | 60+ 分钟 |
有清洁策略:
| 时间 | 构建通过率 | 测试通过率 | 新会话启动时间 |
|---|---|---|---|
| 第 1 周 | 100% | 100% | 5 分钟 |
| 第 12 周 | 97% | 95% | 9 分钟 |
12 周下来,两组之间的构建通过率差了 29 个百分点,测试通过率差了 34 个百分点,新会话启动时间差了 85% 。这是实测数据,差距已经非常明显。
怎么做
1. 清洁状态是完成的必要条件
在 Harness 里明确定义:会话完成的条件是两件事同时满足——任务通过验证,且清洁状态检查通过。缺任何一个,会话就不算完成。在项目的 CLAUDE.md 或 AGENTS.md 里可以这样写:
## 会话退出检查清单
- [ ] 构建通过 (npm run build)
- [ ] 所有测试通过 (npm test)
- [ ] 功能清单已更新
- [ ] 无调试代码残留 (console.log, debugger, TODO)
- [ ] 标准启动路径可用 (npm run dev)
这里解释一下”功能清单”。功能清单(feature list)是一份机器可读的文件,记录了项目中所有功能项的完成状态。每一项功能有三列信息:这个功能具体做什么、用什么命令来验证它、当前状态是什么(未开始/进行中/已阻塞/已通过)。调度器靠功能清单选下一个要做的工作,验证器靠它判断做完没有,交接器靠它生成进度报告。没有功能清单,agent 就不知道”做完”的标准是什么——它可能会用自己的标准判断完成,而你心里的”做完”是完全不同的东西。
2. 双模式清理策略
把清理分成两种模式,配合使用:
即时清理(每个会话结束时):清理本次会话创建的临时文件、更新功能清单状态、确保构建和测试全部通过。原则是用完就清,像引用计数一样——谁产生的垃圾谁负责清掉。
定期清理(每周一次):做一次全面的系统扫描,处理累积的结构性问题、更新质量文档、跑基准测试检测整体质量有没有漂移。原则是定期全身体检,不让小问题拖成大病。
3. 维护质量文档
质量文档是一份持续更新的文件,对代码库中每个模块打分和评价。新会话一打开就能看到上次会话后每个模块的状态。例:
# 质量文档
## 用户认证模块 (质量: A)
- 验证通过: 是
- agent 可理解: 是
- 测试稳定性: 稳定
- 架构边界: 合规
- 代码规范: 遵循
## 支付模块 (质量: C)
- 验证通过: 部分(支付回调未测试)
- agent 可理解: 困难(逻辑分散在 3 个文件)
- 测试稳定性: 不稳定(2 个 flaky 测试)
- 架构边界: 有违规
- 代码规范: 部分遵循
有了这份文档,新会话一上来就知道当前代码库的健康状况,优先处理评分最低的模块。从这个角度说,质量文档是 Harness 可观测性的一部分——它让 agent 的运行结果在代码库层面可见。
4. 定期简化 Harness
Harness 中每个组件的存在,都源于模型在某个方面尚无法独立完成。随着模型能力不断演进,这些前提会逐渐过时。
Anthropic 的实验直观地展示了这一点。他们最初的 Harness 包含一个任务拆分机制:因为当时的模型一次性处理不了太大的任务,所以需要把大任务先拆成多个小步骤,让模型按顺序逐个完成。当 Opus 4.6 发布后,模型自己就能规划好该怎么一步步做事,不再需要外部帮忙拆解,这个机制反而成了多余的步骤。移除后,Builder Agent 能够连续工作超过两小时而不偏离方向,流程反而更流畅了。
Evaluator 的情况则有所不同。 Evaluator 是 Harness 中的评估组件,负责检查生成的代码质量,找出遗漏的功能和未完成的实现。尽管 Opus 4.6 能力更强,当任务难度较高、逼近模型能力的上限时,Evaluator 依然很有用。但如果任务本身很简单,远在模型能力范围之内,Evaluator 可能就是多余的。因此,要不要保留 Evaluator,取决于你的任务有多难、模型有多强——这两者的相对关系才是关键。
推荐做法:每月挑选一个 Harness 组件,暂时禁用它,跑一遍基准任务。如果结果没有退化,就永久移除。如果退化,则恢复该组件,或换一个更轻量的替代方案。
一个更深层的原则:随着模型能力的提升,Harness 中有趣的组合并没有减少,它在位移。过去必须解决的问题被模型增长的能力覆盖了,同时新的能力边界被打开,暴露出过去触及不到的新问题。
5. 清理操作必须幂等
幂等的意思是:一个操作无论执行一次还是执行一百次,结果都一样。清理脚本必须具备这个特性,因为清理失败时你会重跑一遍。如果重跑产生不同的结果,就说明清理脚本存在 bug 。例:
# 幂等的清理操作
rm -f /tmp/debug-*.log # -f 确保文件不存在时不报错
git checkout -- .env.local # 恢复到已知状态,多跑几次结果相同
npm run test # 验证清理没有破坏功能
6. 高吞吐量改变了合并策略
当 agent 的产出远超人类审查能力时,传统的合并策略需要调整。 OpenAI 团队的经验是:在一个 agent 每天开出 3.5 个 PR 的环境里,减少阻塞型的合并检查是正确的选择。 PR 应该尽快合并,测试偶尔的假失败(flake)用后续运行来修正,不必无限期卡住进度。
这里有一个关键的判断标准:修正一个 bug 的平均成本,和等待人类审查一个 PR 的平均成本,哪个更低?当前者低于后者时,快速合并加上快速修正比慢慢审查更好。
注意:这条规则的前提是 agent 的高产出远超过人类的审查带宽。在一个低产出环境里,快速合并没有意义。但在 agent 每天提交几十个 PR 的环境里,等待人工审查的成本远高于修一个漏网 bug 的成本。
实际案例
一个使用 agent 持续开发的 Electron 应用,12 周的演化过程:
无清洁策略(对照组):每个会话做完功能就退出,不做额外清理。第 12 周时,构建通过率 68%,测试通过率 61%,新会话启动 60 分钟以上,过时工件 103 个。
有清洁策略(实验组):每个会话结束时执行完整清洁检查,加上每周一次清理循环。第 12 周时,构建通过率 97%,测试通过率 95%,新会话启动 9 分钟,过时工件 11 个。
到第 12 周,实验组的构建通过率比对照组高 29 个百分点,测试通过率高 34 个百分点,新会话启动时间减少 85% 。每个会话只多花 5 分钟做清理,12 周下来却省了几十个小时的混乱时间。
核心要点
- 清洁状态是会话完成的必要条件。它是”完成”定义的一部分,属于必要条件。代码写完了但状态是脏的,那就不算做完。
- 五个维度缺一不可:构建、测试、进度、工件、启动。每一条都要在退出时显式检查,不能靠”感觉应该没问题”。
- 功能清单让 agent 知道”做完”的标准。没有清单,agent 用自己的标准判断完成,那个标准几乎一定比你的标准低。
- 质量文档让代码库的健康状况可追踪。知道哪里在退化,才能主动修复。不知道问题在哪儿,就只能等它爆发。
- 定期简化 Harness:随着模型能力提升,主动移除不再必要的组件。今天必须有的约束,三个月后可能就是累赘。
- “以后再清理”等于永远不清理。熵增是默认方向,只有主动的清洁操作才能对抗它。每次多花五分钟,长期来看是回报最高的投资。
延伸阅读
- Clean Code - Robert C. Martin — 代码整洁之道的经典著作,清洁状态背后的软件工程原则
- Harness Engineering - OpenAI — OpenAI 团队在 agent 时代如何通过 Harness 设计确保可重复性
- Effective Harnesses for Long-Running Agents - Anthropic — Anthropic 的长期运行 agent 实践,清洁会话退出对可靠性的关键作用
- Programs, Life Cycles, and Laws of Software Evolution - Lehman — 软件演化定律的原始论文,证明了无主动维护时系统复杂性必然增长
- 第八讲:用功能清单约束 agent 该做什么 — 如何用功能清单给 agent 明确的完成标准
- 第九讲:防止 agent 提前宣告完成 — 如何通过验证机制避免 agent 过早说”做完了”
- 第十讲:跑通完整流程才算真正验证 — 为什么端到端测试是唯一可靠的完成验证
- 第十一讲:让 agent 的运行过程可观测 — 如何通过可观测性让 agent 的运行状态不再是一个黑盒
- 第五讲:让跨会话的任务保持上下文连续 — 会话交接的前置知识,如何让新会话快速接上
练习
-
设计你的清洁状态检查表:为你的代码库设计一个会话退出检查表,涵盖五个维度(构建、测试、进度、工件、启动)。在接下来的 5 个连续会话中坚持执行,记录每个维度上违反了几次。
-
基准对比实验:选定一个固定的任务集,分别在两种 Harness 配置下跑一遍——一种要求清洁状态检查通过才算完成,另一种不要求。比较两组的完成率、重试次数和漏网 bug 的数量。
-
Harness 简化实践:从你的 Harness 里选一个组件,暂时禁用它,跑一遍基准任务。比较有它和没它的结果,然后决定是保留、移除还是换一个更轻量的替代方案。
-
质量文档入门:为你的项目创建第一份质量文档。挑 3 到 5 个核心模块,给每个模块打分(A/B/C/D),标注具体的扣分原因。在接下来的 4 周里,每周更新一次评分,观察质量是变好了还是变差了。
放到中国团队里,怎么用
国内个人开发者和小团队常见的问题很具体:项目规则散在飞书、微信群和口头约定里;测试命令没人维护;换一个人或换一次会话就要重新解释;模型调用还有额度、网络、数据合规和代码保密边界。 Harness 解决不了这些外部问题,但能把项目内部的规则、状态和验收方式固定下来。
建议先做一个低风险版本:只允许代理读取和修改一个测试仓库;所有写入动作走 Git 分支;涉及生产数据、客户代码、密钥和外部系统时,先人工确认;每次任务结束保留验证结果和变更说明。国内团队最容易忽略的不是模型选择,而是权限和数据流向。
本期动手清单
- 在自己的项目里找出一个反复返工的任务。
- 记录代理从开始到结束实际读了哪些文件、执行了哪些命令。
- 把缺失的规则、状态或验证补成项目文件。
- 用同一个任务再跑一次,只比较结果和返工,不凭感觉下结论。
- 把失败归因到 Harness 的某一层,下一期继续补。
下一期预告
下一期会把本期的概念进一步落成一套文件结构和模板。到时候我们不再讨论“应该怎样”,而是直接从项目根目录开始搭。