deepseek-harness/.agents/notes/implemented/simplification/2026-06-19-drop-mutable-session-summary.zh.md
Turtle bec6805d6a refactor(session-persistence)!: handle-based seam with a lifecycle-owned write path
The persistence seam is now create/open/stat/list returning per-session
SessionHandles (read/append/flush/close); every log read and write flows
through the owning handle. The seam package exports only the service and
handle contracts, consumer-visible errors, and pure durable-data
validation helpers; each backend owns its complete storage runtime, and
the shared contract suites pin equivalent observable behavior. The
backend routes published sessions' live events by id into the active
write handle; agent-loop only acquires, seeds, and closes the handle.
Resume appends interruptedTurnClosers through its write handle;
session-query owns the revision-keyed cold cache. Legacy-only surfaces
are removed in the same swap: locate/readRaw/supportsRawArtifacts, the
legacy event-shape read migration, zstd torn-frame salvage,
DSH_SESSION_JSONL, and hook transcript_path population; a torn final
zstd frame is discarded whole; the session-list cold blank probe returns
on stat metadata (eventCount derived from the last physical row,
sizeBytes). The WebUI ZIP export serializes the logical log from a read
handle, so both backends export identically.

Refs #3245
2026-09-01 23:19:02 +08:00

4.4 KiB
Raw Blame History

Agent Note: 移除可变的会话摘要

Status: implemented

English | 中文

问题

会话持久化 seam 将会话的日志外元数据拆分为 dsh-session 拥有的两种类型:一个不可变的 SessionHeader(version、id、createdAt、cwd?、parentSession?),在创建时一次性写入;一个可变的 SessionSummary(updatedAt、title?、firstPrompt?),「可在不触碰仅追加日志的情况下更新」。二者合并为 SessionMeta = SessionHeader & SessionSummary,抽象的 SessionPersistence 服务为此多出第七个方法 update(id, summary),用于重写摘要。各后端各自实现可变存储:JSONL 在日志旁先写入临时文件再重命名,并以尽力而为的方式原子发布一个独立的 .summary.json 伴随文件;SQLite 则使用 updated_at/title/first_prompt 列,并在追加事务内更新其中的时间列。

摘要是为未来的会话选择器设计的(通过 updatedAt 排序近期会话,用 title/firstPrompt 做预览)。该选择器从未实现。对整个仓库的审计表明,SessionSummary 的整套相关接口都只是在维护无用状态:

  • SessionPersistence.update() 零个生产调用方(所有 .update( 匹配都是 createHash().update() 或测试代码)。
  • firstPrompt 在生产代码中从未被读取。
  • 会话标题来自持久的 session/title 事件,工具卡片标题来自工具 presenter;二者都不读取可变的会话元数据。
  • 持久化列表的消费方使用不可变 header 中的标识、创建时间、谱系和 cwd 字段。近期排序和预览派生自日志,而非某个 updatedAt 摘要。
  • 决定性的一点:活跃的 Session.header 类型本来就是 SessionHeader 而非 SessionMeta——摘要从未存在于活跃会话对象上;它只存在于持久化层,除了自身的约定测试外无人写入、无人读取。

决策

彻底删除可变的会话摘要。SessionSummary 与 SessionMeta 这个名称均不存在;后端存储和返回的元数据仅为 SessionHeader。抽象服务不包含 SessionPersistence.update()。交付的 JSONL provider 不包含摘要伴随文件机制(writeSidecar/readSidecar/touchSummary/removeSidecars/sidecarPath 或 load/list 覆盖逻辑),仓库外 provider 实现相同的无摘要服务约定。

摘要原本要提供的一切,在消费方真正需要时都可从仅追加日志中派生(firstPrompt = 第一条 user/message;近期度 = 最后一个事件的 time 或文件 mtime),或者已经存在于不可变 header 中(createdAt、cwd)。唯一不可派生的是用户手动编辑的标题,但它从未实现,纯属 YAGNI;如果未来真有功能需要,它可以作为独立的日志事件或 header 字段回归。

这次移除收窄公开服务约定与 JSONL 磁盘格式;摘要是有意为未来设计的结果,而非意外;原 Agent Note 描述 SessionMeta 之处由 SessionHeader 承担,这就是摘要消失的原因。它也简化了当时的共享持久化写入协调器:没有可变摘要后,那套编排不需要 updateSummary 钩子。

无需迁移

交付的 JSONL provider 不存在可变摘要格式或迁移路径:它只读写 SessionHeader 与仅追加日志。仓库不包含 first-party SQLite Session provider。JSONL-only 持久化决策负责删除 provider 写入的数据库的兼容性切断,并要求 operator 在升级前先用旧 build 导出数据。

后果

未来的会话选择器现在必须从日志派生预览/排序信息(或重新引入一个类型化字段),而不能直接读取现成的摘要行。这是正确的代价:为一个尚不存在的功能维护缓存,是每个后端都要承担维护成本、每个约定测试都要承担断言成本的无谓负担。这一原则——通过的测试固定的是当前行为,不一定是正确行为;行为可能是过去妥协的产物——现已作为独立约定记录在根 AGENTS.md 中,本次变更即为其实例。