Appearance
客户端断开后上游该不该停:Disconnect 与 Cancellation Propagation
前置:Agent 事件怎么推给前端:SSE、WebSocket 与 Event Stream
核心问题:浏览器关闭连接时,为什么 Run 不一定应该取消,Stream 生命周期和 Run 生命周期应该怎样解耦?
用户关浏览器后长任务继续;重新打开页面从 last event id 补拉状态。前端点击 Stop → POST cancel → RunState CANCEL REQUESTED → 取消 Tool HTTP 与 shell process。
Mental Model
Agent event stream 要把上游 cancellation 和下游 backpressure 连起来;否则客户端已断开,模型/Tool 仍可能继续消耗资源。
Disconnect
Disconnect 只说明 transport 已经断开,不直接等价于 Run = CANCELLED。产品必须先定义连接生命周期与任务生命周期是否绑定:交互式短 Run 可以在断线后传播 cancellation;长任务通常继续执行并持久化事件,客户端恢复后从 durable event cursor 重新订阅。没有这个显式策略,同一个“关页面”动作既可能误杀重要任务,也可能留下无人关注的后台消耗。
Cancellation Propagation
Cancellation 是协作停止信号,不等于 failure;deadline 应从 run 分配到 node/tool,子调用不能各自使用互不相关的超时。
当产品语义要求取消时,HTTP disconnect/cancel API 要转换成 Runtime cancellation,并向 model/tool/shell 子任务传播,而非只停止向客户端写数据。
Disconnect 到底要不要 Cancel Run
答案取决于产品语义,而不是 HTTP 框架默认行为。
text
聊天式短请求:
client disconnect → 可以选择 cancel generation,避免继续计费
后台研究 / coding run:
client disconnect → Run 继续;用户稍后重新连接查看进度
远程交互 shell:
control connection disconnect → 可能进入 grace period,再决定 terminate因此 Connection 生命周期和 Run 生命周期必须是两个状态机。HTTP handler 在 finally 里只负责释放连接相关资源;如果策略要求取消,应显式向 Runtime 发送 CancelRun(run_id, reason=CLIENT_GONE),由 Runtime 再传播到节点和 Tool,而不是 handler 直接杀掉某个内部 task。
重连时客户端最好带上最后看到的 event sequence,服务端从 durable event/event buffer 补齐缺口后再继续 live stream。这样“网络抖了一下”不会变成“整个 Agent 从头再跑”。
Failure Modes
客户端断开后,Run 是否应该取消?
这不是一个固定答案,而是 API contract。
短交互型请求通常可以让 disconnect 传播为 cancellation:
text
browser closes
→ HTTP task cancelled
→ Runtime cancellation token
→ model/tool receive cancel但已经持久化为后台任务的 Run,客户端断开只代表“没人继续看这个连接”,不代表业务任务应该停止。此时应继续执行,并允许客户端之后通过 run_id 重连事件流。
因此 Transport 层必须明确区分 connection lifetime 和 Run lifetime。
如何验证
| Case | 断言 |
|---|---|
| 客户端断开后上游 Model/Tool 继续执行 | 客户端中途断开,可取消操作在 deadline 内停止。 |
自测
- 浏览器关闭了 SSE,但后台 Run 是用户提交的长任务。为什么 client disconnect 不应自动等同于 run cancel?
- 如果产品明确规定“连接断开即取消”,取消信号应该怎样从 HTTP 层传播到 Runtime/Tool,而不是只停止写 socket?