Agent 执行流恢复

Backend、Agent Service 与 Sandbox 在执行机连接中断后的恢复边界

Agent 执行流恢复

实施状态:已通过受控 Backend、Agent Service、Router 进程重启场景及定向集成验证;真实模型 CLI 的上线验收和分支完整自动化回归仍在进行,不能把以下恢复能力视为已经通过全部生产场景。

当 Backend、Router 或 Agent Service 重启,而 Sandbox 中的 CLI 执行仍然存活时,系统会优先复用已有的 SSE/WS resume。只有 Backend 的本地投影可信、目标执行一致且 Agent Service 能提供连续 replay 时,才会继续使用这条快速路径。

浏览器断流后保存当前订阅最后收到的会话事件游标,刷新历史后从该游标重新订阅;旧连接迟到的终态回调不会关闭新订阅。Backend 无法连接 Rust Chat Runtime 时,尚未发送响应头的 JSON/SSE 请求返回 HTTP 503 和 RUST_CHAT_RUNTIME_UNAVAILABLE,客户端可稍后重试;已经发送响应头的流不会改写成 HTTP 503。

快照兜底

以下情况会切换到 Sandbox execution collector 的 snapshot Attach:

  • Backend 冷启动或无法证明本地 projection 已经提交。
  • 原 stream 不可用、远端 owner 暂时不可达或 replay 出现 gap。
  • 旧 peer 未协商 executionSnapshotAttach 能力时,不会发送未知恢复命令;系统保留既有 handoff/失败兜底。

Attach 会在 Sandbox 内先登记 live receiver,再捕获 snapshot watermark,之后按 watermark 过滤 suffix,排空后继续接收 live 事件。Attach 使用原有 requestId 与 streamId,不会重新提交 prompt。

一致性与缺口

快照是语义校正材料,不是逐 token 日志。sourceSeq 只用于恢复期间的顺序和 gap 检查,不会写入 PostgreSQL。Sandbox 崩溃、spill 淘汰、文件损坏或 suffix 超出保留范围时,结果可能为 partial/gap;可靠正文会保留,不会用空内容覆盖。

重复 snapshot、suffix 或 Attach 不会重复工具调用、通知、计费或控制副作用。控制请求在 Sandbox 无法确认时返回 unknown,系统不会根据网络超时自动重发。

等待交互与控制

新恢复能力下,collector 保留审批、问答的原始交互上下文,读取连接消失不删除等待记录。恢复后的回答继续发送给原 CLI,不用新 prompt 代替原回答;Backend 需先确认当前执行的控制租约,未就绪时返回可重试失败。重复的已接受控制返回原 receipt,不再次执行;完整交互展示与跨重启控制结果收敛仍在验收范围内。

用户新请求接管

已有本地执行正在等待快照校正,或插话明确返回 SESSION_STREAM_NOT_ACTIVE 时,用户的新正文请求先取得旧执行的精确停止确认。审批/问答回答仍走原控制路径;快照已经提交并继续 live 的执行不会仅因经历过恢复而被终止。

Backend 先查询 Sandbox 的 executionTargetStopV1 能力,再按 process/execution/response generation、native session 与 conversation 发送单次停止。旧 peer、身份缺失、网络不通或 unknown 时返回可重试失败,不直接换 response/execution ID 并行运行。普通 cancel.accepted 不等于进程已经退出。

真实退出确认后,在短事务内重新核验原 checkpoint 与数据库 owner,持有本地旧 writer/投影屏障,持久化用户新消息、释放原 owner、清理旧交互关联,再建立新响应。迟到的原执行事件不能回写新响应;重复消息和 payload 冲突在停止副作用之前识别。原生历史导入不能通过提前清空 sessionId 绕过仍存活的 source writer。

没有本地 execution、但 checkpoint 仍为 running/paused 或存在运行 owner 的 pending 时,新正文请求也进入同一门禁。缺少 Gateway target 时,从可信运行时目录按完整租户/工作空间/会话/响应身份寻找候选;最多检查 4 个容器、总发现等待 6 秒。候选为空、重复或身份不符均返回可重试失败,不以数据库 running 行推断 CLI 已停止。会话准入等待限制为 5 秒,不无限排在后台恢复之后。

接管后优先原生 resume。仅在运行时额外协商 executionNativeHistoryFallbackV1、CLI 明确报告原生会话不存在、且尚未输出正文/工具/交互等执行事实时,collector 才执行一次历史兜底;读取连接断开不影响这一步。必须先确认失败进程退出,再使用历史材料创建新上下文,不能并行运行两个 writer。新进程的原生 sessionId 由 collector 更新;旧进程的事件、清理和停止结果不能影响新执行。

Claude 优先使用经过身份与摘要验证的兼容原生 JSONL,不可用时降为本会话数据库导出;Codex 使用数据库导出。导出在新消息写入前生成,最多 200 行、128 KiB,长字段截断,明确标记 partial,不声称完整还原。材料写入受控目录,有并发、文件数和字节配额;配额满或材料不可用时失败,不自动反复重发。原生 resume 成功不会准备历史材料。旧 peer 和普通会话的人工恢复选择语义保持不变。

上述入口已通过受控 Claude/Codex CLI fixture 和隔离数据库验证;完整冷 Backend/Agent Service/Router 进程重启与真实模型请求仍需按故障验收清单验证。

终态与模型请求

终态 collector 可以只读返回 final snapshot,不要求不存在的 live receiver。SSE 恢复成功与模型 HTTP 请求是否中断分别记录:恢复成功不代表模型请求曾经未中断,模型请求失败也不会被标记为无感恢复。

终态保留与清理

  • CLI 已输出真实终态但进程仍存活,或进程已经退出且 collector 已回收时,已绑定会话范围的终态仍可只读 Attach;不重新获取 live 控制租约。只有 EOF、没有真实终态时返回 EXECUTION_DURABILITY_GAP,不能根据断流推断成功或失败。
  • 目录按 process/execution/response generation 保留独立快照。发现页保持每个 native session 一条最新候选和原有 sessionId 分页;已知旧 target 可在保留期内直接查询。跨会话、错误 session 不能读取该材料。
  • 发现集合遵守 Backend 的预算,最多 256 条且为 registration 元数据保留字节余量,优先活动/等待执行,再取近期终态;超预算记录缺口,较旧终态仍可按已知 target 查询,不能据此宣称所有过载候选都能自动找回。
  • 默认保留 24 小时,终态磁盘宽限期 5 分钟,快照内存和磁盘配额各 256 MiB,目录最多 1024 条。磁盘配额 GC 不删除宽限期内或正在读写的文件;内存/目录超预算可能提前淘汰,明确记录 retention_gap,不声称完整恢复。重复捕获或延迟落盘不续长原终态时间。
  • 复用 Sandbox 清理循环分批物化最终快照到 DEFAULT_WORK_DIR/.knodo/execution-recovery,不逐事件落盘。退出时在原有总预算内等待 collector 收尾并写出最终快照;预算耗尽记录 durability_gap。
  • 只清理自有最终快照和残留临时文件,不删除 CLI 原生历史。Sandbox 新进程不会仅凭磁盘旧文件重建“仍在运行”的候选。

低频恢复

快照 Attach 的 8 次快速重试耗尽后,保留原恢复 worker,转入 30 秒、随后最长 60 秒间隔的 reconcile,不发送虚构 CLI 失败终态、不重发 prompt。订阅消费结束或原请求被替换后退出;已被 runtime 明确拒绝的旧租约证明会清除,下一次 Attach 仍由 runtime 仲裁,网络超时本身不构成抢占授权。

快照 worker 接管期间,旧 transport 恢复超时器不能取消或清理该逻辑流;这不等于快照已经提交,原 Agent 游标仍只在投影成功后清除。

冷恢复候选按完整执行与会话身份独立退避,失败间隔上限 60 秒,身份变化不会沿用旧候选退避;单次候选处理有超时和并发上限。完整服务重启、模型 HTTP 故障、真实 CLI 和压力容量验证仍需按特性验收清单执行。