Harness Engineering:为编码 Agent 设计可读、可测的环境

原始来源

Ryan Lopopolo,OpenAI,2026-02-11:Harness engineering: leveraging Codex in an agent-first world。

本页是原创学习导读,不含原文复刻;文章版权与完整内容以原始来源为准。

学完后你应该能做到什么

读完本篇,你应该能够:

  1. 区分“模型会不会写代码”和“环境是否让 Agent 看得见、做得到、验得出”这两类问题。
  2. 用输入、可见状态、动作接口、反馈、不变量和停止条件描述一个编码 Harness。
  3. 解释为什么独立 worktree、浏览器、日志、指标与 trace 不是附属工具,而是 Agent 的感知系统。
  4. 把 AGENTS.md、仓库文档和代码组织成逐层展开的知识地图,而不是不断加长一个总提示词。
  5. 把架构边界与工程品味转成可执行检查,并让失败信息同时告诉 Agent 修复方向。
  6. 为一个真实修复设计从复现、修改、验证、提交到合并后清理的完整闭环。
  7. 判断 Agent 可以自治到哪一级,以及哪些风险必须由人授权或验收。

先把问题说清楚:模型能力不等于工程完成能力

一个模型可能知道怎样修复空指针,却仍然无法完成仓库任务,因为它找不到启动命令、看不到真实页面、不同任务共用同一数据库、日志混在一起、架构规则只存在于某位工程师脑中,或者测试通过后仍不知道产品行为是否正确。

这些失败不能简单归因于“模型不够聪明”。模型负责推理和生成,环境则决定它能获得什么证据、能执行哪些动作以及怎样知道任务真的结束。两者共同决定实际能力:

这不是可计算的论文公式,而是一个工程提醒:任何一项接近零,整体结果都会很差。Harness Engineering 关注的正是第二项——把仓库、工具、知识、约束和反馈设计成 Agent 可以自主使用的工作环境。

一句话抓住它

当 Agent 承担大量实现时,工程师的稀缺贡献从逐行输入代码,转向把目标、知识、运行环境、工具、架构边界、验收证据和持续清理机制做成 Agent 可理解且机器可验证的闭环。

flowchart TD
    I["人类给出目标、边界与风险授权"] --> P["仓库知识地图与任务计划"]
    P --> A["Agent 在隔离 worktree 中观察与修改"]
    T["终端、浏览器、日志、指标、trace"] --> A
    A --> V["测试、lint、架构检查与真实应用验收"]
    V -->|"失败证据 + 修复方向"| A
    V -->|"满足停止合同"| M["提交、评审与合并"]
    M --> O["线上观察与坏模式采集"]
    O --> C["垃圾回收:文档、规则、结构与依赖清理"]
    C --> P

Harness 到底是什么:先写清六项合同

Harness 不是某一个提示词或 CLI。它是 Agent 从接收任务到证明完成所依赖的整个行动环境。可以先用六项合同审计它:

合同要回答的问题例子
输入Agent 到底要实现什么,什么不能改Issue、验收条件、风险边界、相关链接
可见状态Agent 能观察到哪些真实状态文件、git diff、页面、数据库、日志、指标、trace
动作接口Agent 能安全执行什么搜索、编辑、运行测试、启动应用、浏览器操作、创建 PR
反馈每次动作后怎样知道更接近还是更远退出码、失败断言、截图、错误日志、结构检查
不变量哪些边界不能被实现绕过依赖方向、权限校验、数据隔离、API 兼容性
停止条件什么证据足以宣布完成,何时必须停目标测试通过、页面行为正确、无回归;预算耗尽则升级人工

如果只写“请修好这个 Issue”,其余五项依靠隐含经验,Agent 就只能猜。好的 Harness 把这些经验外化,但不会事无巨细规定每一行实现。

第一层:让应用本身对 Agent 可读

每个任务拥有隔离的运行现场

文章描述的环境会为不同任务使用独立 worktree,并让应用实例、日志和可观测数据与该 worktree 对应。这样 Agent A 修改依赖或数据库状态时,不会让 Agent B 看到一个来历不明的错误。

隔离至少应回答:

  • 当前任务对应哪个 commit 和 worktree?
  • 使用哪个端口、数据库或临时存储?
  • 日志、指标和 trace 怎样按任务关联?
  • 进程怎样启动、重置和关闭?
  • 任务结束后哪些状态需要保留,哪些可以清理?

“并行启动了多个 Agent”不等于真正隔离。如果它们共享端口、缓存或测试数据库,反馈就会被污染,Agent 可能针对另一个任务造成的症状修改代码。

浏览器不是截图器,而是可操作的产品表面

对 Web 产品,仅运行单元测试无法证明交互正确。文章中的 Agent 能启动应用,并借助浏览器与 Chrome DevTools Protocol 一类接口检查页面、点击、输入和观察运行状态。

这使反馈从“测试命令是绿色”扩展到:

  1. 页面能否真正加载;
  2. 用户操作后 UI 与 URL 是否变化;
  3. 控制台是否出现错误;
  4. 网络请求、后端日志和 trace 是否对应同一次操作;
  5. 刷新或重启后状态是否仍然正确。

关键不是给 Agent 更多按钮,而是让它能把用户可见症状与代码、请求和后端状态连接起来。

日志、指标和 trace 是 Agent 的感官

人类遇到“保存很慢”会同时看浏览器、服务器日志、数据库指标和调用链。若 Agent 只能看到一句报错摘要,它就缺少同样的诊断能力。

可观测性需要可查询、可关联、可限定范围:任务 ID、worktree、请求 ID 与时间窗口应能把同一次运行串起来;输出应控制噪声并避免泄漏凭据。更多日志不一定更可读,结构化字段与稳定查询入口比一大段混合终端文本更重要。

第二层:把仓库变成知识的事实层

AGENTS.md 应是地图,不是百科全书

文章的做法是保留一份约百行量级的短入口文档,把它当作目录,指向架构、产品、计划、质量和操作资料。核心思想不是必须恰好一百行,而是入口稳定且短,细节按需展开。

一个实用层次可以是:

AGENTS.md
├─ 稳定的全局边界与最常用命令
├─ 架构地图 → docs/architecture/
├─ 业务领域 → docs/product/
├─ 任务计划与决策 → docs/plans/、docs/decisions/
├─ 测试和发布 → docs/quality/、docs/runbooks/
└─ 子目录局部说明 → module/AGENTS.md

Agent 先读入口判断去哪找,再按任务逐层加载。这样既避免完全没有上下文,也避免把几千行规则一次性挤进有限注意力。

仓库内资料为什么要成为 system of record

若关键知识只存在于聊天记录、会议或某个外部页面,Agent 在工作时无法可靠发现,也难以判断哪一版最新。把计划、决策和操作说明留在仓库中,可以让代码变更与其知识依赖一起被版本化、搜索、评审和校验。

但“写进仓库”仍不自动等于真实。文档必须有所有者、适用范围或验证路径;代码变了却不更新说明,Agent 会把过期文本当作权威。因此 Harness 也应检查断链、过期命令和代码—文档契约。

第三层:提高 Agent 可读性,而不只是人类可读性

Agent 可读性是指模型能否从局部文件快速构造正确的系统模型。它通常来自:

  • 目录与业务领域相对应,而不是所有逻辑塞进通用 utils;
  • 依赖方向稳定,跨层调用有明确入口;
  • 名称表达业务概念,状态转移可以追踪;
  • 关键配置、生成步骤和副作用可发现;
  • 常见任务有可以直接运行的短命令;
  • 错误输出说明发生了什么、在哪发生以及下一步查什么。

这并不要求为 Agent 重写所有代码,也不意味着越多注释越好。真正目标是减少只有长期维护者才知道的隐式约定。稳定抽象和清晰领域边界既帮助人,也帮助 Agent。

地图与约束的分工

文档回答“系统怎样组织、为什么这样做、资料在哪里”;可执行检查回答“这条边界现在是否仍成立”。只写文档容易漂移,只写检查又会让 Agent 不知道设计意图和正确修复方向。

第四层:把架构与工程品味变成可执行反馈

强制不变量,不要规定每个实现细节

文章强调通过自定义 lint、结构测试和 CI 执行架构规则。例如:“领域层不能依赖 UI 层”适合成为机器检查;“这个函数必须用三层工厂模式”往往只是过度指定。

好的约束具有三项特征:

  1. 稳定:代表长期边界,而不是一次性的个人偏好。
  2. 可判断:工具能从依赖、AST、配置或运行结果判断通过与否。
  3. 可修复:失败信息给出原因、受影响位置和合规路径。
BAD: architecture check failed
 
BETTER: packages/domain/order.py imports packages/ui/table.py.
Domain code may depend only on domain and platform contracts.
Move the display conversion into packages/ui/adapters/; see docs/architecture/dependencies.md.

第二种信息既是质量门,也是 Agent 下一轮的训练信号。它约束边界,却把具体实现选择留给任务上下文。

“品味”怎样逐步机械化

团队发现重复坏模式时,可以先写成一条“黄金原则”,再决定是否值得自动检查。例如:

  • 服务层不得绕过授权入口;
  • 测试不得依赖公网;
  • 新增状态必须有明确所有者和清理路径;
  • 用户可见错误必须保留可诊断原因。

不是每条原则都能立刻变成 lint。先通过评审和例子校准含义;等模式稳定、误报可控,再转成检查。否则所谓自动品味会阻塞正常设计。

第五层:让反馈循环能够自己闭合

一个可自治的编码循环不是“生成代码—运行全部测试”两步,而是:

  1. 读取任务合同和仓库地图,形成最小计划。
  2. 在隔离环境中复现当前失败,保存基线证据。
  3. 搜索执行路径,做最小修改。
  4. 运行最相关的快速检查,从具体失败中修正。
  5. 启动真实应用,观察用户路径、日志和 trace。
  6. 运行回归、架构、类型、格式和安全边界检查。
  7. 审阅最终 diff,确认没有意外文件、调试代码或越权改动。
  8. 生成带证据的提交或 PR;机器和人按风险继续验收。
  9. 合并后观察线上状态,把重复问题转成文档、测试或清理任务。
def run_task(task, harness, max_attempts=4):
    workspace = harness.create_isolated_workspace(task)
    contract = harness.load_task_and_repository_map(task)
    baseline = harness.reproduce(workspace, contract)
 
    for _ in range(max_attempts):
        change = harness.agent.edit(workspace, contract, baseline)
        feedback = harness.run_targeted_checks(workspace, change)
 
        if feedback.failed:
            baseline = feedback
            continue
 
        acceptance = harness.run_full_acceptance(workspace, contract)
        if acceptance.proves_done:
            return harness.prepare_review(workspace, acceptance)
 
        baseline = acceptance
 
    return harness.escalate_with_evidence(workspace, baseline)

这里最重要的是 proves_done:停止条件必须来自任务合同与外部状态,不能由 Agent 自己说“应该已经好了”。达到预算后也必须有明确出口,而不是无限重试。

走完一次:从“保存按钮没反应”到可合并修复

下面是教学用的完整例子,不是文章中某个真实内部 Issue。

1. 人给出目标与边界

Issue 写明:编辑个人资料后点击“保存”无反馈;成功时应显示提示并刷新姓名;不能改变权限逻辑;需要覆盖失败与成功路径。负责人允许 Agent 修改前后端与测试,但数据库迁移必须人工批准。

2. Harness 创建可复现现场

  • 从指定 commit 创建独立 worktree。
  • 分配独立端口和临时数据库。
  • 启动前端、API 与可观测服务。
  • 将浏览器会话、请求 ID、日志和 trace 绑定到该任务。

Agent 通过浏览器重现问题,保存失败截图/视频,并发现请求返回 403。trace 显示 API 的授权中间件读取了旧字段名;这比“按钮坏了”的描述更接近根因。

3. Agent 沿知识地图定位契约

AGENTS.md 指向账户领域和权限边界文档;结构图说明 UI 不能自己拼授权字段。Agent 找到最近的字段重命名与兼容层,确认修复应落在 API 适配入口,而不是绕过权限。

4. 修改并获得逐层反馈

Agent 先增加一个会失败的 API 回归测试,再修复兼容映射。目标测试通过后,它用浏览器再次保存:页面出现成功提示,姓名刷新;控制台无错误,trace 为 200。随后运行权限失败用例、账户模块测试、类型检查和架构检查。

若 Agent 试图在 UI 中硬编码管理员 token,架构/安全检查应给出明确失败,并指向授权适配层。Harness 不需要预测正确代码,但必须阻止这条错误捷径。

5. 形成可审查交付

PR 包含:问题基线、根因、最小 diff、目标与回归测试结果、成功页面证据、未改数据库的说明。人类重点判断权限风险和产品语义,而不必重新探索整个现场。

6. 合并后做垃圾回收

如果字段重命名已在三个模块造成同类故障,团队不应只合并第三个补丁。应收敛为共享适配器、删除旧字段、增加结构检查,并更新迁移文档。下一批 Agent 才不会把错误先例继续复制。

吞吐提高以后,合并哲学会怎样变化

文章描述,在 Agent 产出速度提高后,团队倾向于更小、更快的变更,修复或后续迭代的成本也下降。这可以缩短分支寿命并减少大型 PR 的冲突。

但它不是“高吞吐,所以可以取消评审”的普遍规则。合并策略仍取决于:回滚是否容易、测试和监控是否可信、变更是否涉及数据迁移、安全、计费或不可逆外部动作。低风险 UI 文案与权限系统迁移不应使用同一自治级别。

“Agent 生成”究竟意味着什么

文章所说的 Agent 生成,不只包括产品代码,也包括测试、CI、文档、可观测性和内部工具。人类仍然:

  • 定义产品目标和接受什么取舍;
  • 设计让 Agent 能工作的环境;
  • 编码架构与安全边界;
  • 处理歧义、风险授权和最终责任;
  • 观察系统何时开始积累错误模式。

所以“没有人工逐行编写产品代码”不等于“没有人类工程”,更不等于责任消失。人的工作从主要生产代码转向设计系统、评价结果和维护长期一致性。

自治不是开关,而是一条梯子

等级Agent 可做什么需要的最低 Harness人类职责
0:建议阅读并提出方案可搜索代码和文档决定是否实施
1:修改在受限分支编辑并生成 diff隔离工作区、精确权限、diff 审计审阅每次修改
2:验证自行启动、测试和迭代可复现环境、结构化反馈、预算边界验收高风险判断
3:提交创建带证据的 PR稳定 CI、所有权和回滚路径风险分级抽查/批准
4:合并/发布对预批准的低风险类别完成合并部署强制策略、监控、快速回滚、审计定义授权范围并处理异常

升级自治前,应先证明环境能观察成功与失败,而不是只证明模型偶尔能写对代码。权限范围、外部副作用和成本预算也必须随等级显式收紧。

为什么高产出会制造熵,以及怎样持续垃圾回收

Agent 会从仓库现有模式中学习。如果仓库里有两个临时绕过,它可能生成第三个;如果文档过期,它会把过期做法扩散得更快。产出越快,重复、废弃抽象、依赖漂移和命名分叉也可能积累得越快。

文章提出的应对不是偶尔进行一次“大重构”,而是持续的小型垃圾回收:

  1. 从评审、失败和代码搜索中收集重复坏模式。
  2. 判断它是个别缺陷还是系统性缺口。
  3. 把稳定结论写成黄金原则、文档、共享抽象或机械检查。
  4. 用小批次删除废弃路径、合并重复实现、修复文档。
  5. 观察规则的误报和实际效果,必要时调整或撤回。

这相当于维护 Harness 本身。否则 Agent 只会在越来越混乱的环境里更快地工作。

文章证据应该怎样看

文章报告了一个内部案例:代码库从空仓库起步,约五个月后达到约百万行规模、约 1,500 个 Pull Request;文章给出的平均吞吐约为每位工程师每天 3.5 个 PR,并称实际完成时间约为传统估算的十分之一。团队还称产品已有数百名内部用户,且应用代码没有由人手工逐行编写。

这些数字证明的是该团队在其模型、工具、产品和统计口径下观察到了高吞吐,并提供了可研究的工程做法。它们不是控制实验:没有随机对照组,代码行与 PR 数也不直接等于产品价值、正确性或长期维护成本。

证据边界

  • “约十分之一时间”是文章中的团队估算,不是跨组织可复用的固定倍数。
  • “零人工手写应用代码”描述生产方式,不表示没有人类提示、评审、架构判断和工具建设。
  • 内部用户与五个月观察期能说明产品被使用,但不足以证明多年尺度的代码一致性和维护成本。
  • 文章是 2026 年特定团队案例;模型、Codex 功能和组织流程会继续变化,复用时应抓住机制而非照抄数字。

常见失败模式

失败模式表面现象根因Harness 改进
巨型说明文件规则很多,Agent 仍漏读无导航层次、注意力被稀释短入口 + 分层文档 + 局部规则
假隔离并行任务随机互相影响共用端口、数据库、缓存或日志worktree 与运行资源一一绑定
只看测试绿灯页面仍坏或状态未持久化验收没有覆盖真实产品路径浏览器、重启与端到端状态检查
只写架构文档越界依赖不断出现规则不可执行结构测试、lint、CI 与修复提示
过度约束小改动频繁被无意义规则阻塞把个人实现偏好当不变量只机械化稳定边界,监控误报
日志很多但不可用Agent 搜索大量文本仍找不到请求缺少关联字段和范围任务/请求 ID、结构化查询、降噪
自治过早Agent 错误合并高风险改动停止条件、回滚和权限不成熟按风险逐级授权,保留人工门
熵增长重复工具、过期文档和临时绕过扩散只追求新功能吞吐周期性垃圾回收与黄金原则

历史贡献与今天的工程解释

这篇文章的贡献不是提出一种新模型算法,而是把“Agent-first 软件工程”具体化为一组仓库与环境设计实践:应用可读、知识在仓库内分层、架构可执行、反馈可闭合、自治有边界、熵要持续清理。

今天可直接采用的核心机制是:先找出 Agent 无法独立观察和验证的状态,再补齐最小工具与合同;把重复的人类纠错变成仓库资料或机器约束;只有当真实结束状态可证明、失败可回滚时,才提高自治等级。

仍不能直接推出:所有团队都应禁止人工写代码;更多 PR 或代码行天然代表更高价值;一个短 AGENTS.md 可以替代架构设计;或者有了测试与 lint 就不再需要人类负责。

与相邻原典的关系

SWE-agent 说明即时的 Agent–Computer Interface 会显著改变仓库任务表现;Building Effective Agents 区分可预测工作流与自主 Agent;Effective Context Engineering 解释怎样管理有限注意力。Harness Engineering 把这些接口、控制流和上下文原则扩展到整个代码库与软件生命周期。

自测:确认你真的掌握了机制

  1. 一个模型能给出正确补丁,但看不到页面和日志。这个失败属于模型能力还是 Harness?还缺哪些合同?
  2. 为什么把全部知识塞进 2,000 行 AGENTS.md 不等于“上下文更充分”?更好的组织是什么?
  3. “领域层不能依赖 UI 层”与“必须使用工厂模式”哪一条更适合机械检查?为什么?
  4. 如果 Agent 的目标测试、lint 都通过,但保存后刷新页面数据消失,它能否宣布完成?停止合同缺了什么?
  5. 什么时候可以把 Agent 从“创建 PR”提升到“自动合并低风险变更”?至少需要哪些条件?
  6. 为什么高吞吐团队反而需要固定的垃圾回收节奏?

上一篇:Effective Context Engineering · 返回 里程碑原典目录