Skip to content

What session query offers

The session-query family provides authorized retrieval over live and durable session logs, independently of compaction. It answers two things an agent or a human might want:

  • Search — find prior work by session (across the corpus) or by event (within one session) using full-text search.
  • Trace & read — follow lineage (parents and descendants of a session), inspect exact events, read surfaces, and export the raw log.

The source of truth for all of this is the per-session session log, persisted by packages/session/session-persistence-jsonl as the durable raw history. Query is a consumer of that log (live ctx.sessions plus dynamically mounted ctx.sessionPersistence); it never writes to it.

The package family

PackageRolectx key
session-queryService Definition: trusted reads, relationship queries, filtering, search contractctx.sessionQuery
session-query-sqliteConcrete provider: SQLite FTS5 full-text search, reconciliation, pagesctx.sessionQuery
tool-session-queryWorkspace-authorized model-facing search/trace/read toolsregisters on ctx.tools
session-log-exportWeb /export command, shared download state, result modalctx.sessionLogDownload
session/session-persistence-jsonlSource-of-truth JSONL durable log backendctx.sessionPersistence

The service contract (dsh-session-query)

SessionQueryEngine is the combined abstract ctx.sessionQuery contract. It implements exact session-history retrieval, relationship tracing, and provider-independent filtering; concrete backends implement its two full-text methods. Matching ids produce one record — live events win — while live and persisted report both source availabilities; conflicting immutable headers fail with SESSION_QUERY_SOURCE_CONFLICT.

Read operations

ReadPurpose
listSessions(signal?)Clone the merged logical corpus, newest-first.
readSession(sessionId)One complete detached raw log with the same core replay validation used by resume; never enters the live store.
filterSessions(filters, signal?)Apply session-metadata predicates to the cloned corpus.
filterEvents(sessionId, filters)Extract first-party semantic documents and filter them in ascending seq order.
listEvents(sessionId)Classify each event as current, shadowed, or log-only.
readSurface(sessionId)Cloned header + complete folded current surface in model-history order.
readEvent(request, signal?)Cloned header + full target event + bounded raw-seq window.
traceSession(sessionId)Immediate-to-outward ancestors plus deterministic recursive descendant trees.
traceEvent(request)Cloned source header with direct positional replacements and cited source links.
readTitleSnapshots(sessionIds) / readTitle(sessionId)Per-session title observations for UI.

listSessions() stays lightweight — it does not load logs or index titles. Persistence is optional and may mount/unmount dynamically; a title/event/trace read targeting a known live session does not consult persistence, so durable backend health cannot make current in-memory state unreadable.

Filters

SessionResultFilter covers id, nullable cwd, created-at range, nullable parent, and source availability. SessionEventResultFilter covers seq/time ranges, event type, surface, and semantic text. Filter arrays are ANDed; values within one list clause are ORed; empty list values match nothing; malformed ranges fail with SESSION_QUERY_INVALID_FILTER. The text clause is deliberate a literal semantic-text scan, not a full-text query: caller text is escaped into a Unicode, case-insensitive regex where each whitespace run matches one-or-more whitespace.

The two abstract search methods

searchSessions(request, exec?) groups the logical corpus by strongest matching event; searchEvents(request, exec?) searches one logical session. Both return pages whose continuation is an owned branded SessionSearchCursor, accept optional cancellation, and expose snippets without provider-specific numeric scores. Search requests accept only metadata event filters.

SessionQueryError.code is a closed union: SESSION_QUERY_ABORTED, …_CORRUPT_SESSION, …_EVENT_NOT_FOUND, …_INDEX_FAILED, …_INVALID_CONFIG, …_INVALID_CURSOR, …_INVALID_FILTER, …_INVALID_LIMIT, …_INVALID_QUERY, …_INVALID_LINEAGE, …_INVALID_SURFACE, …_INVALID_WINDOW, …_PERSISTENCE_FAILED, …_SEARCH_DISABLED, …_SESSION_NOT_FOUND, …_STALE_CURSOR, …_SOURCE_CONFLICT.

ConfigDefaultContract
readWindowMax50Max before/after raw-event count.
persistedInspectConcurrency4Max concurrent persisted-log inspections in one batch read.

The SQLite provider (dsh-session-query-sqlite)

SqliteSessionQueryEngine inherits the exact reads, traces, and filters, and implements the two full-text methods with SQLite FTS5. Queries are required, trimmed, whitespace-normalized literal phrases — FTS5 syntax like OR, NEAR, * is treated as data, not executable MATCH syntax. Metadata filters are parameterized SQL predicates applied before ranking.

Relevance is source-comparable across persistent and TEMP tables: actual FTS5 highlighted-match span count descending, then stored document code-point length ascending; event time, session id, and seq break ties. Snippets are whitespace-normalized, plain text bound in Unicode code points (snippetChars, default 240). Cursors bind to the normalized request and service instance, and fail when the relevant generation changes.

One serialized state machine compares source-qualified lightweight durable snapshot revisions, non-mutatingly inspects only new/changed logs, extracts shared semantic documents, reconciles transactionally, and runs the query. Persisted FTS rows live in a dedicated derived database; connection-local TEMP tables hold live rows, shadowing the durable base for the same session.

ConfigDefaultContract
pathrequiredDedicated derived-index SQLite path; :memory: supported.
openAtstartupstartup / first-search / never.
journalModewalwal / delete / truncate / persist.
defaultLimit20Page size when a request omits limit.
maxLimit100Largest accepted request page size.
snippetChars240Max snippet length in code points.
readWindowMax50 / persistedInspectConcurrency4 — inherited read config.

The tokenizer is FTS5 unicode61: this is token recall, not arbitrary substring recall — AI does not match the token BRAID; use filterEvents() with a text clause for a literal scan. Node's synchronous DatabaseSync blocks the JavaScript thread during MATCH and cannot interrupt a statement already running.

The model-facing tools (dsh-tool-session-query)

The opt-in package registers session_search, session_event_search, session_trace, session_event_trace, and session_event_read. It is not mounted by default in shipped host compositions.

ConfigDefaultMeaning
maxSearchResults100Max authorized non-self hits collected across internal provider pages.
searchTimeoutMs30000Cooperative deadline attached to both full-text search tools.

The caller comes exclusively from ToolExecution.exec.agent. Cross-session access requires exact equality between the target and caller session cwd values; a caller without cwd can inspect only itself. Search never exposes provider cursors or a model-controlled limit. session_search always omits the caller session; a current-session session_event_search stops before the step that invoked it, so the active assistant output and logged tool call cannot match themselves. Lineage output replaces unauthorized ancestor/descendant boundaries with markers that contain no hidden session id. The package deliberately does no byte or character truncation and imports no spill backend; deployments that need bounded inline output mount @deepseek-ai/dsh-spill-policy.

Log export (dsh-session-log-export)

The Web /export command and the Session log header action download the raw session log as a ZIP over the host-streamed endpoint GET /api/session.export?sessionId=<id>&includeDescendants=true. The Host half (in packages/host/apiproxy) owns ZIP generation, raw JSONL/zstd reads, descendants, attachments, backpressure, and HTTP error semantics; this Web package owns the button, one download controller, the shared modal, and the command/executed acknowledgment that triggers the browser download.

/export records a human-command lifecycle; /export <path> returns an error (browser downloads choose their destination through the browser's ordinary download behavior). The Host endpoint flushes a live root Session before readRaw, so a slash-triggered ZIP includes the command/run + command/done pair whose acknowledgment started the download.

The JSONL source of truth (session-persistence-jsonl)

The durable log each session owns is, by default, a Zstandard-compressed JSONL file, laid out as <root>/--<normalized-cwd>--/<encoded-id>/session.jsonl.zstd. The first logical line is the immutable SessionHeader (with type: 'session', seq stays contiguous — events[i].seq === i); each subsequent line is one storage record or a packed chunk row for a run of ≥3 same-block assistant/chunk deltas (packChunks default true). Storage is append-only and crash-repairing: an incomplete tail frame is truncated and reopened with synthetic tool/step/turn closers. The query engine reads this same durable corpus through a SessionPersistence seam, so search, trace, and export all see one source of truth.

Known limitations

  • No caller authorization inside the servicectx.sessionQuery is trusted context-wide infrastructure; only the tool (exec.agent + cwd equality) and the Web actions constrain access.
  • Token recall, not substrings — use filterEvents() for literal scans.
  • Single-owner derived index — one service/process must own each SQLite path.
  • Search caps at the deployment limit — no continuation token; the tool asks the model to narrow its query.

Further reading

  • Subagents and Workflow & Ralph — sibling families; session_query tools let their agents recover prior work.
  • Glossaryturn, step, round frame what query events mean.
  • The subsystem reference in the repo: docs/subsystems/session-query.md.
  • READMEs: packages/session-query/session-query/README.md, packages/session-query/session-query-sqlite/README.md, packages/session-query/tool-session-query/README.md, packages/session-query/session-log-export/README.md.
  • The durable log format: packages/session/session-persistence-jsonl/README.md and src/format.ts.
  • Agent Notes: .agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.md and 2026-07-13-session-query-tracing.md.