Harness Engineering:为编码 Agent 设计可读、可测的环境
原始来源
Ryan Lopopolo,OpenAI,2026-02-11:Harness engineering: leveraging Codex in an agent-first world。
本页是原创学习导读,不含原文复刻;文章版权与完整内容以原始来源为准。
学完后你应该能做到什么
读完本篇,你应该能够:
- 区分“模型会不会写代码”和“环境是否让 Agent 看得见、做得到、验得出”这两类问题。
- 用输入、可见状态、动作接口、反馈、不变量和停止条件描述一个编码 Harness。
- 解释为什么独立 worktree、浏览器、日志、指标与 trace 不是附属工具,而是 Agent 的感知系统。
- 把
AGENTS.md、仓库文档和代码组织成逐层展开的知识地图,而不是不断加长一个总提示词。 - 把架构边界与工程品味转成可执行检查,并让失败信息同时告诉 Agent 修复方向。
- 为一个真实修复设计从复现、修改、验证、提交到合并后清理的完整闭环。
- 判断 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 一类接口检查页面、点击、输入和观察运行状态。
这使反馈从“测试命令是绿色”扩展到:
- 页面能否真正加载;
- 用户操作后 UI 与 URL 是否变化;
- 控制台是否出现错误;
- 网络请求、后端日志和 trace 是否对应同一次操作;
- 刷新或重启后状态是否仍然正确。
关键不是给 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.mdAgent 先读入口判断去哪找,再按任务逐层加载。这样既避免完全没有上下文,也避免把几千行规则一次性挤进有限注意力。
仓库内资料为什么要成为 system of record
若关键知识只存在于聊天记录、会议或某个外部页面,Agent 在工作时无法可靠发现,也难以判断哪一版最新。把计划、决策和操作说明留在仓库中,可以让代码变更与其知识依赖一起被版本化、搜索、评审和校验。
但“写进仓库”仍不自动等于真实。文档必须有所有者、适用范围或验证路径;代码变了却不更新说明,Agent 会把过期文本当作权威。因此 Harness 也应检查断链、过期命令和代码—文档契约。
第三层:提高 Agent 可读性,而不只是人类可读性
Agent 可读性是指模型能否从局部文件快速构造正确的系统模型。它通常来自:
- 目录与业务领域相对应,而不是所有逻辑塞进通用
utils; - 依赖方向稳定,跨层调用有明确入口;
- 名称表达业务概念,状态转移可以追踪;
- 关键配置、生成步骤和副作用可发现;
- 常见任务有可以直接运行的短命令;
- 错误输出说明发生了什么、在哪发生以及下一步查什么。
这并不要求为 Agent 重写所有代码,也不意味着越多注释越好。真正目标是减少只有长期维护者才知道的隐式约定。稳定抽象和清晰领域边界既帮助人,也帮助 Agent。
地图与约束的分工
文档回答“系统怎样组织、为什么这样做、资料在哪里”;可执行检查回答“这条边界现在是否仍成立”。只写文档容易漂移,只写检查又会让 Agent 不知道设计意图和正确修复方向。
第四层:把架构与工程品味变成可执行反馈
强制不变量,不要规定每个实现细节
文章强调通过自定义 lint、结构测试和 CI 执行架构规则。例如:“领域层不能依赖 UI 层”适合成为机器检查;“这个函数必须用三层工厂模式”往往只是过度指定。
好的约束具有三项特征:
- 稳定:代表长期边界,而不是一次性的个人偏好。
- 可判断:工具能从依赖、AST、配置或运行结果判断通过与否。
- 可修复:失败信息给出原因、受影响位置和合规路径。
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。先通过评审和例子校准含义;等模式稳定、误报可控,再转成检查。否则所谓自动品味会阻塞正常设计。
第五层:让反馈循环能够自己闭合
一个可自治的编码循环不是“生成代码—运行全部测试”两步,而是:
- 读取任务合同和仓库地图,形成最小计划。
- 在隔离环境中复现当前失败,保存基线证据。
- 搜索执行路径,做最小修改。
- 运行最相关的快速检查,从具体失败中修正。
- 启动真实应用,观察用户路径、日志和 trace。
- 运行回归、架构、类型、格式和安全边界检查。
- 审阅最终 diff,确认没有意外文件、调试代码或越权改动。
- 生成带证据的提交或 PR;机器和人按风险继续验收。
- 合并后观察线上状态,把重复问题转成文档、测试或清理任务。
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 会从仓库现有模式中学习。如果仓库里有两个临时绕过,它可能生成第三个;如果文档过期,它会把过期做法扩散得更快。产出越快,重复、废弃抽象、依赖漂移和命名分叉也可能积累得越快。
文章提出的应对不是偶尔进行一次“大重构”,而是持续的小型垃圾回收:
- 从评审、失败和代码搜索中收集重复坏模式。
- 判断它是个别缺陷还是系统性缺口。
- 把稳定结论写成黄金原则、文档、共享抽象或机械检查。
- 用小批次删除废弃路径、合并重复实现、修复文档。
- 观察规则的误报和实际效果,必要时调整或撤回。
这相当于维护 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 把这些接口、控制流和上下文原则扩展到整个代码库与软件生命周期。
自测:确认你真的掌握了机制
- 一个模型能给出正确补丁,但看不到页面和日志。这个失败属于模型能力还是 Harness?还缺哪些合同?
- 为什么把全部知识塞进 2,000 行
AGENTS.md不等于“上下文更充分”?更好的组织是什么? - “领域层不能依赖 UI 层”与“必须使用工厂模式”哪一条更适合机械检查?为什么?
- 如果 Agent 的目标测试、lint 都通过,但保存后刷新页面数据消失,它能否宣布完成?停止合同缺了什么?
- 什么时候可以把 Agent 从“创建 PR”提升到“自动合并低风险变更”?至少需要哪些条件?
- 为什么高吞吐团队反而需要固定的垃圾回收节奏?
参考答案与判断要点
- 模型可能已有生成能力,失败主要在 Harness 的可见状态、动作接口和反馈合同。应补浏览器操作、可关联日志/trace、可复现运行环境和真实验收。
- 巨型入口会挤占注意力、混合不同作用域且容易过期。短入口应只放稳定边界和地图,再链接到按架构、业务、任务与目录逐层展开的资料。
- 依赖方向是稳定、可从 import 图判断的不变量,更适合检查;工厂模式通常是实现选择,除非它承载了已证明的长期边界,否则不应机械强制。
- 不能。测试没有覆盖持久化后的用户状态;停止合同应加入真实浏览器路径、刷新/重启后的状态以及必要的后端证据。
- 至少要限定为预批准的低风险类别,并具备稳定 CI、真实验收、明确权限、监控、审计和快速回滚;安全、数据迁移或不可逆操作仍需人工门。
- Agent 会快速复制仓库里的既有模式,产出增加也加速重复、漂移和过期资料。小而持续的清理能把重复纠错升级为共享规则,维持环境可读性。
上一篇:Effective Context Engineering · 返回 里程碑原典目录