← 返回文章

/goal 是给盲眼裁判判题:一个隐喻打通三件实践

原创/技术

你写了第一个 /goal:

/goal 修复登录 bug

按下回车,agent 开始干活。半小时后回来一看,对话已经跑了 20 多轮,每一轮都在说「再确认一下」「再加一段保护」。它没在循环,它在飘。

第二次换个写法:

/goal 部署到 staging 成功

这次 agent 三轮就停了,说部署完成。点开 staging,404。

这两个失败有同一个根:你写的不是给 Claude 看的指令,而是给一个根本看不见现场的裁判判的题目。

/goal 不是循环机制,是判题机制

主流叙事是这样的:/goal 让 Claude 跨轮工作直到条件满足。

这描述没错,但完全错过了它的结构。/goal 的真实形态是:

每轮主 turn 结束,一个独立的小模型(默认 Haiku)被调起来,拿到你写的 condition 和当前对话历史,判 yes/no。yes 就停,no 就把它的理由当成下一轮的 directive 甩给主模型继续干。

这个小模型有个核心特征:它不能调用任何工具。

不能跑 bash。不能 read file。不能 git status。不能 curl。它就是个纯文本判断器,输入是对话流,输出是 yes/no 加一句理由。

把它叫做盲眼裁判 —— 它有判决权,但什么都看不见。

一旦这个隐喻立起来,三件你听过但分散的实践,全部归一到「让裁判看得见」这一个问题上。

裁判能看到什么、看不到什么

能看到看不到
你之前发的所有 prompt任何文件的真实内容(即使 Claude 改过)
Claude 的所有文字回复shell 命令的真实输出(除非 Claude 把它 echo 出来)
Claude 工具调用的返回值git 当前状态
Claude 主动写的验证报告数据库、网络服务、CI、文件系统

这不是 bug,是物理约束。evaluator 跑在另一个调用里,没有工作目录权限,也不应该有 —— 否则每轮都跑一遍测试,成本和延迟会爆炸。

「修复登录 bug」失败是因为:完成的标志是 bug 真的不见,但裁判读到的只是「Claude 说 bug 修好了」之类的字符串。信不信全凭它对对话流的解读,没有可信信号。

「部署到 staging 成功」失败是因为:部署成功是个外部状态,裁判完全看不到 staging。它只能根据 Claude 自己说的「部署完成」判断。Claude 跑了 kubectl apply,stdout 说 created,它就当成功了。但 created 不等于 rolled out,pod 可能还在 CrashLoopBackOff。

推论一:condition 用状态写,不要用动作写

动作和状态的差距不是文风差距,是可判性差距。

动作(难判)状态(好判)
修复登录 bugtests/auth.test.ts 全部通过且 git status clean
重构这个模块src/payment/ 下每个文件 ≤300 行且 lint clean 且原有测试全过
让性能更好bench/login.bench.ts 跑出 p95 ≤ 200ms(baseline 320ms)
整理文档docs/ 下每个 .md 首行有 H1 且最后修改时间 ≥ 今天
优化这个 SQLEXPLAIN ANALYZE 的 Total Cost ≤ 5000(原值 23000)

右列每一条都满足同一个模板:

主体 + 动词的完成体 + 量化谓词

主体(哪些文件 / 哪些 metric)告诉裁判去对话里找什么。完成体(已通过 / 已满足 / 处于 X 状态)让「做完了」二值化。量化谓词(≤ 阈值 / 包含字段 / 等于值)杜绝主观判断。

为什么状态比动作好用:

  • 离散。yes/no 二分不需要「判断质量」
  • 自带验证手段。写「p95 ≤ 200ms」的同时你已经被迫想清楚怎么测
  • 抗 forward-motion bias。动作描述让 agent 觉得「我做了几件事 = 完成」;状态描述要求那几件事真的产出了那个结果

日文社区有句话总结得很准:「『修正されていること』の方が判定しやすいんだわ」—— 被修正过的状态,比「去修正」更容易判定。

推论二:所有证据必须落进对话流

condition 写成状态了,但裁判还要看到证据,否则只能信 Claude 的总结。

/goal config.yaml 里 enable_v2 字段为 true

Claude 改了文件,回复「已修改」。裁判看到字符串「已修改」,判 yes,结束。但配置可能根本没改对,YAML 里可能还有第二处 enable_v2: false

修法:

/goal config.yaml 里 enable_v2 字段为 true,
       完成后必须运行 `grep enable_v2 config.yaml` 并贴出输出

现在 enable_v2: true 这行以 stdout 形式出现在对话里,裁判读到的是原文,不是 Claude 的措辞。

测试通过的 condition 一样:

/goal 所有测试通过

—— 改成:

/goal `npm test` 退出码 0 且 stdout 末尾包含 "Tests: X passed, 0 failed",
       不要压缩测试输出

部署成功更典型:

/goal 部署到 staging 成功

—— 改成:

/goal 跑 `kubectl rollout status deployment/api -n staging`,
       stdout 包含 "successfully rolled out" 且退出码 0

每个 condition 都要补上「必须把验证命令的原始输出贴回」这一步。检验窍门只有一个:

只给一个没有运行权限的人看这段对话,他能判定吗?

不能就回去补证据通道。

推论三:长任务需要外部存档

condition 写好了,证据通道也开了,下一个问题是任务跑长了。跑到 30 轮,对话被 compaction,前面的状态全压成摘要,agent 不记得自己干到哪。或者 session 重启了,需要 --resume,但 turn 计数和 token baseline 全部归零。

这就是 PLANS.md 存在的理由。

社区流传的「三件套」(PLAN.md / EXPERIMENTS.md / NOTES.md)其实是简化版误传 —— canonical source 是 OpenAI cookbook 的单文件 PLANS.md + 4 个固定 section 方法论:

Progress

- [x] (2026-05-12 14:30Z) auth/login.ts 迁移到 v2 API
- [ ] auth/logout.ts 迁移(完成: 删旧调用; 剩: token 刷新)
- [ ] auth/refresh.ts 迁移

每个停顿点更新,时间戳 + checkbox。做了一半就停了的项,拆成两项写。

Surprises & Discoveries

- Observation: v2 API 的 token 字段从 access_token 改成了 accessToken
  Evidence: /v2/refresh 返回 {"accessToken": "..."}, 旧测试期望 access_token

写「观察 + 证据」对,不写主观判断。证据要可粘贴。

Decision Log

- Decision: 保留旧 v1 endpoint 作为 fallback 6 个月
  Rationale: 移动端有 12% 流量未升级,发布节奏对不上
  Date/Author: 2026-05-12 / claude session abc123

Outcomes & Retrospective:阶段性或完成时写一次,对比原始目标 vs 实际产出,记 lessons learned。

PLANS.md 不是文档卫生。它是裁判的外接记忆。 Claude 每轮 cat PLANS.md 把 Progress 输出到对话里,裁判就能读到当前进度,即使前 25 轮已经被 compaction 抹掉。这同时也解决了 --resume 后所有计数归零的问题 —— 计数会丢,但 PLANS.md 不会。

4 个 section 不是工整摆设

为什么是这 4 个 section 而不是 3 个或 5 个?因为它们构成一个完整的认知闭环:

Section干什么缺这一节会发生什么
Progress做事失忆 —— 不知道做到哪了
Surprises & Discoveries学习重复踩坑 —— 同一个 API 坑掉进两次
Decision Log决策反悔无据 —— 后面走错路,但不知道当初为什么选这条
Outcomes & Retrospective复盘经验不沉淀 —— 下次类似任务从零开始

做事 → 学习 → 决策 → 复盘,是 PDCA 在 agent 协作里的最小实例。少一节不是少了点信息,是少了一类完全不同的信息。

标准 condition 模板

把三个推论合在一起,一个能跑的 /goal 长这样:

/goal 按 PLANS.md 的 Progress 清单跑到所有项 checkbox 变 [x],
       期间不修改 src/legacy/ 下的文件,
       每完成一项更新 Progress 时间戳,
       append 任何意外到 Surprises & Discoveries,
       npm test 退出 0 视为单项通过,
       最后 append 一节 Outcomes & Retrospective,
       或 30 turns 上限

拆开看:

  • 按 PLANS.md ... 变 [x] —— 状态化谓词
  • 不修改 src/legacy/ —— 安全护栏
  • 每完成一项更新...append 到 Surprises —— 强制证据落进文件,文件被 cat 进对话流
  • npm test 退出 0 视为单项通过 —— 证据通道
  • append Outcomes & Retrospective —— 收尾节点
  • 30 turns 上限 —— 防暴走兜底

一句话检验

下次写 /goal 之前,把 condition 念一遍,问自己:

盲眼裁判靠这段对话能判吗?

能 —— 跑。 不能 —— 这条 condition 还没写完,回去给它补眼睛。

评论 (0)

加载中...