← [ BACK_TO_NOTES ]

Cancellation

Cancellation 是对正在执行的 Run 发出的显式业务取消命令;它必须与网络断线和前端 Abort 分离,并形成可审计的服务端状态转换、执行传播和 durable terminal fact。

#concept #evergreen #ai #architecture

[!info] related notes

Cancellation

一句话定义

Cancellation 是用户或系统明确要求停止某个 Run 的业务命令与状态转换;它不是“HTTP/SSE 连接断了”的同义词。

最重要的边界:

Transport disconnect
≠ Business cancellation

AbortController.abort()
≠ run.cancelled

为什么必须区分 Transport 与 Run

浏览器可能因为这些原因失去连接:

Wi-Fi 切换
proxy timeout
tab suspend
浏览器刷新
临时网络故障

这些现象只能证明:

客户端失去了当前 transport。

不能证明:

用户希望取消服务器上的 Agent Run。

如果把连接断开自动解释成业务取消,会把本来可以继续执行并恢复的任务误杀。

因此 production runtime 通常要把生命周期拆开:

Transport lifetime
= 当前 HTTP/SSE/WebSocket 连接活多久

Run lifetime
= Agent execution 在服务端活多久

显式取消的正确链路

用户真正点击“停止生成”时,应该形成一个正式 Command:

User clicks Stop
      ↓
CancelRun(run_id)
      ↓
Server authorization / current-state check / idempotency
      ↓
Durable run transition: running|waiting_user → cancelled
      ↓
Propagate cancellation to active execution/resources where possible
      ↓
Persist/emit run.cancelled terminal fact
      ↓
Frontend observes and converges

前端随后可以关闭本地 reader/transport,但那只是 UI/资源清理的一部分,不是业务取消本身。

为什么只 AbortController.abort() 不够

controller.abort();

它首先表达的是:

stop waiting for / reading this browser-side async operation

它不能自动保证:

LLM stopped
Tool execution stopped
Run state became cancelled
Partial durable state was reconciled
Other clients learned the cancellation

如果服务端执行是 detached/durable 的,浏览器断开后 Run 完全可以继续。

所以真正的“停止生成”通常要有独立的 server command/API,而不是依赖 socket close 来猜用户意图。

Cancellation Propagation

服务器接受 CancelRun 后,才进入取消传播:

Durable cancellation intent/state
        ↓
active execution context / cancellation registry
        ↓
LLM stream / Agent loop / cancellable tools
        ↓
resource cleanup

这里的原则是 cooperative cancellation:下游必须支持或检查 cancellation signal,不能假设任意正在执行的外部操作都能瞬间强制终止。

见 Cancellation Propagation。

Durable Terminal Fact

客户端不应仅根据:

fetch aborted
socket EOF
reader error

把 UI 标成 cancelled。

更可靠的是根据服务端 durable state/event,例如:

run.cancelled

它回答的是“取消已经成为系统事实”。

典型 terminal states:

completed
failed
cancelled

应保证终态唯一且不会被迟到事件随意重新打开。

Disconnect 的正确处理方向

network disconnect
→ keep server Run alive if architecture allows
→ reconnect / query durable run state
→ replay/catch up missing events

而不是:

network disconnect
→ cancel Run

这和 Durable SSE Recovery with after_seq 是同一个可靠性模型的两面:

未知网络故障 → Recovery
明确用户意图 → Cancellation

Partial Output 怎么处理

取消发生时是否保留已经生成的部分内容,是产品/领域策略,不应由“socket 被 abort”隐式决定。

系统可以选择:

  • 保留 partial message 并标记 cancelled/partial;
  • 只保留 durable semantic events;
  • 丢弃未达成业务提交条件的临时 projection。

无论选择哪种,策略应由服务端状态机和持久化契约明确表达。

BodySense 当前实现

当前 BodySense 已有显式取消路径:

POST /consultation-runs/:id/cancel
→ Runtime.CancelRun
→ RunService / RunRepository
→ active/waiting_user → cancelled
→ cancellation signal propagated to active execution
→ durable run.cancelled(reason=cancelled_by_user)

同时,普通 transport disconnect 不会自动把 durable Run 标成 cancelled;客户端会走 durable event recovery/catch-up。

主要代码边界:

apps/api/internal/consultation/runtime.go
apps/api/internal/service/run_service.go
apps/api/internal/repository/run_repository.go
apps/api/internal/handler/consultation_handler.go
apps/web/src/features/consultation/services/consultationService.ts

常见错误

  • 把 request.Context().Done() / socket close 直接当成用户 CancelRun。
  • 只 abort 浏览器请求,服务器 Run 仍继续消耗资源。
  • 前端先本地标 cancelled,但服务端实际已经 completed。
  • Cancel 与 Complete race 没有服务端 authority/ordering,出现两个终态。
  • Cancel command 不做幂等,重复点击产生重复副作用。
  • 认为所有工具/外部服务都能立即响应 cancellation。

面试表达

“我会把 transport disconnect 和 business cancellation 分开。网络断线只能证明客户端失去连接,不能代表用户要停止 Run;对 durable Agent execution 应优先恢复。真正点击停止时,我会发送显式 CancelRun command,由服务端做权限和状态校验,持久化 cancelled terminal state,再向实际 LLM/tool execution 传播 cancellation。前端 AbortController 只负责停止本地 transport,不作为业务终态 authority。”

最短记忆

Disconnect → recover
Explicit Stop → CancelRun command
AbortController → client transport cancellation
run.cancelled → durable business/runtime terminal fact
创建于 2026/6/30 更新于 2026/9/10
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本