Skip to content

feat(shared,db): session persistence — SessionMessage/AgentSession schemas + tables (1.X)#29

Merged
cemililik merged 5 commits into
mainfrom
development
Jun 17, 2026
Merged

feat(shared,db): session persistence — SessionMessage/AgentSession schemas + tables (1.X)#29
cemililik merged 5 commits into
mainfrom
development

Conversation

@cemililik

@cemililik cemililik commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

What & why

Phase-1 workstream 1.X — session persistence: the durable, append-only transcript data layer (agent_sessions + session_messages + SessionMessageSchema/AgentSessionSchema). Directly stored as rows (ADR-0003 governs runs, not sessions). Data-layer onlypackages/core never imports @relavium/db; the AgentSession→store wiring + resume are 1.Y/1.AA.

Changes

  • @relavium/shared session.ts: SessionMessage/AgentSession/SessionStatus schemas; content = shared DurableContentPart (reasoning signature + inline media structurally impossible — ADR-0030/0031); modelId = model_catalog id ref.
  • @relavium/db: agent_sessions + session_messages tables (match canonical DDL: CHECKs from shared constants, cascade, unique (session_id, sequence_number), model_id FKs); migration 0001_pale_scorpion.sql + snapshot + drift gate; SessionStore + pure domain↔row mappers (validate on write AND read); content_parts canonical; updateSession freezes created_at.
  • docs: database-schema.md mapping note; roadmap marks 1.W Done; error-handling.md engine-error-families note (PR feat(core): wire session:* onto the shared RunEventBus (1.W) #28 review).

Review

Multi-dimensional adversarial review (8 dims, 18 agents): 5 clean; all 10 confirmed findings folded in.

Conformance

Strict TS; engine purity; no new dep; secrets never persisted; full turbo gate green (707 core / 249 shared / 31 db); format clean; drift gate passes; Leakwatch 0.

Commits: f9b7caf (1.X), 287d19c (1.W-Done docs), b98298b (error-handling note).

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Completed session event streaming for Phase 1.
    • Added session persistence and transcript storage capabilities with automatic lifecycle management.
  • Documentation

    • Updated Phase 1 roadmap to reflect session completion and clarify next development priorities.
  • Tests

    • Added comprehensive session persistence and validation test coverage.

cemililik and others added 3 commits June 17, 2026 07:32
…entBus

PR #28 merged, so mark workstream 1.W ✅ Done across the canonical status docs:

- phase-1-engine-and-llm.md: §1.W heading ✅ Done; the dependency matrix row;
  and a "Landed mechanism" note recording what actually shipped (the combined
  RunOrSessionEventSchema gate + run/session-precise next/emit overloads — NOT
  the originally-planned RunEventDraft widening — the SessionHandle over the
  extracted BoundedEventStream with onClose, and the typed RunLoopInvariantError).
- current.md: Lane-C now has 1.W done, 1.X persistence next; Last updated bumped.
- CLAUDE.md / README.md: status paragraphs reflect 1.W landed.

Lane C (1.m5) continues at 1.X (session persistence). M2 already reached (1.U).

Refs: ADR-0036, 1.W
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…hemas + tables (1.X)

Implement Phase-1 workstream 1.X (session persistence) as a self-contained data
layer — the directly-stored, append-only transcript (ADR-0003 governs runs, not
sessions). No engine change: packages/core never imports @relavium/db; the
AgentSession->store wiring + cross-restart resume are the later sub-spine (1.Y/1.AA).

@relavium/shared (new session.ts):
- SessionMessageSchema / AgentSessionSchema / SessionStatusSchema — the durable
  transcript + session-record contracts (agent-session-spec.md §"Session messages").
  content is the DurableContentPart union, so a reasoning signature + inline media
  bytes are structurally impossible to persist (ADR-0030/0031). The record type is
  AgentSessionRecord (named to avoid clashing with the AgentSession engine class);
  modelId is a model_catalog id reference (host-resolved), not a raw model string.

@relavium/db:
- agent_sessions + session_messages tables matching the canonical DDL exactly
  (fs_scope_tier/status CHECKs from the shared constant sets; session_messages
  cascades from agent_sessions; unique (session_id, sequence_number); model_id FKs).
  Regenerated migration 0001_pale_scorpion.sql + snapshot + journal; the drift gate
  re-generates with no diff; a 0001 fidelity snapshot test pins the DDL.
- SessionStore (createSessionStore) + pure domain<->row mappers — the single
  validation boundary (parse on write AND read). SessionMessage.content is the
  canonical body in content_parts; the scalar columns are optional denormalized
  metadata (SessionMessageMeta). updateSession freezes created_at (only mutable
  columns are written). ISO-8601 <-> epoch-ms converted only at the mapper edge.

Docs: database-schema.md gains the SessionMessage->content_parts mapping note.

Reviewed multi-dimensionally (schema/contracts/mappers/security/purity/style/tests/
migration) with adversarial verification: 5 dimensions clean; the confirmed findings
(updateSession created_at immutability, timestamp/JSDoc precision, and 7 test-coverage
gaps) are all folded in here. Full turbo gate green; Leakwatch clean (0 findings).

Refs: ADR-0003, ADR-0024, ADR-0030, ADR-0031, 1.X
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ling.md (PR #28 review)

Address a PR #28 LOW review finding: RunLoopInvariantError is a public @relavium/core
export but had no mention in the error-handling standard. Add one bullet to the
"Typed errors" section naming the engine's two code-discriminated error families —
EngineStateError (API-boundary) and RunLoopInvariantError (run-loop substrate
invariants) — citing their source (one-canonical-home; no code lists duplicated).
Covers EngineStateError too, so it reinforces the principle consistently rather than
singling out one class. The LlmError seam contract stays the one error detailed in full.

Refs: ADR-0036, 1.W
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @cemililik, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@cemililik

Copy link
Copy Markdown
Contributor Author

@coderabbitai review all

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements workstream 1.X (Session persistence), introducing the database schema, Zod schemas, and persistence store (SessionStore) for agent sessions and their append-only transcripts. It adds two new database tables, agent_sessions and session_messages, along with their corresponding Drizzle migrations, Zod validation schemas, and domain-to-row mappers. It also updates the project roadmap and documentation to reflect that workstream 1.W (session:* event namespace) is complete and details the error handling standards for the engine. I have no feedback to provide.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds Phase-1 session persistence (workstream 1.X): Zod-validated domain contracts (SessionMessageSchema, AgentSessionSchema) in @relavium/shared, two new SQLite tables (agent_sessions, session_messages) with CHECK constraints and a Drizzle migration, domain↔DB mapper functions, and a SessionStore CRUD implementation. Documentation marks 1.W complete and positions 1.X as next.

Changes

Session Persistence (1.X)

Layer / File(s) Summary
Shared Zod session contracts and tests
packages/shared/src/session.ts, packages/shared/src/index.ts, packages/shared/src/session.test.ts
Introduces SessionStatusSchema, SessionMessageRoleSchema, SessionMessageSchema, and AgentSessionSchema with durable content union, sequence ordering, and ISO-8601 timestamps. Re-exports from the shared barrel. Tests pin all schema branches, signature stripping on parse, inline-media rejection, and closed status/role sets.
Drizzle schema tables, SQL migration, and snapshot
packages/db/src/schema.ts, packages/db/drizzle/0001_pale_scorpion.sql, packages/db/drizzle/meta/0001_snapshot.json, packages/db/drizzle/meta/_journal.json, packages/db/src/client.test.ts
Defines agent_sessions and session_messages Drizzle tables with FK cascade, fsScopeTier/status CHECK constraints sourced from shared schemas, partial soft-delete indexes, and a unique (session_id, sequence_number) constraint. Generates migration SQL, full Drizzle snapshot, and journal entry. Extends client.test.ts with new table/index and DDL snapshot assertions.
Domain↔DB mappers and SessionStore
packages/db/src/session-store.ts, packages/db/src/index.ts, packages/db/src/session-store.test.ts
Implements ISO-8601↔epoch-ms helpers, toAgentSessionRow/fromAgentSessionRow and toSessionMessageRow/fromSessionMessageRow mappers (Zod-validated at both boundaries), the SessionStore interface, and createSessionStore. Exports all surfaces via packages/db/src/index.ts. Tests cover round-trips, update semantics, cascade deletion, sequence uniqueness, optional field persistence, corrupt-content read rejection, and raw-insert constraint enforcement.
Roadmap, reference, and standards docs
CLAUDE.md, README.md, docs/roadmap/current.md, docs/roadmap/phases/phase-1-engine-and-llm.md, docs/reference/desktop/database-schema.md, docs/standards/error-handling.md
Marks 1.W complete (PR #28), positions 1.X as next, updates Lane C completion narrative and dependency matrix, adds SessionMessagesession_messages persistence mapping reference, and documents typed engine error classes.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(100, 149, 237, 0.5)
    Note over Caller,SQLite: Session create + message append
    Caller->>SessionStore: createSession(AgentSessionRecord)
    SessionStore->>toAgentSessionRow: AgentSessionSchema.parse + serialize JSON/timestamps
    toAgentSessionRow-->>SessionStore: NewAgentSessionRow
    SessionStore->>SQLite: INSERT INTO agent_sessions
  end

  rect rgba(144, 238, 144, 0.5)
    Note over Caller,SQLite: Append transcript message
    Caller->>SessionStore: appendMessage(SessionMessage, meta?)
    SessionStore->>toSessionMessageRow: SessionMessageSchema.parse + JSON content_parts
    toSessionMessageRow-->>SessionStore: NewSessionMessageRow
    SessionStore->>SQLite: INSERT INTO session_messages
  end

  rect rgba(255, 165, 0, 0.5)
    Note over Caller,SQLite: Resume session
    Caller->>SessionStore: loadFull(sessionId)
    SessionStore->>SQLite: SELECT agent_sessions WHERE id=sessionId
    SessionStore->>SQLite: SELECT session_messages ORDER BY sequence_number
    SQLite-->>SessionStore: AgentSessionRow + SessionMessageRow[]
    SessionStore->>fromAgentSessionRow: parse JSON + AgentSessionSchema.parse
    SessionStore->>fromSessionMessageRow: parse content_parts + SessionMessageSchema.parse
    fromSessionMessageRow-->>Caller: { session: AgentSessionRecord, messages: SessionMessage[] }
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • HodeTech/Relavium#2: Shares the packages/shared/src/index.ts barrel export pattern; this PR builds on it by adding export * from './session.js'.
  • HodeTech/Relavium#17: Introduced the RunEventBus and RunOrSessionEventSchema substrate that the 1.W SessionEventSinkRunEventBus wiring (documented here) adapts into.

Poem

🐇 Hop, hop, the sessions persist!
Into history.db — they can't be missed.
content_parts in JSON, nice and neat,
Sequence numbers marching, row by row, complete.
The rabbit stamps each message true —
Phase 1.X is nearly through! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the primary feature: implementing session persistence schemas and database tables for Phase-1 workstream 1.X.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch development

Warning

Review ran into problems

🔥 Problems

Stopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a @coderabbit review after the pipeline has finished.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/db/src/session-store.test.ts`:
- Around line 280-281: The `as never` cast on the status field assignment (and
similar casts on lines 295-296) bypasses TypeScript's strict typing in negative
tests. Remove the `as never` cast and instead add a `@ts-expect-error` comment
directly above the intentionally invalid assignment. This approach makes the
negative test intent explicit while maintaining TypeScript strict mode
compliance, consistent with the pattern already used in
packages/db/src/client.test.ts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5ef5a004-e16c-4b5a-8456-da14077a412a

📥 Commits

Reviewing files that changed from the base of the PR and between f7b941a and b98298b.

⛔ Files ignored due to path filters (1)
  • packages/db/src/__snapshots__/client.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (17)
  • CLAUDE.md
  • README.md
  • docs/reference/desktop/database-schema.md
  • docs/roadmap/current.md
  • docs/roadmap/phases/phase-1-engine-and-llm.md
  • docs/standards/error-handling.md
  • packages/db/drizzle/0001_pale_scorpion.sql
  • packages/db/drizzle/meta/0001_snapshot.json
  • packages/db/drizzle/meta/_journal.json
  • packages/db/src/client.test.ts
  • packages/db/src/index.ts
  • packages/db/src/schema.ts
  • packages/db/src/session-store.test.ts
  • packages/db/src/session-store.ts
  • packages/shared/src/index.ts
  • packages/shared/src/session.test.ts
  • packages/shared/src/session.ts

Comment thread packages/db/src/session-store.test.ts Outdated
Comment on lines +280 to +281
status: 'paused' as never,
createdAt: TS_MS,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify unsafe casts in TypeScript files (read-only).
# Expected: current branch shows the two `as never` sites in session-store.test.ts.
# After fix: those lines should disappear.
rg -nP --type=ts '\bas\s+(never|any)\b'

Repository: HodeTech/Relavium

Length of output: 987


🏁 Script executed:

cat -n packages/db/src/session-store.test.ts | sed -n '275,300p'

Repository: HodeTech/Relavium

Length of output: 834


🏁 Script executed:

cat -n packages/db/src/client.test.ts | grep -A 3 -B 3 "`@ts-expect-error`"

Repository: HodeTech/Relavium

Length of output: 698


Replace unsafe as never casts in negative tests.

These casts bypass the strict typing rule. Use @ts-expect-error on intentionally invalid literals (same pattern used in packages/db/src/client.test.ts) so the negative DB-constraint test intent stays explicit without unsafe assertions.

Suggested patch
     expect(() => {
       client.db
         .insert(agentSessions)
         .values({
           id: 's2',
           agentSlug: 'x',
-          status: 'paused' as never,
+          // `@ts-expect-error` intentionally invalid to assert DB CHECK enforcement
+          status: 'paused',
           createdAt: TS_MS,
           updatedAt: TS_MS,
         })
         .run();
     }).toThrow();
@@
     expect(() => {
       client.db
         .insert(agentSessions)
         .values({
           id: 's3',
           agentSlug: 'x',
-          fsScopeTier: 'root' as never,
+          // `@ts-expect-error` intentionally invalid to assert DB CHECK enforcement
+          fsScopeTier: 'root',
           createdAt: TS_MS,
           updatedAt: TS_MS,
         })
         .run();
     }).toThrow();

Aligns with coding guidelines: "All source must be TypeScript with strict mode; no any, no unsafe as." Also applies to lines 295-296.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
status: 'paused' as never,
createdAt: TS_MS,
expect(() => {
client.db
.insert(agentSessions)
.values({
id: 's2',
agentSlug: 'x',
// `@ts-expect-error` intentionally invalid to assert DB CHECK enforcement
status: 'paused',
createdAt: TS_MS,
updatedAt: TS_MS,
})
.run();
}).toThrow();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/db/src/session-store.test.ts` around lines 280 - 281, The `as never`
cast on the status field assignment (and similar casts on lines 295-296)
bypasses TypeScript's strict typing in negative tests. Remove the `as never`
cast and instead add a `@ts-expect-error` comment directly above the
intentionally invalid assignment. This approach makes the negative test intent
explicit while maintaining TypeScript strict mode compliance, consistent with
the pattern already used in packages/db/src/client.test.ts.

Source: Coding guidelines

cemililik and others added 2 commits June 17, 2026 12:30
…(sonar)

7 sonarjs/no-negated-condition smells in session-store.ts: row->domain optional-field
spreads flipped from `row.x !== null ? { x } : {}` to `row.x === null ? {} : { x }` —
behavior/type-identical, no cast. Tests/typecheck/lint green.

Refs: 1.X
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e tests

Address an inline review finding on session-store.test.ts: the negative CHECK-constraint
tests cast invalid values with `as never`. Replace with `@ts-expect-error` — placed above
the `.values(...)` call, where drizzle's overload error lands — matching the established
pattern in client.test.ts and keeping strict typing rather than casting the error away.
No behavior change: the invalid values still trip the DB CHECK at runtime.

(The companion sonar no-negated-condition flip in session-store.ts is the parent commit
dc0d037.)

lint + typecheck + test (@relavium/db) green.

Refs: 1.X
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@cemililik
cemililik merged commit 9652b66 into main Jun 17, 2026
8 checks passed
cemililik added a commit that referenced this pull request Jun 17, 2026
PR #29 merged, so mark workstream 1.X ✅ Done across the canonical status docs:
- phase-1-engine-and-llm.md: §1.X heading ✅ Done + a "Landed mechanism" note (data-layer
  only; session.ts schemas over DurableContentPart; the two tables + migration 0001 +
  SessionStore + domain↔row mappers; multi-dimensional review folded in); matrix row.
- current.md: Lane-C now has 1.W + 1.X done, 1.Y/1.Z/1.AA next.
- CLAUDE.md / README.md: status paragraphs reflect 1.X landed.

Lane C (1.m5) continues at 1.Y (session checkpoint/resume). M2 already reached.

Refs: 1.X
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant