DeepSeek Harness Session Log:为什么模型可见内容必须可重建
JotaroRD

模块定位

packages/core/session 是 Harness 的事实账本。它不是“聊天消息数组”,而是 append-only event-sourced session log。模型历史、UI transcript、持久化、fork、resume、telemetry 都从这份 log 派生。

源码入口:

  • packages/core/session/src/index.ts
  • packages/core/session/src/types.ts
  • packages/core/session/src/surface.ts
  • packages/core/session/src/request-header.ts
  • packages/core/session/src/chunk-rows.ts
  • docs/persistence-catalog.md
  • docs/subsystems/session.md

为什么不是 messages 数组

一个真实 Agent 会产生的不只是 user/assistant message。它还有:

  • turn/step 边界。
  • assistant streaming chunks。
  • tool call 与 tool result。
  • request header。
  • usage 信息。
  • compaction replacement。
  • hook、retry、workflow、subagent 等子系统事件。
  • crash repair 事件。

如果只存最终 messages,会丢掉大量运行事实。DeepSeek Harness 的做法是:raw log 保存事实,surface projection 决定哪些事实形成模型历史。

1
2
3
4
5
6
7
flowchart TD
A["append-only session.events"] --> B["surface projection"]
B --> C["deriveMessages() for LLM"]
A --> D["persistence backend"]
A --> E["Web UI replay"]
A --> F["fork/resume"]
A --> G["telemetry/query"]

SessionStore 与 Session

ctx.sessions 是 SessionStore service。它负责创建、发布、flush、fork、查询 live session。

关键 API:

  • ctx.sessions.create(id?, { seed?, meta? })
  • ctx.sessions.flush(session)
  • ctx.sessions.fork(source, boundary?, childSessionId?)
  • ctx.sessions.get(id)
  • ctx.sessions.list()

Session 本身是普通 class,不是 Cordis service。它负责 append、deriveMessages、deriveEventMessage、surface projection、header、seq、events snapshot。

append 做了什么

session.append(type, data, opts?) 不是普通 push。它会:

  1. snapshot durable JSON。
  2. freeze event data。
  3. 校验 marker、sourceEventSeqs、surfaceOp。
  4. 校验 replacement 覆盖关系。
  5. 同步提交 seq。
  6. 通知 session/event observer。

这保证 log 里的 durable data 不会被 append 后的外部引用继续修改。

Surface Projection

Surface 是 raw log 之上的有序投影,用来高效生成模型消息。它处理:

  • append-origin entries。
  • replacement entries。
  • compaction summary。
  • tool result content-only rewrite。

模型历史读的是 surface,而不是 raw log。人类 transcript 则不一定应该只看 surface,因为 surface replacement 会 shadow 旧内容,而人类可能需要看到原始发生过的历史。

Request Header

request/header 记录非历史 request envelope:provider、model、system prompt、tool schemas、adapter defaults 等。它不重复塞进模型 messages,但可以让系统重建当时发给 provider 的请求边界。

这对调试很关键:如果后来默认模型、tool order、prompt section 都变了,历史请求仍然可以按当时 header 复原。

Unknown events 与版本

Session event map 是 merge-extensible 的。插件可以声明自己的事件类型。未知事件默认拒绝读取,除非 event envelope 标记 ignorable: true。这是 pre-release 阶段保持安全读取的方式:不知道怎么解释的必需事件,不应该被悄悄跳过。

Crash repair

恢复时可能遇到 assistant 请求了工具,但工具结果没有完整落盘。系统会生成带风险提示的 synthetic result,例如:

  • TOOL_NOT_STARTED
  • TOOL_OUTCOME_UNKNOWN

这比盲目重试安全,因为某些工具可能有副作用。

常见坑

第一,新增模型可见内容必须考虑 session event。只改 prompt assembly 不够。

第二,工具 canonical value 不一定持久保存;持久的是模型可见 content 和 presentation metadata。

第三,fork 只能切在稳定边界,不能从 open turn 中间随便切。

第四,session format 仍处于 pre-release,旧格式兼容不应默认承诺。

继续读源码

  • packages/core/session/README.md
  • packages/core/session/src/types.ts
  • docs/persistence-catalog.md
  • packages/session/README.md