$Wynn5a 技术博客 - AI编程与软件工程实践
~/blog/harness-engineering

Harness Engineering:让 Coding Agent 实现“可用、可控、可持续”的工程方法

OpenAI 在 2025 年下半年到 2026 年初做了一个极端实验零手写代码,连测试、CI 配置、文档、可观测性与内部工具都由 Codex 生成;他们声称在约 5 个月里做出了一个给真实用户用的产品,代码规模到百万行量级,且构建这个产品所花费的时间大约是手写的十分之一。对开发者来说,这个故事的重点不在“AI 写了多少代码”,而在它迫使团队把原本靠经验和口口相传的工程纪律编码进仓库与流水线,架构边界要能被机器验证、文档要能被自动校验并持续“修剪”、UI/日志/指标要能被 agent 直接读懂并形成闭环,在这种环境下,coding agent 成功实现了项目目标。

如果尽可能精简总结这个实验做了什么,我想会是这三件事:第一,把项目变成可运行、可验证、可回滚的“可控系统”;第二,把规则从写在 Confluence 上升级为写进 CI 和 linter 里;第三,把 agent 用起来的门槛从会提示词变成“有一套稳定的 harness”。

Harness engineering 到底是什么#

如果只用一句话定义:Harness engineering 是一门把工程约束和反馈回路产品化的方法,让 coding agent 能可靠地在你的代码库里工作。OpenAI 自己把工程师角色的变化说得很直白:当团队的主要工作不再是手写代码,而是设计环境、明确意图、搭建反馈回路,让 Codex 做可靠工作,这也是 Harness engineering 给工程师以及团队带来的变化。

传统软件测试里,Test Harness 通常指执行测试所需的测试环境(含 stubs/drivers 等),而 Harness engineering 可以看成把 Test Harness 的思想扩展到 Agent-First 的软件交付世界中,不只是让测试能跑,而是让 agent 的工作从需求拆解 → 修改代码 → 自检 → 提 PR → 过 CI → 合并 → 观察线上信号 → 复盘沉淀规则,整条链路都能在系统里闭环,在这个世界里,测试/QA/CI 等都是 Harness 的“反馈信号源”;Harness engineering 做的是把这些信号连成闭环,并让 agent 能用它们自证与自纠错。

Harness engineering 的目标通常有三层,从保守到激进:

首先是“能用”:agent 不是写完就撒手不管,而是能按项目约定跑命令、修测试、补文档、提 PR。OpenAI 的团队基本通过 prompt 驱动整个过程,让 Codex 自审、再请其他 agent 复审,最后把 PR 推到可合并状态;人类可以审,但不是必须。

其次是“可控”:把隐性规则硬编码成机器可验证的门禁。OpenAI 强调光靠文档无法保持一致性,因此用自定义 lints + 结构化测试强制架构不漂移,并且把错误信息写得足够“可修复”,直接把整改指令注入 agent 上下文。

最后是“可持续”:承认高吞吐会带来熵增,必须有“垃圾回收”。OpenAI 说他们曾经每周五花 20% 时间手工清理“AI slop”,这种模式不可扩展,于是改成把“黄金原则”编进仓库,用后台任务按节奏扫描偏离、更新质量评分、自动开重构 PR。

为什么重要#

很多团队引入 coding agent 之后的真实体验通常是:写得更快,但验收更累;PR 更多,但信任更少。OpenAI 的实验把这个矛盾放大到了极致:三个人驱动 Codex 五个月开合并约 1500 个 PR,平均到单人单日 3.5 个 PR,而且随着团队扩大吞吐还在提升。当变更密度到这种程度,人的注意力立刻变成瓶颈,如果你不把质量判断一类的工作自动化,系统就会卡死在 review 和返工上。

Harness engineering 解决的痛点,基本都跟项目工程化沾边:

  • 环境不够“可运行”与“可解释”

    OpenAI 发现早期进展慢并不是 Codex 不行,而是环境不够规范化:缺工具、缺抽象、缺内部结构,于是人类主要工作变成让 agent 能做有用的事,这在现实项目里通常对应:本地起不来、依赖不锁、测试 flaky、说明散落在各处。如果你只一味地让 agent 猜,它会用更多 PR 把锅甩回来。

  • 知识不在仓库里,agent 就当它不存在

    OpenAI 明确说从 agent 视角,跑任务时拿不到上下文的东西基本等于不存在,因此他们把仓库里的知识库当系统事实源,做成结构化文档存放在仓库的 docs/目录,并把 AGENTS.md 降级为“目录地图”,同时用 linters/CI 等工具自动化校验知识库的结构、交叉链接以及新鲜度,甚至用 doc-gardening agent 定期清理过时文档并开修复 PR。

  • 架构漂移与风格漂移会被 agent 放大

    OpenAI 选择了很硬的分层模型:在每个业务域里只允许按固定方向依赖(Types → Config → Repo → Service → Runtime → UI),跨领域通过 Providers 单一入口,否则一律禁止;这类约束靠自定义 lints 与结构化测试机械强制。直觉上你会觉得“这也太平台化了”,但在 agent-first 场景里它是用自动化来换取交付速度的前置条件,而且约束一旦自动化,就能全局复用。

  • 吞吐改变了流程经济学

    OpenAI 直言道:在 agent 吞吐远超人类注意力时,“纠错便宜,等待昂贵”。所以他们减少阻塞式合并门禁、让 PR 短寿命,遇到测试 flakes 倾向后续修复而不是卡住流水线。这不是建议你不要质量,而是建议把质量做成快速反馈 + 持续纠偏,而不是靠长周期的人工审查。

OpenAI 也强调这套自治能力高度依赖仓库结构与工具投入,不能指望什么都不做,Harness engineering 就能泛化到别的代码库中,所以我们接着看如何开始。

如何开始采用#

给遗留项目上 Harness Engineering,别一上来就试图让 agent 自动写功能,更现实的思路是:先把项目改造成可运行、可验证、可定位、可回滚的系统,再逐步把 agent 放进来跑圈。OpenAI 的经验说白了就是一件事:工程师从"手写实现"转向"设计环境、明确意图、搭反馈回路",让 Codex 在可控边界里做靠谱的事。

下面是一套可落地的分阶段方案,相当于"迁移路线图 + 验收清单",每阶段都要能交付、能量化、能回退;做不动就缩小范围,别硬顶。

rendering diagram…

阶段一:把项目跑稳——可复现构建 + 最小 CI 基线#

目标:让项目在干净环境里可重复运行,把"我电脑能跑"变成"谁跑都能跑"。OpenAI 把团队最稀缺的资源定义为"人类时间与注意力",差的环境会把注意力吃光;遗留项目要上 agent,第一步就是把噪音降下去

具体可执行任务

  • 把启动与验证收敛成 3 个命令:make devmake testmake lint(或者等价脚本),CI 里跑的就是这 3 个命令,而不是散落的脚本串
  • 锁定依赖:语言层(lockfile / toolchain version)、系统层(容器/Devcontainer/CI 镜像版本),把隐式依赖显式化
  • 建一个最小 CI:至少做到 lint + unit tests。别一次把所有慢检查塞进来,先把最快能发现大问题的门禁拉起来
  • 统计并标记 flaky 测试:先不求一次全修完,但要能识别、隔离、重跑。OpenAI 提到随着吞吐上来,传统强阻塞门禁会变得反作用,他们倾向于用后续修复替代无限等待,前提是你能定位与隔离这种不稳定、非确定性的失败测试

验收标准

  • 全新环境(CI 或干净容器)上:make test 连续通过 3 次,make lint 连续通过 3 次
  • CI 从触发到出结果:小项目建议 ≤10 分钟,中大型建议 ≤20 分钟,超过就拆层
  • flaky 测试有清单:至少能回答"哪些在抖、抖的频率、如何重跑"

所需最小工具

  • 统一任务入口(Make/Taskfile/npm scripts 均可)
  • 能被 CI 复用的运行环境(容器/Devcontainer/固定 runner)

常见风险与缓解

  • 风险:一上来就想"全量测试 + 全量静态扫描",CI 直接跑半小时起步

    缓解:把测试拆层(快门禁/慢门禁/夜间全量),快门禁必须稳定;慢门禁允许异步修复但要可追踪

  • 风险:历史包袱太久,修到能跑像翻修危房

    缓解:对老代码库加新约束容易像第一次上静态分析那样引发告警海啸;先选一条黄金路径跑通并固化,再逐步扩展到不同层面

阶段二:最小 Harness 进仓库#

目标:让 agent 直接从仓库里读到工作约定,并且在不污染主工作区的前提下完成"改代码 → 自检 → 提 PR"的黄金流程。OpenAI 实验的关键是给 Codex 一张地图,而不是 1000 页手册,把 AGENTS.md 当目录,把真正知识放进结构化 docs/,再用 CI/linter 校验知识库是否新鲜

具体可执行任务

  • 在仓库根目录加一个短 AGENTS.md:只写三件事
    1. 项目怎么跑(最关键命令)
    2. 变更必须自证(必须补测试/更新文档的规则)
    3. 文档入口(把"真相"指向 docs/)

因为巨型AGENTS.md 必然失败,它将导致上下文稀缺、规则变成无效信息、迅速腐烂且难以校验

  • 按目录拆分规则:敏感模块(认证/支付/权限)在子目录放更严格的 AGENTS.md。大部分 agent 的指令发现机制是"从项目根一路走到当前目录,逐层拼接",默认有大小上限(32KiB),鼓励分层分目录组织
  • 引入隔离工作区:强制 agent 每次任务在独立 worktree 或者分支里跑,避免污染主目录,Git worktree 支持同一仓库挂多个工作区,同时检出不同分支,用完可 remove
  • 把 agent 接入执行工具:Codex CLI 的定位是本地可运行 coding agent,能在选定目录读、改、跑代码;并支持用 exec 做脚本化自动化,还有 approval modes 方便早期控风险
  • 至少跑通一次"agent 提 PR":先从低风险任务开始(补测试、修 lint、改文档一致性),别上来就改核心链路

验收标准

  • 任意中级工程师(不靠口头指导)按 AGENTS.md 能在 15 分钟内跑通基础命令
  • 隔离工作区 + agent 闭环可复现:同一类任务连续 3 次都能走到 PR,不要求一次就过 CI,但要求失败原因可定位
  • AGENTS.md 总体保持短(建议 100–200 行以内),把细节沉到 docs/

所需最小工具

  • AGENTS.md(根目录 + 必要的子目录覆盖)
  • git worktree 工作流
  • Codex CLI(交互或脚本)

常见风险与缓解

  • 风险:团队把 AGENTS.md 写成万能 wiki

    缓解:强制规则:AGENTS.md 只做目录;细节进 docs/;重要约束进测试/lint

  • 风险:agent 乱跑命令、乱改依赖

    缓解:早期用 approval modes;把"新增生产依赖必须先确认"等约定写进 AGENTS.md

阶段三:硬约束上墙#

目标:把"不能这样写"从 code review 争论升级成 CI 的红灯;同时把架构边界固定住,避免 agent 高吞吐下加速架构漂移。OpenAI 的结论是:文档不足以维持一致性,需要通过自定义 linters 和结构化测试机械执行边界与品味不变量

具体可执行任务

  • 把确定性门禁做成快且可解释,类似 formatter、lint、类型检查、单测
  • 引入结构化架构测试:Java 体系里,ArchUnit 的定位是"用普通单元测试框架自动测试架构与规则",包括检查包/类依赖、分层、循环依赖等
  • 把核心模块边界写成规则:例如 UI 不能直接依赖核心域,Service 不能绕过 Repository 直接碰 DB adapter;规则失败时给出清晰 remediation。OpenAI 甚至强调错误信息要能把整改指令注入 agent 上下文
  • 对新增代码先上规则,再慢慢扩到旧代码,避免一次性把历史债全炸出来

验收标准

  • 新增/改动代码必须过 lint + unit tests + arch tests;arch tests 基本稳定(每周 flaky < 1 次)
  • 关键边界(最怕被改坏的那一块)至少有 3 条硬规则,并且在 CI 中必跑
  • 规则失败能定位到哪个包/类破坏了边界,应该怎么修

所需最小工具

  • ArchUnit(如 Java)或等价工具
  • CI 门禁(把架构测试当单测跑)

常见风险与缓解

  • 风险:规则太细,导致天天红

    缓解:先抓"边界方向"这种高杠杆规则,别从命名/排版开始;并允许对旧代码做渐进豁免

  • 风险:agent 复制仓库里的坏模式,把坏味道扩散

    缓解:把坏味道识别写成 lint/test,再让 agent 修

阶段四:语义评估 + 可观测性闭环#

目标:遗留项目最难的是行为回归,特别是引入 LLM/agent、或让 agent 自己做更多判断时,输出本身是不确定的。OpenAI 官方说:生成式输出有变异性,传统测试方法不足,需要用 eval 这种结构化测试来保证可靠性。

同时,OpenAI 在 harness engineering 里把可观测性与 UI 可操作性都做成 agent 可用的上下文:按 worktree 启动隔离实例、把 Chrome DevTools Protocol 接进运行时以获得 DOM 快照/截图/导航能力;并给每个 worktree 配一套临时观测栈,agent 可以用 LogQL/PromQL 查询日志与指标。

具体可执行任务

  • 语义评估(eval)先从"最贵的失败"开始:挑 20–50 条高风险用例(线上事故、投诉、核心转化链路、权限边界等),做成可重复运行的数据集
    • OpenAI "Working with evals"给了一个工程化的三步法:把任务描述成 eval → 用测试输入运行 → 分析结果迭代,类似 BDD(先写行为再实现)
    • 如果做 agent 工作流,OpenAI 的 agent evals 还强调:工作流级别错误更适合用 trace grading 来定位
  • 选一个落地工具:
    • OpenAI Evals(平台/框架/开源仓库)强调可以用自己的数据构建私有 eval,不必公开
    • promptfoo 提供 CI/CD 集成指引,典型用法是用 npx 跑 eval 并输出结果文件
    • DeepEval更像"LLM 单元测试版 pytest",提供 agentic metrics(Task Completion、Tool Correctness)
    • lm-evaluation-harness 偏学术/标准基准,支持大量学术 benchmark
  • 可观测性:至少把关键链路打通三件套:trace、metrics、logs,并且能互相关联,OpenTelemetry 规范把 tracing/metrics/logs 作为核心能力,是个不错的选择
  • 查询语言侧:Prometheus 用 PromQL,Loki 用 LogQL,Tempo 用 TraceQL。这些对 agent 的意义是:可以把"性能预算/错误率"写成可查询条件,让 agent 自己验证修复是否达标
  • UI 验证(适用于有前端/控制台的遗留系统):把关键旅程做成可自动驱动的脚本。Chrome DevTools Protocol 允许工具对 Chrome 做检查、调试与性能分析,截图、DOMSnapshot、导航这类能力可被工具化

验收标准

  • 关键旅程至少 3 条:每条都能自动跑通并产出可核验证据(日志/指标/trace 或截图)
  • PR 级别的 eval 至少覆盖 20 条高风险用例,能产出稳定分数/通过率
  • 发生回归时:能快速回答"是确定性测试没覆盖,还是语义评估没覆盖",并能把回归样例加入数据集形成闭环

所需最小工具

  • 一套 eval 工具(OpenAI Evals / promptfoo / DeepEval 任选其一先跑起来)
  • OTel 基础埋点或自动化采集,接入 Prometheus/Loki/Tempo

常见风险与缓解

  • 风险:eval 太贵,CI 一跑就爆成本/爆时间

    缓解:把 eval 拆成"PR smoke + 夜间全量",PR 上只跑小样本,夜间跑大样本,失败就自动开 issue/PR

  • 风险:可观测性没做关联,数据一堆但定位依旧靠猜

    缓解:先强制一条规则:关键请求必须有 trace id,日志必须带上它,把数据串起来

阶段五:规模化与治理#

目标:当 agent 开始高吞吐产出时,会遇到两个现实:一是坏味道会被复制扩散,二是文档会腐烂。OpenAI 的解法是把知识库结构和质量/品味不变量机械化校验,并跑周期性 agent 做 doc gardening 与一致性扫描,像垃圾回收一样对抗熵增

具体可执行任务

  • 建周期性清理节奏:每周至少一次自动扫描(文档过期、架构违例、命名/结构化日志要求等),自动开 PR
  • 把人类品味固化:当 code review 反复指出某类问题(日志格式、文件过大、模块依赖方向),把它升级为 lint/test
  • 引入 agent review:在 GitHub 上可以用 @codex review 触发 Codex 做代码审查;也能配置自动 review
  • 大团队/多工具面:如果想把 harness 深度嵌进 IDE/内部平台,Codex App Server 被设计成可复用的双向 JSON-RPC API

验收标准

  • 质量债有自动化偿还:每周自动产生一批小 PR,且大部分能自动合并或低成本审查
  • 文档不过期:至少对核心 docs 做结构/交叉链接校验,发现漂移能自动开修复
  • agent 引入后,人工 review 负担可控:P0/P1 类问题能被 agent 先拦一层

所需最小工具

  • 周期性任务 runner(CI cron / 内部调度)
  • PR 自动化(GitHub Actions + bot 账号)
  • Codex GitHub review(可选)

常见风险与缓解

  • 风险:团队拒绝"自动开 PR",怕噪音

    缓解:先从只改文档一致性/只改格式化/只修 lint 开始,确保 PR 极小且可回滚

  • 风险:治理规则变成平台组独裁

    缓解:学 OpenAI 的思路:中央强制边界,局部允许自由

团队角色与阻力处理#

角色建议#

Harness Owner(平台/架构负责人,可兼职)

  • 责任:定义哪些东西必须稳定(边界、门禁、知识库结构),把规则从文档升级到 lint/test/CI

Agent Shepherd(带飞/任务编排,建议轮值)

  • 责任:把需求翻译成可验收标准,驱动 agent 在隔离工作区跑完闭环;一旦 agent 卡住,把它当成"缺工具/缺护栏/缺文档"的信号,补进仓库

Quality & Signals(测试/可观测性负责人)

  • 责任:把质量信号做成可机器读取:快门禁要稳、eval 数据集要能回归、观测链路能定位

CI Gardener / Doc Gardener(轮值,可选)

  • 责任:治理 flaky、清理过期文档、维护规则库

常见阻力与缓解#

文化阻力:觉得"又是新流程/又是平台化束缚"

  • 缓解:把目标说清楚——不是为了管人,而是为了让 agent 的高吞吐不把仓库写崩。中央强制边界、局部允许自由,关注不变量、可复现与可维护
  • 做法:把规则写成能跑的检查;只要机器能判,就尽量别靠人吵

技术债阻力:历史代码熵太大,上规则像炸药

  • 缓解:老代码库第一次加约束容易告警淹没,要先评估是否值得改造,采取渐进策略
  • 做法:只对新增/改动范围强制;旧代码逐步偿还;用黄金路径先拿到收益

测试覆盖不足:eval/agent 进来只会把问题放大

  • 缓解:先补最贵失败的确定性回归(关键链路/权限/计费),再扩面
  • 做法:用 PR smoke 小集 + 夜间大集,把回归样例持续回灌

CI 太慢:任何门禁都变成等待税

  • 缓解:吞吐提升后,等待变得昂贵、修正相对便宜,减少阻塞式门禁、让 PR 短寿命
  • 做法:给快门禁设 SLA;慢检查移到夜间;对 flaky 做隔离与治理