feat(shared,db): session persistence — SessionMessage/AgentSession schemas + tables (1.X)#29
Conversation
…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>
There was a problem hiding this comment.
Sorry @cemililik, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
|
@coderabbitai review all |
There was a problem hiding this comment.
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.
📝 WalkthroughWalkthroughAdds Phase-1 session persistence (workstream 1.X): Zod-validated domain contracts ( ChangesSession Persistence (1.X)
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsStopped 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 Comment |
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (1)
packages/db/src/__snapshots__/client.test.ts.snapis excluded by!**/*.snap
📒 Files selected for processing (17)
CLAUDE.mdREADME.mddocs/reference/desktop/database-schema.mddocs/roadmap/current.mddocs/roadmap/phases/phase-1-engine-and-llm.mddocs/standards/error-handling.mdpackages/db/drizzle/0001_pale_scorpion.sqlpackages/db/drizzle/meta/0001_snapshot.jsonpackages/db/drizzle/meta/_journal.jsonpackages/db/src/client.test.tspackages/db/src/index.tspackages/db/src/schema.tspackages/db/src/session-store.test.tspackages/db/src/session-store.tspackages/shared/src/index.tspackages/shared/src/session.test.tspackages/shared/src/session.ts
| status: 'paused' as never, | ||
| createdAt: TS_MS, |
There was a problem hiding this comment.
🧩 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.
| 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
…(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>
|
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>



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 only —packages/corenever imports@relavium/db; the AgentSession→store wiring + resume are 1.Y/1.AA.Changes
session.ts: SessionMessage/AgentSession/SessionStatus schemas;content= sharedDurableContentPart(reasoning signature + inline media structurally impossible — ADR-0030/0031);modelId=model_catalogid ref.agent_sessions+session_messagestables (match canonical DDL: CHECKs from shared constants, cascade, unique(session_id, sequence_number), model_id FKs); migration0001_pale_scorpion.sql+ snapshot + drift gate;SessionStore+ pure domain↔row mappers (validate on write AND read);content_partscanonical;updateSessionfreezescreated_at.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
Documentation
Tests