Run 生命周期解耦(INV-RUN-DETACH)

Run 生命周期始终与传输层(SSE / HTTP 连接)解耦。SSE 断开不取消 Run。


核心原则

SSE 连接断开 ≠ 取消 Run
Run 取消的唯一途径是显式操作

取消 Run 的五种途径

途径触发者说明
POST /sessions/{sid}/interrupt用户主动点击停止按钮
CycleDetector HITL 超时引擎自动真循环检测后用户未响应
ErrorStreakDetector HITL 超时引擎自动伪进展检测
BudgetCheckFn 返回 Exhausted外部限制商业预算耗尽
Cell Stop生命周期Cell 停止或进程退出

引擎不因 counter 阈值(轮次/token/时间)主动终止(INV-TERM-01)。


RunEventBuffer

SSE 断线后重连可续播:

GET /cells/{id}/runs/{runID}/events?from_seq=N

终端事件

runLoop 的每个 return 路径必须在返回前发出终端事件(INV-TERM-04):

事件含义
EventDone正常完成
EventError错误终止
EventInterrupted用户中断
// callLLM/execute 返回 phaseReturn 时
if ctx.Err() != nil {
    // 必须发 EventDone("context_cancelled")
    // 不能依赖 for 循环顶部的 ctx.Err() 检查
    // 因为 phaseReturn 直接 return,永远到不了那里
}

缺少终端事件 → terminal guard 合成虚假 EventError(missing_terminal_event) → 消费方显示误导性错误 + 无效重试按钮。


RunEnd 摘要消息

RunEnd 时引擎写入 role=system 摘要消息到 wes_messages。


孤儿 Run

可标记 status=resumable,通过查询发现:

GET /observe/runs?status=resumable

各消费方的处理

wesclaw 桌面

SSE 断开 → Go 后端继续 Run → 前端重连后通过事件重播恢复。

wescode

chatInFlight 锁绑定用户可见轮次(INV-WS-11):

企业版(teleclaw)

Run 不随 SSE 断开而死 → Java 返回 stopReason=detached。 hasActiveRun 门控防止新 Run 与活跃 Run 冲突。


反模式

禁止正确做法
SSE 断开取消 RunRun 继续,客户端重连续播
用 counter 阈值终止 Run只有显式途径取消
phaseReturn 后不发终端事件检查 ctx.Err() 发 EventDone
假设 SSE 连接与 Run 生命周期相同始终解耦