目标驱动循环:从口头指令到完成合同

先讲两次亲身经历,一次爽的,一次疼的。

爽的那次:我们 CI 里有个 flaky test,间歇性挂,复现概率大概 5%,三个同事断断续续查了三周没抓到。后来我用 goal 模式挂了个任务:目标写清楚(「找到并修复该测试的间歇性失败根因,验证方式为连续 200 次运行全绿」),扔到后台。它跑了四个多小时、迭代了几十轮——反复运行测试收集失败样本、加日志、缩小范围——最后定位到一个测试间共享的临时目录清理竞态。修复后 200 连跑全绿。第二天早上看结果的心情,做这行十几年没体验过几次。

疼的那次在更早,也是我第一次用 goal 模式。目标我写的是——现在看简直是行为艺术——「优化这个服务的性能,不要引入 bug」。它勤勤恳恳跑了一整晚,产出了一堆微小的、方向发散的改动:换了两个数据结构、加了三处缓存、重排了若干循环。都「对」,都没用。烧掉的 token 折成钱够请全组喝一周咖啡。

两次的模型是同一个。差别只在一处:第一次我定义了「完成」,第二次我在许愿。

goal 模式到底是什么

各家 Agent CLI 最近都在出 goal 类功能:设定目标,Agent 在同一个上下文里持续迭代,不达成不罢休。听起来就是个 while 循环,但真正值钱的是循环里防「假完成」的机制设计。以 Codex 的实现为例(Eric Traut 写的,Pyright 的作者,这个出身就很说明品味),几个机制值得逐个看:

完成审计协议。每轮开始系统注入一条隐藏消息,强制模型:把目标分解成可交付物、建一张「需求→证据」的对照表、逐项检查实际的文件/输出/测试结果。核心是防一个坍缩:模型天然倾向把「我产出了东西」当成「我达成了目标」。写过代码的都懂,这两件事差着十万八千里。

模型只能标记 Complete,不能自己 Pause。暂停权只属于人。这个不对称设计防偷懒——如果模型能自己喊停,它会在困难任务上找台阶下。我第一次读到这条设计时笑出声,太懂了。

零工具调用抑制。连续几轮光说不做(没有任何工具调用),自动终止。防空转。

预算硬约束。token 和时间双上限。有个细节:只计非缓存 input 加 output,缓存命中免费——所以 goal 循环这种 append-only 长会话,恰好是前缀缓存最友好的负载,机制之间是咬合的。

六要素:把愿望改写成合同

机制再好,goal 写砸照样白烧。我那句「优化性能,不要引入 bug」错在哪?拆开看全是窟窿:没有终点(优化到多少算完?)、没有验证面(拿什么证明?)、没有边界(能动哪些文件?)、没有停止条件(卡住了怎么办?)。

后来我照着 lencx 总结的「强 Goal 六要素」重写了一版模板,flaky test 那次用的就是它:

Outcome        完成时什么为真:该测试连续 200 次运行全绿
Verification   拿什么证明:CI 脚本输出,附运行日志
Constraints    什么不能回归:其余测试全部保持通过
Boundaries     能动什么:tests/ 和被测模块内部,不改公共接口
Iteration      失败后怎么选下一步:先收集失败样本分析模式,再改
Stop           什么时候停下来问人:需要改基础设施配置时

写这份合同花了我十分钟。事后想,那三周人肉排查之所以低效,部分原因恰恰是我们自己也没把这六件事想清楚过——写合同的过程逼着你想清楚,六要素里写不出来的那一条,就是你自己还没想明白的那一块。这可能是 goal 模式对人最大的隐性价值。

预算怎么定:阶梯式试探

六要素之外还有个实操问题没人教:token 和时间预算填多少?填小了任务没跑完就被掐断,填大了空转烧钱。

我现在的做法是阶梯式:先给一个明显不够的小预算跑一轮侦察。比如 flaky test 那次,第一轮我只给了半小时预算——明知修不完,目的是看它的开局姿势:问题拆解得对不对、第一批动作是不是在收集信息而不是瞎改。开局对路,第二轮再给足预算放开跑;开局就跑偏,半小时的学费买到了「goal 写得有问题」这个信息,回去改合同,比直接给一整晚预算便宜二十倍。

阶梯的另一个好处是校准预期。第一轮侦察结束时模型通常会报告进展和剩余工作的判断,这个判断虽不精确,但比我拍脑袋准——第二轮的预算就按它的估计加一倍余量给。

还有一条硬规则:预算翻倍两次还没完成的任务,强制停下来人工介入。不是钱的问题——一个在三倍预算内收敛不了的任务,大概率是 goal 本身有结构问题(终点不可达、验证面有歧义、或者撞上了环境层面的阻塞),继续加预算是在用钱掩盖设计缺陷。这条规则替我们止损过两回,两回最后都发现是环境问题(一次是测试数据被别的流程动了,一次是依赖服务在维护窗口),模型再聪明也修不了它权限之外的世界。

什么任务适合,什么不适合

我现在的筛选标准三个词:路径不确定、终点可验证、需要迭代。flaky test、复杂 bug、性能调优(有明确指标的那种)、依赖升级——人做也要试错的任务,循环只是把试错自动化了,完美命中。

两类任务别塞给它。一是终点没法机械验证的——「把代码变优雅」,它会在一个虚假的完成标准上过拟合给你看。二是大规模并行批量任务——全仓库迁移三百个模块,那是工作流编排的事,硬塞进单一上下文的循环,窗口先爆。

另外记录一个社区里的有趣分野:goal 是「马拉松」模式(同一窗口跑到底,上下文连贯),社区还有一派「接力赛」模式(每轮重开窗口,靠 git 和进度文件传状态,Ralph Loop 是代表)。我两种都试过,体感是:几小时内的任务马拉松更顺,跨天的超长任务接力更稳——马拉松跑太久窗口还是会腐烂,哪怕有压缩。选哪个,看任务时长。

尾声:难度换了个地方

Karpathy 的 Software 3.0 讲从 how 到 what 的迁移,goal 模式差不多是这个理念目前最完整的产品化——执行、方法、过程全部交出去,人手里只剩一个问题:你到底要什么。

听起来像解放,其实是难度搬家。写六要素合同比写代码难——「连续 200 次全绿」这个验收标准,是我想了一会儿才定下来的(100 次够不够?要不要跨机器?),而这个决定直接框定了它整晚工作的方向。定义「完成」正在变成一项核心工程技能,而我们这代工程师在学校里没人教过这个。

好在有个笨办法:每次写 goal 之前,假想它跑完了、过来找你验收,你会检查什么?把你会检查的东西原样写进合同。我的六要素模板,说穿了就是这个问题的展开。