Files
deepseek-harness/packages/session-persistence
Tianyi Cui 7c400e9c02 docs: unify ADR/RFC trees into one lifecycle-organized RFC tree
Collapse docs/adr/ and docs/rfc/ into a single docs/rfc/ with proposed/,
implemented/, and rejected/ subfolders. Every file is renamed to
yyyy-mm-dd-topic-title.md, where the date is when the topic was first
proposed (from git history). ADRs and RFCs that covered exactly the same
topic are merged (property-based testing, session persistence); the
umbrella RFC 005 stays split across its three implemented decisions, and
RFC 006's deferred part-3 (API extractor reports) splits into its own
proposed RFC. All cross-references become machine-checkable relative
links instead of bare "ADR NNNN" / "RFC NNN" prose.

Add a verify-md-links doc-sync gate (scripts/verify-md-links.ts) that
checks every relative Markdown cross-link resolves, wired into doc-sync
alongside verify-md-wrap. This makes the reorganization self-verifying:
the same change that rewrote ~forty inter-doc links adds the check that
proves none dangle. Document the cross-link convention in a new
docs/AGENTS.md and record the gate as an implemented RFC.

doc-sync, typecheck, lint, and the full test suite (667) all pass.
2026-06-18 02:18:24 +08:00
..

@deepseek-ai/dsh-session-persistence

The abstract durable session-persistence seam (ctx.sessionPersistence). Defines WHAT a persistence backend does — durably store, reload, list, and update sessions — without saying HOW. Mirrors the dsh-bash capability-seam template (capability seams): an abstract service here, a concrete implementation in a sibling package, consumers that inject the interface.

The persisted unit IS the existing SessionEvent (event-sourced model — the log is the single source of truth), so there is no parallel "persisted message" type. Metadata that is NOT replayable conversation state (format version, cwd, lineage) travels separately as SessionMeta, owned by dsh-session and re-exported here.

Service API (ctx.sessionPersistence)

Method Contract
create(meta): Promise<void> Register a new session's metadata. MAY defer the physical write until the first append (lazy materialization).
append(id, events): Promise<void> Durably persist a batch (from the session/flush drain). Append-only; first event seq == stored next-seq after any repair; rejects non-JSON-serializable data naming the offending type.
load(id): Promise<{ meta; events }> Reload meta + log. Preserves an interrupted (unclosed) final turn and closes it with synthetic closers — an error tool/result per unanswered tool-call, then step/end?+turn/end {interrupted} (a turn can be huge — never truncated); only a torn tail fragment is dropped. Events contiguous (events[i].seq === i); rejects a committed-region gap/parse error or unknown version.
list(): Promise<SessionMeta[]> Lightweight listing from metadata, no full-log parse.
has(id) / delete(id) Existence / removal. A zero-event lazily-materialized session is absent from has/list.
update(id, summary): Promise<void> Update mutable SessionSummary fields without touching the append-only log.

Invariants every backend must honor

  • Append-only; a crashed turn is closed, not truncated. Committed events (at or below a flushed turn/end) are never rewritten. A crash can leave an unclosed final turn whose events are real and possibly large; load preserves them and durably appends synthetic closers (an error tool/result per unanswered tool-call, then step/end?+turn/end {interrupted}) to balance the log and keep the rehydrated history a valid provider transcript. Only a never-fully-written torn tail fragment is discarded.
  • Contiguous seq. load rejects a seq gap/parse error in the MIDDLE of the log; append's first seq must equal the stored next-seq.
  • JSON-serializable data. append rejects non-serializable event.data; backends snapshot each event when buffering (the live session.events object is mutable).
  • Durability. append returns only once the batch is durable.

Testing backends

Import runPersistenceContract from tests/contract.ts and call it with a factory that yields a fresh, empty backend plus a teardown. Every backend is held to the same append-only / contiguous-seq / lazy-materialization / serializability semantics; a backend's own spec adds implementation-specific tests (crash repair, path sanitization) on top.

Two backends run this suite: dsh-session-persistence-jsonl (append-only file log) and dsh-session-persistence-sqlite (node:sqlite, each SessionEvent one row (session_id, seq, type, time, data)). Both passing the same contract is the proof that the seam is genuinely backend-agnostic — lazy materialization, crash-tail-on-load, and contiguous-seq hold identically over file bytes and over a transactional store.

Metadata types

Re-exported from dsh-session: SessionHeader (immutable: version, id, createdAt, cwd?, parentSession?), SessionSummary (mutable: updatedAt, title?, firstPrompt?), SessionMeta (their intersection).