4.4 KiB
Agent Note: 锁存取消收敛窗口内到达的唤醒请求
Status: implemented
English | 中文
问题
Agent.cancel(cause, { keepInbox: true }) 在触发 abort 信号后立即返回,但活动 driver 可能尚未收敛到 idle:LLM 流拆除、工具取消与 turn/end 落盘都会在 abort() 返回后异步展开。在该窗口内到达的唤醒 send 被放入 next-turn,而 wakeDriver() 对仍处于 running 的 phase 直接返回,退出的 driver 也从不重放这次唤醒——消息会一直停放到下一条唤醒 send 到达。被中止的 runMaintenance 活动周围也存在同样的唤醒丢失窗口。多个测试固化了停放行为(「等待下一次唤醒」);该缺陷同时破坏了 session.cancel 与 subagent.interrupt 组合路径(issue #1838)。拥有取消与发送约定的既有决策是显式轮次取消与统一发送;生产环境中的 keepInbox 消费方是Web 停止保留队列。
决策
running phase 携带 wakeRequested 锁存,与既有的 maintenance phase 字段对称。wakeDriver() 在当前活动无法投递唤醒时锁存——maintenance 任务从不读取队列,被中止的活动收敛后不会重启——而存活的 driver 不需要锁存,因为它自己会认领排队的工作。退出中的活动在其自身收敛边界(kick 的 finally 与 runMaintenance 的 finally)重放锁存:这一位置保证 turn/end N 先于重放 driver 打开 turn/start N+1 落盘,并保证 whenIdle() 通过其 activityDone 循环看到重放 driver。两个重放点仅在 inbox.hasPending 时执行,因此收敛前被从 inbox 移除的锁存唤醒不会启动空 driver。而 agent 已处于 idle 时发送的唤醒,即使消息在 driver 认领前被清除,仍会打开自己的 turn 边界——这趟 idle → running → idle 转换是可观察约定:goal-session driver 的 pause/disarm 回退依赖取消预订后的 idle 转换触发(把守卫放进 wakeDriver() 后该边界被抑制,CI 发现了这一点)。不带 keepInbox 的 cancel() 会连同 inbox 一起清除锁存。
signal.aborted 判别项是承重的:它区分「中断前已排队的工作」——keepInbox 将其停放以待后续唤醒(验收条件 1)——与「abort 后显式的唤醒」,后者必须在收敛后执行。
备选方案
让 cancel() 立即把 phase 置为 idle。 不予采用:driver 仍在展开收尾,这会重叠两个 driver。重放逻辑位于旧 driver 的 finally,而该 finally 此后不再执行——83 个测试中有 14 个失败,多个死锁。修复它需要基于身份的 phase 所有权外加 turn 打开时的 quiescence 屏障,机制上严格更重,而且该屏障就是换了个形态的锁存。
对每个非 idle 唤醒无条件锁存。 不予采用:中断前的唤醒会在 keepInbox 取消后自动启动,违反验收条件 1;「停放排队工作」测试与错误窗口的 steering 测试双双失败。
通过链式 promise(activityDone.then(...))重放。 不予采用:重放会运行在活动自身结算之外,whenIdle() 的循环可能在重放 driver 启动前就 resolve;修复它需要在 send 时同步替换 activityDone,并依赖微任务反应顺序——比同步 flag 更脆弱。
在 subagent adapter 中等待 quiescence。 被 issue 范围否决:修复由取消/唤醒状态机拥有,而不是消费方。
影响
running phase 新增 wakeRequested 字段;不带 keepInbox 的 cancel() 会连同 inbox 一起清除它,且 disposed 取消从不锁存——dispose 开始后到达的唤醒保持停放,whenIdle() 不会在拆除中的会话上等待一个完整模型 turn。落在 driver 最后一次 hasPending 检查与退出之间微任务间隙的唤醒仍会停放——没有锁存触发,因为 phase 是 running 且未 abort;关闭该间隙需要无条件锁存,刻意留作范围外。在被中止的 turn 与重放 driver 之间,状态转换会发出一次瞬态 idle → running 对。唤醒 send 的消息在任何 driver 认领前被清除时,仍会打开一个空的 completed turn,保留可观察的唤醒边界。