7.7 KiB
RFC:显式轮次取消能力
Status: implemented
English | 中文
问题
取消是一种生命周期短于 Agent(智能体)驱动器的控制能力。自由文本字符串无法穷尽地区分调用方,步骤级控制器也无法中断提示词提交、提示词组装、继续决策或轮次终止策略。持久化 Error、AbortSignal.reason 或后端私有对象还会向持久化回放暴露不稳定的运行时细节。
Agent 执行上下文决策有意让 AsyncLocalStorage 帧保持为 { agent }。若把轮次、步骤或 signal 状态加入这个与驱动器同生命周期的帧,陈旧的异步后代就会看似仍对后续轮次拥有权限。因此,取消需要一个轮次归属方并显式传播,且不创建另一套环境上下文或公开的轮次包装层。
决策
Agent 拥有仅用于运行时的 AgentCancelCause 联合类型 { kind: 'user' } | { kind: 'parent' };agent.cancel() 默认使用 user。规范化边界只接受恰好包含一个受支持 kind 的普通对象或原型为 null 的对象,并返回供当前轮次 signal 使用的、与调用方分离且已冻结的值。即使 Agent 处于空闲状态,字符串、额外字段或符号字段、未知 kind、数组、类实例、Error 和 AbortSignal 也会被同步拒绝。
正在运行的轮次被中断后,以粗粒度的持久化结果 { kind: 'aborted' } 结束。终态事件记录轮次发生了什么,运行时 signal 标识谁请求了取消;回放不会重复保存 user 或 parent。未来若有审计需求,应使用独立的控制请求事件,让请求与最终结果保持为两项事实。持久化事件不包含调用栈、signal、错误对象、自由文本取消原因或后端私有细节。
AgentLoop 为每个待启动轮次私有地持有一个 TurnCancellation。它在通知 agent/status = running 前安装该持有者,使其中唯一的 AbortController 持续覆盖提示词处理、提示词组装、每个步骤、模型与工具执行、继续决策、agent/turn-stop、turn/end 和持久化刷新,随后清除该持有者。所有参与的方法、事件和请求值都会收到同一个显式 signal;下一个轮次会收到全新的 signal。
对于轮次被认领前已取消的排队工作,驱动器只保留一个不携带取消原因的运行前标记。它会清除 cancel() 调用时已存在的排队工作和 steering(中途引导)工作,但不会预先取消未来的提示词。若 running 监听器同步取消旧工作并发送替代提示词,驱动器会丢弃已中止的持有者,并为替代提示词创建全新的持有者。同一活跃持有者上的重复取消遵循首次请求优先,后续调用仍可清除新入队的待处理工作。
显式事件签名保留位置参数形式,并把 signal 放在 waterfall(瀑布式事件)的最后一个参数 next 之前。提示词提交、请求配置、步骤结果处理、继续决策和终止停止加入已有的步骤前处理、会话前缀、模型生成、工具执行、审批以及 subagent 或工作流请求的显式 signal seam。SystemPrompt.assemble() 在 AssembleContext 中携带 signal?: AbortSignal,因为该对象是显式请求值。监听器可以配合该 signal 取消,但不得保留它来控制其他轮次。
ctx.agentExecution 仍只提供身份。环境中的 Agent 并不代表存活、当前轮次或取消权限,agentInterruptReasonOf(signal) 也只读取其显式参数。并发 Agent 会同时隔离各自的 ALS 身份和轮次 signal;子 Agent 会遮蔽父 Agent 身份,而父请求 signal 仍通过 subagent seam 传递。
Agent dispose(资源释放)会在活跃持有者上请求仅用于运行时的 { kind: 'disposed' } 中断。若取消已经先占用控制器的中断原因,该原因便无法改写,因此终态分类会先检查生命周期状态:资源释放结果优先,之后受支持的 user 或 parent 取消原因形成粗粒度的中止结果,其他异常保留现有错误路径。ACP(Agent Client Protocol)取消映射为 user;进程内 spawn 和 fork 的传播映射为 parent。远程 ACP subagent 保持现有协议。
取消仍然是协作式的。AgentLoop 会在异步等待边界前后检查中断,但不会用 Promise.race 放弃进程内监听器、适配器或工具 Promise。忽略 signal 的工作必须真正结算,whenIdle()、句柄 dispose 和作用域清理才会报告静止状态。
验证
契约测试验证严格的运行时取消原因校验、冻结且与调用方分离、默认行为与首次请求优先行为、粗粒度的会话 JSON 往返、ACP user、进程内 subagent parent 以及 dispose 优先级。AgentLoop 测试让协作式监听器在提示词提交、系统提示词组装、会话前缀、步骤前处理、请求、模型流、步骤结果、工具执行、继续决策和终止停止处等待 signal;并断言同一轮次使用一个 signal,不同轮次使用全新的 signal。
执行上下文测试断言所有钩子仍只观察到 { agent },并发 Agent 保持独立的身份与 signal,嵌套子 Agent 创建只遮蔽身份。竞态测试覆盖空闲状态取消、运行前取消、从 running 监听器提交替代提示词、重复取消以及取消与 dispose 竞争下的静止状态。
考虑过的替代方案
把 signal 存入 ALS。 ALS 会在整个驱动器生命周期内跟随异步后代,而取消权限在一个轮次结束时就已终止。泄漏的回调可能观察到陈旧 signal,或者迫使实现替换为可变帧,因此身份帧保持 { agent },控制能力继续显式传递。
持久化自由文本原因。 字符串允许拼写漂移、阻碍穷尽分支判断,还会鼓励消费方解析展示文本。运行时使用封闭的可辨识联合类型,终态记录只需要稳定的中止结果。
在 turn/end 中持久化类型化调用方取消原因。 当前没有任何生产环境中的回放、UI、ACP、遥测或工作流消费方区分 user 与 parent。把请求来源复制到终态结果会混淆两项事实,还会在没有消费方的情况下引入会话特有校验;未来的审计接口可以记录独立的取消请求事件。
现在就定义推测性的 superseded、timeout 和 shutdown 变体。 当前没有 Agent 取消生产方实现这些语义。shutdown 已经属于生命周期 dispose;超时或替代只有在拥有明确归属策略和唯一终态含义时才应进入联合类型。
公开轮次或步骤上下文包装类型。 现有位置参数 seam 已经标识 Agent、轮次和步骤。包装类型会加宽所有 API、重复归属,并诱导调用方把捕获的对象当成持久权限。
在宽限期后放弃不协作的工作。 同进程工作仍在运行时就报告空闲状态,会破坏资源清理与资源归属保证。硬终止需要 worker 或进程隔离边界,不属于该控制 seam。
后果
取消拥有一个运行时归属方、每个轮次一个 signal,以及一套类型化的运行时调用方词汇。会话保留其消费方实际使用的粗粒度 aborted 结果,与运行时对象保持隔离,也不再需要取消专用的规范化逻辑。协作式取消覆盖每个异步轮次 seam,包括第一个步骤之前和最后一个步骤之后的工作。
显式 signal 会给多个公开事件增加参数,并要求插件有意识地转发取消。这是有意设计:权限在调用边界可见,生命周期与轮次匹配,陈旧的环境异步后代无法获得控制能力。不协作的进程内工作可能延迟取消,但所报告的静止状态仍然真实。