DeepSeek Harness 里四个都叫 version 的数字:0、15、8、1
翻 deepseek-harness 的会话与存储这一块,最容易出的一类事故不是看不懂代码,而是看懂了、但把版本号认错了对象。这个仓库里至少有四个不同的常量都在表达”版本”,它们的数字分别是 0、15、8、1,出处分散在四个包里,含义互不相干。你要是在排查一个”日志读不进来”的问题时脑子里只有一个版本号,基本一定会走错方向。
先说清限定:以下全部来自 2026-08-16 我们采集的仓库快照 47f9438,版本 0.1.0-rc.5。这个仓库建立于 2026-08-13,README 自述处于开发者预览阶段,并明确写了未来会有破坏兼容性的变更——下面每一个常量名和数值都带着这层限定,不是稳定契约。
四个数按住
| 常量 | 值 | 所在文件 | 版本化的对象 |
|---|---|---|---|
SESSION_FORMAT_VERSION | 0 | packages/core/session/src/types.ts:56 | 会话日志的事件词汇表 / 逻辑格式 |
SCHEMA_VERSION | 15 | packages/session/session-persistence-sqlite/src/schema.ts:20 | 会话持久化 SQLite 后端的盘上表结构 |
SESSION_QUERY_SQLITE_SCHEMA_VERSION | 8 | packages/session-query/session-query-sqlite/src/schema.ts:8 | 会话查询全文检索后端的派生索引表结构 |
STORAGE_SQLITE_SCHEMA_VERSION | 1 | packages/storage/storage-sqlite/src/schema.ts:20 | 通用 storage 子系统 SQLite 的物理布局 |
四条线之间不是嵌套关系,是并列关系。packages/session/session-persistence-sqlite/src/schema.ts:16-19 的注释自己就把话挑明了:盘上 schema 版本与会话自身的 version 是正交的,后者版本化的是事件词汇表。会话自身的那个 version 是会话元数据 SessionHeader 的一个字段,而 SessionHeader 本身不进事件日志(docs/subsystems/persistence.md:47-89)。
为什么会长成四条独立的线
从子系统的切分能看出来。docs/subsystems/storage.md:5 原文说,storage 子系统持久化的是”除会话事件日志以外的一切”,会话日志有它自己的 seam。也就是说,从设计上会话日志和通用 storage 就是两套东西,各自的盘上格式当然各有版本。
再往里看,会话日志这条线本身还要再分两层:
- 逻辑层是
Session的事件日志。docs/subsystems/session.md:5把它描述为 append-only 的SessionEvent日志,是一次 agent 交互的唯一真相源,给模型看的消息历史是从日志派生的、不单独存一份。这一层要版本化的是”日志里可能出现哪些事件类型”。 - 物理层是后端怎么落盘。同一份逻辑日志,JSONL 后端写成
<root>/--<normalized-cwd>--/<encoded-id>/session.jsonl.zstd(packages/session/session-persistence-jsonl/README.md:9-15),SQLite 后端则一条事件落一行,列是(session_id, seq, type, time, data, source_event_seqs, surface_op),另有ignorable(docs/subsystems/persistence.md:236、schema.ts:48-60)。表结构变了,逻辑格式未必要动。
SCHEMA_VERSION = 15 只管后面那一层。而查询侧的 8 又是另一码事:那是一个派生索引,不是真相源。
SESSION_FORMAT_VERSION = 0 是四个里最特别的一个
它特别在三点。
第一,它是一个单一单调整数,没有主版本次版本之分(packages/core/session/src/types.ts:40-55)。第二,是否要 bump 的判断标准是”写入方发出什么”,而不是”新的读取方能接受什么”——方向和大多数人的直觉相反。第三,packages/core/session/README.md:143 原文写着:SESSION_FORMAT_VERSION 仍钉在 0,属于 pre-release,不承诺广泛兼容;比它新的日志会报”written by a newer harness — upgrade”,比它旧的日志则说明当前 build 不带升级路径。
这个常量在运行路径上是硬校验。packages/core/session/src/index.ts:101-102 里,头部的 version 与常量对不上就直接抛错,错误文案是 session header version must be ${SESSION_FORMAT_VERSION}, got ...。注意这里的 version 来自 SessionHeader——那是会话元数据,不进事件日志,字段有 version、id、createdAt、cwd?、parentSession?、seedLength?、origin?: 'subagent'、delegationDepth?、agentPreset?(docs/subsystems/persistence.md:47-89)。
顺带一提,这个 0 校验的对象——事件词汇表——本身也有可以数的数:packages/core/session/src/types.ts 里 SessionEventMap 的核心成员是 13 个,而全仓已声明的事件类型被生成到 packages/core/session/src/known-event-types.ts:19-64,我们数出 44 个。读日志时遇到不在这张表里的事件类型,行为是拒绝重建,除非该事件信封上带 ignorable: true(docs/subsystems/persistence.md:94、known-event-types.ts:8-17)。所以”版本号对得上”和”这条事件认得出”是两道各自独立的关卡。
三个 SQLite 版本号各管各的库
它们连”这个库是不是我的”都是各判各的。会话持久化用 SESSION_PERSISTENCE_SQLITE_APPLICATION_ID = 0x44534850(schema.ts:23),会话查询用 SESSION_QUERY_SQLITE_APPLICATION_ID = 0x44534851(src/schema.ts:11)——只差最后一位。
会话持久化侧的拒绝规则写得很硬:非纯净的无版本库、外来 application id、任何非当前版本,都在改 journal 模式之前就直接拒绝,理由是这个未发布格式没有迁移(packages/session/session-persistence-sqlite/README.md:13)。同一句”没有迁移”在这个仓库里出现了不止一处:docs/subsystems/storage.md:47 与 packages/session/session-persistence-jsonl/README.md:38 各以不同措辞写了同一条。
通用 storage 这边给的是错误码而不是文案:版本不匹配报 version-mismatch,解析不了报 malformed-medium,storage.md:47 明写 “no migration, pre-release stance”。
查询侧的 8 则有一个容易被忽略的前提:出厂 bundle 基线给的是 path: ':memory:' 加 openAt: never(packages/bundle/base/cordis.patch.yml:117-121),上方 :109-116 的注释解释了这个选择——全文检索是 opt-in。openAt 的三态是 'startup' | 'first-search' | 'never',库里默认 'startup',而 'never' 意味着彻底关掉全文检索:精确读、过滤、追踪仍在,searchSessions / searchEvents 以 SESSION_QUERY_SEARCH_DISABLED 失败,SQLite 永远不会被 import 或打开(packages/session-query/session-query-sqlite/src/index.ts:85,95-101)。换句话说,按出厂基线跑,这个版本号对应的库压根不会被创建。
还有第五、第六个”version”,别一起混进来
排查时最好把下面这两类也隔开:
- npm 包版本。上述这些包在采集时全是
0.1.0-rc.5,根package.json是@deepseek-ai/dsh-root的0.1.0-rc.5。它和任何一个盘上格式版本都没有换算关系。 SessionHeader.version字段。它是每份会话日志里存的那个值,被拿去和SESSION_FORMAT_VERSION比对;它不是”schema 版本”。
撞上问题时怎么区分
给一套可执行的判定顺序,不用猜:
- 先看报错的形状。文案里带
session header version must be ...的,是逻辑格式那条线,去核packages/core/session/src/types.ts:56。返回码是version-mismatch/malformed-medium的,是通用 storage 那条线,去核packages/storage/storage-sqlite/src/schema.ts:20。 - 再看是哪个库。若报错涉及 SQLite 文件,用
application id区分:0x44534850是会话持久化,0x44534851是会话查询。这一步能直接排掉一半可能。 - 对照你的 composition 实际装了什么。持久化后端是 JSONL 还是 SQLite,决定了
SCHEMA_VERSION = 15是否与你有关;openAt是不是never,决定了查询侧那个8是否与你有关。 - 确认自己在读哪一份文件。
packages/core/session/与顶层packages/session/是两个不同的目录(前者是核心会话模型@deepseek-ai/dsh-session,后者是一堆持久化、标题、遥测、投影的外围包),写路径时很容易串行。 - 验证的办法:把你手上的常量名逐个 grep 回仓库,看它是不是我上面表格里那四个之一、文件路径对不对得上。四个常量名互不相同,这一步不会有歧义。
- 什么情况说明不是版本号的问题:如果日志能被加载、只是某一条事件被拒绝,那更可能是事件类型不在
known-event-types.ts的那 44 个里且没带ignorable: true,这与版本校验是两道关。同样,如果你遇到的是查询搜不到东西而不是报错,先看openAt是不是never,那不是版本不匹配。
一句话收尾
这四个数字(0 / 15 / 8 / 1)在仓库里长得像同一类东西,实际管的是四件事:事件词汇表、会话持久化表结构、派生索引表结构、通用存储物理布局。它们各自独立地 bump,对不上时的表现也各写各的:会话格式版本对不上直接抛 session header version must be ...,会话持久化 SQLite 在改 journal 模式之前就拒绝这个库,通用 storage 则返回 version-mismatch。把这四个数当成一个版本号来推理,是这一块最省时间的翻车方式。
延伸阅读
- 从头读起:DeepSeek Harness 是什么:建仓三天、13 万 star 的 Agent 框架
- 本专题共 45 篇,完整分组目录见专题页
- DeepSeek Harness 的压缩事件有几个:文档三种、源码四个
- DeepSeek Harness 的 token 计量:文档两种 baseline,源码三元联合
本文依据 DeepSeek Harness 官方仓库(github.com/deepseek-ai/deepseek-harness)的 README、docs/ 下的
架构与子系统文档、以及 packages/ 下的源码整理,核对日 2026-08-16,对应仓库快照 47f9438(版本 0.1.0-rc.5)。
本文内容为仓库源码与文档口径,我们没有安装、也没有运行过这个项目,
因此不涉及界面外观、操作手感与运行速度的任何描述。
该仓库建立于 2026-08-13,README 自述处于开发者预览阶段并明确说明未来会有破坏兼容性的变更,
文中出现的命令、配置与默认值随时可能变动,请以仓库最新内容为准。