3.9 KiB
Agent Note: 保留单一公开停止原语
Status: implemented
English | 中文
问题
公共 Agent handle 暴露了两种相互重叠的在途工作停止方式:仅针对步骤的 abort() 和感知队列的 cancel()。前者保留已排队输入,后者原本只暴露广义默认行为,该行为会清除已排队和 steering(中途引导)工作,同时中止活动轮次。cancel(cause, { keepInbox: true }) 现在无需暴露私有轮次 holder 即可覆盖生产环境的 Web 停止策略;ACP 保留广义取消,生命周期拥有者则通过 AgentHandle.dispose() 拆除 agent(智能体)。没有生产调用方需要一个裸的、仅针对步骤的 abort。
行为差异确实存在,但已发布代码不需要独立的更窄动词。AgentLoop 为整个轮次拥有一个私有取消 holder。cancel(cause, options?) 携带显式且类型化的 user 或 parent 原因;其广义默认行为丢弃待处理输入,keepInbox 则为后续轮次保留待处理工作。资源释放仍是单独的生命周期中断。完整的归属与传播约定位于显式轮次取消 Agent Note。
多余的公开接口使循环承载了一个本质上属于内部拆卸的公开动词。带选项的 cancel() 可以表达调用方策略,而无需暴露第二个 holder 形态的操作。
决策
cancel() 是 Agent 上唯一的公共停止原语。生命周期拥有者使用 AgentHandle.dispose() 停止并注销 agent;非拥有者使用广义 cancel() 放弃当前和已排队工作,或使用 keepInbox 中止活动轮次并保留待处理工作。实现保留一个私有轮次取消 holder,但它不属于面向插件的 Agent 约定。Web 停止决策是生产环境中的 keepInbox 消费方。
whenIdle() 保留为公开的完全停稳观测原语(agent 从 running 状态稳定后 resolve,已处于 idle 时立即 resolve,dispose 后等待循环退出)。它不是停止动词;它是非所有者在不 dispose agent 的前提下观测停止完成的方式。它的活跃消费方是 ACP 和通过此公开约定等待结算的 agent 测试(packages/acp/acp/tests、packages/core/agent-loop/tests);生产环境的 ACP 桥接层拥有其 agent 并通过 AgentHandle.dispose() 销毁它们,因此 packages/acp/acp/src 本身没有 whenIdle() 调用。
公共 abort() 已不存在,disposer 仍为异步并等待循环停止。测试通过公共类型化原因和显式 signal API 验证取消,而不会伸入 holder 内部。
曾考虑的替代方案
同时移除 whenIdle():最初提案的形态,在对照代码验证前提后被推翻:它是承重的完全停稳原语,能安全处理等待者结算与替换轮次竞态,迫使消费方手动观测 running→idle 转换正是防御性模式所警告的脆弱路径。
验证
Agent 不再暴露公开的 abort(),而 cancel()、whenIdle() 和 steer() 保留;ACP 取消调用广义 cancel(),Web 停止调用 cancel(..., { keepInbox: true }),拆卸则通过 handle disposal 等待完全停稳。whenIdle() 在完全停稳时为非所有者观测者 resolve;测试套件覆盖取消和资源释放这两条受支持的停止路径。
后果
插件可以通过 keepInbox 在保留已排队提示词的同时中止活动轮次,但不能只中止某一个模型/工具步骤而让该轮次继续运行。仅步骤用例需要具名消费方和更窄约定;暴露私有循环机制仍缺乏正当理由。
相关
本 Agent Note 只移除冗余的停止动词。轮次中途 steering 仍是一条有意保留的消息路径;完全停稳观察仍通过 whenIdle() 完成。最终的消息投递接口包括 followup()、steer() 和 inject();停止与观察仍通过 cancel() 和 whenIdle() 完成。