第 9 章:Goal 目标驱动——持续推进,但绝不盲目循环
← 上一章:Hooks 与评估 · 下一章:Mini Emperor 综合项目
开场:装修队说「做完了」,你信吗?
你请了一支装修队。开工前签了合同,上面写得很清楚:三个月交付;验收标准——水电走线合规、墙面平整度两米内误差不超过三毫米、甲醛检测合格。
装修队干了两个多月,突然来电话:「我们做完了,你来看看吧。」
你到了现场,客厅确实在动工,墙刷了一半,地上堆着电线。装修队拍着胸脯说:「你看我们天天在干活,肯定没问题。」但这时候你真正想问的是:「忙」和「按合同完成」,是一回事吗?
你拿着合同逐项对:水电打压测试的记录在哪?墙面用两米靠尺量了吗?甲醛检测报告呢?装修队说「我们正在弄」——但你很清楚,没有这些证据,这句话等于没说。
反过来,装修队也会提意见:吊顶设计方案太贵,能不能换一种?可以,方案可以改。但合同里「甲醛检测合格」这条,不能因为他们改方案就悄悄删掉。合同是稳定的北极星,方案是可变的路线。
这一章就把「装修合同」翻译成 Agent 工程的 Goal。本章结束时,你能回答:为什么「持续干活」不等于「朝目标推进」,以及连续三次没进展时为什么必须暂停?
装修队「天天在干活」,为什么还不能说目标完成了?
- A:因为他们没有按合同逐项验收——忙不等于完成,要有证据
- B:因为他们干得太快
- C:因为合同没写验收标准
- D:因为进度条没有动
五个词,五个生命周期
聊「目标驱动」最容易栽的坑是术语混用。五个概念,各自的生命周期完全不同:
| 概念 | 精确定义 | 生命周期 | 示例 |
|---|---|---|---|
| Turn | 一次用户/模型输入到回复或工具循环结束 | 秒到分钟 | 「检查发布状态」 |
| Run | Runner 的一次执行实例,有唯一 ID 和事件序列 | 可含多个 turn | run-202 |
| Task | 可分派、可验证的工作单元 | 完成或失败后关闭 | 「运行保留集评估」 |
| Plan | 当前任务顺序与依赖,是可修改假设 | 遇到新证据即可调整 | 「先评估,再批准,再发布」 |
| Goal | 稳定的结果、范围、成功判据和边界 | 直到完成、取消或终止 | 「Skill 1.1.0 可安全发布」 |
一句话记住五个词:
Turn 是交互,Run 是实例,Task 是工作,Plan 是路线,Goal 是验收合同。
混用会带来一连串假判断:
| 错误说法 | 实际问题 |
|---|---|
| 「这一轮就是目标」 | 一个 turn 不足以承载长期验收 |
| 「程序启动了一次,所以任务完成」 | run 只是执行实例,不是成功证据 |
| 「计划写完了,目标就完成了」 | plan 是假设路径,不是结果 |
| 「等待审批也是失败」 | waiting 是可恢复状态,不应丢失上下文 |
| 「Agent 还在输出,所以有进展」 | 活动量不等于新证据 |
第 4 章你已经见过 Plan 与 Goal 的边界,这里把它放进五个词的坐标系里看:Plan 可以重排,Goal 不能被 Agent 偷偷改。 运行时间再长、输出再多、调用工具再频繁,都不能证明质量——这一章就是给「完成」加上证据门槛。
run-202 是下面哪个概念?
- A:一次用户与模型的交互(Turn)
- B:Runner 的一次执行实例,有唯一 ID 和事件序列(Run)
- C:一份验收合同(Goal)
- D:一个可修改的路线(Plan)
Goal Contract:把愿望写成能验收的合同
「我想发布一个客服 Skill」是愿望,不是 Goal。工程化的 Goal 至少是一份带范围、成功判据和停止边界的合同:
goal = {
"objective": "发布 customer-support 1.1.0",
"scope": ["candidate 1.1.0", "Skill Hub", "remote MCP"],
"success_checks": [
"deterministic_tests_passed",
"safety_regressions == 0",
"approved_by is not None",
"published_version == 1.1.0",
],
"limits": {
"max_no_progress_cycles": 3,
"max_cost": 5.0,
"deadline": "2026-08-03T18:00:00+08:00",
},
}四个字段各管一件事:objective 说清楚要达成什么;scope 圈定边界,防止目标悄悄膨胀;success_checks 是可逐项核对的验收清单;limits 给推进上锁——最多连续几次无进展、最多花多少钱、最晚什么时候。
Goal 的成功条件应尽量由测试、API 状态、校验和或人工批准记录来证明,不能只依赖模型自评。「我说做完了」是最常见的一种幻觉来源,上一章的 Verifier 就是为它准备的。
首选方案失败,Agent 换了一条路继续走。哪一层变了?
- A:Goal 层——验收标准变了
- B:Task 层——但 Goal 也会被偷偷改
- C:Plan 层——路线可以改;Goal 不应因 Plan 失败而改变
- D:Turn 层——但 Goal 同样会变
做、验、找缺口、重规划
有了合同,推进就不是「想到哪做到哪」,而是一条受控循环:
Do:执行当前 Plan 的下一项
Verify:收集独立证据
Gap:一次只选择一个最关键缺口
Replan:调整路线,但不偷偷改变 GoalVerify 是这条循环的发动机。 每做一步,都要拿合同对一下:哪个成功检查还没满足?有没有证据证明它满足了?「做了」不自动等于「满足了」,中间必须过验证。
Gap 一次只挑一个最关键缺口。 一堆缺口同时摆在面前,模型很容易挑简单的那个先做——但这未必是通往完成的唯一瓶颈。优先级应该来自 Goal Contract 的 success_checks,而不是模型当下的舒适区。
Replan 改的是 Plan,不是 Goal。 这是本章最硬的一条边界:验收标准若要改变,应由人明确修改 Goal Contract,并保存修订记录。Agent 自己不能因为某条路走不通,就把 success_checks 悄悄删掉一条。
什么才算「真进展」
现在回答开场的问题:「忙」到底算不算进展? 不算。进展不是「又执行了一次」,而是至少产生一项真实变化:
- 一个未满足的成功条件变为满足;
- 一个未知事实被证据确定;
- 阻塞原因被解除;
- 失败空间缩小;
- 产生了新的、可验证的产物。
反过来,相同命令、相同错误、相同假设连续出现,就是无进展周期。判断进展不能看活动量,要看结构化的证据差异——上一章 Verifier 产出的东西,正好用来做这件事。
暂停、等待、恢复、终止
长期任务一定会被打断:等人工审批、预算见底、用户改主意。每个状态都要有明确的恢复语义:
- pause:系统主动停止推进,状态和恢复点仍保留;
- waiting:pause 的特例,明确等待谁、等待什么外部事件;
- resume:阻塞证据到达后,从检查点继续,而不是重建整个任务;
- cancel:用户主动取消,不再自动恢复;
- terminate:预算耗尽、致命错误或安全策略触发的强制结束。
恢复必须匹配条件。 一个因「等待人工批准」进入 waiting 的 Goal,只有当 approval_received 这类证据到达时才能恢复;一个无关事件(比如「构建完成」)不应该能把它唤醒。否则重启后无法说清「这个 Goal 为什么被唤醒了」。
暂停与终止的差别是「还能不能回来」。 暂停保留状态和恢复点,条件满足就能继续;终止是终态,不再自动恢复。Mini Emperor 当前的 GoalTracker 诚实地说,只实现了 running / paused / completed / cancelled,没有 waiting 和 terminated——这是下一步工程扩展,不能把设计稿说成现有能力。
完整状态流转长这样:
推进算法则是把上面的循环再收紧一步——三次无进展就停下来,而不是无限重试:
读取 Goal Contract
→ 选择一个未满足的 success check
→ 生成或更新 Plan
→ 执行一个 Task
→ 保存 Run/Turn 事件
→ Verifier 产生证据
→ 有进展:清零停滞计数
→ 无进展:计数 +1
→ 连续三次:pause,不再盲目重试
→ 全部检查有证据:complete下面哪一项才算「真进展」?
- A:又调用了一次工具
- B:输出了更多文字
- C:程序运行时间更长
- D:一个未满足的成功条件变为满足,或被证据确定的事实
观察当前状态:从事件流看出它在哪
只凭「在跑」看不出 Agent 卡在哪。每次状态变化至少记录一份可诊断的快照:
{
"goal_id": "goal-17",
"status": "waiting",
"revision": 4,
"active_task": "publish-version",
"run_id": "run-202",
"last_event_id": "38",
"no_progress_cycles": 0,
"waiting_for": "course-maintainer approval",
"resume_condition": "approved_by != null",
"satisfied_checks": ["tests", "safety"],
"unsatisfied_checks": ["approval", "published"],
"last_evidence": "evaluation-1.1.0.json"
}五条观察规则能立刻暴露五种坏状态:
status=running但active_task为空 → 调度器可能卡住;status=waiting但没有resume_condition→ 无法可靠恢复;status=completed但unsatisfied_checks非空 → 完成判定有 bug;no_progress_cycles在真实进展后没清零 → 会误暂停;- resume 后必须产生新的 run,但继续引用同一个 goal 和检查点。
观察一个运行中的 Goal,你要能同时说出:它处于哪个状态、哪个检查还没满足、它等的到底是什么、恢复条件是什么、上次证据在哪里。
动手:让两个失败发生,再用一个测试收住
理论和手感之间隔着一道墙,亲手撞一次才会留下印象。下面两段代码都是故意写错的。
失败一:把「活动量」当「进展」。
# 错误:活动量不等于新证据
success_checks = {"tests_passed": False, "approved": False}
events = ["retry", "retry", "retry", "retry"]
for i, event in enumerate(events):
print(f"第 {i + 1} 轮:执行了 {event} → 有进展!") # ← 错:什么都没变
print("结论:四轮都在忙,但没有任何一项成功检查被满足")如果系统把这四轮记为 progress=True,说明你的进展判断依赖的是活动量而不是证据。真实的 GoalTracker 只认 record(progress=True)——真进展必须伴随结构化差异。
失败二:任何事件都能把一个 waiting 的 Goal 唤醒。
# 错误:恢复不核对条件
goal = {"status": "waiting", "resume_condition": "approval_received"}
def resume(goal, event):
goal["status"] = "running" # ← 错:没检查 event 与 resume_condition
return "已恢复,继续执行"
resume(goal, {"build_finished": True}) # 一个无关事件也把 Goal 唤醒了
print("Goal 状态:", goal["status"])
print("但人工批准从未到达——恢复条件根本没满足")恢复是门禁,不是开关。resume() 必须核对 resume_condition 与事件类型,否则重启后任何信号都能唤醒不该醒的 Goal。
跑完后用现成测试验证「真进展清零停滞计数」这条机制真实有效——它是「三次无进展暂停」的镜像:真进展必须能让计数器回到零:
cd "$(git rev-parse --show-toplevel)"
uv run --python 3.12 --extra dev pytest \
mini-emperor/backend/tests/test_evolution.py::test_goal_progress_resets_stall_counter -v如果它不过,按这个顺序排查:
- 计数没清零 →
record(progress=True)有没有把no_progress_cycles置 0; - 真进展后仍被暂停 → 清零逻辑是否在达到
max_no_progress之前执行; - 测试根本没跑到 → 文件名与函数名是否一致、路径是否在
mini-emperor/backend/tests/下。
门禁·代码:跑通上面这条测试,把输出保存为证据。
本章小结
这一章把「持续干活」升级成了「朝目标推进」。五件事连起来看:
五个词,五个生命周期。 Turn 是交互,Run 是实例,Task 是工作,Plan 是路线,Goal 是验收合同。混用任何一个,都会产生假判断。
Goal 是合同,不是口号。 objective、scope、success_checks、limits 四件套,让「想做什么」变成「怎么验收」。成功条件尽量用测试、API 状态、校验和或人工批准记录证明,不能只信模型自评。
推进要过循环。 Do → Verify → Gap → Replan,Verify 是发动机,Gap 一次只挑最关键缺口,Replan 改路线但绝不偷偷改 Goal。
进展看证据,不看活动量。 一个成功条件被满足、一个事实被证据确定、阻塞被解除、失败空间缩小、新产物出现——都算进展;相同命令、相同错误连续出现就是无进展。
三次无进展必须暂停。 相同输入、相同错误没有新信息,继续执行只会浪费预算。暂停不等于失败,恢复必须匹配条件;终止才是终态。
一句话记住这一章:
有合同(Goal),有路线(Plan),有证据(Verify),有刹车(三次无进展暂停)——缺一样,Agent 就分不清「在忙」和「在推进」。
如果上面这些话你自己也能说得出来(不看资料),就说明这一章的知识已经连成一片。
复述与证据
合上资料,画出五个概念的层级:Turn → Run → Task → Plan → Goal,标出各自的生命周期和结束条件。再画出推进循环:Do → Verify → Gap → Replan,标出证据产生在哪一步、暂停插在哪一步。
再追问四个问题:
- 为什么 Plan 可以改,Goal 不能被 Agent 偷偷改?
- 证明一次循环产生进展,至少需要什么?
- pause、waiting、cancel、terminate 的恢复语义分别是什么?
status=completed但unsatisfied_checks非空,说明什么坏了?
门禁·证据:提交一份 Goal 状态快照(含 status、active_task、no_progress_cycles、satisfied_checks、unsatisfied_checks、resume_condition),并说明它为什么还不能标记为完成。
24 小时后:不看资料,写一个 GoalTracker 的最小扩展——加 Goal ID、success checks 与证据字段,并让 complete() 在没有通过证据时抛错。
有兴趣可以继续看
以下资料按优先级排。至少完成一项 P0,并保存完成证据。
视频P010 分钟 · 视频 09 [00:00:00–00:02:19] 长时间运行不等于完成,再看 [00:04:50–00:07:35] 完成与等待人工
- 预计
- 10 分钟
- 位置
视频 09 [00:00:00–00:02:19] 长时间运行不等于完成,再看 [00:04:50–00:07:35] 完成与等待人工- 阅读任务
- 暂停视频,分别写出 Goal、Plan、Verify、Waiting 的职责。
- 完成证据
- 提交一份含结果、范围、成功检查和停止边界的 Goal Contract。
教学仓库P025 分钟 · templates/agent/identity.md 的 Goal Tree 规则
- 预计
- 25 分钟
- 位置
templates/agent/identity.md 的 Goal Tree 规则- 阅读任务
- 检查目标树如何拆分子目标,并为每个叶子补一个可执行检查。
- 完成证据
- 说明目标分解为什么仍不能替代验证。
书与实验P050 分钟 · chapter4/async-agent/runtime.py 与 demo.py
- 预计
- 50 分钟
- 位置
chapter4/async-agent/runtime.py 与 demo.py- 阅读任务
- 依次运行 state、interrupt、parallel 三个子命令,映射到 run、pause、task。
- 完成证据
- 保存三个命令的输出,画出中断前后的状态变化。
工程P030 分钟 · mini-emperor/backend/src/mini_emperor/goals.py
- 预计
- 30 分钟
- 位置
mini-emperor/backend/src/mini_emperor/goals.py- 阅读任务
- 跑通三次无进展暂停和进展清零测试,再列出现有状态机缺口。
- 完成证据
- 提交测试输出与一张扩展状态图。
论文P145 分钟 · ../source-notes/papers/classic-papers.md 的 Reflexion 与 Self-Refine
- 预计
- 45 分钟
- 位置
../source-notes/papers/classic-papers.md 的 Reflexion 与 Self-Refine- 阅读任务
- 比较基于轨迹反思与迭代反馈,找出它们仍需外部成功判据的原因。
- 完成证据
- 写出一个「模型自评会产生假进展」的反例。
本文提到的代码与出处(有兴趣逐条核对时用):
claude-agent-examples@54a18980334541773f13940aa0c0475728d30ee0:ppt/第九期-目标驱动agent.htmlclaude-agent-examples@54a18980334541773f13940aa0c0475728d30ee0:templates/agent/identity.mdai-agent-book@1c18370279f8f0457bf2c44dfa585d08d4d5f281:chapter4/async-agent/runtime.pyai-agent-book@1c18370279f8f0457bf2c44dfa585d08d4d5f281:chapter4/async-agent/demo.pymini-emperor/backend/src/mini_emperor/goals.pymini-emperor/backend/tests/test_evolution.py