Skip to content
My Blog
Go back

第 06 课 · 持久层、投影与「日志化状态」

前置:第 05 课 | 预计时长:60 分钟 | 动手环节:必须完成

你将学会

问题引入

第 05 课说内存日志是「上半身」。这一课看下半身:重启之后会话怎么回来?状态(计划、目标、待办)存在哪? dsh 给出的统一答案有点反直觉:状态就是日志的纯函数。没有独立的状态机,恢复即重放。

正文

6.1 持久化是订阅插件,不是核心功能

对应 packages/session/session-persistence/src/coordinator.ts:1196-1207

// PersistenceCoordinator 挂四个监听(注意:它不是 backend)
on('session/created',  s => initFor(s))
on('session/event',    (s, e) => initFor(s).writes.enqueue(copy(e)))  // 入队,不阻塞生产者
on('session/flush',    s => flush(s))        // 显式排空到静止
on('session/disposed', s => retire(s))

要点:

6.2 两个后端与版本拒绝

后端实现抽象 SessionPersistence(locate / create / append / load / readFrom / list / listSnapshots):

版本策略(coordinator.ts:77-81

assertVersion(version) {
  if (version > SESSION_FORMAT_VERSION) throw '由更新的 harness 写入——升级后打开'
  if (version < 0) throw '低于支持的 v0,本构建无升级路径'
}

SESSION_FORMAT_VERSION = 0,无迁移承诺(预发布姿态:基础正确性 > 兼容面)。后端对过新/过旧格式方向感知拒绝(区别于损坏);未知事件类型对照 KNOWN_SESSION_EVENT_TYPES 同样拒绝重建——「不知道的事件不猜」。

6.3 投影与查询:两条独立机制

A. 实时值投影packages/session/session-projection/):

B. 查询视图packages/session-query/):

投影(A)查询(B)
产出当前值切片可检索文档集
索引无(状态在内存/缓存)FTS5 派生索引
共同点都用 surface 折叠,与 deriveMessages 同源

6.4 日志化状态:plan / goal / todo

共同模式:状态 = SessionEventMap 声明合并出的事件 + 纯折叠函数,无活体镜像,恢复/分叉/压缩全靠重放。

// plan(plan-mode/src/index.ts:46-54, 129-138)
interface SessionEventMap { 'plan/mode': { active: boolean } }  // log-only、整值、最后生效
foldPlanMode(events) { /* 遍历日志,最后一条生效 */ }  // 状态 = 日志前缀的纯函数

// goal:'goal/change' 携带变更后完整快照或 clear 墓碑(整值规则)
foldGoal(events) → { goal, roundsStarted }  // 续轮由 user/message.source.kind==='goal' 归因

// todo:'todo/write' 每次追加整表快照(条目刻意无 id)
exec.agent.session.append('todo/write', { todos })  // 最后写生效;投影在 turn/start 清空

三条设计规则贯穿其中:整值写(不写增量 diff)、最后生效(折叠即找最后一条)、只在提交点发布(遵守「模型可见 ⟺ 已记录」)。标题同理:session/title 是 log-only 事件,foldSessionTitle(events) = findLast(...)session-title/src/index.ts:191-193);遥测则是日志的一对一镜像(session-telemetry/src/coordinator.ts:91-95),默认禁用。

6.5 storage / spill:日志之外的两块

动手环节

  1. 读快照文件头:打开一个录制会话的 JSONL 首行,找出格式版本与事件词汇;故意把版本改成 99,用 test:snapshot 跑一次,观察「方向感知拒绝」的报错文案。
  2. 折叠练习:手写 foldPlanMode(10 行以内),对第 04 课的快照跑出「plan 模式是否激活」;再对 session/titlefoldSessionTitle
  3. 找事件:在快照里各找一个 approval/askedtodo/writesession/title 事件,确认它们都是 log-only(没有 surfaceOp)。

自检清单

常见误解

延伸阅读


Share this post:

Previous Post
模型看到的必须在日志里:dsh 的 Turn 执行流
Next Post
第 07 课 · Profile 与 Bundle:组合即配置