DSHarness 系统拆解 固定基线 47f943859b · 36 已复核 / 0 撰写中 / 36 章
English
会话与持久化·第 12 章

Projection、Checkpoint 与冷启动

全量事件之外,系统保存了哪些可丢弃的加速状态

已复核上游 47f943859b范围: 区分语义 checkpoint、projection cache、watermark、write-behind、tail replay 和恢复门槛。

结论:事件日志掌握真相,Checkpoint 与 Projection 掌握时机和成本

DeepSeek Harness 没有在 Session 日志旁边再持久化第二套“对话真相”。它让 append-only SessionEvent 流和 SessionHeader 保持权威,再叠加三种不同机制:不可逆工作前的语义 flush barrier、快速读取当前视图的内存 projection cell,以及缩短部分 fold 的耐久 projection checkpoint。浏览器还会收到另一层派生数据——带 watermark 的 baseline 和非耐久 push frame——并让它们重新向日志收敛。

只有把这些层严格分开,架构的优势才成立。语义 checkpoint 证明更早的事实已经到达存储;projection checkpoint 只记住一次 fold;watermark 表示一个值观察到了哪里;baseline 是传输 cut;client row 则是可丢弃的收敛状态。把其中任何一层误当成日志本身,恰好会制造这套设计试图避免的恢复错误。

1. 六个名称对应六种不同合同

表示权威性能够证明什么
Durable factSessionHeader + append-only SessionEvent事实源Backend 实际保存了哪些 metadata 与 committed events
Semantic checkpoint被 await 的 sessions.flush(session)耐久屏障,不是数据已接纳前缀的所有参与 flush listener 均已 settle
Live projection cell每个 unit、每个 live Session 的 {state, observedSeq}可丢弃派生状态最后一个通过该 unit fold 的事件
Projection checkpoint每个 key 的 {ver, seq, val}仅为耐久 cache绑定到某一日志生命周期的版本化 fold shortcut
Host baseline{asOfSeq, values}派生 wire snapshot一个可用来 seed client 的一致 cut
Client rowkey -> {value, seq}可丢弃收敛状态该 key 最新被接受的 Host value
持久化合同

持久化单元就是既有的 Session event,而不是一套平行 persisted-message 模型;不可 replay 的 metadata 保留在 Header 中。Projection checkpoint 类型也明确声明:row 从不具有权威性,版本不适用或它声称的日志端点无法成立时必须丢弃。ARCH-LOGSESSION-FORMAT-HEADERPROJ-AUTHORITY-LAYERS

2. Semantic checkpoint 是 await barrier,不是存储中的 checkpoint object

把事实 append 到 live Session
        ↓
sessions.flush(session)
        ├─ persistence listener 排空 write-behind
        ├─ 其他 scoped durability listener settle
        └─ 所有 listener settle 后才抛出第一个失败
        ↓
构造 model stream 或进入顶层 tool body

SessionStore.flush() 解析精确 live Session 的 scoped listeners,调用全部 listener,以 Promise.allSettled 等待,若存在拒绝再抛出第一个。它的返回值只表示是否有 durability listener 参与;这个 API 不会生成任何耐久的“checkpoint record”。

语义策略

标准 policy 会在构造下游 model stream 前、顶层 tool body 运行前,以及每一个 agent/pre-step 边界放置 barrier。Model 与 tool 边界 fail-closed;嵌套 tool dispatch 复用已经耐久的外层 call。LOG-CHECKPOINT-POLICY

3. Write-behind 把热路径接纳与耐久静止点分开

1

接纳时复制

每个 committed event 通过 structuredClone 进入 persistence 自己拥有的队列。

2

固定 deadline 组批

第一个 pending event 启动一个有界 timer;热 append 路径不等待磁盘。

3

失败仍保留

失败 batch 按原顺序放回队首,并暂停自动重试。

4

加入同一 barrier

并发显式 flush caller 共用一次排空,直到队列静止。

生产 controller

显式 flush 会取消 batching timer、等待重叠工作、重试 retained events,并一直排空到 active 与 pending write 都不存在。按 Session ID 的 serialization 阻止同一日志的耐久 append 相互交错。LOG-WRITE-BEHIND

Persistence 与 checkpoint scheduling 是两个独立插件。只加载 backend 合法,但崩溃可能丢失仍处于 batching window 或 outstanding write 中的事件。发布的 base composition 会显式同时挂载 JSONL persistence provider 与 checkpoint policy。PROJ-SHIPPED-COMPOSITION

4. Projection unit 是对每个 committed event 的同步 fold

ProjectionDefinition<K, S> = {
  key,
  schema,             // 校验 wire-facing view
  init(): S,
  apply(S, event): S, // pure 且同步
  view(S): Value,
  stateVersion
}
Registry drive

Registry 只订阅一次 session/event。每个事件都会通过每个已注册 unit 的 apply;不关心该事件的 unit 必须返回同一 state reference。只有 reference 改变才会触发 schema validation 与 change notification。缺失 cell 会延迟 fold 完整内存日志,而 observedSeq 即使在 state reference 未变化时也会前进。PROJ-REGISTRY-DRIVE

同步要求具有关键作用。Carrier 可以复制 Session 的 event snapshot,并在同一个 JavaScript turn 中立刻调用 snapshot(),得到共享 asOfSeq 恰为 session.seq - 1 的 values;异步 unit 会撕裂这个 cut。

5. 十三个生产 key 共用一个框架,却表达不同生命周期

Key / versionFold 语义生产源码
todos v2最新 whole todo/write;下一个 turn/start 清空packages/todo/tool-todo/src/index.ts:122-147
plan v1command/run(plan) 记录意图;plan/mode 提交 active 并清除 pendingpackages/plan/plan-mode/src/index.ts:235-265
goal v4Durable goal/change 的 last-wins projectionpackages/goal/goal/src/index.ts:201-212
title v1最新 session/titlepackages/session/session-title/src/index.ts:304-316
sessionStats v1全日志 Turn、Step、model、tool、TTFT 与 decode totalspackages/session/session-stats/src/projection.ts:1-21,88-182
tokenUsage v1Usage sample 替换同一 Turn/Step 的前一份 samplepackages/llm/token-meter/src/usage-projection.ts:97-140
contextPressure v4Provider prompt sample 加 Surface signed movement 估计下一请求packages/llm/token-meter/src/usage-projection.ts:142-205
contextBreakdown v2最新 request envelope 加 message-surface shadow pricingpackages/llm/token-meter/src/breakdown-projection.ts:31-69
subagentTiming v2Descriptor 之后已完成及当前活跃 Turn 的持续时间packages/subagent/subagent/src/projection.ts:36-85
subagent v2最后一个有效耐久 mode/label descriptor,并包含其 seqpackages/subagent/subagent/src/projection.ts:131-155
permissions v1三个 whole-value knob events,再相对 composition defaults 生成 viewpackages/interaction/permission-presets/src/index.ts:227-251
sessionListMetadata v1供 listing 使用的 blank-to-nonblank 状态与最新真人 prompt 时间packages/host/apiproxy/src/api-proxy.ts:1289-1300
imageLimits v1每次启动固定的 attachment limits;constant state、live-service view、只走 baselinepackages/host/apiproxy/src/api-proxy.ts:1302-1323

这是固定 commit 上对生产注册的全仓库清点,并不承诺每种部署都激活全部十三个 key。注册取决于 composition;process-wide unit table 还意味着某个 preset 挂载的 key,可能出现在自身 preset 并不会产生有意义值的其他 Session snapshot 中。

6. Watermark 在观察时前进,Push 只在变化时发生

seq N 的 event 进入每个 unit
  ├─ apply 返回同一 reference
  │    observedSeq = N
  │    不发送 session/projection frame
  └─ apply 返回新 reference
       observedSeq = N
       schema.parse(view(state))
       emit { key, value, seq: N }

这个区分解释了为何 imageLimits 从不发送 push,却仍会出现在 tail baseline 中;也解释了为何完整 snapshot 中一个安静的 value 可以合法地标记为日志当前 asOfSeq。Watermark 表示“已观察至此”,而不是“最后一次变化发生在此”。

Host carrier

Host 把 registry change 翻译为非持久的 session/projection mux frame。History tail——并且只有 tail——携带完整 baseline。Attached Session 在复制 events 与读取 projection snapshot 之间没有 await;detached Session 则 fold 被 inspect 的 events。PROJ-HOST-CARRIERLOG-HISTORY-CUT

7. Durable projection cache 是版本化 shortcut,从不是读取权威

Guard为何存在失败行为
stateVersionState shape 或 fold semantics 可能变化丢弃该 key 的 row 并重新 fold
createdAt + cwdSession ID 表示一个槽位,不会永远唯一指向同一生命周期丢弃整个无关 record
Anchored floorCached row 可能声称超过缩短或修复后的 stored log检测 overreach 并从 seq 0 重读
Lossless JSON boundaryUnit 内部状态必须可耐久序列化让该 cache write 失败;不修改 Session log
存储形状

session_projcache domain 版本为 3。每个 Session record 把完整的 key-to-row checkpoint 绑定到一个 log identity。Domain version 不兼容时可以丢弃整个 cache medium,因为事件日志仍足以重建。PROJ-CACHE-SCHEMA

Restore 数学

restoreFloor() 选择最低可用 watermark 前的一条事件。restore() 只有在 row version 匹配、且 seq 位于所提供 suffix 能够证明的边界中时才接受它。不安全的 partial restore 会 throw,让 caller 从零重读,而不是混合不兼容状态。PROJ-REGISTRY-RESTORE

8. 已实现的 cold-cache ladder 没有接入发布的 Cold History

已实现的 SessionProjectionCache.coldSnapshot(id)
  cache rows → restoreFloor → persistence.readFrom(tail)
  → registry.restore → fail-soft refreshed write-back

发布的 ordinary cold History
  persistence.inspect(full logical log)
  → registry.restore({}, all events, 0)

发布的 Session list
  只调用 cachedSnapshot(header),零 log I/O
已实现 ladder

coldSnapshot() 会校验 log identity,从 anchored floor 做 tail fold,在 identity 或 endpoint 失败后回退到一次 full read,并在不影响读取的前提下写回 refreshed rows。PROJ-CACHE-LADDER

全仓库 call-site 审计

在固定 commit 上,生产源码搜索只找到 coldSnapshot 方法定义与生成的 API catalog,没有任何发布 caller。session.list 只使用 cachedSnapshot;ordinary 与 subagent cold History 已经读取 inspect 后的完整日志,并从零 fold;subagent listing 在 cached row 足以定论时使用它,否则执行完整 inspection。因此这个 cache 当前是发布的 list/metadata accelerator,而不是活跃的 cold-History tail-replay accelerator。PROJ-CACHE-INTEGRATION

9. Inspect、Prepare 与 Resume 构成冷启动协议,而不是三个别名

操作逻辑恢复耐久突变Ownership / publication
inspect(id)校验并在内存加入确定性 closers不提交 repair无 Agent;可保留 exact prepared graph 供复用
readFrom(id, seq)无;返回有效 physical stored suffix不经过 preparation cache,也不发布
prepare(id)构造平衡的 exact Session必要时提交 repair,再重新加载独占预留 unpublished Session
Agent resume消费 preparation后续 live suffix 按正常路径持久化运行 setup,发布 exact Session + Agent,或 rollback
Persistence 读面

Cold inspection 可以呈现平衡的 logical Turn,同时不触碰 torn 或 interrupted physical tail。Preparation 使用 loadingreadycommittingreserved 四个 phase;同 ID inspector 共享一次 load,只有被独占预留的 exact Session 能够 publish。LOG-PERSISTENCE-READ-FACESLOG-PREPARED-COMMIT

Agent transaction

Agent factory 把 caller、owner fiber 与 factory cancellation 融合到 persistence.prepare 周围。Setup 在 publication 前运行;失败会 dispose prepared Agent 并释放 reservation。SessionStore.enter 是最终同 ID collision boundary。PROJ-COLD-RESUME

10. 浏览器 Cold Open 被刻意设计为不同于 Agent Resume

1

恢复选择

浏览器记住当前 Session address,并在 list 到达后选择它。

2

打开 History

Session.open() 请求 tail page;它不请求 Agent。

3

Inspect 冷存储

Host 优先使用 attached Session,否则使用 persistence inspection。

4

仅按需 Resume

Prompt、rename、model、command 等需要 Agent 的操作才进入共享 resolver。

Cold Session 必须能被 persistence list 找到,并携带 project cwd,才可由 Web 服务。若日志记录的 preset 已无法提供 standing presenter scope,History 仍可降级为 generic tool card。真正 resume 更严格:它必须通过 format、continuity、ownership、preset composition、setup 与最终 publication 检查。

没有静默 Fresh fallback

共享 resolver 复用 live Agent,对同 ID resume 去重,inspect ownership 与 composition,再调用 agents.resume。Not-found 与 subagent ownership 得到 typed error;其他真正的 resume failure 仍是 error。只有显式 create/adopt 路径在 persistence 确认 ID 缺失后才创建 fresh Session。PROJ-HOST-COLD-OPENPROJ-COLD-RESUME

11. Client Convergence 组合完整 Baseline、稀疏 Push 与 Generation Repair

history tail baseline { asOfSeq: N, values: all registered keys }
                       ↓ seed
resident ProjectionValueStore
                       ↑ 仅 frame.seq 更大时 apply
session/projection { key, value, seq }

新 mux generation:
session/subscribed { lastSeq: D }
→ 删除 client 中 seq > D 的 rows
→ 重新拉 list,并 resync 已打开的 History windows
Value store

即使 Session object 尚不存在,manager 也为每个 Session 拥有一个 projection store。Baseline 与 push frame 都使用严格 higher-seq-wins。完整 History baseline 还会清理 cut 及更早时刻未携带的旧 key;局部 session.list block 只 apply 自己携带的 key,刻意不清理其他 key。PROJ-CLIENT-STORE

重启恢复

session/subscribed.lastSeq 会删除声称超过 Host durable endpoint 的 row,让 crash 后重建出的较低 seq 真相能够落地。随后 History installation 排空 buffered live events,丢弃 overlap,并在检测到 seq hole 时重新拉 tail。PROJ-CLIENT-MANAGERLOG-CLIENT-STITCH

Mux 与 host feed 是两条独立 stream,没有共享 total-order 声明;v1 的 events.mux({since}) 也会忽略 since。被支持的收敛方式是重开 stream、刷新 list、重拉 History,并按各 domain 规则 repair,而不是从一个全局线性 stream offset 继续。

12. Rebuild 与 Invalidation 发生在四种不同 Scope

变化被失效的状态真相如何回来
追加另一个 eventLive cell eager 前进变化 unit 发 push;完整 baseline 之后把每个 value 标记到新 cut
Unit state 或 fold semantics 变化stateVersion 的 persisted row丢弃 row,并从可证明的日志前缀 fold
Session ID 被重建或 storage root 变化Identity 不匹配的整个 checkpoint record丢弃该无关生命周期的全部 rows
最后一个 registrant 卸载该 key 的 registration 与全部 live WeakMap cells后续 snapshot 省略 key;重新注册后延迟 fold 内存日志
Projection-cache domain format 变化整个 derived medium全部丢弃;Session log 仍足以重建

没有任何 live registry API 会从 persisted checkpoint hydrate 自己的 WeakMap cells。Cache 的 detached restore() 路径会返回 snapshot 与 refreshed checkpoint,但 resumed live Session 在 registry 第一次访问时仍会 fold 内存 event array。Cache 也没有 eviction 或 retention surface;record 会一直累积,直到 out-of-band maintenance 删除。

13. 四项文档声明已经偏离当前生产控制流

文档声明当前实现证据
重复 projection key 会 throw同 version 注册共享一个 ref-counted unit;只有 version mismatch 才 throwsession-projection/README.md:11 对比 src/index.ts:194-220
Title 不进入 session.listList row 携带 projection block;cold cached title 会在 open 前 seed managerhost/apiproxy/README.md:33 对比 api-proxy.ts:1725-1764
History 会 resume unattached Session,且没有 persistence-only read pathCold History 使用 inspect(),并明确不发布 Agentclient/connection/README.md:23-26 对比 api/sessions.ts:264-283
skill.list 可从 Header 解析 cold SessionHandler 首先要求 ctx.sessions.get(sessionId);unattached Session 返回 not foundhost/apiproxy/README.md:59 对比 api-proxy.ts:3205-3245

14. 源码级风险:Detach 可能短暂让 Projection Cache 领先日志

Session detach
  1. 从 SessionStore 移除 Session
  2. emit 被 contain、未 await 的 session/disposed observers
       ├─ persistence 启动异步 retirement flush
       └─ projection cache 取 snapshot 并写入
            live-entry 检查此时失败 → 不调用 sessions.flush()
高置信静态推导;未执行 crash injection

Cache 文档声明日志永远领先 cache。普通 live write 确实执行这个保证,但 detach 时并不严格成立:Session 已经离开 store,因此 cache 跳过显式 flush,只依赖 persistence listener 的另一条 retirement drain。Cache 源码自身也承认可能存在 residual overreach。若进程在两次耐久写之间崩溃,就可能留下 seq 领先 stored log 的 checkpoint。PROJ-CACHE-DETACH-WINDOW

coldSnapshot() 原本可通过 anchored floor 检出 overreach,但发布路径没有 caller 使用它;zero-I/O list read 也不校验 stored log endpoint。因此 phantom list projection 可能进入 client;后续较低 seq 的 cold-History full-fold baseline 会在 higher-seq-wins 下失败,直到真正 resume 发出 session/subscribed 并 truncate row,或日志前进超过它。

15. 源码级风险:Equal-seq Replacement 无法跨越 Host Generation

确定性 client-rule 推导;未执行该场景

Client 会拒绝 seq 小于或等于 resident row 的所有 update,而 generation truncation 只删除严格超过新 Host durable lastSeq 的 rows。如果 Host 在相同日志端点上重启、但在该端点计算出不同 value,旧 row 会保留,新 equal-seq baseline 无法替换它。PROJ-CLIENT-EQUAL-SEQ

imageLimits 让场景变得具体:它的 state 永远是 null,view 读取当前 boot 的 attachment config,并且刻意从不 push。浏览器若跨 Host restart 保持存活,就可能在 seq N 保留旧 limits;subscribed 不会删除它,因为它没有超过 N,而新的 N baseline 会因 equal 被拒绝。只有另一个 event 推进 baseline、client 完整重置,或协议携带 generation/version 维度后,value 才会收敛。

同一个边界也适用于 unit view semantics 在日志 watermark 不变时改变,以及同 key 的 unload/reload。stateVersion 能保护 Host persisted cache,却没有出现在 client wire row 中,因此无法解决这个 client-generation 歧义。PROJ-IMAGE-LIMITS

16. 源码级风险:一个 Optional Unit 可以让 Ordinary History 不可用

Carrier 不一致

Detached History baseline 调用 registry.restore({}, events, 0),会 fold 每一个已注册 unit。如果任何 unit 的 applyview 或 schema 拒绝日志,ordinary session.history 会返回 internal,而不是在没有 projections 的情况下继续提供 transcript。Session listing 明确按 row contain projection failure,subagent History 也会降级为不带 block;ordinary History 没有这样做。PROJ-HISTORY-FAILURE

这不只是 failing domain 内部的 corrupt state:process-wide registry 的 all-unit fold 允许一个无关 projection 阻断基础 transcript read。Fail-loud 是否符合预期需要明确产品决策;当前 carrier 行为并不一致。

Capability invalidation 延迟

注销最后一个 unit 只会删除 registration 与 cells,不会发送 invalidation frame。已连接 client 会保留该 key,直到后续完整 tail baseline 省略它;如果相同 key 在同一 log seq 重新加载,即使 baseline 到达,严格 equal-seq 规则仍可能保留旧 value。PROJ-REGISTRY-REGISTRATIONPROJ-CLIENT-STORE

17. 设计评价与验证状态

选择收益代价
唯一权威事件日志每个派生层都可丢弃并重建加速路径未接线时,cold full fold 仍昂贵
同步、whole-value projection units原子 cut 与简单 client replacement 语义每个 event 都触碰每个 unit;大 value 随每个 tail baseline 传输
版本化 persisted fold shortcuts语义变化无需 cache migrationVersion discipline 依赖人工,cache retention 无界
Inspect 与 Resume 分离Cold browsing 不激活 Agent,也不突变 durable recovery state读取与执行路径会暴露不同的 logical/physical 时刻
Client higher-seq-winsStale baseline 与 replayed frame 无法回退当前状态单独 seq 无法区分 equal-watermark Host generation
本章验证范围

本章在固定 upstream commit 上追踪了 Session flush dispatch、checkpoint policy、persistence write-behind、projection registry 与全部生产注册、projection-cache schema 与读写路径、Host History/list carrier、cold inspection 与 exact preparation、Agent resume,以及浏览器 projection/gap-repair store;同时阅读了相关合同测试源码。依赖未安装,本地没有执行测试、crash injection 或真实 reconnect 实验,因此上面的每个风险段都被标记为静态推导,而非已复现行为。

我的学习体会

内容仅自动保存到当前浏览器,不上传、不进入仓库。你可以导出 Markdown 自行归档。