English | 中文
JSONL 持久会话存储后端:SessionPersistence 的一个具体实现(dsh-session-persistence seam)。每个会话有一个仅追加的逻辑 JSONL 日志,默认存储为 .jsonl.zstd;禁用压缩时使用原始 .jsonl。
<root>/
--<normalized-cwd>--/ # readable project directory (or _no-cwd/)
<encoded-id>/ # session-owned directory
session.jsonl.zstd # default: checksummed header frame + append frames
session.jsonl # only with compression: 'none'
SessionHeader,标记为 { type: 'session', version, id, cwd?, createdAt, parentSession?, seedLength?, origin?, delegationDepth, agentPreset? }。delegationDepth 在磁盘上必需,顶层会话为 0;缺失或无效值会拒绝日志。agentPreset 必须持久化,因为它决定了被恢复会话的工具与提示词——恢复成另一套组装,就会回放模型已无法据以行动的历史。后续每个逻辑行是一条存储记录;assistant/chunk 事件绝不丢弃,且 seq 在解码日志中保持连续(events[i].seq === i)。SessionEvent JSON,或在 packChunks 已启用且连续段符合条件时写入的打包分片行(text-chunks / reasoning-chunks / tool-call-chunks;像 header 的 session 一样不带斜杠,因此行 tag 不会与事件类型混淆):一行保存至少 3 个连续同 block assistant/chunk delta 事件,seq0/time0 和各成员的 dt 间隔精确重建每个成员的 seq/time。无损 codec 位于 @deepseek-ai/dsh-session(packChunkRuns/decodeStorageRecord),并使用精确形态 allowlist:任何未识别内容原样存储。读取与布局无关:load 始终解码行,因此打包、非打包和混合文件加载结果一致。| 键 | 类型 | 说明 |
|---|---|---|
root |
string(必需) |
所有会话文件的根目录。无默认值:process.cwd() 默认值会随进程 cwd 变更(bash 调用、子进程)而分散文件。现有根必须是可读目录;缺失根在第一次实体化时创建。 |
packChunks |
boolean(默认 true) |
将符合条件的 delta 分片连续段写为打包行(在真实编程会话上测得逻辑日志约小 60%)。设为 false 可用于每事件一行诊断;无论该写入侧开关如何,都能读取打包行。 |
compression |
'zstd' \| 'none' |
默认 'zstd';'none' 保留换行分隔 UTF-8 文本。 |
preparedSessionCacheSize |
正整数(默认 5) |
冷历史检查后保留、供恢复复用的未发布会话数量上限。 |
writeBatchMaxDelayMs |
正整数(默认 200) |
空闲的活动事件队列收到待写入事件后开启的固定合并窗口。后续事件不会重置窗口;flush 与 teardown 会绕过它。该值不限制事件循环、串行化操作或后端延迟。最大值为 Node 计时器上限 2_147_483_647 ms。 |
locate(meta) 返回已解析项目/会话目录内固定 transcript 的 { kind: 'jsonl', path }。它不执行文件系统 I/O:可以在目录或文件存在前返回目标,现有文件也只包含最近一次 flush 完成的前缀。
默认产物是独立 Zstandard frame 的标准拼接:一个仅包含 header 行的带 checksum frame,后跟每个持久 append 批次一个带 checksum frame。后端使用 Node 内置 Zstandard API 和默认压缩级别,不提供级别开关。列表只读取并验证 header frame。compression: 'none' 在原始表示中保留相同逻辑行。
一个根只属于一种编码。启动发现和定向查找会拒绝相反 suffix,错误会命名不兼容产物,并指示调用方选择匹配 mode 或独立根。平铺 <project>/<id>.jsonl* 产物也会被拒绝,而不是忽略。不提供迁移、混合根回退或双写。
create(meta) 不写入;第一次 append 将编码 header 和第一批写入临时文件并执行 fsync。POSIX 通过硬链接无覆盖发布,并对父目录 fsync。Windows 通过 MoveFileExW(..., MOVEFILE_WRITE_THROUGH) 无覆盖发布,并通过同一 write-through pattern 创建缺失目录。已创建但从未 append 的会话不留下磁盘内容,不在 list 中;生命周期 consumer 显式调用 ensureMaterialized 时除外,此时会发布一个 header frame,但不添加事件。fsync,并在捕获到写入或同步失败时回滚到之前字节长度。load 验证每个完整压缩 frame,并扫描解压 JSONL。最后 frame 结构不完整时,读取器保留其完整解码记录,从 frame 开头截断,并使用共享持久化约定 需要的合成工具、步骤和轮次 closer 重新编码这些记录。原始 mode 从第一个不完整行截断。已经存在却没有完整 header frame 的压缩工件、完整 frame 中的 checksum/解压失败,或位于最后已提交的 turn/end 处或之前的缺陷都属于损坏,会被拒绝。inspect() 返回不可变、平衡的逻辑视图,并可在内存中合成恢复 closer,但不会截断不完整尾部或更改轻量修订。append 拒绝第一个 seq 不继续已存储日志的批次,并拒绝无法 JSON 序列化的 event.data,同时命名违规事件类型。listSnapshots(signal?) 使用 device、inode、size 和纳秒时间戳标识日志,避免解析完整日志;该标识会在 append、修复、替换或存储变更后改变。完整前缀读取要求读取字节前后的身份一致,readStoredRevision() 使用同一身份校验保留的 preparation,而不加载日志。快照列表通过产物发现原样转发该信号,并在每个 stat 前后检查取消;由于文件系统 stat 不可中断,取消会等待活动调用完成,然后在不启动另一次调用的情况下拒绝。插件将冻结的会话事件复制到每个活动会话各自的 controller。第一个待处理事件会开启配置的固定批处理窗口,后续事件会加入但不会重置截止时间。窗口到期后会启动一次持久化追加;该次写入期间接纳的事件会形成另一个独立有界的后续批次。session/flush 会取消等待并排空当前与待处理批次。每会话游标防止恢复后的会话重新 append 已存储事件,插件加载时会为活动会话设置初始状态。所属后端实例串行化单会话操作;dispose(资源释放)会在拆卸前排空每个保留的 controller。每个逻辑事件都会保留:批处理只让单个压缩帧或一次原始 JSONL fsync 承载更多记录。
JSONL 存储不会向当前请求提供提示词或 schema。加载会恢复已存储的表层历史,并保留之前的请求 header 用于重建;新 loop 组合当前 envelope。恢复会用 TOOL_NOT_STARTED 平衡没有已持久化调用的 assistant 请求;已持久化调用无结果时则变为 TOOL_OUTCOME_UNKNOWN,它要求模型只重试只读或幂等工作,并验证可能的副作用或询问用户。原始 assistant/chunk 记录不会重复生成消息。
当前请求不会新增 token。恢复后的 agent(智能体)会因保留的历史、当前 envelope,以及每个中断调用中以引用形式加入的修复结果文本而消耗 token。
JSONL 存储不修改实时请求前缀。只有重建历史、当前 envelope 和模型路由匹配时,恢复 loop 才能重用提供方缓存;崩溃修复结果仅追加。
SESSION_FORMAT_VERSION(v0):更改压缩需要独立/全新根,或选择遗留原始 mode;预发布格式没有迁移。compression: 'none',以便外部行 reader 使用。root 下累积,直到外部移除(seam 无删除接口)。link(),使同 id 竞态失败,而不覆盖已提交日志;Windows 使用无替换 write-through rename。