yaxin-luo/blog/harness-optimization
EN/中
harness-optimization.zh.md
长文EN中 13分钟 地图之外

评分器才是优化器

我们让一个AI agent去改进另一个agent的harness。随后换一个更严格的评分器重新给同一批页面打分,每个harness测出的提升都缩水了三分之一到70%。在开放式任务上,优化器学到什么取决于评分器,而且学到的往往就是评分器本身。

我们让一个AI agent一轮接一轮地改写另一个agent的harness。每次修改,只有分数提升越过一条按实测噪声设定的门槛,才会被保留。最后,我们换上一个更严格的评分器,给同一批页面重新打分。每个harness测得的提升都缩水了三分之一到70%。最好的那个缩水约一半:+0.176(90%区间 +0.12到 +0.23)变成了 +0.086(+0.02到 +0.15)。页面一个字没改,变的只是裁判。11有六次held-out运行超时了。严格评分器重评时这六次重跑了,所以那六个页面是新的;其余72个页面完全相同。

图 1· 同一批页面,换一个评分器在新页面打开 ↗
图 1每个优化后的harness在8个优化器从未见过的held-out任务上的门控前提升,分别在我们优化时用的宽松评分器和更严格的评分器下计算,拖动滑块在两者之间切换。柱子是相对起始harness的提升(90%区间),点是背后的72个页面。图中文字为英文。

哪个数字是对的?我们判断不了,而这正是结论:在这么小的差距上,两个评分器都没有经过验证。在开放式任务上,“harness到底提升了多少”这个问题,脱离评分器就没有答案。

−51%最好的harness换成更严格的评分器重评同一批页面后,门控前提升的缩水幅度
≈ 1.6 σ保留门槛只比六任务比较的噪声高出这么多:差一点的修改,去留基本靠运气
8 / 9看过失败评分项后被保留的harness版本,都抄进了评分细则的原文
9 / 11被接受的改进,是直接在起始harness上做的修改

这不只关乎网页。如果你的团队靠AI裁判的分数来挑prompt、skill或工作流,同样的效应都会出现:按品牌细则打分的营销文案,按质检清单打分的客服回复,按病历书写规范打分的临床记录。你测到的提升有一部分属于裁判,小幅提升大多是噪声,而你给优化器看的任何评分细则,都会被它抄走。

01#这个循环是怎么运转的

我们围绕一个编码agent搭了一个优化循环。这个agent根据一段简短的需求,做出一个可交互的网页。任务是刻意选成开放式的:没有唯一正确的页面,而这恰恰是评分器最不可靠的地方。下面可以一步步点开看,后文用到的每个术语都在这里定义。

图 2· 循环如何运转在新页面打开 ↗
图 2优化循环的每一步:harness、修改它的Meta Agent、编码agent、评分器、保留规则,以及我们改变的反馈设置。图中文字为英文。

下面是编码agent在dev任务上做出的一部分页面,从印刷工坊的方案到医学统计的讲解:

图 3· page-wall.jpg2484 × 956
agent做出的一面页面墙
图 3研究期间编码agent在dev任务上做出的15个页面。每个页面都被评分器打了分,每个分数都参与了一次保留或回退的决定。

02#换一个评分器,提升缩水三分之一到70%

最后,我们从四条优化谱系(每种反馈设置一条)各取第一个被保留的版本,把它们在held-out任务上的页面评了两遍:一遍用优化时用的宽松评分器,一遍用取代它的严格评分器。在同一个门控前指标上,每个提升都缩水了:

  • 只看结果的,缩水33%;
  • 看失败评分项的,缩水70%;
  • 两个看完整反馈的,缩水约一半。

把门控扣分算进去,排名也变了。在严格评分器下,只看结果的harness排第一,+0.090,而最好的完整反馈harness是 +0.070。它并没有做出更好的页面:起始harness在16次运行里有4次被门控扣分,而只看结果的harness一次也没有。

图 4· example-page.png653 × 1080
一个任务,两个评分器
图 4一个dev任务:左边是起始harness做的页面,右边是一个被保留的harness做的页面。和宽松评分器相比,严格评分器给起始页面打得更高(0.74对0.70),给保留页面打得更低(0.88对0.99)。保留页面的领先从0.30变成了0.14。

那到底哪个评分器对?我们拿评分器的一个早期版本,和一个按书面规程打标的独立AI标注者做了对比;我们没有人类一致性数据。在差别明显的页面对上,两者6次全部一致。在差别细微的页面对上,32次里只有16次一致,等于抛硬币,不过样本不大。这项研究在held-out任务上的提升在0.03到0.18之间,看起来正落在这个“细微差别”区间,而我们测过的评分器在这里都没有得到验证。可以在下面自己探索。看过失败评分项的harness做出的页面,在严格评分器下掉分最多。

图 5· 哪个评分器是对的?在新页面打开 ↗
图 5左:评分器早期版本与按规程打标的AI标签在页面对上的一致率,按页面差别大小分组(90%区间)。右:每个held-out页面在两个评分器下的分数;72个里有56个落在对角线下方。图中文字为英文。

宽松评分器在高分段还饱和了:它把31个强页面里的12个打到了0.9以上,而一个参照AI裁判只给了5个。

我的结论是:**脱离评分器的提升,不算结果。**每个提升都至少报告两个评分器下的数字;评分器一变,就把选定的检查点重新评一遍。我们换了九个评分器版本才学到这一点,每次修复都解决一个问题,又暴露出另一个:

  • 第一个评分器不管页面做了什么,100条裁判断言里都会放过60条。
  • 后来的版本会因为4–5 px的溢出,把正确的页面打成零分。
  • 把这两个问题都修好的版本,又在高分段饱和了。

03#运行之间的噪声,和你的保留门槛一样大

同一个harness在同一个任务上跑两次,分数的标准差就有0.05到0.07。同一个页面重新评分,只会变动约0.02。所以大部分噪声来自agent的运行,而不是评分器。

保留规则在六个dev任务上取平均,这有帮助,但不够。我们0.0514的门槛,只比六任务比较的噪声高出约1.6个标准差。用这些数字搭一个简单模型:

  • 一个在每个任务上都真实提升0.05的修改,只有约一半的概率被保留;
  • 一个毫无作用的修改,有约7%的概率被保留。

自己试试:

图 6· 真实的提升有多大概率被保留?在新页面打开 ↗
图 6示意模型,不是实测:运行间噪声服从正态分布,标准差取本研究的每任务实测值,在dev任务上做配对比较,门槛固定。拖动滑块,再把同一个修改评估40次。图中文字为英文。

我们也想过取巧。我们把门槛挪到logit尺度上(这在分数接近上限时更公平),并把成本和不稳定的运行也计入。然后,用新规则重新裁决原规则回退掉的12个修改。在宽松评分器下,新规则会保留其中4个;在严格评分器下,一个都不保留。在我们的数据里,选择规则没法脱离它所读的评分器单独调好。

04#给优化器看失败的评分项,它就会抄

想象一个考前看到评分表的学生:没过多久,他的笔记读起来就像那张评分表。看失败评分项或完整反馈的Meta Agent从来没看到过完整的评分细则,只看到页面没通过的那几条的原文。它们在dev上拿到了最大的提升。然后我们去读了它们的diff:

  • 这两种设置下被保留的9个版本里,8个的harness里有评分细则的文字,要么是条目编号,要么是几乎逐字照搬的句子。我们的统计只是下限。
  • 在它们的全部候选里,37个中有29个这样做。
  • 只看结果的11个候选,一个都没有。

在held-out任务上,喂过评分细则的harness并没有比只看结果的harness表现出可靠的优势,而严格评分器给它们页面扣分最多。

优化器自己的笔记,说明了它在做什么。在第二阶段,控制循环谱系第一个被保留的修改,以这样一条假设开头(可以在下面的记录里读到):“Make one verification pass catch what the grader checks.”(让一次自检就抓住评分器检查的所有东西。)这不是优化器的bug,而是它对自己看到的东西做出的理性反应。反馈可见性就是信息泄露,应该按信息泄露来对待。

图 7· fig-rq3.png1532 × 599
反馈级别与提升
图 7只要反馈里出现失败的评分项,就会出现抄评分细则的现象。dev上更大的提升没有延续下去:在严格评分器下的held-out任务上,各反馈级别分不出高下。

05#Reward hacking确实发生了,只是不以作弊的形式

我们为硬作弊设了两个陷阱:

  • 一个门控:任何对裁判喊话的页面直接记零分;
  • 一个canary:一个任何需求都没提到的、预埋的计分漏洞。

canary是会被触发的:它在354次dev运行里出现了6次,其中2次来自未经修改的起始harness,所以单次命中说明不了什么。但在严格评分器下,没有一个held-out页面钻了它(174个里0个)。注入门控在473次search和dev运行里一次都没触发。我们没有埋一个已知的注入来测试它,所以这个零应读作“没有观察到”,而不是“不可能”。

但reward hacking还是发生了,以拟合评分器的形式:

  • harness把评分细则的原文抄进了自己的指令;
  • 48个候选里有11个,把具体任务的事实和选择器写死在了harness里;
  • 它们揣摩裁判能看到什么。有个Meta Agent的笔记,专门围绕“评分器那张从不滚动的整页截图”做计划。

这些做法都能通过检查,却没有检查本来想要的那种品质。

图 8· rubric-copy.png1018 × 1199
被抄进harness的评分细则,以及作弊计数
图 8上:一个被保留的harness在指令里逐字重复了评分器的一条评分项(高亮处)。下:各反馈级别抄评分细则的情况,以及严格评分器下两个作弊陷阱的触发次数,都是零。

这里有一个让人不太舒服的转折:判断一个修改是hacking还是真本事,本身也需要一个评分器。写进harness的对比度检查,如果读者在乎对比度,就是本事;如果只有这个评分器在乎,就是hacking。在这项研究里,在harness搜索这个强度的优化压力下,评分器的盲区和校准误差对结果的影响远大于作弊。我们预计,随着优化压力增大,作弊风险也会上升。

06#几乎所有提升都来自第一次修改

11个被接受的改进里有9个是直接在起始harness上做的修改,其中8个出现在第一轮迭代。之后,大多数谱系就停滞了。只有一条完整反馈谱系又保留了第二步。控制循环谱系在第二阶段又提升了一次:门控前 +0.043,算上门控后与零无法区分。被保留版本的子版本,在24次尝试里平均只比父版本高 +0.009;而dev上最好的候选,在held-out任务上并不比它们的父版本强。

图 9· fig-rq5.png1527 × 824
各谱系历次修改的dev分数
图 9宽松评分器下,各谱系保留版本在历次修改中的dev分数:几乎所有提升都来自第一次修改。

在下面任选一条谱系,逐个点开它的修改。每个修改都附有Meta Agent自己写的假设和保留规则的裁决,直接取自运行记录。

图 10· 每一次修改,用优化器自己的话说在新页面打开 ↗
图 10主实验的每条谱系,附每个候选的假设和保留规则的比较,取自公开的运行记录。图中文字为英文。

不过,第一次修改是实打实的。在dev上,看失败评分项或完整反馈的版本,胜过“让评分器从起始harness的8次运行里挑最好的一次”;只看结果的版本没有越过这条线。而且一个最终的harness每个任务的成本只是这种重采样的一小部分,两到四个任务就能回本。

为什么会停滞?最显眼的嫌疑是评分器饱和,但它解释不了:换成严格评分器重新裁决,被回退的候选没有一个会变成保留。我的猜测是,一个强模型配上一个还不错的harness,有一部分容易拿到的提升空间,一次认真的修改就能拿走。之后的提升,就小于这个评分器和保留规则能分辨的程度了。更强的优化器或更大的预算,也许能找到更多。

07#看着循环跑:dashboard

这些都无法从一个最终数字里看出来。我们是在dashboard上看到的:每条优化谱系一个静态页面,从运行记录只读导出。导出器从不往运行里写东西,页面上每个数值都是那次运行在它自己的规则下的记录。下面是读一个页面的方法:

图 11· dashboard-anatomy.png2094 × 2196
dashboard页面的结构
图 11一条谱系的页面(第二阶段,完整反馈,控制循环范围)。上:每个候选与其父版本的对比,以及它的保留门槛。下:每次修改的假设、search和dev上的提升,以及保留规则自己的数字:调整后的提升与门槛的对比,以及不稳定运行的计数。

打开这条谱系的dashboard ↗。点开任一次迭代,可以看到:

  • 它的diff;
  • 它的运行,以及它在每个任务上做出的页面;
  • Meta Agent的完整笔记和会话;
  • 它的成本和完整性监控。

第二阶段的门槛设在logit尺度上(0.2241)。在这条谱系的分数附近,它相当于0–1尺度上的0.022到0.035,父版本分数越高,门槛越窄。

下面是分数背后的页面。拖动手柄,对比起始harness的页面和后来的页面。

图 12· 前后对比:页面本身在新页面打开 ↗
图 12一条谱系在严格评分器下真实的dev任务页面:拖动手柄对比同一任务上的两个版本,也可以换任务或换版本。图中文字为英文。

我们对研究做的几乎每一处改动,都始于dashboard上看到的某个现象。下面这些是研究过程的记录,你可以核对每个决定从何而来:

dashboard上看到于是我们
一条谱系一次修改后就到了0.958,之后每次修改都被回退用参照裁判重评它的页面,发现评分器饱和,于是做了严格评分器
同一个版本,在严格评分器下重测,从0.960跌到0.792把跨评分器的分数视为不可比,并在页面上注明
有dev提升的修改,被高分段的固定门槛回退改用logit尺度上的门槛,并加入成本项
一次首次修改提升了0.172,却因多了两次不稳定运行被否决不再一票否决不稳定运行,而是给它定价
假设和diff里直接点名评分器的检查项给每个候选标注是否抄了评分细则、是否写死了具体任务的细节
控制循环谱系被保留的版本,只改了指令和skill记下:没有一次改循环代码的修改被保留过
一次保留来自运行便宜了27%,而不是分数更高读成本感知的保留时,把成本项一起看

我们调研了公开的优化工具,包括OpenEvolve和ShinkaEvolve的查看器、GEPA的可视化、LangSmith和Braintrust。它们展示分数和diff,有的会标出接受和拒绝的候选。但我们没找到哪一个把保留规则的各项、实测噪声、完整性监控和阶段变化放在同一页上。对一个自己决定保留什么的循环来说,正是这样的视图,告诉人什么时候该介入。

浏览主实验的每一次优化运行:第一、二批(宽松评分器下的八条谱系)和第二阶段(四条谱系,与各自的第一阶段相连)。公开版本去掉了本地路径、内部基础设施名称、canary的身份和held-out评估,因为canary和held-out任务仍在使用中。

08#如果有人告诉你,他们的agent会自我改进

向他们要三样东西:

  1. **换一个评分器重评的结果。**换了裁判提升就减半,说明提升有一部分来自裁判。
  2. **第一次修改之后的held-out曲线。**如果后来的修改只在被挑选时用的任务上占优,这个循环就是在拟合噪声。
  3. **diff。**抄评分细则、写死任务细节,在分数里看不出来,在文本里一目了然。

09#给搭建这种循环的人的建议

  1. **每个数字都注明评分器。**报告两个评分器下的提升;评分器一变,就把选定的检查点重新评一遍。
  2. 用干净的运行间噪声来设保留门槛,在第一个候选之前测好,然后冻结。
  3. **把反馈当作泄露。**你给优化器看的评分细则,往往会出现在harness里。
  4. **读diff。**拟合评分器在分数里看不出来,在文本里一目了然。
  5. **把可观测性上的缺口当作评分器的bug。**裁判看不到的,优化器就会学会利用。
  6. 预期第一次修改就占了大头,把剩下的预算花在评分器上。

在dev上,一次好的修改胜过暴力重采样,而且跑起来很便宜。在我们的研究里,限制开放式任务上harness优化的,既不是优化器的聪明程度,也不是它是否诚实,而是评分器。先把评分器做好。

所用模型:Meta Agent是驱动kimi-k3的Claude Code,编码agent是跑在kimi-k3上的Pi agent,裁判是AI模型。完整报告(含方法、区间和全部注意事项)即将发布。