Skip to main content
目标模式(goal)是会话模式的第四档。切到这一档之后,你说的第一句话就是目标, 模型跨轮自治地把它做完,中途不需要你点「继续」,直到停机阀叫停为止。
goal 拿的是完整工具集(含 write/edit/bash),不含 plan 三件套。 理由很直接:目标模式的价值是「放手做完」,给它只读工具就退化成 plan 了。

目标是一个显式状态机

goal 不是「跑到底」的布尔标志。终态和暂停态必须可区分, 全部迁移收敛在 transitionGoal 里,非法迁移返回「没生效」而不是抛异常—— 绝不让一次状态竞争毁掉整个回合。

两条停机阀 + 你输入即接管

判定收敛在一个纯函数里,逐条短路,顺序即优先级:
1

provider 报错或被中止

立即暂停。
2

轮次上限

超过这条目标的 maxAutoTurns(默认 300,0 = 不限)就暂停。 上限挂在每条目标上而不是全局设置——「补个 README」和 「把整个鉴权重构完」差两个数量级,全局值注定对一半任务是错的。
3

无进展停滞

连续 3 轮零实质工具调用、且输出指纹不变 → 暂停。 指纹是「本轮全部文本 → NFKC 归一 → 去空白 → sha256」, 模型卡住时的典型表现正是把同一句话换几种标点再吐一遍。
轮次只在结算时 +1:上下文溢出时同一轮会用原文本重跑, 在注入时就计数会让轮次凭空多算。 你输入即接管:goal 档里有 active 目标时,你发的任何消息都会把它暂停 并重置安全轮次计数——你中途发消息通常是在纠偏,这条消息本身已经占掉一轮上下文, 模型拿到的是全新起点。

过期护栏(stale-turn guard)

两个出口工具 goal_complete / goal_blocked 的 goal_id 是必填参数, 执行时与当前目标比对,不一致就拒绝并把当前目标原文回给模型。 这不是防御性编程,是真实竞态:模型的 goal_complete 在工具调用排队期间, 你可能已经发了新消息或换了目标。没有 id 比对就会出现 「旧目标的完成把新目标标成完成」——而这个 bug 的症状是静默的, 日志里什么都看不出来。

token 账是现累的,不是从转录重算的

Goal.tokensUsed 单调递增:每个轮边界把 run 上攒的增量折进来,只加不减。 早期实现是「会话累计 − 建目标时的基线」,有三个毛病,换掉的理由分别是:
子代理是独立 Agent、独立上下文,用量从不写进父会话转录。 而 goal 档的工具表带 Task 组,也就是鼓励委派—— 「目标靠子代理干完了大半活」时读数会严重少报。 现在子代理自己记账,结算时汇给父 run,重复结算被状态守卫挡住。
基线只在建目标时取一次,resume 不重取——你在别的模式里干的活 全被记在这条目标账上。现累的增量只在「目标是 active 且这一轮正在跑」时累加。
旧实现每个轮边界都全量重读转录文件,300 轮的目标就是 300 次全长重读, 文件还在变长。现累是四个加法。
口径是四项相加(input + output + cacheRead + cacheWrite),与用量统计页同源。 含 cacheRead 意味着长循环里这个数会比直觉大一个数量级—— 这是全局口径,不是目标模式特有的偏差,单改一处会让两个页面数字对不上。

为什么没有 token 预算

曾经有第四道阀:目标可带 token 预算,超限转入收尾。它被整体删除了, 因为设计上就说不清:
  • 你无法预判。一个「把测试跑通」的目标可能花 20k 也可能 2M, 预算填多少都是猜。填小了目标被腰斩,填大了等于没设。
  • 它按累计量截断,不看进度。同一目标第 4 轮和第 24 轮花掉的 token 可能一样多,预算并不知道该在哪停。
  • 收尾轮本身还要再烧一次模型请求去写完成说明,而此时你已经看不到 「还剩多少没做完」这个事实。
所以 tokensUsed 降级为纯展示字段(常驻条上一个计数,悬停注明 「仅供参考,不参与停机判定」)。想省配额的正确反应是让人看见消耗再自己决定, 而不是让程序在你没定的数字上自动刹车。

停下之后,提示词必须跟着改口

一个很隐蔽的坑:模式段 GOAL_MODE_PROMPT 是给 active 状态写的, 通篇是「别收手、别问、系统会自动续下一轮」,而它不随状态变化。 如果目标暂停后提示词不改口,模型会同时读到 「你正在自治循环里,别停下来问」和「目标已暂停」—— 而后者如果不存在,模型就继续埋头干目标,而界面上明明写着「已暂停」。 你看到的是「上面说暂停了,对话还在进行中」。 修复前正是如此。现在 goalPromptBlock 是状态敏感的: 这也是为什么「你输入即接管」这条阀看起来生效了(状态确实变了)却没真的接管: 状态机停了,提示词没停。

状态是 active ≠ 循环在跑

这是本模块最容易踩的一条,一次踩出过三个 bug (「继续」按钮点了没用、切档后卡死、重启后谎报进行中), 共同症状都是「条上说在跑,实际什么都没跑」。 唯一的循环驱动源是:有一个 run 正在跑,并且它走到了回合边界。 所以把状态改成 active 不会启动任何东西;一个已停的目标不会被任何定时器唤醒。 凡是「让目标重新跑起来」的入口,都必须自己补起那一轮。

下一步

目标模式鼓励委派——看看子代理是怎么设计的。