自我改进的智能体仍然需要人

古德哈特定律是跑基准的人的诅咒:当一项度量变成目标,它就不再是好的度量。编程智能体基准几乎就是为触发它而设计的。任务是公开的,结果是一个数字,排行榜不可避免地挤满了那些往往并非有意、却对基准过拟合的运行框架。
这并没有让事实上的标准 Terminal-bench 变得无用,但确实改变了 goose 团队使用它的方式。排行榜是对通用智能体能力的嘈杂度量。真正的信号是失败的模式:goose 反复卡住的地方,或者 goose 失败而另一个运行框架成功的地方。
这也是我们通常用 Sonnet 而不是最强模型来跑基准的原因。我们并不是要拿到尽可能大的数字。我们希望桌上还留着足够多的失败,好看出智能体缺了什么支持。
自我改进的智能体很热门,但我们目前信任的版本仍然需要人。这个循环是:跑基准,让 goose 比较同一任务上一个运行框架成功、另一个失败的情况,并要求它用具体说法解释差异。然后人看几处这样的失败,判断普遍的教训是什么,再让 goose 去实现这项更广泛的改进。
人这一步让循环不会塌缩成针对基准的小技巧。没有它,自我改进的智能体可能会选择为每个任务只写一个 Skill;智能体和人一样懒。有了它,我们才有希望把一次任务上的失败,变成对尚未见过的任务也该有帮助的能力。
相关工具在 goose 仓库的 evals/harbor。主要入口是 ./evals/harbor/cmd.py,一个带有 run、list、show、task、compare 和 pull 子命令的小型 Python 命令。它把 Harbor 包在特定的 goose 二进制、模型和一组扩展之外,然后留下一个装满按任务划分的 JSON 和日志的目录。
这是 Python 特别合适的地方。cmd.py 是一个 PEP 723 的 uv 脚本,所以依赖就写在文件顶部。没有打包仪式,没有新仓库,也不用记住哪个虚拟环境被“加持”过。你让智能体加一个子命令、埋一个观测值,或者让输出表格不那么难看,它就去做。脚本既是控制面,也是实验的文档。这正是 vibe coding 发光的地方:小型工具表达需要做什么,而它们是否写得优雅其实并不重要。
真正的基准运行发生在一台远程 Linux 机器上,因为完整跑一遍又慢、又并行、又重度依赖 Docker。但分析不必留在那里。cmd.py pull tbench@...:/path/to/goose 用 rsync 把运行目录拉回来,然后本地工具就可以列出运行、检查单个任务,或比较两个任务。这比听起来更重要。如果每个问题都要 SSH 进基准机器、手工在日志里摸索,你问的问题就会更少。
PR #9637 里的比较配方实现了 goose 查看结果的那一步。它们读取同一任务的两次运行、智能体轨迹、验证器输出和参考解。有用的输出会解释机制:“A 注意到了那张图,B 从未打开它”,或者“A 在正确文件已经存在后就停了,B 一直重写,直到把它写坏”。
最近的工作里有两个发现。第一,goose 有时在已经有足够信息可以收尾时还继续探索。它已经接近答案,却不做出结论,而是继续戳来戳去,最终用完回合,基准失败。普通对话里也会发生同样的事:如果 goose 不知道这次交流进行到了哪一步,它就没有真正的理由停止探索、转向解决方案。PR #9636 给 MoIM 加上了回合数感知。MoIM 是 goose 注入的上下文,用来让模型保持方位感。于是现在 goose 知道什么时候该收工了。
第二,goose 失去了从磁盘读取图片的能力。最初,当你把一张图片拖进 goose 对话时,我们会插入那张图片的路径,并给模型一个读取它的工具。那样很别扭,所以我们把它换成了在对话里正确加载图片。在这个过程中,我们不小心也删掉了磁盘图片工具。在基准里,这表现为 goose 用 PIL 去检查图片,而不是使用模型的视觉能力。比较配方让这次失败变得明显,因为成功的那次运行看了图片,失败的那次则绕开了它。
多亏这个循环,我们发现了 goose 的两个真实弱点并修好了它们。基准变好了,这很好;更重要的是,这两处修复应该让 goose 在日常使用中也更好:该收尾时更不容易漂走,并且又能查看磁盘上的图片文件。
基准有用的时候,就是它不再是排行榜,而开始成为缺陷报告的时候。
