取消语义:Abort Signal 不能撤销已经发生的副作用

自动关联目录:取消语义:Abort Signal 不能撤销已经发生的副作用

核心判断:取消只表示调用方不再等待;工具是否停止、外部效果是否发生和能否重试是三个不同事实,必须分别记录。

一、业务冲突

用户点击停止时,模型请求、并行读取、文件写入、Shell 子进程、编辑器命令和远端生成任务可能处于不同阶段。若统一标成 Cancelled,已经提交的副作用会被误判为未发生,下一次自动重试就可能重复写入、重复启动或重复生成。

二、实际实现

实际调度器已经区分三类结果:尚未派发的调用是 Cancelled Before Dispatch;已启动且在取消或超时后正常收敛的调用返回中断结果;副作用工具在 Drain Deadline 前不确认终态则记录 Unknown Outcome。

副作用派发前写入持久检查点。恢复时,同一幂等身份若已有成功终态就复用结果;若只剩 Dispatch Checkpoint 或 Unknown Outcome,就要求 Reconcile,禁止自动重放。父 Agent 可以取消整个子树,子 Agent 失败则不默认反向取消父级和兄弟节点。

三、值得保留

这是成熟的取消模型:停止等待不被伪装成撤销,未知结果也不会被压成普通失败。按树向下传播取消、为每个工具创建独立 Abort Signal、等待有限 Drain,再持久化唯一终态,都是长任务可靠性的核心。

四、结构性缺陷

只有真正消费 Abort Signal 并清理子进程、网络请求和编辑器任务的工具才会停止;Promise 先返回取消,不代表工作体已经结束。若工具缺少查询接口和外部幂等键,Unknown Outcome 最终仍只能人工判断。旧执行路径与新调度器并存时,两套超时、分阶段和回滚语义也可能不一致。

五、目标设计

每个副作用工具都实现同一恢复协议:

interface EffectTool {
    PreparedEffect Prepare(Call call);
    EffectHandle Dispatch(PreparedEffect effect, IdempotencyKey key);
    EffectStatus Query(EffectHandle handle);
    CancelResult RequestCancel(EffectHandle handle);
    Receipt Reconcile(EffectHandle handle);
}

switch (Query(handle)) {
case Committed:  return ReuseReceipt(handle);
case Aborted:    return RetryAllowed;
case Running:    return WaitOrRequestCancel;
case Unknown:    return ManualOrToolSpecificReconcile;
}

取消策略由 Effect 类型决定:只读可以丢弃结果,文件写使用事务提交,远端生成持有任务句柄,发布类动作必须对账。

六、取舍与验证

可查询任务和幂等回执需要工具服务配合,但这是可靠自动化的必要成本。MCP 在 2026-07-28 版本中把长任务建模为显式 Task Handle,并提供获取、更新和取消流程,也体现了“取消需要可观察任务状态”的方向。MCP Tasks

故障测试要在 Prepare 前、Dispatch 后、效果提交后但响应前、取消后仍运行、进程崩溃和恢复重放等位置注入失败。唯一可接受的不变量是:每个调用都有一个可对账终态;Unknown 不自动重试,Cancelled 不冒充 Rolled Back。