Files
deepseek-harness/packages/session/session-projection-cache

@deepseek-ai/dsh-session-projection-cache

English | 中文

The persisted projection cache (ctx.sessionProjectionCache): durable checkpoints of every projection unit's state, one record per session on the domain data form (session_projcache domain — the shipped json backend lands it beside workspace.json under the configured storage root). Design authority: the session-projection RFC (persisted projection cache section).

A stored row (key → {ver, seq, val}) is a disposable fold shortcut, never an authority. The zero-I/O listing path can expose it only as a tentative hint: the row may lag the log, or crash repair may truncate the log below its claimed seq. An exact cold or opening read validates the current log extent and refolds instead of accepting a row that no longer fits. Consequences the implementation commits to:

  • Every background write is fail-soft. A failed durable write logs a warning and retains the previous row; the next write or exact cold read self-heals. A crash between writes normally costs a longer tail replay, while crash repair can turn the retained row into a tentative overreach until the exact path validates it.
  • A ver mismatch against the live unit's stateVersion discards, never migrates. A unit bump invalidates its rows at read time; the key refolds from the log.
  • A row must pass the live unit's stateSchema. A malformed row is omitted from the zero-I/O view and rejected by restore so the cold-read ladder refolds it from the log.
  • Whole-record writes. Each write replaces the session's full checkpoint (the registry cut is always complete), snapshotted through the lossless-JSON boundary — a unit state violating the plain-JSON contract fails loud.
  • Records are bound to a log lifecycle, not just an id. Each record stores the header identity (createdAt, cwd) it was folded from; every read validates it (the live or stored header is the witness) before accepting a row, so a deleted-then-recreated id or a persistence store swapped under a surviving cache discards the unrelated record instead of seeding phantom values.
  • The log leads each checkpoint write. A live checkpoint flushes the session's buffered events durably BEFORE the cache row lands, so the cache cannot lead the log when the write commits. Later crash repair may truncate the log below an existing row; exact reads detect that overreach before returning authoritative state.

Write policy

Two mandatory points, throttled in between:

Trigger Nature
turn/end Mandatory — the turn-final value is what cold reads want.
Session disposal (detach) Mandatory — the live-to-cold moment; after it the cold ladder serves this session.
writeEveryEvents committed events Config throttle (count).
writeIntervalMs since the first dirty event Config throttle (interval).

Both Config fields are required (no defaults): flush cadence is a deployment choice with no universally correct value, stated in cordis.yml.

Listing read (cachedSnapshot(meta))

The zero-I/O rung: client values viewed straight from the identity-matching stored record (version- and state-schema-matching keys only), returned as a {asOfSeq, values} cut. asOfSeq is the lowest served-row watermark, and the list carrier uses the block only to prewarm tentative rows. Newer hints may replace older hints, but no hint replaces an authoritative opening baseline or control frame; a successful exact opening replaces or clears hints regardless of their claimed sequence. The record may lag the log or overreach a crash-repaired truncation, which the exact cold/open path validates and refolds. Host-only rows are never returned. undefined means no usable client row exists (unknown id, unrelated lifecycle, or no usable rows); the api-proxy list carrier turns that into an absent column.

Cold read (coldSnapshot(id, signal?))

The read ladder, zero full-log load on the happy path: cached rows → sessionProjections.restoreFloor (anchored one event below the lowest usable watermark) → persistence readFrom(id, floor)sessionProjections.restore → fail-soft write-back of the refreshed rows. The anchor makes a shrunk log (crash-repair truncation) provable: an overreaching row triggers exactly one full re-read from seq 0 instead of serving a ghost value. No registered units serve {asOfSeq: -1, values: {}} without touching persistence; a session with no persisted log rejects with the seam's not found.

write(session) is the synchronous-cut checkpoint both mandatory points use; carriers may call it directly (not fail-soft — the fail-soft wrappers own containment).

Composition

- id: session-projection-cache
  name: '@deepseek-ai/dsh-session-projection-cache'
  config:
    writeEveryEvents: 200
    writeIntervalMs: 5000

Injects storageDomain, sessionProjections, sessionPersistence, sessions. Without this row the projection system runs live-only (watermark cache; cold reads fall back to full log loads wherever a carrier implements them).

Model Experience

None, as the cache only persists and restores host-side read models of already-logged session state and touches no prompt, message, schema, stream, or tool result.

KV Cache effect

None; the cache never assembles or sends provider requests.

Known Limitations and Deferred Work

  • No eviction or retention surface — records accumulate per session; pruning stored checkpoints is out-of-band maintenance, same stance as session persistence itself.
  • Interval throttle is per-session coarse — the timer arms at the first dirty event after a clean write; a steady sub-threshold trickle writes once per interval, not a sliding window.
  • coldSnapshot reads are not deduplicated — two concurrent cold reads of one session each run the ladder; last write-back wins (rows are equivalent), acceptable for listing-scale call rates.