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

内部会话结构与事件模型

append-only SessionEvent 如何成为模型上下文的事实源

已复核上游 47f943859b范围: 列出 envelope、事件类型、seq、scope、来源关联、格式版本和 model-visible invariant。

结论:完整日志保存事实,Surface 决定模型此刻看见什么

DeepSeek Harness 的 Session 不是消息数组,也不是 Provider SDK transcript。它是一条不可变、连续编号、可扩展的 SessionEvent 日志;其上再折叠出一个可替换的 model-visible Surface。Compaction、tool-result pruning 和其他上下文改写只会在 Surface 上遮蔽旧节点,原始事件仍留在完整日志中。

这个设计同时保留三种语义:原始事实用于审计和 replay,当前 Surface 用于下一次模型请求,append-origin 事件用于不应被压缩重写的人类 transcript。理解这三层,是理解后续持久化、压缩、恢复和 UI 的前提。

1. SessionHeader 与 SessionEvent Log 分工不同

保存内容变化方式语义 owner
SessionHeaderformat version、session ID、createdAt、cwd、parent、seedLength、subagent origin/depth、agent preset创建时快照,事件日志之外生命周期 identity 与恢复所需 composition SESSION-FORMAT-HEADER
SessionEvent[]对话、请求、工具、策略、压缩、编排和诊断事实只 append;seq 从 0 连续增长Agent interaction 的逻辑事实源
SessionSurface当前 model-visible event seq 顺序append 或 positional replace下一次 deriveMessages() 的输入

Header 中的 parentSessionseedLength 是持久 fork lineage;delegationDepth 防止子 Agent resume 后被误当顶层;agentPreset 绑定恢复时的工具与 prompt composition。它们不需要伪装成 conversation event。

2. Event envelope 很小,但约束非常强

固定 envelope

每个事件是按 type 区分的 union:{type, seq, time, data},可选字段只有 surfaceOpsourceEventSeqsignorable: true。任何额外 envelope key 在 seed/load 时拒绝。SESSION-EVENT-ENVELOPE

  • seq 是会话内唯一排序权威;live append 直接取 log.length,seed 必须从 0 无洞连续。
  • timeDate.now() 的 wall-clock 记录,不承担严格单调排序;同毫秒或系统时钟回拨都不改变 seq 顺序。
  • data 必须是 lossless JSON,而不是“能被 JSON.stringify 大致处理”即可。
  • 只有 user/messageassistant/messagetool/result 能携带 Surface metadata。
  • 未知 required event 不能静默忽略;只有 writer 明确标 ignorable: true 才表示丢失它不改变重建。
机制结论

所有跨事件关联都以 seq,而不是 timestamp、array identity 或数据库 row ID 表达。因此 JSONL、SQLite、内存 replay 和浏览器传输可以共用同一逻辑坐标。

3. 固定基线识别 44 种事件

生成 catalog

当前构建的 known-event catalog 从全仓库的 SessionEventMap 声明生成,共 44 种。核心包声明 13 种,其余由各 owner 通过 TypeScript module augmentation 加入。SESSION-CORE-EVENTSSESSION-KNOWN-EVENTS

所有权组事件(无省略)数量
核心会话turn/start, turn/end, step/start, step/end, user/message, assistant/chunk, assistant/message, tool/call, tool/result, todo/write, request/header, request/context, session/end-seed13
控制与策略agent-preset/selected, agent/inbox/spliced, approval/asked, approval/decided, approval/policy, permission/preset, sandbox/mode, plan/mode, goal/change, schedule/change10
压缩与重试compaction/start, compaction/prune, compaction/summary, compaction/end, llm/retry, llm/retry-started6
辅助模型与元数据session/title, session/title-llm-request, web/deepseek-search-llm-request, feedback/record4
命令与 Hookcommand/run, command/done, hook/invoked, hook/result4
子 Agent、Workflow 与 Code Modesubagent/descriptor, tool-workflow/run-start, tool-workflow/run-end, tool-workflow/agent-start, tool-workflow/agent-end, tool/code-dispatch, tool/code-dispatch-start7

Catalog 说明“这份 build 理解哪些 vocabulary”,不说明所有事件都会进入模型。大多数事件是 log-only audit/control facts;只有三类 Surface event 可能派生 Message。

4. Lossless JSON 是写入边界,不是存储后补救

一次遍历 snapshot

snapshotJsonValue() 用显式 task stack 遍历 graph,在同一次 property read 中完成校验与复制,避免 stateful getter 在“先检查、后复制”之间换值;深嵌套受内存而非 JavaScript call stack 限制。SESSION-LOSSLESS-JSON

接受拒绝原因
null、boolean、string、有限 number-0、NaN、Infinity必须 JSON round-trip byte-semantically 等价
dense plain Arraysparse Array、额外 own property、Array subclassJSON 会补 null 或丢属性/原型
plain/null-prototype objectDate、Map、Set、class instance、伪造 prototype不调用 toJSON,不接受隐藏转换
own enumerable string keysSymbol key、non-enumerable key、function、undefined、BigInt、cycle这些值会被丢弃、改形或无法编码
不可变性测试

事件和嵌套 Message 在接纳时 deep-freeze;派生历史共享这些冻结对象。测试对 content 和 tool result 的修改都会抛 TypeError,非法 graph 在源头拒绝且日志长度不变。SESSION-IMMUTABILITY-TEST

5. Session.append() 是一条两阶段本地事务

1

拥有输入

Snapshot data 与 Surface metadata;caller 后续 mutation 不再相关。

2

构造候选

分配 seq = log.length、读取 wall time,deep-freeze 完整 event。

3

计划 Surface

只验证 candidate,尚不修改已提交 node list。

4

Precommit dispatch

internal/dispatch 让 invariant/diagnostic 纯验证;任何 veto 都发生在日志变化前。

5

Commit

log.push(event) 是逻辑提交点,同时使旧 events array cache 失效。

6

Postcommit publish

按 snapshot 的 listener list 通知 session/event;同步 throw 和 async reject 都逐 listener 记录并隔离。

生产实现

同一个 frozen candidate 从 precommit validation 穿过 commit 到 observer。Observer 期间的 reentrant append 被拒绝,避免嵌套提交重排后续 listener。SESSION-APPEND-COMMIT

原子性测试

测试证明 internal dispatch veto 不写日志、不发布事件、不推进 Surface;commit 后 hostile observer 不能回滚事件,也不能阻止后续 observer 看见它。SESSION-APPEND-BOUNDARY-TEST

6. Surface 是 append-only log 上的一门小型替换演算

唯一可见类型

Runtime 只承认 user/messageassistant/messagetool/result 为 Surface-eligible。它们必须声明 surfaceOp;boundary、chunk、request 或 plugin audit event 携带 Surface metadata 会失败。SESSION-SURFACE-PROJECTIONSESSION-SURFACE-PROVENANCE

Operation当前 node list 变化完整日志变化主要用途
append把当前 event seq 加到 tail追加同一 event正常 user/assistant/tool result
{op:'replace', start, end}按当前 Surface 顺序,把 inclusive range 原子替换为新 event seq只追加 replacement event;旧事件不删除Compaction summary、tool-result content rewrite

start/end 是当前 Surface node 的事件 seq,不是原始日志中的连续切片。两端必须仍在当前 Surface 且顺序合法;被遮蔽节点可能被其他 log-only 事件隔开。SESSION-SURFACE-REPLACE

Tool result 特权收窄

一个 replacement tool/result 只能目标一个当前 tool result,并且只能改嵌套 result block 的 content;call ID、error flag、message identity、turn/step 与 meta 都必须结构等价。这让 spill/prune 可以缩短输出,却不能伪造另一次工具执行。

7. sourceEventSeqs 是显式血缘,但不是全覆盖 lineage

校验规则

Source list 若存在,必须 dense、无重复、全是非负 safe integer 且严格早于当前 event。Surface replacement 还必须至少包含每个被遮蔽 node;否则无法证明新节点替代了什么。SESSION-SURFACE-PROVENANCE

生产者典型 source语义
Assistant assembler该 attempt 的全部 assistant/chunk seq最终 Message 来自哪些 raw stream facts
Tool resulttool/call seq结果关联哪个已记录的副作用意图
Compaction replacement全部 shadowed Surface nodes,另可含摘要输入事实模型可见替换的来源集合
普通 user/plugin message通常缺席自身就是新事实,不声明派生来源

8. deriveMessages() 是 Surface 的纯投影

逐节点规则

user/message 原样返回 user message;非空 assistant/message 返回模型消息;tool/result 返回 user-role result。空 content assistant 只用来承载 usage,不进入 transcript;其他 41 种事件均返回 null。SESSION-SURFACE-PROJECTION

增量 cache

没有 replacement 时只折叠上次之后的新 Surface node,复杂度是 O(new nodes);replace generation 变化时清空并从当前 node list 重建。每次 API 返回新的 array snapshot,内部 Message object 继续共享冻结 event data。SESSION-DERIVED-CACHE

Replay oracle 测试

测试在普通 append、空 assistant 和 replacement 后,用完整事件创建 fresh Session,要求其派生结果与 live incremental cache deep-equal;之前返回的 array 不会因后续 append 自动增长。依赖未安装,本研究未执行。SESSION-DERIVED-ORACLE-TEST

设计评价

Message framing 由 producer 写进 content,Surface projector 不再按 source 二次包裹。这减少隐藏 prompt mutation,却要求每个 context producer自己维护正确的 model-facing framing。

9. 同一日志支持三个不同读视图

视图包含什么不应拿它做什么
Complete event log44 类事实、raw chunks、被遮蔽消息、所有 control marker不能直接当 Provider messages
Current model Surfacereplacement 后的当前 node 顺序,再经 message projection不能当用户“曾经看过什么”的永久 transcript
Append-origin transcript只取最初以 surfaceOp:'append' 进入的 message events不能代表压缩后下一请求的上下文

Surface 模块明确警告:如果人类 transcript 直接读取当前 Surface,compaction 一落地就会让用户已经看过的对话消失;replacement copy 是 model-only 视图。反过来,只用 append-origin transcript 构建下一次请求,又会完全忽略压缩。

10. Seed 把恢复前缀与本生命周期分开

接纳路径

Seed 的每个 event 被 detach/adopt、freeze、校验 envelope、request header、连续 seq 和 Surface transition;失败不会留下半个 Surface。构造完成时 firstLiveSeq 记录 seed 长度,若尾部尚无 session/end-seed 则追加 marker。SESSION-SEED-ACCEPTANCE

幂等测试

Fresh Session 不带 marker;显式空 seed 也会得到一个 marker;已有 marker 的 seed 再打开不会继续追加。Seed prefix 原样保留,派生 Message 与原 Session 相同。SESSION-SEED-REPLAY-TEST

Marker 不是“当前是否有其他 writer”的锁,也不是 crash checkpoint。它只表示 constructor seed 与本生命周期新增事件的边界;fork、resume 和测试 replay 可据此区分继承事实与新工作。

11. Version 0 的兼容策略是 fail-closed

格式规则

SESSION_FORMAT_VERSION 当前固定为 0。Header/envelope/core semantics/Surface mechanism 的破坏性变化应 bump;本基线没有 upgrade chain 或迁移,版本不等即拒绝。普通新事件理论上不必 bump,因为 envelope 有 ignorableSESSION-FORMAT-HEADER

未知事件门

Persistence normalization 后用 generated known catalog 检查整个日志。未知 required event 会产生 SessionFormatUnsupportedError,而 ignorable:true 的未知 event 会保留在 loaded log,让知道它的消费者仍可读取。SESSION-UNKNOWN-EVENT-GATESESSION-UNKNOWN-EVENT-TEST

哲学

默认“required”会导致过度拒绝,但不会让旧 runtime 在不知道新语义的情况下悄悄重建出错误对话。这是把 forward compatibility 的风险偏向可见停机,而不是 silent corruption。

12. Always-on storage guards 与 optional relational invariants 分离

Always-on Session rootOptional dsh-session/invariant
Lossless JSON、snapshot、freezeTurn 从 1 连续编号且不重叠
Envelope 与 seed seq 连续Step 在当前 Turn 内从 1 连续编号且闭合
Surface eligibility、range 与 source coverageAssistant/chunk/tool 等 execution event 必须在当前 Step
Tool-result replacement 只能改 content同 Step 的 tool call/result 配对
Request-header shape 的少量兼容校验插件扩展事件由各自 owner 的 invariant 检查
关系检查

Companion 从已有日志重放 trace;新 candidate 在 internal/dispatch 中只生成暂存 transition,真正的 session/event 到达后才推进。后续 precommit listener veto 不会让 invariant state 超前。SESSION-RELATIONAL-INVARIANTSESSION-INVARIANT-COMMIT

13. 三个完整性缺口与两个有意取舍

缺口 1:end-seed writer authority 未执行

类型注释称 constructor 是 session/end-seed 的唯一合法 writer,并直接承认 plugin 追加会错误重分类此前 live bracket;但 public append() 接受该 type,optional invariant 也明确不约束它。当前安全性依赖 in-process plugin 自律,而非 capability 或 runtime guard。SESSION-CORE-EVENTSSESSION-APPEND-COMMIT

缺口 2:ignorable 是 reader 能力,不是现成 writer API

Envelope 定义 ignorable?: true,loader 也实现了 forward-compatible gate;但 Session.append(type, data, surfaceIntent?) 不接受 envelope options,生产源码没有任何 append 写入 ignorable:true。因此本基线的普通新事件 producer 无法通过公共路径使用文档描述的“无需 bump 的可忽略事件”机制。SESSION-EVENT-ENVELOPESESSION-APPEND-COMMIT

缺口 3:已知 payload 不是统一 runtime-schema

Always-on root 深校验 JSON、三类 Message、request header 和 Surface,但没有为全部 44 个 payload 运行统一 schema。关系校验又在可选 companion。对受信代码这是轻量扩展点;若把第三方插件视为不可信 writer,这不是完整防线。

  • 有意取舍:sourceEventSeqs 对普通 append 可缺席,避免每条新事实都维护昂贵 lineage;代价是 provenance 不完整。
  • 有意取舍:live commit 与 disk durability 分开,保持 hot-path 同步和无 I/O;代价是调用方必须在外部副作用前正确使用 flush barrier。

14. 优势、代价与验证状态

设计选择主要收益主要代价
完整 append-only log + replaceable Surface保留审计事实,同时允许上下文治理每个消费者必须选对视图
Lossless JSON + deep-freeze跨后端 replay 稳定,消除 caller alias mutation无法直接记录 Date/Map/typed object 等丰富 runtime 值
seq 统一坐标日志、Surface、source lineage、client dedup 共用身份跨 Session 因果需要额外 header/event 关联
Module-augmented event vocabularyPackage owner 可独立扩展需要 generated catalog、版本门和 owner invariant 保持同步
Precommit validation + contained observersVeto 与 notification failure 的语义清晰Observer 只能补偿,不能回滚已提交事件
验证状态

本章复核了类型、生产 append/surface/invariant 控制流、generated vocabulary 与测试源码。上游依赖未安装,因此所有测试段落都表示“测试定义的预期合同”,不是本地执行结果。下一章将在这份事件模型之上继续追踪 raw log、fork、repair、history 与 client replay。

我的学习体会

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