Files
deepseek-harness/website/zh-CN/api/harness/session-persistence.md
T
lintianle efba9fab0a website: generate the API reference from source (cordis + all 15 harness services)
scripts/gen-website-api.ts renders website/zh-CN/api/{cordis,harness}/* and the
api-sidebar.json fragment the VitePress config imports, so pages and navigation
can never drift from the code: signatures, @param/@returns prose, dispatch
modes, and GitHub source links are extracted, never transcribed, and the
generator hard-errors on any rendered member missing docs. verify-website-api
(doc-sync + run-gates) is the freshness gate.

Replaces the hand-written zh api pages (7 pages covering 7 of 15 services,
with phantom APIs: Context.current/Context.events, agent/post-step, tool/call,
compact/*, llm/pre-request none of which exist) with generated English
references: 5 cordis pages, 15 per-service pages, and a 35-event catalog
grouped by scope. The hand-written hub api/index.md stays and now indexes the
full surface; zh for these pages arrives with the unified translation flow.
2026-07-16 18:13:34 +08:00

5.3 KiB

ctx.sessionPersistence

SessionPersistence (abstract seam) — provided by @deepseek-ai/dsh-session-persistence.

Abstract durable session-persistence service. Subclass, implement the abstract methods, and load the subclass as a plugin — it registers as ctx.sessionPersistence (one implementation per context; loading a second throws, cordis' standard duplicate-service behavior). Contracts every implementation MUST honor (a DB backend asserts them inside a transaction; a file backend appends at EOF):

  • Append-only; a crashed turn is closed, not truncated. Committed events — those 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 closes the orphaned turn with synthetic boundary events (see load). Only a never-fully-written torn tail fragment is discarded.
  • Contiguous seq. A persisted log is contiguous: events[i].seq === i. load rejects a parse error or a seq gap in the COMMITTED region (unloadable); append's first event seq MUST equal the backend's stored next-seq (after load has balanced any interrupted turn).
  • JSON-serializable data. SessionEventMap is merge-extensible and event.data is typed only as SessionEventMap[K], so append REJECTS non-JSON-serializable data with an error naming the offending event type. A backend snapshots (serializes/clones) each event when it buffers, since session.events hands out the live mutable object.
  • Durability. append returns only once the batch is durable (the file backend fsyncs; a DB commits). create MAY defer the physical write until the first append (lazy materialization).

Source

ctx.sessionPersistence.create(meta)

abstract create(meta: SessionHeader): Promise<void>

Register a new session's metadata. A backend MAY defer the physical write until the first append (lazy materialization), in which case a created-but-never-appended session is absent from list — abandoned sessions leave nothing behind.

  • meta — the immutable header (id, version, cwd, lineage) to record.

Source

ctx.sessionPersistence.append(id, events)

abstract append(id: SessionId, events: readonly SessionEvent[]): Promise<void>

Durably persist a batch of events (called from the write-behind drain at the session/flush checkpoint). Honors the append-only and contiguous-seq contracts: the first event's seq MUST equal the stored next-seq (after load has durably closed any interrupted turn). Rejects non-JSON- serializable event.data with an error naming the offending event type.

  • id — the session the batch belongs to.
  • events — the contiguous batch to persist, in seq order.

Source

ctx.sessionPersistence.load(id)

abstract load(id: SessionId): Promise<{ meta: SessionHeader; events: SessionEvent[] }>

Reload a session: its SessionHeader plus the event log up to the last durable checkpoint. Returns meta AND events so the live session is reconstructed with its cwd/lineage, not just its log. The loop only flushes at turn/end, so a crash can leave a durable log whose final turn never closed: real, fully-written events sit after the last turn/end. Those events are PRESERVED — a single turn can be huge in a long-horizon task, so truncating it would destroy real work — and load CLOSES the orphaned turn by durably appending the minimal synthetic boundary events: an error tool/result for every tool-call the crash left unanswered (so the rehydrated history is a valid provider transcript — a dangling assistant tool-call is otherwise rejected), then a step/end if a step was open, then a turn/end carrying the { kind: 'interrupted' } reason. The returned events therefore end on a balanced turn/end and are immediately usable as a session seed. Only a never-fully-written TORN tail fragment (a half-written final record) is discarded. Returned events are contiguous (events[i].seq === i); a parse error or a seq gap in the COMMITTED region (at or before the last real turn/end) makes the session unloadable (reject). Rejects an unknown format version. See the session-persistence RFC for the crash-recovery contract.

  • id — the persisted session to reload.

Returns the header plus the event log, ending on a balanced turn/end — immediately usable as a session seed.

Source

ctx.sessionPersistence.list()

abstract list(): Promise<SessionHeader[]>

Lightweight listing from metadata, without a full-log parse.

Returns one header per materialized session.

Source