LISAOS // DOCS
GATEWAY // SESSIONS

Gateway Module — `sessions/`

1. Purpose

The sessions module is the cross-channel session registry (Dispatch 399/650) — it tracks live VS Code and Genkan (Hermes) sessions, their heartbeats, working state, active dispatches, and conversation turns, and provides the cross-channel directive-injection mechanism. It carries the module's own SSE event stream (GET /events), a heartbeat update path, a stale detector that auto-ends sessions silent >30min, and three transcript-read carve-outs (vscode, hermes, :id/turns) that leak operator conversation state and are therefore explicitly Bearer-gated post-AK-415/AK-334.

2. File Inventory

FileLinesResponsibility
routes.ts4479 endpoints incl. SSE /events + 3 transcript readers
repository.ts296active_sessions + session_turns prepared statements; merge-on-heartbeat; stale sweep
types.ts114Zod schemas, ActiveSession, SessionTurn, repository + stale-detector interfaces
index.ts70createSessionLayer + createStaleSessionDetector (5-min interval, 30-min threshold)
events.ts40SessionEventBus singleton — directive + session_state events
Total967

3. Public API Surface

REST Endpoints (mount: index.ts:752/api/sessions)

MethodPathAuthBody SchemaResponseSide Effects
POST/registerBearer (write)RegisterSessionInputSchema201 {data: ActiveSession}INSERT OR REPLACE active_sessions; emits session_state
PUT/:id/heartbeatBearer (write)HeartbeatInputSchema{data: ActiveSession}Merge-update (incoming ?? existing); emits session_state; 404 if absent
PUT/:id/endBearer (write)EndSessionInputSchema{data: ActiveSession}status → ended; emits session_state
GET/active?channel=Bearer{data: ActiveSession[]}Reads active/idle; operator-state leak → gated
POST/directiveBearer (write)PostDirectiveInputSchema201 {cache_entry_id, ...}Writes a directive psychic-cache entry via WriteCacheInputSchema; emits directive
GET/events?channel=Bearertext/event-streamStreams SessionEventBus; 30s keepalive
POST/:id/turnBearer (write)WriteTurnInputSchema201 {data: SessionTurn}INSERT session_turns; content truncated to 2000 at repo
GET/:id/turns?limit=&since=Bearer{data, count}Reads turns; full conversation → gated (gray-fox D1021 exfil demo)
GET/vscode/transcript?limit=Bearer{data, session_id, count}Reads Claude Code JSONL (env-gated: 404 when CLAUDE_CODE_JSONL_DIR unset)
GET/hermes/transcript?limit=Bearer{data, session_id, count}Reads Hermes state.db read-only (env-gated: 404 when HERMES_STATE_DB_PATH unset)

Several session endpoints that would otherwise be broadly readable are explicitly Bearer-gated because they expose session metadata, transcripts, or full conversation turns. The event stream is the exception: it uses a read-only-token handshake instead of an Authorization header, because EventSource cannot set request headers.

MCP Tools

list_sessions (RO → /active); post_session_directive_raw (RAW-ONLY → /directive). No MCP tool targets the transcript readers or SSE stream.

4. Internal API

createSessionLayer(db, psychicCacheRepo){repo, router, staleDetector}. SessionRepository: register, heartbeat, end, findActive, findById, cleanStale, writeTurn, readTurns. Re-exports sessionEventBus + event types from events.ts. The /directive route depends on PsychicCacheRepository.write — sessions has no cache table of its own; directives are ordinary psychic-cache directive entries.

5. Background Services

ServiceTrigger / IntervalSide Effects
Stale session detector (index.ts)5min (300_000) + an initial sweep on start()cleanStale(30) → UPDATE active/idle sessions with last_heartbeat < now-30min to ended; logs count to stderr. Started/stopped from server/index.ts:938/949.

6. Data Contracts

  • RegisterSessionInputSchema: id, channel (vscode|hermes), current_mission_namespaces?, active_dispatches? (positive int[]), working_state_prose?, metadata?.
  • HeartbeatInputSchema: same optional fields + last_exchange_summary? (≤500), status? (active|idle).
  • PostDirectiveInputSchema: content, directive_target, directive_priority (default medium), mission_id, tags (min 1), plus targeting/source attribution optionals.
  • WriteTurnInputSchema: role (user|assistant|system), content (min 1, max 10000 validated; truncated to 2000 at repo).
  • SessionEvent = DirectiveEvent | SessionStateEvent (events.ts). Row JSON columns (current_mission_namespaces, active_dispatches, metadata) parse with fail-safe defaults (class-(b) silent-catch, intentional).

7. Dependencies

  • Gateway modules: psychic-cache/types.js (PsychicCacheRepository, WriteCacheInputSchema), shared/zod-error.js.
  • External: better-sqlite3 (incl. a second read-only Database handle opened per-request for HERMES_STATE_DB), express, zod, Node fs/os/path.
  • Environment variables: CLAUDE_CODE_JSONL_DIR, CLAUDE_CODE_SESSION_ID_PATH (default ~/.claude/session-env/.active-session-id), HERMES_STATE_DB_PATH.

8. Test Coverage

LayerFileNotes
Routestest/sessions-routes.test.ts9 endpoints, SSE, directive→cache, transcript env-gates
Repositorytest/sessions-repository.test.tsMerge-heartbeat, turn truncation, stale sweep
Layer/detectortest/sessions-index.test.tsStale detector lifecycle

9. Known Limitations

  • register uses INSERT OR REPLACE — re-registering an existing session id silently overwrites started_at/turn linkage rather than erroring. Intentional for reconnect, but destructive if an id collides across channels.
  • The hermes/transcript reader opens a fresh read-only SQLite handle per request and closes it inline; no pooling. Low-frequency endpoint, acceptable, but not free under burst.
  • Two commented-but-absent route markers remain in routes.ts (a stray GET /hermes/transcript header comment mid-file and a POST /:id/turn marker) — cosmetic drift, no functional impact.
  • /events channel filter logic is inverted-looking: it returns (skips) when event.*_channel === channelFilter, i.e. the channel= param excludes that channel rather than including it. Worth confirming against dashboard intent. [DIVERGENCE — LOW, verify]

10. Change History

DateDispatchSummary
2026-07-042296Module spec authored (Phase 4 §5.2), smoke-clone-2, HEAD 5c9a304
399Cross-channel session registry, SSE, directive injection, stale detector
650/652Session turns; VS Code + Hermes transcript readers
1027AK-334/FUP-4 Bearer carve-outs for session-metadata endpoints
1689AK-415 — /events moved to read-only-token handshake gate

On this page