Skip to content
My Blog
Go back

崩溃的 turn 要关闭,不是截断:dsh 的会话持久化

第二篇说过,session log 是 dsh 的唯一事实来源。那这条日志怎么落盘、崩了怎么办、又能从里面长出什么?这篇讲 packages/session/ 这个组。

一个组,四个家族

packages/session/README.md 把整个组拆成四族:

家族内容
durable storage持久化接缝 + 后端 + 检查点策略
projections把已提交事件折叠成整体当前值,加冷读缓存
titles会话标题
telemetry遥测

注意后三族全是「派生」——标题、投影视图、遥测都不是独立存储的事实,而是从日志算出来的。

两个后端:JSONL 默认,SQLite 可选

持久化本身是一条能力接缝(ctx.sessionPersistence),出厂两个后端。README 的原话:「JSONL is the shipped default, SQLite an opt-in single-database backend.」

「拒绝旧 schema 不迁移」又是那个预发布姿态:正式发版前不为旧格式背兼容包袱。

崩溃的 turn 怎么处理

这是整篇最值得一看的细节。进程在 turn 进行到一半时挂了,日志里留下一个开着没收尾的 turn。持久化层的修复动作不是把这段删掉,而是给它补一条合成事件:

it closes the orphaned turn with a synthetic turn/end { reason: { kind: 'interrupted' } }interrupted is the one TurnEndReason no loop emits.

interrupted 这个结束原因,任何正常运行的循环都不会自己发出来——它只属于持久化层在冷会话上做的修复。也就是说,「被崩溃打断」这个事实本身也进了日志,而且是唯一来源。活的会话(还有人在用的)开着 turn 则直接拒绝修复,不动它。

格式层面同样强硬:读到不认识的事件类型,宁可拒绝也不跳过,抛一个专门的 SessionFormatUnsupportedError,和数据损坏错误分开。不知道就是不知道,别猜。

从日志长出来的三样东西

顺带厘清一个容易混的包:packages/storage/ 和会话没关系,它管工作区记录这类非会话数据。README 特意声明:「registers no tools, injects no prompts, and writes no session events, so the model and the agent loop never see it.」模型永远看不到它。

小结

「只追加 + 崩溃补关闭事件 + 一切派生自日志」三件事合起来,效果是任何时刻的会话状态都能从一份不可变的记录重建。第五篇的压缩锁、第七篇的 plan 状态,全都站在这块地基上。

本系列


Share this post:

Previous Post
一切皆插件:读 DeepSeek Harness 的架构
Next Post
第 05 课 · 会话日志与「模型可见 ⟺ 已记录」