Host-initiated goal pause aborts the live turn
宿主发起的 goal 暂停中止当前轮次
Clicking "pause goal" in the Web UI moved the goal to `paused` and disarmed automatic continuation, but the model turn already running kept going. The model could keep acting and call `update_goal resume` inside that same turn, immediately undoing the pause, so a manual pause had no real control over goal execution.
English
Problem
Clicking "pause goal" in the Web UI moved the goal to paused and disarmed automatic continuation, but the model turn already running kept going. The model could keep acting and call update_goal resume inside that same turn, immediately undoing the pause, so a manual pause had no real control over goal execution.
Decision
The goal round driver now reads the change on every goal/changed event. When operation === 'pause' and the pause was not initiated by the agent's own turn, the driver aborts the live turn with agent.cancel({ kind: 'user' }, { keepInbox: true }). The Web button runs outside any agent initiator boundary, while a model's update_goal pause runs with the agent as the current initiator; the driver distinguishes them with ctx.agents.currentInitiator() !== agent. The abort is intentionally broad — it stops any live turn, not just a goal round — because a manual pause is a strong "stop now" signal and disarming alone stops future rounds but not the execution already under way.
keepInbox preserves pending work. A queued goal round already fails the existing pre-step reservation check once the goal is disarmed, so it cannot run after the pause.
The idle handler that pauses a cancelled goal is fenced to the dropped attempt's exact { goalId, revision }. A resume bumps the revision, so a pause followed by an immediate resume — before the aborted turn converges to idle — is preserved instead of being re-paused by the stale cancelled attempt.
Alternatives considered
Cancel on every pause, including the model's own. Rejected: a model that pauses in response to a direct human request should finish its turn and report; aborting mid-tool-call cuts off that acknowledgment without adding control.
Put the cancellation in the goal service's pause. Rejected: pause is one shared entry point for host and model callers, so the service would still need the same initiator test. Keeping control handling in the round driver leaves the goal service a durable state and event owner.
Scope the abort to a turn actually running a goal round. Rejected: the live turn is the execution the user asked to stop, and the extra attempt-state check adds a subtle path without changing the outcome the issue asks for.
Consequences
A Web "pause goal" now aborts the running turn, so the model cannot keep acting or resume the just-paused goal in that turn. A pause followed by an immediate resume keeps the resumed goal running. Model-initiated pauses are unchanged. The change is confined to the round driver and its tests; the goal domain, tool authority, and durable formats are unchanged.
中文
问题
在 Web UI 点击「暂停目标」会把 goal 改成 paused 并解除自动续跑的武装(disarmed),但已经在跑的模型轮次不会停止。模型还能继续行动,并在同一个轮次里调用 update_goal resume,立刻撤销这次暂停,因此人工暂停对 goal 执行没有真正的控制力。
决策
goal round driver 现在会读取每个 goal/changed 事件里的 change。当 operation === 'pause' 且暂停不是由 agent 自己的轮次发起时,driver 用 agent.cancel({ kind: 'user' }, { keepInbox: true }) 中止当前轮次。Web 按钮运行在任何 agent initiator 边界之外,而模型调用 update_goal pause 时当前 initiator 就是该 agent;driver 用 ctx.agents.currentInitiator() !== agent 来区分两者。中止是有意放宽的——它会停掉任何正在运行的轮次,而不只是 goal round——因为人工暂停是强烈的「现在停止」信号,仅 disarmed 只能阻止后续轮次,停不掉正在进行的执行。
keepInbox 会保留待处理工作。一旦 goal 被 disarmed,已排队的 goal round 就会在既有的 pre-step reservation 校验里失败,因此暂停后不会再运行。
暂停被取消 goal 的 idle 处理器被栅栏限定到被丢弃 attempt 的精确 { goalId, revision }。resume 会推进 revision,因此在被中止轮次收敛到 idle 之前「暂停后立即 resume」会被保留,而不会被过期的 cancelled attempt 再次暂停。
考虑过的替代方案
对每次暂停都中止轮次,包括模型自己发起的。 否决:响应人类直接请求而暂停的模型应当完成本轮并给出回复;在工具调用中途中止只会截断这层确认,却换不来更多控制力。
把中止逻辑放进 goal 服务的 pause。 否决:pause 是宿主与模型共用的唯一入口,服务里同样需要这个 initiator 判断。把控制处理留在 round driver,可以让 goal 服务保持为持久状态与事件的拥有者。
把中止限定到真正在跑 goal round 的轮次。 否决:正在运行的轮次正是用户要求停止的执行,额外的 attempt 状态检查只会增加一条微妙路径,却不改变本 issue 要求的结果。
后果
现在 Web 的「暂停目标」会中止正在运行的轮次,模型无法继续行动或在同一轮次里恢复刚被暂停的 goal。暂停后立即 resume 会保留被恢复的 goal 继续运行。模型发起的暂停行为不变。改动局限于 round driver 及其测试;goal 领域、工具授权与持久化格式都不变。