Skip to content

2.5 会话持久化:恢复、fork 与投影

本章概览:会话日志如何跨进程存活——持久化 seam 的契约、崩溃恢复语义("崩溃的轮次被关闭而非截断")、恢复/检查/增量读,以及服务 UI 的投影(projection)机制。

持久化是能力缝

ctx.sessionPersistence 是 Service Definition(session/session-persistence),后端是 Provider(session-persistence-jsonlsession-persistence-sqlite),消费方是 loop(resume)、工具与 hook 桥。持久化的单元就是已有的 SessionEvent——不存在平行的"持久化消息"类型;不可回放的元数据(格式版本、cwd、lineage、seed 边界、delegation depth)作为 SessionHeader 单独存放。

出处:packages/session/session-persistence/README.mddeepseek-harness-src/packages/session/session-persistence/README.md

服务 API 要点

方法契约要点
create(meta)注册新会话元数据;可延迟物化(首个 append 才落盘)
append(id, events)持久追加一批;只追加;首个事件 seq 必须等于存储的 next-seq
load(id)返回不可变的平衡逻辑日志;活会话先 flush 快照、开着的轮次会拒绝;冷会话保留被中断的最终轮次并持久追加合成的关闭事件
inspect(id)升级、校验、深冻结的逻辑视图,不提交恢复也不发布 Session
readFrom(id, fromSeq)只读 seq >= fromSeq 的后缀(checkpoint 消费者用)
list(signal)轻量列出元数据,不解析全日志

每个后端必须遵守的不变式

  1. 只追加;崩溃的轮次被关闭,而非截断:已 flush 的事件绝不重写。崩溃可能留下未关闭的最终轮次(事件真实且可能很大);load 保留它们并持久追加合成关闭器(每个未应答的助手工具调用一个风险分类的错误 tool/result,然后 step/end? + turn/end {interrupted}),使重水合的日志平衡、历史合法。只有从未完整写入的撕裂尾部(torn tail)片段被丢弃。
  2. 连续 seqload 拒绝日志中间的 seq 间隙/解析错误;append 首 seq 必须等于存储 next-seq。
  3. JSON 可序列化:通过共享的"单遍无损 JSON"边界物化。
  4. 持久性append 只在批次持久落盘后返回。

IMPORTANT

崩溃语义的精髓:绝不假装崩溃没发生。日志保留真实事件,追加合成的关闭事件让历史保持平衡——恢复后的历史仍然是一份合法的 provider transcript。

写协调器:共享生命周期

PersistenceCoordinator 拥有每 id 状态与串行化、每活会话一个有界写控制器、延迟物化、崩溃尾部修复、会话收养(adoption)与安静 dispose。JSONL 与 SQLite 都组合一个 coordinator,只实现小的 PersistenceBackend 存储钩子接口——两者共享生命周期正确性,各自保留不同的存储原语。

写批处理策略:每个 session/event 把事件拷入会话控制器;首个待处理事件开启一个固定批窗口,后续事件加入但不重置期限;writeBatchMaxDelayMs 约束这个有意的等待(不是事件循环/初始化/后端延迟);session/flush 取消等待,是共享的安静屏障。

出处:packages/session/session-persistence/README.mdshared-persistence-write-coordinator Agent Note

恢复(resume)与 fork

  • ctx.agents.resume({ resumeSessionId, ... }):经 ctx.sessionPersistence 加载持久化会话,在相同 id 下注册 agent,重建历史,然后对未发布的新 agent 作用域做 setup,再走回滚保护的发布序列;轮次编号与派生历史从加载的日志延续。resume 需要持久化后端(无持久化时给出明确报错)。
  • ctx.sessions.fork(source, boundary?, childSessionId?):以 boundary(含)为 seed 前缀创建子会话;要求前缀边界不在开着的轮次内;携带 lineage 元数据(parentSessionseedLengthdelegationDepth)。

出处:packages/core/agent-loop/README.mdpackages/core/session/README.md

投影(projection):为 UI 服务的状态折叠

ctx.sessionProjectionssession/session-projection)是投影能力缝:框架驱动、领域计算

  • 领域注册一个 ProjectionDefinition{ key, schema, init(), apply(state, event), view(state), stateVersion }——三个纯同步函数 + 声明;
  • 注册表订阅一次 session/event,每个已提交事件急切经过每个单元的 apply
  • 同一引用 = 无工作apply 对不相关事件必须返回同一 state 引用(Object.is 门控变更通知);
  • 整值事件规则:携带状态的日志事件必须携带完整的变更后状态(last-wins),绝不只是 delta;
  • 消费方(dsh-host-apiproxy 的 history tail 页与 session/projection 推送帧)读 snapshot()——同一 tick 的一致切面 { asOfSeq, values }

投影单元的例子:plan 状态(plan/mode 事件折叠)、todo 状态(tool-todo)。session-projection-cache 把单元状态持久检查点(节流 + turn/end/detach 强制点),冷读走"缓存行 + 持久化尾部回放"阶梯,列表无需加载全日志。

出处:packages/session/session-projection/README.mdpackages/session/session-projection-cache/README.md

其它会话派生服务

服务作用
ctx.sessionTitle从日志折叠最新标题;确定性回退 + 唯一异步提供方(首提示 LLM / 全部提示 LLM)
ctx.sessionQuery会话检索:逻辑语料、有界读取、lineage、事件关系、语义过滤、SQLite 全文搜索
ctx.sessionReferenceResolver把有界的当前 surface 会话快照投影为持久化的不可信消息上下文(跨会话引用)
ctx.sessionTelemetry采集、脱敏、把会话记录交给一个后端(OTel);输出离开进程

出处:docs/capability-seams.mddeepseek-harness-src/docs/capability-seams.md

小结

持久化把"事件日志"变成"跨进程真相":只追加 + 崩溃轮次关闭 + 连续 seq + 持久后返回,使恢复(resume)可以重建任何历史;投影与缓存则为 UI 提供廉价的实时读模型。下一部分进入模块精讲,从 3.1 Shell 执行能力族 开始。

参考资料

本文档为学习用途的原创讲解,基于 MIT 许可的开源项目 deepseek-ai/deepseek-harness 编写;所有引用均注明出处。