第 12 章 · Session、Thread 与恢复
本章目标:理解一次聊天、一次模型调用和一个长期任务不是同一对象,并学会设计恢复、分叉、打断与事件流。
1. 三个层级
可以把运行状态分成 Thread、Turn 和 Item:
- Thread 是一个可持续任务,保存上下文、配置和历史;
- Turn 是用户输入后到 Agent 停止之间的一次工作;
- Item 是 Turn 中的文本、工具调用、工具结果、审批或 diff 事件。
这种层级让界面可以流式展示,也让存储系统准确恢复。只保存最终文本,会丢失工具结果和审批;只保存原始 SDK 对象,又会与 Provider 强耦合。
2. Resume 与 Fork
Resume 在同一 Thread 上继续,新的 Turn 继承原历史和任务身份。Fork 复制某个历史点创建新 Thread,适合比较两个方案或保留原路线。
它们的区别类似 Git:继续在当前分支提交,与从某个提交创建新分支。Fork 后两个任务必须拥有独立状态、预算和后台进程引用,不能共享可变 history。
3. Interrupt 与 Steer
Interrupt 请求停止当前生成或工具。它不等于删除历史:已经发生的文件写入和外部动作必须保留记录。安全中断需要取消模型流、阻止新工具、终止或托管后台进程,最后写入 interrupted 状态。
Steer 则在任务运行中追加人类输入,例如“不要改数据库,先只写测试”。Runtime 要把它作为高优先级用户事件加入活动 Turn,让模型尽快调整。若当前工具不可取消,界面应明确说明输入将在工具结束后生效。
4. 持久化什么
可恢复 Thread 至少需要:
| 数据 | 用途 |
|---|---|
| thread_id / turn_id | 定位任务和事件 |
| 标准化消息与工具事件 | 重建模型上下文 |
| cwd、模型、权限配置 | 恢复运行环境 |
| 当前计划与完成状态 | 避免从头探索 |
| 变更文件和 Git 信息 | 理解工作树 |
| 后台进程引用 | 决定重连还是清理 |
API key 不应写进 transcript。恢复时重新从安全凭据源注入。
5. Event Stream 驱动界面
模型文本、命令输出、审批请求和状态变化不会同时完成。Runtime 可以发出 thread.started、turn.started、item.delta、tool.started、tool.completed、turn.completed 等事件。CLI、IDE 和桌面 App 只是这些事件的不同呈现方式。
事件需要顺序号和可重放性。客户端断线重连后,可以从最后序号继续,否则会漏掉一次工具完成或重复显示输出。
6. 回滚的真实含义
回滚对话历史不自动回滚文件和外部世界。删除最近两个 Turn,只改变模型将看到的历史;已经发送的消息、创建的 PR 或写入的数据仍存在。产品界面必须区分“上下文回退”和“副作用撤销”。
失败案例
两个终端同时 resume 同一 Thread 并继续写 history,如果没有并发控制,事件顺序和工具结果会互相穿插。恢复能力必须同时设计独占、分叉或版本检查。