Flat config with two layers. Correctness (type-checked): the headline rules for this codebase are no-floating-promises / no-misused-promises (a lost promise in the agent loop is our primary bug class), switch-exhaustiveness-check (we switch over merge-extensible unions everywhere), no-unnecessary-condition, require-await, and no-explicit-any. Style (@stylistic): 2-space, no semicolons, single quotes, trailing commas, max-len 140 — the existing house style, now enforced instead of drifting between agents. vendor/ is excluded (vendored source keeps upstream style); tests relax the rules that fight test ergonomics (non-null assertions after expects, async mock signatures, non-Error throws). Code adjusted to pass: registry disposers wrap ctx.effect's promise-returning disposer behind a sync () => void (our public API), BlockAssembler gains an invariant-checking mustGet instead of non-null assertions, lastTurnNumber uses findLast, waterfall tails return Promise.resolve instead of async-without-await arrows, and the two deliberate suppressions (non-exhaustive derivation switch, unbound execute pass-through) carry justification comments. yarn lint / yarn lint:fix added.
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, seed?: SessionEvent[]): SessionCreate a session.seedreplays/forks an existing event log. 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.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.id
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. Seeexamples/echo-agent/src/session-jsonl.tsfor the pattern. - Replay/fork:
ctx.sessions.create(id, seed)seeds a new session with an existing event log.
What is NOT here (TODO)
- Real persistence backends (JSONL per session dir, sqlite) — future phase.
- Session event vocabulary review —
TODO(review)once the loop and a persistence plugin coexist. - Session branching/tree (pi-style entry tree) — defered unless needed beyond seed-based forking.