Harness 工程十二条心法:Google 首席工程师一年实践,浓缩成可直接照做的流程和模板

核心操作反直觉:选定的模型和 Agent 锁死不动,一个参数都不调,只改它周围的上下文和工具。
作者:lopopolo/harness-engineering07-19 · 20 分钟⚑ 方法教程
🎧 听播客版0:00 / --:--
0:00 / --:--
深度 · 小互解读
一分钟速览
  • Ryan Lopopolo 现在是 Google Cloud 的首席工程师,此前在 OpenAI。他把过去一年的工作提炼成十二条心法,做成一个公开仓库,可以直接喂给自己的 Agent 当参考资料;十二条里的实证多来自 OpenAI 那段经历。
  • 核心操作反直觉:选定的模型和编码 Agent 当成不可改的黑盒锁死,一个参数都不调,只改它周围的两样东西,上下文和工具。
  • 十二条分三层。三条划边界:锁死 worker、把私有流程数据送进去、按实测结果衡量。六条是杠杆:整件活交一个 Agent、上下文按需路由、能力要用得动、让仓库自己教、权限写明白、在真环境举证。三条管复利:反馈变基础设施、守住长期连贯性、把已定的活跑成常驻循环。
  • 撑起这套说法的实证:一个内部产品五个月从空仓库长到约一百万行代码,约 1500 个合并 PR,全部由 Codex 写成,人没有手写一行。
  • 能直接带走:一套六步改进流程、三份填空模板、一张「你声称什么就得在哪取证」的十一行对照表、一份「什么情况下你的对照实验不作数」的九条清单。
这是什么

十二条心法,和一个被译反的词

先把词摆正。harness 在这里是马具的意思,鞍、缰、蹄铁、挽带那一套,仓库简介开头就挂着一个马的表情符号。它很容易被读成「束缚」,方向正好反了。

这个词的意思

马已经足够能跑。缺的是鞍、缰和跑道,让它的力气真的落到地上,去到该去的方向。harness engineering 做的就是配这一套东西。

写这套东西的是 Ryan Lopopolo。他现在是 Google Cloud 的首席工程师,做 agentic GCP 方向;在这之前他在 OpenAI,参与建设西雅图办公室和面向企业的 OpenAI Frontier,工作内容正是给 Agent 配上下文、工具、权限、评估、可观测性、编排和反馈回路。十二条里那些实证,多数来自这段经历。更早他在 Snowflake 做数据市场的技术负责人,在 Brex 带一个 40 人的团队支撑 350 人的工程组织,在 Stripe 做基础设施和支付的技术负责人,经历了公司从 600 人长到 6000 人。业余时间他用 Rust 写了一个 Ruby 实现,叫 Artichoke。

他把这十二条发出来的时候说,这代表了他过去一年的工作,是实践,也是技术。

他把自己的实践提炼成十二条心法,连同证据、案例和可执行的流程一起放进一个公开仓库,仓库自撰的内容采用 CC BY 4.0 授权。用法有两种:人直接读,或者把它和你要改进的系统一起交给编码 Agent,让 Agent 按里面的分流索引自己去查该看哪一条。

整套方法的操作定义只有一句:把选定的模型和编码 Agent 当成一个不可改的黑盒固定住,只改它周围的两根外部杠杆,上下文和工具。
🐎

为什么值得看:十二条里每一条都挂着可查证的实证。一个内部产品五个月从空仓库长到约一百万行代码、约 1500 个合并 PR,代码全部由 Codex 写成,人没有手写一行;一次 60 小时、三亿多 token 的大改造只用了初始提示词加两次追问;打开一条代码检查规则(lint,自动揪出不合规范写法的工具)之后,约 600 处违规在同一个 PR 里修完并补齐了测试。

下面是十二条的全貌。分成三层是为了看懂它们的关系:最上面三条划定边界,中间六条是实际的杠杆,最下面三条决定这套东西能不能随时间累积。接下来一条一条讲。

LAYER 1 · 划定边界 把干活的人固定住 Hold the Worker Constant 模型换了就得重新验收 部署进私有数据 Private Process-Data Iceberg 权重里只有冰山尖 按实测结果衡量 Measured Effectiveness token 是输入,不是产出 LAYER 2 · 六根杠杆 一个 Agent 领整件活 Give One Agent the Whole Job 委托结果,不委托步骤 上下文按需路由 Route Context Just in Time 给地图,不给说明书 能力要用得动 Capabilities Legible, Operable 六步闭环缺一环即摆设 仓库自己教 Agent Repository Teaches the Agent 代码就是提示词 权限要写明 Autonomy Inside Authority 能做和能批分开 在真环境里验证 Prove in Real Environment 绿勾只证明它自己 LAYER 3 · 让它复利 反馈变基础设施 Feedback Into Infrastructure 纠正一次就固化下来 守住连贯性 Preserve Coherence 为下个五千次改动 跑成常驻循环 Continuous Loop 已定的活不必排期
十二条心法全景。分层和排序为本文解读所作,依据是各条之间的引用关系;仓库自身的索引顺序略有不同。粗框的三条在后面各有一节展开。
第一层 · 划定边界

第一条:把干活的那个「人」固定住

一次评估或部署周期里,模型、编码 Agent 的运行时、原生工具接口三样锁死不动。这三样里任何一样变了,就算换了一代,整套环境要重新验收一遍。

锁住黑盒的收益是失败变得可归因。一个明明很能干的 Agent 干不成一件活,原因通常落在五处:缺相关的机构上下文、缺一个真能用的能力、缺有用的反馈回路、缺权限,或者出口处缺证据。这五样都是部署环境的属性,都能查、都能改。不锁黑盒,你就永远卡在「到底是模型不行还是我没配好」之间打转。

最反直觉的一条推论

你自己攒下的那套经验,也属于要重新验收的东西。旧模型教会你的那些习惯:任务该切多小、需要多少编排、内循环能等多久、哪些地方必须人盯着,原封不动带到新一代上,新模型多出来的本事就白给了。反过来,假设每个小版本都在每个维度变强,是另一个方向的错误。

他自己记下的一次观察:GPT-5.3 比 GPT-5.2 更不愿意去啃困难的活。这是一条单独的使用观察,不构成趋势。

换代能带来多大差别,有两个数字。GPT-5.2 发布的时候他正在休假,回来发现团队在没有任何额外环境投入的情况下,每个工程师每天多产出一到两个 PR。后来一次单独的 Symphony 环境改造,报告的是每个工程师每周 PR 数量十倍的增长。前一个来自模型换代,后一个来自环境改造,两者要分开记账,这正是锁住黑盒的意义。

换代带来的

GPT-5.2 发布期间,环境一行没改。

每人每天多出一到两个 PR。

环境改造带来的

Symphony 那次干预,worker 没换。

报告每人每周 PR 数量十倍增长。

内循环的延迟也属于验收范围。Agent 能推理到什么程度,取决于它在这一趟(一条轨迹,就是 Agent 干一件活从头到尾的整个过程)跑散之前能拿到多少反馈。GPT-5.2 时代的 Codex 环境没有后台 shell,Agent 会老老实实等阻塞的构建脚本跑完;GPT-5.3 加了后台 shell 之后它就不愿意等了。团队用一周时间把仓库的构建从自己写的脚本一路换到专业构建工具(先后试了 Bazel、Turbo、Nx),停手的条件是全量构建进一分钟以内。这一分钟被定成硬上限,一旦破线就停下手上的活去拆构建图。

验收还包含减法。为了让 Agent 看见界面,团队曾经自建过一套虚拟显示加录屏的东西;模型自己能看屏幕、点鼠标的能力(computer use)出来之后,这一整套被删掉了,端到端的验证并没有因此丢失。

第一层 · 划定边界

第二条:部署进私有流程数据的水下部分

通用模型的权重里装着的,只是一个组织知识的尖。水线以下才是干活真正要用的那些东西,而且它们是私有的、一直在变的。

通用模型权重里有的 公开知识、通用做法、语言和框架 水线 干这份活真正要用的 当前的记录和状态 本地术语:哪个「收入」口径 质量标准:做到什么程度算完 流程惯例与例外史 谁能批谁 哪个客户在试点 私有 还在变
组织不能假设这些私有的、还在变的流程数据会自己长进模型权重里,也不能假设 Agent 能自己猜中哪部分重要。把它们做成可检索的上下文和有边界的工具,就是最后一公里的活。

这一条要求把两套东西说清楚。一套叫数据本体:有哪些实体、指标、标识符,它们之间什么关系。另一套叫工作本体:有哪些结果、工作流、角色、工具、例外、证据和授权关系。

有个例子很能说明问题。一个内部的数据 Agent,光给它数据仓库的权限是不够的,它解决不了「收入」和「活跃用户」这两个词在公司内部互相打架的定义。它还需要知道公司有哪些产品线、客户怎么分层、有哪些团队、哪些客户在试点、当前的计划是什么、共享的指标定义是怎么写的。有了这些关系,它才能给出一个属于这家公司的答案。

每种真相留在它自己的主人那里

当前的记录留在数据仓库、工单系统、文档系统、日志、代码库、客户系统里,用搜索和连接器取一片授权过的切片,不要造第二份会过期的副本。

组织形态上有一个可以直接抄的做法。Basis 这家公司有两个仓库:Arnold 是生产用的单体仓库,Atlas 是第二个仓库,专门放公司的上下文,在生产代码树之外。Codex 可以把个人笔记、公司运营知识和生产代码组合起来用,而这两批信息各自保留独立的更新路径。实现形态可以很朴素,他的原话是「就是 Codex 加一个装满 Markdown 的 Git 仓库」。

还有一件事属于这一层:稳定下来的决策要提拔到它管得着的那一层去。某个团队和安全部门一起选定了一个批准使用的加密实现,这个决定留在了 Slack 里。后来一位新工程师用 Codex 干活,Codex 装了另一个 npm 加密包。团队把这个决定捞回来,在仓库里写成守卫规则,然后重新跑了一遍那次改动。

第二层 · 杠杆

第三条:一个 Agent 领整件活

委托的是结果,交给一条主轨迹全权负责:拆解、执行、整合、举证、安全收尾。步骤留给它自己定。人只在三个地方出手,方向、判断,以及有后果的授权。

他经常给接近零约束的提示词,把这种提示词叫做「懒 prompt」,懒在方法留白,怎么做由 Agent 自己定。稀疏的委托同时是一种测试手段:它测的是你的环境有没有让真实要求变得能被 Agent 自己找回来。Agent 一旦会错意,暴露出来的正是环境里哪一块没做出来。配套的循环是「提出、观察、改进、重跑」:看完轨迹,改环境,把这次的结果丢掉,重新跑一遍。

一个懒 prompt 的完整结果

一句请求要求做完整的红队分析,并且要求每一个发现都必须证明影响或者可利用性。Codex 产出了报告、复现器、补丁和回归测试,随后应对了详细的人工代码审查并修改了实现。作者审查后批准实现,再单独授权它准备一个小版本更新、合并 PR、向 RustSec(Rust 生态的安全公告库)提交公告。

提示词只规定了结果和验收线,实质性审查和交付处的授权仍然留在流程里。

两个反例,比正例更有用

保护措施挡住了它要保护的东西
  • 有一条技能规定:本地构建通过之后才能开 PR。
  • 某个任务恰恰需要先开 PR,好让同事进来一起修那个坏掉的构建。
  • 技能拒绝执行。平时的安全阀,变成了它本要服务的那个结果的障碍。
字面满足最后那个动词
  • 指令是:建分支、提交、推送、合并。
  • Agent 为了满足「合并」,动用管理员权限强行绕过。
  • 而这件活的本意,包含等测试跑完。

一句请求通常只说功能结果,兼容性、可维护性、无障碍、回滚、生成物同步、以及未来改动的需要,全是隐含的。这些隐含要求有个名字,叫「问题背后的问题」。Agent 要自己把它们找回来:去仓库里翻、看当前行为、查历史记录、弄清这个结果和用户的关系。刨不出来,而且缺的这个答案会改变产品意图、会让人承担没预料到的风险、或者需要它没有的权限,这时候才问人。

关于上下文压缩的一条操作心得

长轨迹会反复压缩自己的历史。他给出的工作假设是:用户消息里的意图留存度高,较早的工具输出更容易被摘要掉或者丢弃。这是特定 worker 的行为,换模型之后要重新验证。

按这个假设,阶段性的细则塞进用户消息,会在那段活早就干完之后继续抢注意力。所以用户消息那一栏只放三样:结果、验收线、授权边界。细则交给仓库读取、工具输出、测试失败,在决策发生的那一刻才浮出来。

第二层 · 杠杆

第四条:一千页手册,换成一张索引

知识库要大而可搜,工作集要小。根部的那份指引应该是一张地图。

这一条有个来历:他们最早那份单体的 Agent 手册,长到没法用了。换成一个简短的根目录索引,背后挂着编好索引的设计文档、产品规格、执行计划和参考资料,把渐进式披露做成仓库自己维护的一个特性,再用链接检查和新鲜度检查保证这些路由可信。

上下文窗口变大,解决的只是容量压力。注意力、检索质量、指令之间互相打架、以及「这一步到底哪条事实要紧」,这几样仍然稀缺。确定性地往上下文里塞东西,在跨越多次压缩之后必然失效。Agent 必须有能力再次找回那条路,指望第一次塞进去的东西还在,是靠不住的。

送一张极简的地图。任务属于哪一类、相关的架构和已有决策、关键的用户旅程、以及能确立当前状态的那几条命令。

目的是让 Agent 知道自己在改什么、该去哪里看,同时保留通往更深来源的路径。这一段不该膨胀成清单。

让代码、工具输出、测试失败、浏览器状态、日志自己揭示下一片上下文。报错信息应该说清违反了哪条不变量,以及大概怎么修。

这一段是探索性的。写死一份步骤清单没有用,因为没人能预测每一个分叉。

等成品的形状稳定下来,再把角色化的审查者、验收标准、举证要求和交付政策搬回来。

结果和关键旅程从打底阶段就一直可见,落地阶段带回来的是它们详细的审查视角。最后把整条轨迹压缩成一个人能直接读的决策包。

skill:教一种做法
  • 描述里只写「这是什么、什么时候可能用得上」,正文按需加载。
  • 写成长篇命令式的清单,容易过拟合单一路径,让 Agent 拒绝合理的例外。
  • 可以把大的技能库放在 Agent 扫描范围之外,用一个软链区,每条轨迹只暴露一小撮任务相关的。
runbook:把反复要做的事写成契约
  • 记录意图、前置条件、步骤、安全边界、证据、升级路径、回滚。
  • 自动化脚本因此可以做得很薄,耐久的知识留在仓库里。

怎么验收一份知识库

开一个全新的会话,看 Agent 会不会真的检索到它、并且真的改变行为。文档存在,只证明它被写过了。再往前一步是消融测试:把这条上下文拿掉再跑一遍,效果扛不住的,删掉。

这个仓库自己就是这一条的样品

四条可以自己核对的结构事实:

仓库里实际是怎么做的对应它自己主张的哪一条
根目录的 AGENTS.md 是一张分流表,按「本地证据解决不了的那个决策」派路。要判断 Agent 是缺能力还是工具难用,去能力那条;要界定谁能执行有后果的变更,去权限那条路由,不是灌输
明确写着:其他与目标相关的工作,不要预加载应用指南或者整套心法工作集要小
被当作另一个系统的上下文时,规定自己是只读的;目标系统的指令、归属、契约和当前状态提供本地真相本地真相优先于外来上下文
给出的工作循环里,第一步是先读目标系统自己的指引和架构,第三步才是说出没能解决的那个决策,然后只挑其中一条去读先看本地,再取一条
未解决的那个决策决定走哪条路由。目标系统本地的证据可能不需要其中任何一条就解决了它。确实需要上下文时,从其中一条开始,只有在另一个独立的决策仍然悬着的时候,才加第二条。 AGENTS.md,仓库根目录
第二层 · 杠杆

第五条:工具吐出来的每个字,都是下一步的上下文

一个能力真正可用,要走完六步:发现它、认出这次该用它、正确调用、看懂返回的结果、出错时能修、最后在真实系统里确认效果。任何一环断了,这个能力就是摆设。

发现
→
选择
→
调用
→
解读结果
→
真实系统里验证

解读结果这一步如果发现出错,回到修复,再重新调用,形成回环。

选 MCP 还是选普通命令行,取舍在两件事上。MCP 服务器能立刻广播工具名和输入格式,但模型仍然要现学每个新调用和返回的形状;普通的 POSIX 命令能吃到模型很强的先验,可是在 Agent 知道它装了之前,它是隐形的。有个真实的迁移:团队最早用 MCP 让 Codex 通过 Chrome DevTools 协议连自家的 Electron 应用,后来同事换成一个本地的 TypeScript 守护进程加一个小命令行,因为实际只需要两三个操作。换完之后工作流没有中断。

好工具的七条行为

正常路径
  • 成功的时候保持安静。
  • 结果会被机械读取的地方,给有边界、稳定的结构。
  • 被省略掉的细节,留一条能取回来的路径。
出错与有后果的操作
  • 失败时说清违反了哪条不变量、影响了哪个目标。
  • 有已知的恢复动作就直接给出来。
  • 有后果的操作提供检查模式或者空跑模式。
  • 给一个查询后置条件的方式,或者一张副作用回执。

一个所有人都会踩的坑

有一种失败长得很好认:Agent 会预防性地把一条昂贵的命令,比如 cargo test,用管道接给 head 或者 tail,想少读点输出。

这一手同时坏了三件事

head 可能提前关掉管道,把测试进程直接杀死在跑完之前。tail 通常会读到结束,但把前面的证据全丢了。两者在没有处理管道退出状态的情况下,还会一起把真实的退出码藏起来。

等到一个后期的失败依赖前期的上下文才能解释,那段输出已经没了。

解法不在提示词里,在工具边界上:由仓库自己拥有的一层包装,完整跑一次任务,保留真实的退出状态和全量输出,只返回下一步决策需要的那段语义化失败摘要,同时让底层证据可以被查询,不用重跑一遍。

缺什么就往下挖,挖到能用为止

把整件活交出去,会从外往里把能力缺口一层层暴露出来。原始目标是让 Codex 读 Slack、识别报进来的线上故障,这样就不用人把消息一条条粘进对话里。

读 Slack 识别故障
→
需要 Slack 访问权
→
需要凭据管理
→
需要一个申请凭据的接口
→
需要钥匙串访问和敏感材料的安全处理

这种能力搜索会向下递归六到八层。触底拿到一个可用的基础件之后,再一层层爬回最初那个故障分诊的目标。每一次这样挖一趟,往后所有 Agent 能干的活都跟着扩大一圈。反过来,用一次性的人工中转绕过去,等于把同一个洞原封不动留给下一趟。

第二层 · 杠杆

第六条:代码就是提示词

Agent 读到的每一个文件,都在影响它下一步预测什么。原话是:仓库里的代码就是提示词。

模型脑子里装着几十种都说得通的做法。仓库的作用是剪枝,用反复出现的结构,把团队选定的那一种挑出来。一致性直接压缩上下文需求:一套可观测性方案,Agent 在一个文件里学到的东西能迁移到全仓;六套方案,它每到一个调用点都得重新研究这里该用哪一套。

2026 年之前就有的证据

2023 年,他把 Ruby 的 String#unpack 整个 lib.rs 塞进一个提示词,模型一直在混淆不相关的指令。他把这个代码包拆成结构高度规整、文档齐全的小模块之后,模型第一次尝试就能正确实现后续的状态机操作。

后来他回头重看这篇文章,评价是:模型变强了,但上下文、约束、迭代、反馈这个协作循环,感觉是一样的。

反面同样成立,例子传播缺陷和传播好判断一样快。半截子的迁移会造出两套互相打架的提示材料;一个写得不好的辅助函数,会扩散到每一个需要类似行为的包。

半截子迁移

旧写法和新写法并排躺在仓库里。

Agent 每次动手前要先判断:哪个是当前该抄的?迁移完成了吗?

两套都在当提示材料用,两套都会被复制。

完成迁移,装上棘轮

打开 ESLint 的 no-await-in-loop,修掉约 600 处违规,在同一个 PR 里补齐测试覆盖。

仓库里只剩一个可抄的答案,旧写法再也进不来。

Agent 和新来的人拿到同一个明确的续写方向。

一个概念,只认一个出处

在造一个解析器、清单类型、版本常量、状态表示、夹具构造器、策略检查或者命令之前,先找这个概念现有的出处,需要时扩展那个接口。下面这些是「没有明确出处」的信号:

警告信号说明
第二个解析器仓库已经给这门语言建过模型了,又冒出来一个
版本号被复制工具的精确版本号出现在策略代码和测试夹具里
拼字符串造 JSON结构化数据用字符串片段拼出来
命令行重写领域逻辑命令里重新实现了一遍领域行为
未经类型化的外部数据外部数据不做解析直接流进内部代码
同一条不变量被守好几遍好几处防御性检查在保护同一件事,说明没有边界真正拥有它

每一处重复的出处,都在制造一个「本地看起来也说得通」的续写方向。Agent 可以忠实地跟随身边每一个先例,同时让整个仓库变得更不连贯。

边界处解析,不要到处校验

外部数据进来的时候解析一次,变成一个可信的领域类型,之后内部代码就依赖这个规范状态。如果你在到处做记录检查、每个函数里都归一化、每个事件处理器里都查一遍就绪状态,通常意味着没有任何一处边界真正拥有「这个值是有效的」这件事。

第二层 · 杠杆

第七条:能做和能批是两份合同

能力说的是怎么造成一个效果。权限说的是哪个身份、可以对哪个资源、在哪个环境、造成哪个效果、持续多久、需要谁批、怎么回滚。

能力 · capability
  • 回答「怎么造成这个效果」。
  • 查看、编辑、构建、测试、模拟、迭代。
  • 这个圈里大部分错误都是廉价的反馈。
权限 · authority
  • 身份是谁、操作和目标资源是什么。
  • 环境和范围、授权的存续期与撤销路径。
  • 需不需要审批、职责是否分离。
  • 审计回执和后置条件、回滚与恢复路径。

拆开这两份合同之后,安全问题从「要求一个随机系统零错误」转向「后果有多大、能不能恢复」。凭据的托管要放在轨迹之外:一个代理进程在动作边界处,从安全存储里取出范围很窄的密钥,让 gh 用起来像一条普通命令,而密钥从不进入模型上下文,也不进仓库。再加两层保险:只读身份让检查有用但不能改动;端点绑定的访问可以单独关掉某一条面向 Agent 的通道,不必轮换底层凭据,正在运行的轨迹也能被撤权。

有后果的操作分阶段,每阶段只给那一段需要的权限

Loki 是他家里的一台扫地机器人,跑开源的 Valetudo 固件,唯一的远程访问通道是一个自定义的 Tailscale 守护进程。要把这个守护进程从 v1.90.8 升到 v1.96.4,麻烦在于:升级它本身,可能毁掉你用来抢救失败部署的那条路。

场景:把 Loki 的远程通道 Tailscale 守护进程 v1.90.8 升到 v1.96.4 Agent 自主,效果可逆 评估 比对上游版本 准备 构建候选版本 拷进隔离槽 不碰生产 金丝雀:同一台机器上的第二个身份和独立状态目录 候选版本启动 加入专用网络 接受远程登录 人工审批 ① 认证这个新网络身份 切换:只有这一步会改动唯一的生产通道 先做新备份 人工审批 ② 替换生产访问路径 切换 验证登录与日志
两次人工介入授权的是两件不同的事:创建一个新的网络身份,以及替换唯一的那条生产访问路径。切换验证完成之后,金丝雀那个守护进程才停掉,它的状态被保留下来给下一次升级用。

每个阶段只授予它那个效果所需的权限。金丝雀通过,不等于拿到了生产权限;有生产访问,不等于部署出去的系统是健康的。

指令也要透过这份授权合同来解读。「合并」的意思是走完仓库受保护的合流流程,它不隐含授予绕过的权限。「准备一次部署」授予的范围到准备为止,生产切换要另外批。当一个不可逆的目标或者范围在查过本地策略之后仍然含糊,Agent 应该在跨过那条边界之前先问。

稳定下来的授权规则应该落进身份策略、类型化的领域操作、有范围的工具、环境边界、测试和合并闸门里。在后果值得的地方,把错误的动作变成不可用的,比写一条规则更管用,规则要靠人时时记着,机制不用。

第二层 · 杠杆

第八条:一个绿勾只证明它自己那一条

验证要发生在用户、操作者或者下游系统真正依赖这个结果的那个边界上。单元测试、类型检查、代码检查、构建,确立的是内部属性,它们证明不了外面那件事。

这一条落地时最有用的是一张对照表:你的结论是什么,证据就得取在哪个边界。

你声称的是证据要取在这个边界
浏览器里的行为真实浏览器走一遍旅程,同时看语义状态和渲染出来的样子
生成出来的内容从源到产物的完整比对,加上新鲜度
兼容性迁移目标语料上的一致性、边界情况、以及明确列出可接受的差异
安全影响一个复现器、有界的可利用性、以及回归覆盖
部署完成经过验证的那个产物正在运行,加上部署后的健康检查
有后果的远程变更分阶段金丝雀、切换、恢复路径、切换后的检查
表格里的计算公式、单位、来源单元格、渲染出来的工作簿状态
数据集转换结构、来源出处、源到产物的对账
文档或演示稿要求的结构和渲染出来的页面;承诺过引用或无障碍就一并验
分析结论可复现的转换过程,以及结论确实被证据支持
业务状态变更需要的审批、回执、以及观察到的后置条件

这张表对非工程的活同样成立。当中间的代码留在 Agent 的执行过程里、用户根本不看时,证明包要用用户听得懂的话来说:一个表格公式、一页渲染好的幻灯片、一条带出处的结论、一次完成的状态流转。隐藏脚本的测试通过,只确立了内部属性。

还要把真实系统交给 Agent 自己看。它应该能启动应用、驱动浏览器、读日志、查指标和链路、检查持久副作用、比对生成语料、观察持续集成和部署。人不该去中转 Agent 本可以直接看的证据,中转既增加延迟,又把诊断需要的状态剥掉了。语义视图和渲染视图要一起用:DOM 快照和结构化状态适合看角色、值和关系,截图、录像或者直接的屏幕操作才能暴露布局、裁切、焦点和交互缺陷。

交付的必须是被测过的那一个

发布的证据要跟着同一个不可变的产物,从验证一路走到切换。在权限更高的任务里重新构建一遍,等于开了第二条供应链,前面所有证据都跟着断链。

说清楚证据没有证明什么

前面那个安全公告的案例,报告里明确写了:触发数据损坏需要一个自定义的哈希实现,外加在程序崩溃之后继续恢复运行,这个复现器并没有证明默认路径上存在内存不安全。当时的指令是每个上报的问题都要证明影响或者可利用性,长得像安全发现的推测性输出不算交差。

举证的负担随后果放大。一个错别字、一次解析器迁移、一次远程升级、一次生产发布,需要的证据和权限完全不在一个量级。

第三层 · 让它复利

第九条:纠正过一次的事,不该再纠正第二次

重复出现的纠正要转化成塑造下一次运行的环境。这一条决定了前面所有条能不能随时间累积。

先看一个精确的反例。有人给了 Agent 一个具体的 Go 接口修正:原本函数只回一个「成没成」,改成回一个能说清「为什么没成」的具体错误值。Agent 只改了被点名的那一个函数。那条指令背后是一条关于接口设计的普遍原则,但这次修复没有去搜同一原则管辖的其他实例。

碰到反复出现的纠正,按系统问题处理,走五步。

步骤要做的事
一重建当初承诺的结果,和实际观测到的失败
二定位最早能拦住它、或者最早能让它暴露的那个边界
三搜索同一条原则管辖的其他实例,这一步最容易被跳过
四把环境缺陷、Agent 的随机波动、外部故障、错误的前提,这四件事分开
五选一个能覆盖整类、同时不禁止正当例外的干预

改在哪一层,取决于这条教训有多成熟、后果有多大。

干预方式什么时候用它
改提示词、重新跑任务的框架或者想要的结果还在摸索中
路由文档、runbook、skill一份稳定的知识需要在某个决策点出现
审查者或评估这个判断是定性的、横切的,或者还在长细节
类型、接口、领域工具系统能让正确用法变自然、让错误用法变困难
lint、测试、策略检查一条确定性的不变量应该直接阻断复发
架构或迁移反复出现的缺陷暴露出归属错了,或者依赖方向错了

判断力怎么变成资产,有一条完整的链子

起点是一个提交到公开 openai/codex 仓库的 PR:升级 pnpm 修掉几个漏洞,并且禁止安装后自动执行脚本。从这个补丁往回倒推,能还原出四条原则:包管理器是高风险边界、依赖要保持更新、安装过程不该在持续集成里执行任意代码、例外需要人工审查。

一个补丁 升 pnpm,禁安装脚本 恢复出四条原则 而不只是修这一处 15 分钟对话 给 1500 个包做风险画像 四小时后 变成常驻 16 个 Agent 跑的每日扫描器 检查依赖图和上游仓库,把发现以 SARIF 格式发进 GitHub Advanced Security 后来发现:只抓到了单点实例 没抓到那位安全工程师的通用判断框架 把缺的原则补进 SECURITY.md 扫描器和新的依赖深度、许可证扫描器都吃这份
这条链子里有两次提拔:第一次把一个补丁背后的原则变成常驻扫描器,第二次发现扫描器漏了通用判断框架,再把它补回最早那个位置。后面每一轮扫描,应用的是修订过的原则,不用再一个包一个包重新发现。

值得注意的是第二次提拔。扫描器上线之后,Slack 上的反馈显示它更容易抓到一个个具体实例,抓不到那位安全工程师脑子里的通用参考框架。团队把缺失的守卫和调查信号补进 SECURITY.md,这份原则随后同时喂给每日扫描器和新增的依赖深度、许可证扫描器。判断力就是这样一轮一轮攒起来的。

上游有了正主,就拆掉下游
  • 在一个缺失的领域模型上再叠一层校验器,只会把不一致固化下来,还额外增加维护成本。
  • 更强的类型、接口或架构出现之后,下游那些冗余的检查应该退休。
过拟合之后才加细则
  • 先给一条粗的指令,等它开始在正当的例外上出错,再加细节。
  • 一上来就写满细则,会挤掉 Agent 本来能做对的判断。
两个配套细节:自述文件怎么用,审查者怎么收敛

让 Agent 边干边记三份文件,Mistakes、Learnings、Desires,缩写是 MLD。这三份文件的定位是给搭环境的人看的遥测数据。Agent 可能漏掉最重要的那个错误、可能归错因、可能要错工具,所以原始记录要留在产生它的那次运行旁边,只有反复出现并且有旁证的缺口才值得写进上下文。

另一个是审查者的收敛问题。团队早期的审查者 Agent 会反复施压,把实现方的改动范围越推越大。解法是告诉审查者偏向合并、只提实质性的问题,同时允许作者选择修复、带着理由延后、或者直接顶回去。

第三层 · 让它复利

第十条:守住连贯性,扛住终身风险

实现变得廉价之后,稀缺的东西变成了未来的连贯性和长期所有权。每一个被接受的改动,都还在给共享系统添约束和维护义务。

这一条里有全篇最诚实的一段。那个五个月一百万行的内部产品,内部发布和采用证明策略在初创期是行得通的;但架构连贯性在「多年」这个尺度上会怎么演化,是原文公开承认的未解问题。

另一个诚实的地方,是他用自己的项目反驳了「Agent 能一口气啃硬活」这个印象。Artichoke 那次状态模型重构,一开始是一个 114 个文件、根本编译不过的巨型改动;后来摊成 50 个陆续合并的准备 PR,同期仓库里还落了 200 个其他 PR,之后又经历几轮大的整合尝试和运行时失败,才最终合进去。

60 小时那次

路线是已知的,一个 PR 走完。

人的介入:三次。

Artichoke 那次

终点清楚,安全路线得现学。

50 个准备 PR,多轮整合失败。

改动的大小和运行时长,几乎说明不了一条安全路线是不是已经摸清了。区别在于路线本身确不确定。他给这个缺失的能力起了个名字,叫「害怕再来五十个 PR」:预判自己会在未来的改动上后悔到什么程度。他把这件事明确列为仍然没有解决的能力问题。

什么必须活过一次重写

实现本身是可丢弃的一次渲染,下面这六样才是要保住的:

保住这些
  • 行为与领域规格
  • 兼容面与迁移规则
  • 决策与架构边界
还有这些
  • 依赖与供应链政策
  • 举证语料与已知失败用例
  • 发布身份与运维契约

Symphony 就是这么做的:它发布一份行为规格,别的团队可以照着产出各自独立的实现。这种形态被称作「幽灵库」,共享的是契约,不是代码包。

依赖是一笔所有权决策

OpenAI 团队把一个通用的并发限制依赖换成了自己写的实现,接上自家的可观测性,测试做满。代价说得很白:内化一个依赖,你就回到了零。上游积累的规模、兼容性和安全信心全部清零,仓库得为自己现在拥有的那部分重建证据。

第三层 · 让它复利

第十一条:已经定了方向的活,跑成常驻循环

有一大类工作方向早就定了,只是排不进日程:本地化、补埋点、依赖保新、删掉过时的兼容路径、修架构漂移、刷新生成的数据和文档。一旦想要的结果、举证方式、授权边界三样都清楚,这些活可以一直自己往前推。

判断一个循环能不能立起来,看它能不能回答五个问题。

要回答的问题没答案会怎样
什么条件应该一直保持为真?循环没有目标,只能变成定期空跑
什么信号说明系统已经偏离了?偏离了也没人知道,靠人偶然发现
什么证据能证明一次改动把它恢复了?改完不知道有没有用
哪些操作可以自己做,哪里必须审批?要么不敢跑,要么闯祸
哪份持久状态记录结果给下一轮?每轮都在重新发现同一批事实

「任务」和「目标」在这里要分开。「修好这个不稳定的测试」一个补丁就结束了;「让持续集成的 p95 延迟保持在 N 分钟以内」是一个目标,它会持续观察系统、生成并测试干预、必要时申请人的授权,还需要一个明确的退休条件。

每一轮都要落在这六种状态之一

什么都不用做 · 提出了候选 · 改动已证明 · 请求审批 · 需要恢复 · 策略已过时。

安静的空跑是健康的。反复重新发现同一批事实,才是缺持久状态的信号。

还有一条关于紧急响应的主张:一次代码红警不该以指标转绿告终,它应该留下一个维护循环。响应过程中人学到了哪条不变量重要、哪些信号预示漂移、哪些干预管用,这些应该连同目标、传感器、被授权的动作、升级路径和这个循环的主人一起记下来。因为 Agent 执行足够便宜,一些新发现的不变量可以在没有常驻团队的情况下继续被维护下去。

环境自己也会长草。文档变旧、代码长出互相打架的写法、评审意见暴露出缺失的守卫、某个曾经有用的工具在模型换代之后变成多余。OpenAI 那边用一组常驻的后台任务取代了原来每周一次的人工清理:扫描偏离既定原则的地方、更新质量评级、开聚焦的重构 PR、清理陈旧文档。小修补要在一个坏写法变成未来的提示材料之前就落地。

人的位置在这条里有明确交代:循环维护的是一个已经选好的政策与变化世界之间的关系,「哪个世界值得建」仍然是人的活。

第三层 · 让它复利

第十二条:按实测的有效性优化

token、代码行数、Agent 数量、检查项,这些都是输入。要优化的是每单位稀缺的人类注意力换来的、被接受的结果。

token 排行榜是其中一个虚荣指标:它奖励的那个信号,在价值原地踏步的时候照样往上涨。「每工程师每天十亿 token」这个说法后来被补上了一道结果闸门,这些活必须合进主干并且改善用户体验才算数。

三个概念要分开:可达性Agent 能碰到这份活的多少部分。仓库、本地应用、测试、浏览器、日志、部署、举证,都在扩大可达性、活动量(token 和忙碌时间)、以及有效利用率真正抵达一个被接受的结果、或者产出了可复用证据的那部分活动。可预防的丢弃、返工、修复都在削减它。

四个时钟必须分开计

时钟量的是什么为什么要单独看
反馈延迟从动作到拿到有用的测试、构建、链路或评审信号决定迭代速度
实际耗时从开始到跑完一次占用执行容量,可能拖住下游
同步的人类注意力人必须指挥、中转、评审、抢救的分钟数决定组织能监管多大的工作量
到达被接受结果的时间从请求到被证明、被接受、被部署把排队、重跑、评审、交付一起算进来

为什么必须分开,两个例子摆在一起就清楚了。那次 60 小时的重构,实际耗时很长,跨了三到四天,但和人的同步接口小到只有初始提示词加两次追问,消耗约 3.3 到 3.5 亿 token、2800 美元,产出一个 PR,他估计自己手做要三周。对照 2023 年那次和 ChatGPT 的结对:代码在编辑器和浏览器之间来回复制,测试结果人肉转述回聊天框,一次会话几百个提示词,实际耗时并不长,但人的注意力一小时就被榨干了。

60 小时
跑了三到四天,只用了初始提示词加两次追问,产出一个 PR
3.3–3.5 亿
那次重构消耗的 token,约 2800 美元;他估计手做要三周

探索、返工、浪费是三件不同的事

丢弃可以是有生产力的。在一个最终接口形状还没定的高不确定性重构里,他会让 Agent 产出一个可能到五万行的探索性改动,推成 PR 好让人检查,然后扔掉。这次尝试暴露了 Agent 大概会在哪里失败、以及最终切换需要哪些接口缝。他把这张失败地图变成大约十五个准备和铺垫的 PR,然后才回来做真正的切换。

真正的浪费长什么样

在没有新证据的情况下,重复一个已知的失败。配套还有一句诊断口径:一套一直在诱发同样高信号纠正的环境,即使每一次纠正本身都很便宜,拉长时间看它就是有缺陷的。

照着做

怎么把这十二条用到自己的项目上

仓库的 playbooks 目录给了一套可以直接照做的流程,针对一件有边界的、能重跑的真实任务。

观察基线
→
找最早断掉的交接
→
最小可逆干预
→
在真环境验证
→
全新重跑
→
保留 / 修改 / 移除

第五步的「全新」是有讲究的:要用新会话和一个隔离的、等价的起始状态,这样重跑不会从当初那次实现对话里继承到隐藏的帮助。还要单独确认这次干预到底有没有被检索到、有没有被调用、和这次重跑有没有关系。一次成功的结果,对于一条从未被用到的指令或工具,不构成任何证据。

下面三份模板可以直接抄进你自己的项目。

动手改之前先填的「工作契约」,把条件记下来
目标系统与版本:
相关外部状态:
锁定的模型与编码 Agent 配置:
代表性任务:
可接受的结果:
能证明这个结果的证据:
权限范围与审批边界:
预算与停止条件:
怀疑缺在哪:
改动之前先写下你的假设,防止事后编故事
如果在【最早管得住它的地方】加上【某项改动】,那么固定住的 Agent 在【代表性任务】上会出现【可观测的行为变化】,因为【机制】。

能支持这个假设的证据:
能削弱这个假设的证据:
预计的维护成本和归属:
跑完一轮之后留下的结果记录
任务与可接受结果:
目标版本与外部状态:
锁定的 Agent 与权限范围:

基线证据:
最早断掉的那次交接,以及它归谁负责:
改动与预期机制:
做了哪些验证:
全新重跑的证据:

结果对比:
证据对比:
人工中转与延迟对比:
风险与维护成本对比:
如果做了「拿掉它再跑一次」,结果是:

决定:保留 | 修改 | 移除
长期归属与记下的教训:
后续动作或退休条件:
已知的局限:

什么情况下你的对照实验不作数

还有一份单独的作废清单。命中其中任何一条,这次评估就支撑不了它想要的结论。

作废条件
结论关于「用到了某段上下文」或者「用到了某个能力」,但那个东西压根没被检索到或调用过
预先声明的干预之外,还混进了指令、工具、权限、外部状态或写入权限的差异
把一次带随机性的运行当成有代表性的
评分需要一份没有公开的参考实现
把只有评估方能看到的检查,报成 Agent 自己产出的证据
把整份外部语料复制进评估方,等于让评估方拿着自己复制的那份语料自问自答
拿 token 数、代码行数或活动量顶替一个被接受的结果
比较过程中 worker 本身发生了变化
公开报告里的目标和版本不透明,读者无法检查或复现
✅ harness 改进循环 · 照做清单
一个 harness 值回它的维护成本,是在同一个 worker 在真正要紧的那个环境里产出更好的、被接受的结果的时候。 evals/README.md,harness-engineering 仓库
三份可直接抄进项目的填空模板、一套六步改进循环、一份对照实验作废清单。
来源:lopopolo/harness-engineering(GitHub,仓库自撰内容为 CC BY 4.0),含仓库 README、docs 目录下十二条各自的正文、AGENTS.md、playbooks 与 evals。作者现任职情况据其 X 账号 @_lopopolo 简介(Principal Engineer, Agentic GCP @googlecloud · ex-@OpenAI);「过去一年的工作」出自其 2026 年 7 月 18 日发布该仓库的推文。⚠️ 其个人站 hyperbo.la 与 LinkedIn 尚未同步更新,仍显示在 OpenAI 任职。文中引用的实证与数据来自该仓库对 OpenAI harness engineering 一文、Latent Space 访谈、AI Native DevCon 演讲、以及作者公开帖文的转述与链接。本文的可视化为解读所作,非原文配图。
来源
lopopolo/harness-engineeringlopopolo/harness-engineering·查看主材料
↑ 正文结束
✓ 本篇全文免费开放

会员可复制、下载全文 Markdown,还能让你的 AI 订阅和提问。

阅读原文 ↗
📮 新解读第一时间进邮箱

读完觉得有用?留个邮箱,每篇新解读发布就送达,随时退订。