Docs: per-folder README.md for packages/ (family overview + one per package: service, events, API, extension points, TODOs), examples/, and examples/echo-agent/; folder-level AGENTS.md (+ CLAUDE.md symlinks) for packages/ and vendor/; module-level doc comments in every packages/*/src file; richer JSDoc on all exported API (event side effects, disposal contracts, error behavior). Root AGENTS.md gains a "Type Safety and Documentation" policy section: the codebase aims to be very type-safe and well documented; type gymnastics are acceptable in core packages when they improve plugin-author DX; verbose docs are fine as long as they stay strictly in sync with the code. Type safety: removed the upstream-inherited "noImplicitAny": false from tsconfig.base.json — packages/* now compile under full strict mode; vendor/loader and vendor/include set it locally (vendor/cordis already did). Eliminated every `: any` / `as any` from packages and examples (catch clauses use unknown + a CodedError narrowing type; event data access uses discriminated-union narrowing). Typed tool schemas: new @deepseek-ai/dsh-tools schema DSL — SchemaSpec with per-property `required: true` booleans, type-level InferArgs<S>, a runtime SchemaSpec → JSON Schema converter, and defineTool() so first-party tools get typed execute(args) with zero casts (raw JSON Schema still accepted for MCP interop; chosen over schemastery because it targets JSON Schema generation directly). echo-tool and all test tools migrated; +7 tests.
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.