Session file format
Persyk’s repository is a directory of self-contained file-based sessions. There is no database or conversion layer for prior storage. Existing user files are not rewritten automatically.
Identity and layout
<session-id>/ session.jsonl assets/ <recording-id>.wav protocol-<uuid>.bin- SessionId: durable identity in the header, independent of directory spelling.
- ContinuationId: local selected-leaf state, not a persisted global registry.
- RecordingId: one audio capture period and its unique asset.
- ConnectionId: one provider connection; remote item IDs are scoped to it.
- MessageId: canonical local message entry ID, never a provider item ID.
The header and recording identity are durable before capture. A user-only session
is valid without an assistant response. This exact example is checked against
packages/session-model/fixtures/user-only.jsonl:
{"type":"session","version":1,"id":"00000000-0000-4000-8000-000000000002","timestamp":"2026-01-01T00:00:00.000Z"}{"type":"message","id":"user-message-1","parentId":null,"timestamp":"2026-01-01T00:00:01.000Z","message":{"role":"user","timestamp":1767225601000,"content":[{"type":"text","text":"User-only finalized transcription"}]}}Version 1 contract
The authoritative schemas are packages/session-model/src/schema.ts; native validation and
shared golden/rejection fixtures verify the same contract. The header has only
type: "session", version: 1, id and timestamp. Every subsequent entry has
{type,id,parentId,timestamp} plus its type-specific fields. IDs match
[A-Za-z0-9][A-Za-z0-9._-]{0,127}; new writers use collision-resistant full IDs.
Parents reference an earlier committed entry or null. Envelope timestamps are ISO
8601 with a timezone; message timestamps are nonnegative epoch milliseconds.
| Entry type | Fields beyond the common envelope | Conversation effect |
|---|---|---|
message | message | Finalized canonical message |
custom | customType, data | Branch-linked state, not context |
custom_message | customType, content, display, optional details | Explicit conversation content |
session_info | Optional name, archived | Global display/archive metadata |
label | targetId, nullable label | Annotation, not context |
model_change | provider, modelId, optional api | Configuration provenance |
api_connection | connectionId, provider, endpoint, requestedModel, api, originId | Non-secret connection provenance |
realtime_event | Connection, observation and sanitized payload fields | Evidence only |
recording.started | recordingId, asset | Capture lifecycle |
recording.finished | recordingId, durationMs, outcome, optional gaps | Complete/incomplete outcome |
recording.interrupted | recordingId, reason, optional durationMs | Explicit interruption outcome |
message.removed | Ancestor canonical/custom-message targetId | Branch-local exclusion |
Messages have role user, assistant or toolResult, typed content and
timestamp. Optional fields retain non-secret provenance, usage, replay metadata,
stop reason and error message. Tool results also require toolCallId, toolName
and isError; arbitrary tool details do not enter context.
Known content types are text, image, thinking, toolCall and audio. Unknown blocks
round-trip unchanged; malformed known blocks fail. Audio/image blocks reference
assets; audio may include recording identity and a meaningful millisecond range.
Available assets include a confined assets/<name> path, MIME type and optional
byte range, encoding and SHA-256. Missing assets carry a reason and optional source
metadata; their absence does not erase saved text.
Records are newline-terminated strict UTF-8 JSON, at most 1 MiB each and 128 nested containers. Keys/strings must be Unicode scalar text; valid surrogate pairs are supported. Unknown top-level semantics, extra envelope fields, unsupported versions, missing parents and invalid removals block authoritative projection and mutation. Unknown custom namespaces and nested provider event types remain readable. Diagnostics never include message bodies or secret values.
Branches and context
Omitting a leaf selects the last appended entry of any type; explicit null selects empty ancestry. An absent explicit ID fails rather than substituting the default. Opening creates no entry or branch. Each continuation advances only after its own acknowledged append; sibling writers never silently rebase it.
Conversation reconstruction follows selected ancestry and applies removals. Custom state, recording/protocol observations and configuration are not messages. Custom message details remain separate from content. Session name/archive metadata resolve globally, while custom state is branch-local.
Effective history, generic context conversion and target-API validation are separate layers. Unsupported target content must not break history reads. Orphan tool results may be omitted only at target conversion, without deleting them from history. Historical API events are never replay commands.
Native writes, assets and recovery
Opaque native handles authorize configured roots and validated sessions/assets; webviews cannot authorize arbitrary paths. Reject traversal and escaping symlinks, revalidate file identity and use exclusive creation rather than truncating assets.
A short-lived OS advisory lock plus in-process serialization protects committed validation, append and sync. Preserve supplied parents. Identical stable-ID retries return the committed record; conflicting content fails. Batches can commit a prefix, so retry the same IDs/content instead of assuming atomicity. Appends accept up to 256 records/4 MiB per batch. Paged reads are bounded and native scanning retains an entry index rather than entire audio or protocol bodies.
Read/open never repairs. Explicit recovery preserves a genuinely incomplete trailing fragment before truncating it, or adds the missing newline to an otherwise valid record. Malformed complete/interior data, definite syntax errors and resource limits are not tail-repair candidates.
Each capture owns an OS asset lock. Another live capture cannot be repaired or deleted. Interrupted-WAV recovery preserves samples, fixes sizes and records an interruption; it never reconstructs canonical messages from events. Operations that need both locks acquire asset ownership before the session mutation lock. Confirmed whole-session deletion stops local capture, rejects external live capture, revokes stale handles and stages outside discovery before retryable cleanup. Other directories and copies are untouched.
Roots and search
sessions.directory optionally sets an absolute write root. Otherwise use
Documents/persyk/sessions in production and Documents/persyk-dev/sessions in development.
sessions.additionalDirectories lists other absolute discovery roots. Changing roots
does not move files. A missing default folder produces an empty picker; the first recording
creates it. Explicit missing roots and genuine access errors remain diagnostics.
Search direct-child sessions, deduplicate canonical locations and report distinct locations with the same header ID. Search is case-insensitive literal substring matching over current labels and surviving default canonical/custom-message text. Protocol/custom-state/provisional text, removed content and sibling branches do not match. Fallback labels and excerpts are derived from surviving content. Preserve created-time sorting and bounded requests. Results pin location, identity and leaf; external appends cannot silently change what a selected result opens. Refresh from files; no persistent catalog is required.
Realtime retention and ordered publication
Sanitize credentials, authorization, ephemeral secrets and credential-bearing URLs
before persistence or diagnostics. Each realtime_event records connection identity,
direction, observation, observedAt, sequence, payload and optional remote event
ID, redaction paths and media references. Observations are send_attempt, send_failed
or received; an outgoing attempt is not acknowledgement.
Externalized media references retain exact byte ranges/encoding. Write and sync bytes
before acknowledging their references. Capture WAV ranges may be reused only when
byte-identical; otherwise retain distinct assets. Live input chunks carry local WAV
offsets through startup buffering and wait for their capture write before exact native
verification. Matching input retains WAV ranges, not duplicate .bin files. Provider
output, mismatched bytes and unavailable captures still use separate protocol assets.
Existing sessions are not rewritten; do not manually delete their referenced assets.
Order canonical messages from provider predecessor/speech order, not completion
arrival. B completed before A stays provisional until A resolves. Explicit failures
record gaps rather than fabricated text. Assistants become canonical at response.done,
not merely text completion. Repeated completions remain evidence without duplicate
messages; late completions after closed gaps cannot reorder or resurrect history.
The observation queue is bounded to 512 jobs/8 MiB, ordered work to 256 items/ 4 MiB text. Persistence/backpressure failure visibly halts capture. Ordering limits enter finishing rather than silently dropping required observations. Stop has a 10-second realtime/deletion drain deadline, including requested/streaming assistant responses; filesystem operations still must settle. Close unresolved gaps, publish available finalized messages and finalize the same recording. Restart reports unfinished/unmaterialized work without generating missing messages.
Canonical completed user/assistant removal awaits provider acknowledgement, then
appends message.removed. Explicit rejection leaves content unchanged. Timeout or
transport uncertainty closes the connection and stops capture; local acknowledged
history remains authoritative with an incomplete outcome. A late acknowledgement
on the closed connection is evidence only. Remote success/local persistence failure
retains an intent for retry without resending a successful remote delete.
Removal retains historical text/audio. Provisional items are labeled not yet saved and cannot be copied/removed as canonical content. Empty/all-removed conversations remain valid saved sessions. Only explicit discard deletes the newly created current session. Clipboard/notification effects use surviving canonical content and do not control persistence success.
Attribution and boundaries
Pi-aligned vocabulary and selected tree/context code/tests are adapted from revision
107d79f11072bbc8a3a757ed7fd69596bee7d68c; see THIRD_PARTY_NOTICES.md for the MIT
notice and differences. No coding-agent runtime dependency is included.
Storage support does not imply tool/modality execution. An extension loader, API replay, saved-session connection restoration, picker/branch UI, automatic crash-time message reconstruction, physical per-message erasure and distributed/manual editing conflict resolution are outside this implementation.
See session verification for automated coverage, representative performance measurements and the manual macOS checks still unperformed.