description: "The shipped JSONL session-persistence backend for deployments and maintainers choosing, configuring, or debugging per-session durable logs with optional Zstandard compression."
English | 中文
dsh-session-persistence-jsonl stores each session in its own append-only JSONL log — checksummed Zstandard frames by default, raw newline-delimited lines when compression is disabled. It serves the same logical SessionEvent stream as any persistence backend, so choosing it changes nothing for the agent loop, the model, or replay; compression, packing, and crash recovery are storage-internal details. Choose it when consumers need a per-session artifact on disk: locate(meta) returns the transcript path, and the logs are readable as plain lines when compression: 'none' is selected. A root directory is the one required configuration; durability, lazy materialization, and interrupted-turn recovery come with the backend.
Mount this backend when a composition needs durable sessions backed by per-session files. The common path is explicit: load the session service, mount the backend, and give it a root directory.
Choose this backend when consumers benefit from one artifact per session — navigation, external tooling, or a raw line-readable log. Choose SQLite when a single queryable database fits the deployment instead. The backend keeps sessions under a deployment-controlled root: project-local, shared, temporary, or centralized.
- name: '@deepseek-ai/dsh-session'
- name: '@deepseek-ai/dsh-session-persistence-jsonl'
config:
root: /absolute/path/to/session-logs
root is required and has no default: a process.cwd() default would scatter session files as the process's cwd changes. An existing root must be a readable directory; an absent root is created on first materialization.
| Field | Default | Meaning |
|---|---|---|
root |
required | Root directory for all session files |
packChunks |
true |
Write eligible assistant/chunk runs as packed rows; false keeps one event per line for diagnostics |
compression |
'zstd' |
Physical encoding: 'zstd' checksummed frames, or 'none' newline-delimited UTF-8 text |
preparedSessionCacheSize |
5 |
Cold session preparations retained for resume reuse |
writeBatchMaxDelayMs |
200 |
Fixed live-event coalescing window, in milliseconds |
The generated configuration catalog is the exhaustive source for every accepted field and its JSDoc.
Each session gets a session-owned directory under a readable project directory; the first logical line of the log is the immutable SessionHeader, followed by one storage record per logical event (or one packed chunk row per eligible run):
<root>/
--<normalized-cwd>--/ # readable project directory (or _no-cwd/)
<encoded-id>/ # session-owned directory
session.jsonl.zstd # default: checksummed header frame + append frames
session.jsonl # only with compression: 'none'
Session ids are injectively escaped to one safe path segment before use (no traversal, no collision). The normalized cwd keeps the project directory readable for navigation; cwd strings that normalize alike share a project directory while session ids still select distinct session directories. locate(meta) returns { kind: 'jsonl', path } for the fixed transcript inside the resolved directories, performing no filesystem I/O.
A session is materialized lazily: create(meta) writes nothing, and the first append writes and fsyncs the encoded header and first batch through a no-overwrite publish — so a created-but-never-appended session leaves nothing on disk unless a lifecycle consumer calls ensureMaterialized, which publishes one header frame without an event. Flushed events are never rewritten; each subsequent batch appends lines or one compressed frame, and a caught write or sync failure rolls the file back to its prior length. After a crash, load preserves an interrupted final turn: it keeps the complete decoded records of an incomplete last frame, truncates from that frame's start, and re-encodes the records with the synthetic tool, step, and turn closers required by the shared persistence contract. Only a never-fully-written torn tail is discarded; checksum, decompression, or structural failure in the committed prefix rejects as corruption.
inspect(id) returns an immutable balanced view without committing recovery. readFrom(id, fromSeq) returns stored events at or past a sequence number for watermark consumers; sequential media like JSONL parse the whole artifact and skip forward. With compression: 'none', the log is newline-delimited text an external reader can consume directly; the compressed default must be read through the backend.
Read these pages when the package-level contract is not enough. They move from the shared persistence model to the sibling backend and the physical-format decisions.
JSONL storage contributes no live prompt or schema. Loading restores stored surface history and preserves prior request headers for reconstruction; the new loop composes its current envelope. Recovery balances an assistant request without a durable call with TOOL_NOT_STARTED; a durable call without a result becomes TOOL_OUTCOME_UNKNOWN, which tells the model to retry only read-only or idempotent work and to verify possible side effects or ask the user. Raw assistant/chunk records do not duplicate messages.
Zero live-request tokens. A resumed agent pays for retained history and its current envelope, plus the quoted repair result for each interrupted call.
JSONL storage does not mutate live request prefixes. A resumed loop can reuse provider cache only when its reconstructed history, current envelope, and model route match; crash-repair results append.
These limits define when this backend is a poor fit or needs special operational care. They are current package constraints, not a task backlog.
SESSION_FORMAT_VERSION (v0) load — changing compression requires a separate or fresh root, or selecting raw mode; the pre-release format has no migration.compression: 'none' before writing a fresh root when external line readers are required.root until removed externally; the seam has no deletion API.link() so same-id races fail instead of overwriting a committed log; Windows uses write-through rename without replacement.