Files
deepseek-harness/docs/rfc/implemented/feature/2026-07-10-session-query-service.md

4.7 KiB

RFC: Exact session query service

Status: implemented

Problem

Session history exists in two places: current SessionStore objects and an optional persistence backend. Consumers that need exact inspection would otherwise duplicate live-versus-persisted precedence, persistence lifecycle handling, raw-event surface classification, and defensive cloning. Durable state can lag the live log between checkpoints, so persistence alone is not a truthful current source.

Full-text search is related but materially larger. Designing provider registration, extraction, synchronization, invalidation, ranking, and cursor contracts before a real backend exists creates two speculative state machines: one in the interface service and another in the eventual database package.

Decision

@deepseek-ai/dsh-session-query owns ctx.sessionQuery, a small trusted exact-read service over one logical corpus. It exposes listSessions(), listEvents(sessionId), and bounded readEvent(request). It does not expose filters, lineage or provenance traversals, text extractors, search requests, provider registration, or derived-index synchronization.

The service observes the optional ctx.sessionPersistence binding dynamically but retains no persisted cache or invalidation listener. Each cross-corpus list asks the active backend for authoritative metadata, then overlays a fresh live-store list. Matching ids become one SessionRecord: the live header wins and live/persisted independently report source availability. Immutable header disagreement is SESSION_QUERY_SOURCE_CONFLICT.

An exact target read first checks the live store and snapshots the live header and event log. This path never consults persistence, so a failing durable backend cannot make known live history unreadable. With no live target, the service lists current persistence metadata, proves the id exists, loads it, and rejects a list/load header mismatch. All returned headers and events cross one structured-clone boundary.

Surface semantics

dsh-session exports foldSurface(events), and SurfaceManager uses the same transition functions for its incremental cache. The fold returns detached current nodes and each replacement's actual removed seqs. listEvents() uses that result to classify every raw event as current, shadowed, or log-only, so inspection cannot disagree with model-history derivation about positional replacement semantics.

readEvent() returns the complete target plus raw neighbors by contiguous seq. before and after default to zero and are independently bounded by readWindowMax, default 50. The result carries a cloned SessionHeader, not a source-availability record, because determining a live target's persisted flag would violate the guarantee that live exact reads do not depend on persistence health.

Security boundary

The service is context-wide trusted infrastructure, not an authorization layer. A future model-facing history tool or human UI applies explicit caller/session scope. This phase adds no model-facing tool and changes no transcript or snapshot surface.

Alternatives considered

  • Put logical-corpus resolution directly in every consumer — rejected because source precedence, conflicts, optional-service lifecycle, cloning, and surface classification are shared correctness rules.
  • Query only persistence — rejected because checkpoints can lag the current live log.
  • Cache persisted metadata and listen for writes/removals — rejected because exact reads can ask the authoritative sources directly, while cache invalidation adds lifecycle and concurrency state before scale requires it.
  • Define a provider-neutral search protocol now — rejected because no provider consumes it. The first SQLite FTS package should own one reconciliation/transaction state machine; a smaller shared seam can be extracted later only when a second implementation proves the boundary.
  • Include lineage, provenance, and generic filters in phase one — rejected because no current consumer requires them and canonical logs remain sufficient to add them with evidence later.

Consequences

Phase one has one source-resolution state variable: the currently mounted persistence service. There are no provider queues, fingerprints, extractor registries, observation generations, or derived index updates. Exact reads remain usable in live-only deployments and deterministic when persistence is present.

Cross-corpus listing and persisted exact reads perform backend I/O on each call. That is deliberate: correctness comes from current authoritative state, and scale-oriented search belongs to the phase-two database. Full-text search is unavailable until that package defines and implements its complete contract.