dsh-session
Event-sourced session log and in-memory store. A Session is the append-only source of truth for an agent's whole interaction history — the LLM message history is derived from it.
Service: SessionStore (ctx key: sessions)
Creates and holds event-sourced Session instances. Persistence is intentionally not implemented here — plugins subscribe to session/event and flush on session/flush.
Public API
ctx.sessions.create(id?: string, options?: { seed?: SessionEvent[]; meta?: { cwd?: string; parentSession?: SessionId; createdAt?: number } }): Session— Create a session.options.seedreplays/forks an existing event log;options.metaattaches creation metadata (validated absolutecwd,parentSessionlineage) as the immutableSessionHeader. The store fillsversion/idand defaultscreatedAtto now; a caller reconstructing a persisted session passes the originalcreatedAtto preserve it. Disposed with the calling fiber.ctx.sessions.get(id: string): Session | undefinedctx.sessions.list(): Session[]
Events
| Event | Mode | Purpose |
|---|---|---|
session/created |
emit | A session was created |
session/event |
emit | An event was appended (sync, fire-and-forget) |
session/flush |
parallel | Awaited durability checkpoint (persistence plugins drain buffers here) |
Class: Session
Plain class (not a Cordis Service). Create via ctx.sessions.create().
session.append(type, data): SessionEvent— synchronous, never blocks on I/O. Throws ifdatais not losslessly JSON-serializable (BigInt, function, symbol, undefined, non-finite number, circular ref, or an exotic object like Map/Set/Date) — the event log is the durable source of truth, so this invariant is enforced at the source (exported asisJsonValuefor backends to reuse on their replay/fork entry points).session.deriveMessages(): Message[]— derive the LLM message history from the event log. Rawassistant/chunkevents are skipped;context/messageandsteering/messagerender as tagged synthetic user messages.session.events,session.seq,session.idsession.header: SessionHeader— immutable creation metadata (version,id,createdAt, optionalcwd/parentSession). Kept out of the event log (a storage concern, not replayable state); a minimal v1 header is synthesized for bareSessionconstruction.
Metadata types (types.ts)
SessionHeader— immutable, written once:{ version, id, createdAt, cwd?, parentSession? }.SessionSummary— mutable, updateable without touching the log:{ updatedAt, title?, firstPrompt? }.SessionMeta = SessionHeader & SessionSummary— owned here (besideSessionId) becauseSession.headeris typed by it; persistence backends re-export these rather than own them (which would force a package cycle).
Session event vocabulary (types.ts)
The append-only log: turn/start, turn/end, step/start, step/end, user/message, assistant/message, assistant/chunk, tool/call, tool/result, steering/message, context/message, usage, error.
Merge-extensible via SessionEventMap — a compaction plugin adds compaction/marker, etc.
Also defines TurnTriggerMap and TurnEndReasonMap (merge-extensible sum types for typed turn boundaries — kind-tagged instead of strings).
Extension points
- Persistence plugins: subscribe to
session/event(write-behind) and drain onsession/flush(awaited) and fiber dispose. A durable backend reads the log and reloads it into a live session; the metadata seam (SessionHeader/SessionSummary/SessionMeta,session.header) is what such a backend stores beside the log. - Replay/fork:
ctx.sessions.create(id, { seed })seeds a new session with an existing event log.
What is NOT here (TODO)
- Session branching/tree (pi-style entry tree) — defered unless needed beyond seed-based forking.