Skip to content

feat(2.5.F): context compaction — /compact, /trim, and automatic compaction (ADR-0062)#65

Merged
cemililik merged 18 commits into
mainfrom
development
Jul 5, 2026
Merged

feat(2.5.F): context compaction — /compact, /trim, and automatic compaction (ADR-0062)#65
cemililik merged 18 commits into
mainfrom
development

Conversation

@cemililik

@cemililik cemililik commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

What & why

Phase 2.5.F — the CLI conversation-history commands. Per an explicit maintainer scope decision, this reverses the roadmap's Phase-3 deferral of /compact and builds the full context-compaction story: a long relavium chat / Home conversation no longer hard-fails at the model's context window. Design: ADR-0062 (Accepted).

Delivered in three reviewed steps, each with an Opus + Sonnet adversarial review round (findings fixed and committed before moving on):

Step Scope Commits
1 — Foundations @relavium/shared (session:compacted/session:trimmed events + SessionMessage.compaction field + [chat].auto_compact/compact_threshold), @relavium/llm seam (estimateTokens/contextLimit/managesOwnContext + 3 adapters), @relavium/db (nullable boundary column, migration 0006) 67a7ba67954fd37120afc
2 — Engine primitive AgentSession.compact()/trimHistory() + system-prompt preamble (keep last exchange) + auto-threshold + reconstructSessionState marker-honoring 20a4f9e8184de466c3899
3 — CLI host + docs config→SessionDeps, persister append-only marker (role-filtered boundary), /compact + /trim, auto-compaction notice + input-gated spinner + Esc-abort dd5a68ae277699d697b5d

Design highlights

  • Append-only, resume/reseat-preserving. Compaction writes one role:'system' marker row (summary + droppedThroughSequence); nothing is deleted — the full transcript survives for /export + audit. Resume separates the drop boundary (max(droppedThroughSequence)) from the preamble (newest marker with a summary), so a later summary-less /trim never blanks a prior /compact summary.
  • Summary → system-prompt preamble (not a transcript message → no alternation hazard); the last exchange stays verbatim.
  • Automatic compaction at [chat].compact_threshold (0.8) of the serving model's window, driven by real provider usage; thrash-guarded, cost-accounted, and surfaced as an inline notice — never a silent spend. Degrades to a deterministic /trim on failure.
  • One shared resume projection (resumableMessageSequences) so the engine and the host persister can never drift (the fix for a silent kept-message-loss trap the review caught).
  • Input-gated compaction moment on both interactive surfaces — a keystroke during summarisation can't crash the REPL, and Esc aborts it (session survives).

Two review rounds caught real bugs (all fixed)

  • A transcript-wipe in /trim 1; a lone-dangling-user kept slice; an unbounded summary (thrash-guard hardening).
  • A silent data-loss on resume→compact (role-filter vs the engine's rollback); a REPL crash typing during compaction.

Scope note

/clear (a fresh-session lifecycle swap, orthogonal to compaction) + two compaction-moment UX polishes (a labeled "Summarizing…" spinner — needs a session:compacting start signal — and a footer context-fullness indicator) are the remaining 2.5.F items, documented honestly in chat-session.md + the roadmap rather than shipped as a forbidden no-op stub.

Verification

pnpm turbo run lint typecheck test build 24/24 green (core 967, CLI 1294 tests) + format:check + lint:bundle-closure. Engine purity + @relavium/llm seam gates pass. No new runtime dependency; no vendor type crosses the seam.

Not marked Done in the roadmap until merged (roadmap-done-after-merge).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added chat history commands: /compact, /trim [n], and interactive-only /clear, with durable, resume-preserving compaction/trim boundaries.
    • Introduced automatic context compaction settings: auto_compact and compact_threshold, plus new compaction/trim lifecycle events and notices.
    • Enhanced the chat UI with a “Summarizing…” moment and a footer context-fullness indicator when token data is available.
  • Bug Fixes
    • Improved input gating during in-flight turns and session swaps; notices are now sanitized and boundary/summary handling is more reliable after resume.
  • Documentation
    • Updated CLI command reference, chat config keys, and SSE/event/contract docs for compaction/trim and /clear.

cemililik and others added 12 commits July 3, 2026 23:28
…del refinement

PR #64 merged (2026-07-03), so 2.5.D (chat input ergonomics + the `@`/`!`
pending-attachment "chip" model) flips from "implemented, pending merge" to
✅ Done across the canonical status homes:

- roadmap/current.md + phases/phase-2.5-cli-consolidation.md: 2.5.D status → Done;
  note the post-implementation refinements recorded in ADR-0061's "Refined at
  implementation" append — the chip model (inline `@path` marker + read-only `!`
  preview, expanded into the SAME UNTRUSTED nonce-fenced frame only at submit),
  the coupled `[chat]` allowlist resolution, and the Backspace/`key.delete` fix.
- CLAUDE.md: status paragraph advanced to 2.5.D Done; next pickup is 2.5.F / G.
- `@`/`!` phase bullets updated to the chip presentation (frame applied at submit).

The spine (2.5.A/B/C/E) plus the first experience-arm workstream (2.5.D) are now
complete; 2.5.F / G remain in the experience arm.

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

Accept ADR-0062 for Phase 2.5.F, reversing the roadmap's deferral of model-
summarised `/compact` to Phase 3 (maintainer scope decision — build the full
manual + automatic compaction story now).

Core decisions (revised after four adversarial review passes):

- Summary is a session-level system-prompt PREAMBLE (not a transcript message —
  no alternation hazard); the last user+assistant exchange stays VERBATIM (K≥1).
- Append-only persistence: one `role:system` marker row + a typed nullable column
  (`compaction_dropped_through_sequence`) via a standard migration — NOT a new
  arm on the closed `DurableContentPart` union (which would be breaking-to-old-
  readers); nothing is ever deleted; full transcript survives export/audit.
- Resume separates the drop boundary (max droppedThroughSequence) from the
  preamble (newest marker WITH a summary), so a later summary-less `/trim` never
  blanks a prior `/compact` summary; reseat (ADR-0059) carries the preamble.
- Seam amendment: optional `estimateTokens`/`contextLimit`/`managesOwnContext`
  on `LlmProvider` (Relavium/Zod types only — no vendor type crosses); real
  provider usage is the authoritative auto-compaction trigger, estimate is the
  pre-first-turn fallback.
- Auto-compaction at `[chat].compact_threshold` (0.8) with thrash/cost guards,
  a deterministic `/trim` failure-degrade, and honest cost accounting.

Purges the house-rule-violating references to the gitignored goose analysis;
corrects the pricing-catalog-import and forward-compat claims the first draft got
wrong. Adds the README index line.

Refs: ADR-0062
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eam, migration)

The platform-free foundations for context compaction (ADR-0062), ahead of the
engine primitive (step 2) and the CLI wiring (step 3):

- @relavium/shared: `session:compacted` + `session:trimmed` arms on the closed
  SessionEvent union (+ SESSION_EVENT_TYPES); an optional, additive
  `SessionMessage.compaction.droppedThroughSequence` boundary field on the lenient
  transcript schema; `[chat].auto_compact` + `compact_threshold` config (a fraction
  in (0,1]). `max_messages` now documented as consumed by `/trim` + auto-compaction.
- @relavium/llm: optional `contextLimit` / `managesOwnContext` / `estimateTokens`
  on the LlmProvider seam — Relavium/Zod seam types only, no vendor type crosses —
  with shared `contextLimitFor` (pricing-catalog window; undefined for an unrated
  model) + `estimateRequestTokens` (a character heuristic; a pre-first-turn fallback,
  since real usage is authoritative) helpers, implemented in all three real adapters
  (Anthropic, OpenAI/DeepSeek, Gemini).
- @relavium/db: one additive nullable `compaction_dropped_through_sequence` column
  (drizzle migration 0006) round-tripped by the SessionMessage mapper — an older
  reader that lacks it reads a marker as an inert `system` row (forward-compatible;
  no destructive change).

Tests: new-event accept/reject, config-range, adapter seam-method delegation, and
the marker-column round-trip. Full toolchain + format:check + lint:bundle-closure green.

Refs: ADR-0062
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…overage

Verified findings from the Opus review round of step 1 (no blockers/correctness
defects were found; these are the low/nit fixes):

- Canonical home (CLAUDE.md #8): document `session:compacted` / `session:trimmed`
  where the wire contract lives — sse-event-schema.md's SessionEvent union + the
  "complete stream" prose (five→seven), and chat-session.md's `--json` NDJSON
  enumeration. Fix the stale "five session:* events" comments in session-handle.ts
  + agent-session.ts, and soften an over-stated run-event.ts comment (consumers use
  a `default` arm — there is no `assertNever` over the union; a new arm is a
  forward-compatible no-op until each opts in).
- Coverage: estimateRequestTokens adversarial cases (empty→0, mixed/empty-text
  parts, and a pinned CJK under-count documenting the heuristic's known imprecision
  — real usage stays authoritative); a summary-less /trim marker db round-trip
  (empty content + boundary); session:compacted nonNegativeInt count rejects; and a
  type-level SessionEvent-discriminant pin mirroring RunEvent's.
- ADR-0062 scope bullet corrected to name `session:trimmed` as a distinct cost-free arm.

Toolchain + format:check green.

Refs: ADR-0062
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ements

Verified findings from the Sonnet review round of step 1 (HIGH: canonical-doc
drift, flagged by both reviewers; the rest are integration-seam clarifications):

- Update the four canonical reference homes CLAUDE.md #8 requires (the shapes this
  step changed): llm-provider-seam.md (the three new LlmProvider methods),
  database-schema.md (the compaction_dropped_through_sequence column),
  agent-session-spec.md (the SessionMessage.compaction field), and config-spec.md
  (auto_compact / compact_threshold + prose).
- Reconcile the 2.5.F roadmap scope: /compact is no longer Phase-3-deferred — it is
  built now under ADR-0062 (engine primitive + automatic compaction). Removed the
  stale "recognized-but-deferred stub" plan and the Phase-3 deferral bullet; noted
  the 2.5.H context-overflow hint is now the secondary net (auto-compaction pre-empts).
- Append "Refined during step-1 review" notes to ADR-0062: the role-filtered
  write-side boundary algorithm (avoids a silent-data-loss off-by-one when a prior
  marker row is interleaved), using result.model (not agent.model) for the threshold,
  the resolve.ts config plumbing obligation, disambiguation from the reserved
  agent:context_compacted steering event, and the marker-persistence sign-off.

format:check green (docs-only).

Refs: ADR-0062
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…062)

The platform-free engine primitive behind /compact, /trim, and auto-compaction:

- AgentSession.compact(reason): summarise the earlier working context via the
  session's OWN bound model into a session-level `#contextPreamble` (XML-wrapped into
  the per-turn system prompt — reseat-free, no alternation hazard), keep the last
  user+assistant exchange VERBATIM, and emit session:compacted. The conversation to
  summarise rides a USER message (never the authored system prompt); the summariser's
  cost is accounted via cost:updated, but its agent:token/tool events are NOT forwarded
  (the internal summary never streams into the transcript as a reply). Classified
  CompactionResult (compacted/nothing_to_compact/failed/cancelled).
- AgentSession.trimHistory(n): deterministic, no-LLM drop to the last n messages,
  snapped to a `user` boundary; emits session:trimmed; leaves the preamble intact
  (a trim drops older turns without summarising — a prior /compact summary survives).
- Auto-compaction: after a CLEAN turn settles, compact if the turn's REAL input tokens
  exceed compactThreshold × the SERVING model's context window (result.model, canonical
  id). Guarded by autoCompact config, managesOwnContext, an unknown window, and a ≤1-
  exchange thrash guard; a summarisation failure degrades to a deterministic /trim to
  maxMessages. Wired via new SessionDeps (autoCompact/compactThreshold/maxMessages).
- reconstructSessionState honours boundary markers with preamble≠boundary: drop
  boundary = max(droppedThroughSequence); preamble = the NEWEST marker WITH a summary
  (a later summary-less /trim advances the boundary but never blanks a prior /compact
  summary — the B4 correctness fix). resume restores the preamble, so a compacted
  session stays compacted across resume AND a model reseat (same reconstruct path).
- Seam: export EstimateTokensInput (@relavium/llm) + CompactionResult/TrimResult and
  COMPACTION_SYSTEM_PROMPT/DEFAULT_COMPACT_THRESHOLD (@relavium/core).

Tests: compact folds+preamble-injects+keeps-last-exchange; nothing_to_compact;
trimHistory drop/no-op; auto-compaction fire + all skip guards + failure-degrade;
and reconstructSessionState marker/preamble/boundary cases (incl. trim-after-compact
and multi-compaction). Engine purity gate + full toolchain + both CI gates green.

Refs: ADR-0062
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… coverage

Verified findings from the Opus review round of the engine primitive (no blockers /
crashes / seam or purity violations were found; the state machine was verified sound):

- Implement ADR §5 thrash guard (b) — all three reviewers' top finding. Auto-compaction
  now skips when system+preamble+kept-exchange ALONE would still exceed the budget
  (an oversized single turn / huge system prompt), so it no longer pays a summariser
  call on every turn for a small-window session; the overflow surfaces instead.
- #maybeAutoCompact consults the SERVING provider (the plan entry whose model produced
  the turn under fallback), not just the primary, for managesOwnContext/contextLimit.
- Degrade an EA7-aborted `cancelled` auto-compaction to a deterministic /trim too (not
  only `failed`), matching ADR §5 "never a silent overflowing resend"; a terminal
  cancel() leaves status != idle so a dead session is untouched.
- compact() robustness: compute tokensBefore INSIDE the try (a throwing optional
  estimator can't wedge #status at 'running' past the finally); re-throw a truly
  UNCLASSIFIED error instead of masking it as 'failed' (mirrors #settleTurnError;
  BudgetPauseError stays a bounded 'failed').
- Reconcile ADR §7: the summariser's agent:token/tool events are NOT forwarded to the
  transcript (they'd render as a reply) — the "Summarizing…" moment is driven off the
  compaction lifecycle (the running indicator / a spinner), not token streaming.

Coverage (the Sonnet+Opus gap list): cancel/abort mid-summarisation (cancelled + context
unchanged + session usable vs terminal); re-compaction folds the prior preamble; non-zero
before>after token deltas; thrash-guard-b skip; failure WITHOUT maxMessages (no throw/trim);
compact()/trimHistory() pre-start guard; trimHistory user-boundary snap; and trim-only /
droppedThroughSequence:0 / multi-marker resume. Core 963 tests; full toolchain + gates green.

Refs: ADR-0062
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ummary bound

The Sonnet review round found real HIGH bugs the Opus round missed:

- trimHistory(1) WIPED the entire transcript: tailFromUserBoundary forward-snapped
  the last-1 slice (an assistant, for a transcript ending in assistant) to empty, and
  trimHistory committed the wipe as a "success". `[chat].max_messages` is positiveInt,
  so `max_messages: 1` is a legal value the auto-compaction failure-degrade could pass.
  Fixed: never return an empty slice when there was history — floor at the last exchange.
- splitFoldable could keep a LONE dangling `user` as the "kept exchange" (a completed
  empty-text turn leaves a dangling user; sendMessage only appends the assistant when
  result.text is non-empty), breaking the next turn with two consecutive user messages.
  Fixed: keep from the last `user` that HAS an assistant reply after it — never a lone user.
- The summariser output was UNBOUNDED (no maxTokens), so a compaction could produce a
  huge preamble that defeats compaction; and thrash-guard-b used the STALE pre-compaction
  preamble as a proxy (a false-skip risk). Fixed: bound the summary via a new
  COMPACTION_MAX_SUMMARY_TOKENS (4096) maxTokens, and rewrite guard-b to a PROJECTED FLOOR
  = base system (not the current preamble the summary replaces) + kept exchange + that bound.
- estimator-throw safety was applied to only one of three call sites (compact's
  tokensBefore); tokensAfter and guard-b were still exposed. Fixed comprehensively: the
  estimate helper now NEVER throws (a throwing seam estimator is swallowed to 0), so no
  estimate can wedge #status, escape compact()'s finally, or crash the auto-compaction gate.

Tests reworked to realistic windows (guard-b's 4096 floor makes toy windows unrealistic)
via a parametrized inputTurn helper; new regressions for trim(1), the dangling-user
no-op, and the degrade kept/dropped counts. Core 965 tests; full toolchain + gates green.

Refs: ADR-0062
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ion UX

Wires the ADR-0062 engine primitive into the interactive chat surface:

- Config: [chat].auto_compact / compact_threshold resolve through ResolvedChatConfig
  and thread into SessionDeps (with max_messages) — never hardcoded, so the config is
  not re-dead. The engine auto-compacts after a turn per the resolved threshold.
- Persister: on session:compacted / session:trimmed, append the append-only boundary
  marker (role:'system' + summary + a ROLE-FILTERED droppedThroughSequence — the
  step-1-review trap: keptMessageCount is mapped over the real user/assistant
  sequences, EXCLUDING prior marker rows; seeded from loadMessages on resume) and add
  the summariser's token usage. Nothing durable is deleted.
- Commands: /compact (write) + /trim [n] (read, a FREE positional) in REPL_COMMANDS +
  ReplCommandContext; /compact shows a Summarizing… notice then the token deltas +
  the (sanitized) summary; /trim uses [chat].max_messages by default or an inline n,
  with an actionable no-bound notice. validateSlashTokens gains a free-positional mode.
- Auto-compaction UX: the session view surfaces an automatic compaction as a concise
  inline ⟳ notice (reason 'auto-threshold' only — a manual /compact is noticed by the
  command), so it is never a silent context swap.

Docs: chat-session.md gains a Context-compaction section (the mechanisms, the durable
boundary, and the summary-prompt invariant — the ADR's named canonical home).

SCOPE NOTE: /clear (a fresh-session lifecycle swap, orthogonal to compaction) is the
remaining 2.5.F command — deferred as a clearly-noted follow-up rather than a forbidden
no-op stub, so /compact (the explicit ask) + /trim + auto-compaction land complete.

Tests: persister role-filtered marker + resume; /compact + /trim command behavior +
free-positional validation; config resolution; notice formatters. Full toolchain +
format:check + lint:bundle-closure green (1291 CLI tests).

Refs: ADR-0062
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…mpaction crash

Two HIGH findings from the Opus review round of the CLI wiring:

- SILENT DATA LOSS on resume→compact: the persister seeded its boundary-mapping from
  a bare role filter, but the engine resumes from reconstructSessionState's fuller
  projection (drops empty-text rows + rolls back a trailing unanswered `user`). A
  session that ended on an empty-text turn (a dangling user) therefore seeded the
  rolled-back row, making the next compaction's droppedThroughSequence off-by-one and
  silently dropping a kept message (then a two-consecutive-users 400). Fixed by
  extracting the projection into ONE shared helper (resumableMessageSequences /
  projectResumableRows in @relavium/core) that BOTH the engine and the host seed from —
  they can never drift.
- REPL CRASH during compaction: auto-compaction runs inside sendMessage AFTER
  session:turn_completed already flipped the view idle, so a message typed during the
  multi-second summarisation reached sendMessage → SessionStateError → crash (the same
  "engine busy, view idle" hazard 2.5.D fixed for `!`-shell). Fixed by a submit-busy
  flag on BOTH interactive surfaces (chat-ink ChatApp + the Home controller) that gates
  input + drives the spinner for the WHOLE submit (streaming AND compaction), so Esc
  still aborts and no keystroke can crash the session.

Tests: resumableMessageSequences (dangling-user + boundary + marker exclusion), the
session:compacted auto-notice reducer arm (auto surfaces, manual does not), and a
reverse-search test updated to await the turn settle (input is now correctly gated
while a submit is in flight). Full toolchain + gates green (core 967, CLI 1292).

Refs: ADR-0062
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…summary cap

The Sonnet review round found no blockers (the Opus fixes verified correct), but
flagged that the fixes lacked regression guards + two UX-polish items:

- Add the resume→/compact integration test: a durable transcript ending on a dangling
  user, resumed via buildResumedChatSession + a second persister (the adopt/seed branch),
  then /compact → asserts the boundary is droppedThroughSequence 1, NOT 2. A straight
  revert of the persister seed line now fails this test (the silent-data-loss guard).
- Add the submit-busy input-gate test: a deferred turn holds a submit in flight (the
  auto-compaction window); a message typed then is gated (never reaches a second
  processLine, onError not called) and the flag clears on settle (no stuck-busy).
- Cap the inline /compact summary preview at 800 chars (a long summary is previewed with
  a pointer to the full text via /export, never a scrollback-flooding dump).
- Disclose honestly (chat-session.md) that the compaction spinner is unlabeled and the
  footer context-fullness indicator is a follow-up — the compaction engine, persistence,
  resume, auto-compaction, /compact, and /trim are complete; Esc aborts a compaction.

Full toolchain + gates green (core 967, CLI 1294).

Refs: ADR-0062
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ar remaining

ADR-0062's compaction primitive, auto-compaction, persistence, /compact, and /trim
landed (pending merge) across three reviewed steps. Note in the 2.5.F acceptance that
/clear (a session-lifecycle swap, orthogonal to compaction) + two compaction-moment UX
polishes (a labeled spinner + a footer fullness indicator) are the remaining items, and
that 2.5.F stays un-marked-Done until merge (roadmap-done-after-merge).

Refs: ADR-0062
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, your pull request is larger than the review limit of 150000 diff characters

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds ADR-0062 context compaction, deterministic trimming, and /clear session swapping across core session handling, CLI commands, persistence, TUI state, shared contracts, and documentation. It also adds config and LLM provider hooks used to drive automatic compaction.

Changes

Context Compaction Feature

Layer / File(s) Summary
Shared event/schema contracts and DB compaction column
packages/shared/src/{constants,run-event,session,config}.ts, packages/db/src/{schema,session-store}.ts, packages/db/drizzle/*, packages/llm/src/{types,pricing,index}.ts, packages/llm/src/adapters/*, packages/shared/src/run-event.test.ts, packages/shared/src/config.test.ts, packages/db/src/session-store.test.ts, packages/llm/src/{pricing,test,adapters/shared}.ts
Adds compaction/trim session events, SessionMessage.compaction, chat compaction config keys, the durable boundary column, and LLM context/token estimation contracts and tests.
AgentSession compaction and resume projection
packages/core/src/engine/{agent-session,session-resume,session-handle}.ts, packages/core/src/index.ts, packages/core/src/engine/*test.ts
Implements compact(), trimHistory(), auto-compaction gating, resume-time boundary reconstruction, and the new exported constants/types.
CLI persister boundary marker writes
apps/cli/src/chat/{persister,session-host}.ts, apps/cli/src/config/resolve.ts, apps/cli/src/chat/*test.ts, apps/cli/src/engine/media-wiring.test.ts
Persists append-only boundary markers and threads chat compaction limits through config resolution and session runtime wiring.
/compact, /trim, and /clear REPL commands
apps/cli/src/commands/{chat,repl-commands}.ts, apps/cli/src/chat/repl-info.ts, apps/cli/src/home/drive-home.tsx, apps/cli/src/commands/*test.ts
Adds the slash commands, registry entries, validation, driver outcomes, rebuild wiring, and notice formatting.
TUI submit busy and compaction rendering
apps/cli/src/render/tui/{chat-ink,home-app,home-controller,session-view-model,chat-projection,chat-store}.tsx/ts, apps/cli/src/render/tui/*test.ts
Tracks submit-in-flight state, gates input and paste handling, and renders compaction status and footer context fullness.
ADR, reference, roadmap, and status docs
docs/decisions/*, docs/reference/*, docs/roadmap/*, CLAUDE.md
Updates ADR text, reference contracts, roadmap status, and milestone notes for the new compaction flow.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • HodeTech/Relavium#6: Both PRs touch the shared SSE/session contract layer, including packages/shared/src/run-event.ts and session-event typing.
  • HodeTech/Relavium#26: Both PRs modify packages/core/src/engine/agent-session.ts and its session lifecycle/cost handling.
  • HodeTech/Relavium#54: Both PRs work in the same chat REPL and session plumbing area that this PR extends with /clear, /compact, and /trim.
🚥 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 clearly matches the main change: ADR-0062 context compaction with /compact, /trim, and automatic compaction.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch development

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

@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 context compaction and history trimming for CLI chat sessions as specified in ADR-0062, introducing manual /compact and /trim commands, automatic threshold-triggered compaction, and database persistence for compaction boundaries. The review feedback focuses on code safety and optimization: it suggests removing redundant optional chaining on guaranteed plan entries, optimizing splitFoldable to O(N) complexity, simplifying tailFromUserBoundary with standard array methods, wrapping onSubmit to prevent REPL freezes on synchronous throws, and adding a reason field to session:trimmed to prevent silent fallback trims in the UI.

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.

if (!plan.ok) return 0;
try {
// Call inline (not via an extracted method reference) so `estimateTokens` stays bound to its provider.
return plan.entries[0]?.provider.estimateTokens?.({ system, messages }) ?? 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Since plan.ok is checked above, plan.entries is guaranteed to be a non-empty array, meaning plan.entries[0] and its provider property are guaranteed to exist. The optional chaining plan.entries[0]?.provider is redundant. More importantly, if plan.entries[0] were actually undefined, this expression would throw a TypeError when trying to access .estimateTokens on undefined. We should use direct property access instead.

Suggested change
return plan.entries[0]?.provider.estimateTokens?.({ system, messages }) ?? 0;
return plan.entries[0].provider.estimateTokens?.({ system, messages }) ?? 0;
References
  1. Do not use optional chaining or nullish coalescing on fields that are guaranteed to be present by upstream schema validation (e.g., Zod). Unreachable defensive code masks contract violations and prevents the system from failing loud when an upstream contract breaks.

Comment on lines +1108 to +1123
function splitFoldable(
messages: readonly LlmMessage[],
): { readonly foldable: LlmMessage[]; readonly kept: LlmMessage[] } | undefined {
let keptStart = -1;
for (let i = messages.length - 1; i >= 0; i -= 1) {
if (
messages[i]?.role === 'user' &&
messages.slice(i + 1).some((message) => message.role === 'assistant')
) {
keptStart = i; // the last `user` that has an assistant reply after it — the start of the last exchange
break;
}
}
if (keptStart <= 0) return undefined; // no earlier turn precedes the last complete exchange
return { foldable: messages.slice(0, keptStart), kept: messages.slice(keptStart) };
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The current implementation of splitFoldable has an $O(N^2)$ worst-case complexity due to the nested slice and some calls inside the loop. We can optimize this to $O(N)$ and make the code much cleaner by using findLastIndex to locate the last assistant message, and then finding the last user message before that index.

function splitFoldable(
  messages: readonly LlmMessage[],
): { readonly foldable: LlmMessage[]; readonly kept: LlmMessage[] } | undefined {
  const lastAssistantIdx = messages.findLastIndex((m) => m.role === 'assistant');
  if (lastAssistantIdx === -1) return undefined;

  const keptStart = messages.slice(0, lastAssistantIdx).findLastIndex((m) => m.role === 'user');
  if (keptStart <= 0) return undefined; // no earlier turn precedes the last complete exchange

  return { foldable: messages.slice(0, keptStart), kept: messages.slice(keptStart) };
}

Comment on lines +1162 to +1171
function tailFromUserBoundary(messages: readonly LlmMessage[], maxKeep: number): LlmMessage[] {
if (maxKeep <= 0 || messages.length === 0) return [];
let slice = messages.slice(Math.max(0, messages.length - maxKeep));
while (slice.length > 0 && slice[0]?.role !== 'user') slice = slice.slice(1);
if (slice.length > 0) return slice;
// The forward-snap emptied the slice — floor at the last `user` onward (the last exchange), never a wipe.
let start = messages.length - 1;
while (start > 0 && messages[start]?.role !== 'user') start -= 1;
return messages.slice(start);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

We can simplify tailFromUserBoundary by replacing the manual while loops with standard array methods like findIndex and findLastIndex. This improves readability, maintainability, and performance.

function tailFromUserBoundary(messages: readonly LlmMessage[], maxKeep: number): LlmMessage[] {
  if (maxKeep <= 0 || messages.length === 0) return [];
  const slice = messages.slice(Math.max(0, messages.length - maxKeep));
  const firstUserIdx = slice.findIndex((m) => m.role === 'user');
  if (firstUserIdx !== -1) return slice.slice(firstUserIdx);
  // The forward-snap emptied the slice — floor at the last `user` onward (the last exchange), never a wipe.
  return messages.slice(Math.max(0, messages.findLastIndex((m) => m.role === 'user')));
}

Comment on lines 475 to 498
const submit = (message: string, display?: string): void => {
// Mark the submit in flight so input is gated + the spinner runs for the WHOLE operation (streaming AND any
// after-turn auto-compaction, ADR-0062) — cleared in EVERY settle branch (success / reject / defensive catch).
applySubmitBusy(true);
// Two-arm: a settled turn checks for exit; an UNEXPECTED rejection (the turn core's loud re-throw) goes to
// onError so the driver always unblocks + tears down (else `exited` never settles and the REPL hangs). The
// trailing .catch is defensive: a throw inside either callback still routes to onError, never silently lost.
void props
.onSubmit(message, display)
.then(
() => {
applySubmitBusy(false);
if (props.shouldStop()) props.onExit();
},
(err: unknown) => props.onError(err),
(err: unknown) => {
applySubmitBusy(false);
props.onError(err);
},
)
.catch((err: unknown) => props.onError(err));
.catch((err: unknown) => {
applySubmitBusy(false);
props.onError(err);
});
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If props.onSubmit throws an error synchronously (e.g., during synchronous validation or due to a reference error before returning a promise), the promise chain is bypassed, and applySubmitBusy(false) is never called. This would leave the REPL permanently busy and frozen. Wrapping the call in Promise.resolve().then(...) ensures that any synchronous throws are safely caught and handled by the promise chain.

  const submit = (message: string, display?: string): void => {
    // Mark the submit in flight so input is gated + the spinner runs for the WHOLE operation (streaming AND any
    // after-turn auto-compaction, ADR-0062) — cleared in EVERY settle branch (success / reject / defensive catch).
    applySubmitBusy(true);
    // Wrap in Promise.resolve().then() to safely catch any synchronous throws from props.onSubmit
    Promise.resolve()
      .then(() => props.onSubmit(message, display))
      .then(
        () => {
          applySubmitBusy(false);
          if (props.shouldStop()) props.onExit();
        },
        (err: unknown) => {
          applySubmitBusy(false);
          props.onError(err);
        },
      )
      .catch((err: unknown) => {
        applySubmitBusy(false);
        props.onError(err);
      });
  };

Comment on lines 290 to 293
default:
// session:exported and any forward-compatible additions: no view change.
// session:trimmed (the manual /trim command notices its own result), session:exported, and any
// forward-compatible additions: no view change.
return base;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

When automatic context compaction fails, the engine degrades to a deterministic /trim (by calling this.trimHistory), which emits a session:trimmed event. However, since session:trimmed is ignored in the view model (assumed to only be triggered by the manual /trim command which prints its own notice), this automatic fallback trim is completely silent to the user. To fix this, consider adding a reason: 'manual' | 'auto-fallback' field to session:trimmed (symmetric with session:compacted), and handling the 'auto-fallback' case in the view model to display an inline notice so the user is aware of the automatic trim.

@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: 3

🧹 Nitpick comments (3)
docs/decisions/0062-context-compaction-and-cli-history-commands.md (1)

134-149: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep the contract details in the canonical reference docs.

This ADR is duplicating concrete seam signatures and schema/event field names that should live in docs/reference/*. Link to those artifacts here instead of restating them, or the decision text will drift from the spec.

Also applies to: 268-281

🤖 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 `@docs/decisions/0062-context-compaction-and-cli-history-commands.md` around
lines 134 - 149, The ADR section is duplicating the concrete `LlmProvider` seam
contract by restating `estimateTokens`, `contextLimit`, and `managesOwnContext`
signatures. Update the decision text to point to the canonical
`docs/reference/*` docs instead of embedding these method/schema details here,
and keep the discussion at a high level so the ADR does not drift from the spec.
Use the existing `LlmProvider` seam and the referenced canonical docs as the
anchors when revising the wording.

Source: Coding guidelines

apps/cli/src/chat/persister.ts (1)

76-116: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Ignored appendMarker() boolean return could mask a token/transcript desync.

appendMarker() returns false when there is nothing older to supersede (Line 103), but both call sites (Lines 185, 192) discard the result. If a live session:compacted/session:trimmed event ever fires with a keptMessageCount that the host's realMessageSeqs disagrees with (e.g. a future host/engine drift), the token totals still get incremented and the session flushed to active even though no boundary marker was durably written — silently disagreeing with the in-memory "compacted" state on the next resume.

Given how carefully this PR already hardened boundary-mapping drift (the step-3 resume fix), surfacing this case (e.g., a dev-only assertion or warning) would make a future regression fail loudly instead of silently.

🛡️ Optional: surface an unexpected no-op marker write
       case 'session:compacted':
-        appendMarker(event.summary, event.keptMessageCount);
+        if (!appendMarker(event.summary, event.keptMessageCount)) {
+          // Should be unreachable if host/engine tracking stay in sync — surfaces a future desync loudly.
+          console.warn('session:compacted: no boundary marker written (keptMessageCount >= real rows)');
+        }
         totalInputTokens += event.tokensUsed.input;

Also applies to: 181-194

🤖 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 `@apps/cli/src/chat/persister.ts` around lines 76 - 116, The appendMarker()
call sites in persister.ts currently ignore its boolean result, which can hide a
no-op boundary write when keptMessageCount no longer matches realMessageSeqs.
Update the session:compacted and session:trimmed handling around appendMarker()
to check the return value and surface an unexpected false with a dev-only
assertion or warning, so token accounting and flush-to-active only proceed when
the durable marker was actually written.
packages/core/src/engine/agent-session.ts (1)

796-895: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reduce compact()'s cognitive complexity (SonarCloud gate).

Static analysis flags compact() at 17/15 allowed cognitive complexity. Consider extracting the catch block's error classification into a private helper (mirroring #settleTurnError's pattern), e.g. #classifyCompactionError(err): CompactionResult.

♻️ Sketch of the extraction
-    } catch (err) {
-      if (this.#statusIs('cancelled') || err instanceof ToolCancelledError)
-        return { kind: 'cancelled' };
-      if (this.#abortingTurn) return { kind: 'cancelled' };
-      if (err instanceof AgentTurnError) {
-        if (err.code === 'cancelled') return { kind: 'cancelled' };
-        return { kind: 'failed', message: err.message };
-      }
-      if (err instanceof BudgetPauseError) return { kind: 'failed', message: err.message };
-      throw err;
-    } finally {
+    } catch (err) {
+      return this.#classifyCompactionError(err);
+    } finally {
       this.#abort = undefined;
       this.#abortingTurn = false;
       if (this.#statusIs('running')) this.#status = 'idle';
     }
   }
+
+  `#classifyCompactionError`(err: unknown): CompactionResult {
+    if (this.#statusIs('cancelled') || err instanceof ToolCancelledError) return { kind: 'cancelled' };
+    if (this.#abortingTurn) return { kind: 'cancelled' };
+    if (err instanceof AgentTurnError) {
+      return err.code === 'cancelled' ? { kind: 'cancelled' } : { kind: 'failed', message: err.message };
+    }
+    if (err instanceof BudgetPauseError) return { kind: 'failed', message: err.message };
+    throw err;
+  }
🤖 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/core/src/engine/agent-session.ts` around lines 796 - 895, Reduce the
cognitive complexity in compact() by moving the error-classification logic out
of the catch block into a private helper, following the same pattern as
`#settleTurnError`. Have compact() delegate caught errors to a new helper such as
`#classifyCompactionError`(err) that returns the appropriate CompactionResult for
cancelled, AgentTurnError, BudgetPauseError, and the default rethrow path,
keeping the main compact() flow focused on the happy path.

Source: Linters/SAST tools

🤖 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 `@apps/cli/src/render/tui/session-view-model.ts`:
- Around line 290-294: The default handling in session-view-model’s event switch
is collapsing the automatic trim fallback into the manual /trim path, so the TUI
never shows a notice for that fallback. Update the session event handling around
the session:trimmed branch to distinguish the auto-compaction fallback from the
manual trim result, and surface a dedicated notice for the fallback case while
keeping the existing manual /trim behavior unchanged. Use the existing
session-view-model flow and any trim-related event symbols in that switch to
route the fallback separately instead of leaving it in the generic default
branch.

In `@docs/reference/cli/chat-session.md`:
- Around line 95-98: The resume behavior description in the chat-session docs is
too broad and incorrectly implies any newest marker overwrites the preamble,
including empty `/trim` markers. Update the wording around the durable boundary
to say that the newest marker with a summary, or the latest non-empty summary,
becomes the preamble, and keep the existing references to the `role: 'system'`
marker row and `chat-resume` behavior aligned with that rule.

In `@packages/core/src/engine/agent-session.ts`:
- Around line 926-963: Best-effort compaction is still using unguarded provider
seam calls in `#maybeAutoCompact`, so exceptions from
serving?.managesOwnContext?.() or serving?.contextLimit?.(model) can escape and
reject sendMessage() after the turn has already completed. Wrap those optional
provider calls in try/catch inside `#maybeAutoCompact`, treat any thrown error as
a non-fatal skip of auto-compaction, and ensure the method returns cleanly just
like the guarded `#estimateTokens` seam does.

---

Nitpick comments:
In `@apps/cli/src/chat/persister.ts`:
- Around line 76-116: The appendMarker() call sites in persister.ts currently
ignore its boolean result, which can hide a no-op boundary write when
keptMessageCount no longer matches realMessageSeqs. Update the session:compacted
and session:trimmed handling around appendMarker() to check the return value and
surface an unexpected false with a dev-only assertion or warning, so token
accounting and flush-to-active only proceed when the durable marker was actually
written.

In `@docs/decisions/0062-context-compaction-and-cli-history-commands.md`:
- Around line 134-149: The ADR section is duplicating the concrete `LlmProvider`
seam contract by restating `estimateTokens`, `contextLimit`, and
`managesOwnContext` signatures. Update the decision text to point to the
canonical `docs/reference/*` docs instead of embedding these method/schema
details here, and keep the discussion at a high level so the ADR does not drift
from the spec. Use the existing `LlmProvider` seam and the referenced canonical
docs as the anchors when revising the wording.

In `@packages/core/src/engine/agent-session.ts`:
- Around line 796-895: Reduce the cognitive complexity in compact() by moving
the error-classification logic out of the catch block into a private helper,
following the same pattern as `#settleTurnError`. Have compact() delegate caught
errors to a new helper such as `#classifyCompactionError`(err) that returns the
appropriate CompactionResult for cancelled, AgentTurnError, BudgetPauseError,
and the default rethrow path, keeping the main compact() flow focused on the
happy path.
🪄 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: 22067ce7-a288-4cb1-8735-4f3ad6900c57

📥 Commits

Reviewing files that changed from the base of the PR and between d84d6b8 and 8ada82a.

📒 Files selected for processing (56)
  • CLAUDE.md
  • apps/cli/src/chat/persister.test.ts
  • apps/cli/src/chat/persister.ts
  • apps/cli/src/chat/repl-info.test.ts
  • apps/cli/src/chat/repl-info.ts
  • apps/cli/src/chat/session-host.test.ts
  • apps/cli/src/chat/session-host.ts
  • apps/cli/src/commands/chat.test.ts
  • apps/cli/src/commands/chat.ts
  • apps/cli/src/commands/repl-commands.test.ts
  • apps/cli/src/commands/repl-commands.ts
  • apps/cli/src/config/resolve.test.ts
  • apps/cli/src/config/resolve.ts
  • apps/cli/src/engine/media-wiring.test.ts
  • apps/cli/src/render/tui/chat-ink.tsx
  • apps/cli/src/render/tui/home-app.tsx
  • apps/cli/src/render/tui/home-controller.test.ts
  • apps/cli/src/render/tui/home-controller.ts
  • apps/cli/src/render/tui/palette-reducer.test.ts
  • apps/cli/src/render/tui/session-view-model.test.ts
  • apps/cli/src/render/tui/session-view-model.ts
  • docs/decisions/0062-context-compaction-and-cli-history-commands.md
  • docs/decisions/README.md
  • docs/reference/cli/chat-session.md
  • docs/reference/contracts/agent-session-spec.md
  • docs/reference/contracts/config-spec.md
  • docs/reference/contracts/sse-event-schema.md
  • docs/reference/desktop/database-schema.md
  • docs/reference/shared-core/llm-provider-seam.md
  • docs/roadmap/current.md
  • docs/roadmap/phases/phase-2.5-cli-consolidation.md
  • packages/core/src/engine/agent-session.test.ts
  • packages/core/src/engine/agent-session.ts
  • packages/core/src/engine/session-handle.ts
  • packages/core/src/engine/session-resume.test.ts
  • packages/core/src/engine/session-resume.ts
  • packages/core/src/index.ts
  • packages/db/drizzle/0006_mean_adam_destine.sql
  • packages/db/drizzle/meta/0006_snapshot.json
  • packages/db/drizzle/meta/_journal.json
  • packages/db/src/schema.ts
  • packages/db/src/session-store.test.ts
  • packages/db/src/session-store.ts
  • packages/llm/src/adapters/anthropic.ts
  • packages/llm/src/adapters/gemini.ts
  • packages/llm/src/adapters/openai.ts
  • packages/llm/src/adapters/shared.test.ts
  • packages/llm/src/adapters/shared.ts
  • packages/llm/src/index.ts
  • packages/llm/src/types.ts
  • packages/shared/src/config.test.ts
  • packages/shared/src/config.ts
  • packages/shared/src/constants.ts
  • packages/shared/src/run-event.test.ts
  • packages/shared/src/run-event.ts
  • packages/shared/src/session.ts

Comment thread apps/cli/src/render/tui/session-view-model.ts
Comment thread docs/reference/cli/chat-session.md Outdated
Comment thread packages/core/src/engine/agent-session.ts
cemililik and others added 3 commits July 4, 2026 23:04
…bustness

Address the PR review + SonarCloud findings (verified against current code; invalid
ones skipped):

- The auto-compaction failure-degrade trim was SILENT on the interactive surface (it
  fell to the view-model's default branch). Add a `reason: 'manual' | 'auto-fallback'`
  to `session:trimmed` (symmetric with `session:compacted`); the engine tags the degrade
  trim `auto-fallback` and the view surfaces it as a notice, keeping manual `/trim`
  (self-noticed by the command) unchanged.
- Guard the optional provider seam calls in `#maybeAutoCompact` (managesOwnContext /
  contextLimit) in try/catch — a throwing provider method now SKIPS auto-compaction
  instead of rejecting sendMessage after the turn already completed (mirrors #estimateTokens).
- Extract `#classifyCompactionError` from compact()'s catch (reduces cognitive complexity
  below the 15 ceiling, mirroring #settleTurnError).
- splitFoldable + tailFromUserBoundary: O(N²)→O(N) via index scans (drop the in-loop
  slice/some), behavior-preserving.
- Docs: chat-session.md now says the preamble is the newest marker *with a summary* (not
  any marker); ADR-0062 §4 points to the canonical seam doc instead of restating the three
  method signatures; sse-event-schema.md carries the new `session:trimmed.reason`.
- Test: `.length).toBe()` → `.toHaveLength()` (SonarCloud).

Skipped (with reason): the persister `appendMarker` boolean (false is unreachable given
the engine/persister count invariant, and gating the summariser's REAL cost on it would
be incorrect); the `plan.entries[0]?.provider` optional chain (required by
noUncheckedIndexedAccess — direct access is a type error — and it short-circuits, no
throw); the chat-ink onSubmit sync-throw wrap (onSubmit is `processLine`, an async
function that cannot throw synchronously — the `.then(_,onError).catch` already clears
submitBusy on every real failure).

New tests: the auto-fallback trim view-notice (auto surfaces, manual silent) + the
`session:trimmed.reason` enum. Full toolchain + format:check + lint:bundle-closure green
(core 967, CLI 1295).

Refs: ADR-0062
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ext-fullness footer (ADR-0062)

Completes Phase 2.5.F (ADR-0062 §7): the fresh-session lifecycle command plus the two
compaction-moment UX polishes.

- /clear — host-level fresh-session swap across `relavium chat`, `chat-resume`, and the
  in-Home chat. Standalone re-drives via a new ChatDriver outcome ({kind:'exit'|'clear'});
  the Home uses a build-first `clearChat`. `buildChatSession` gains an `agent` override so
  the fresh session rebinds the same agent (chat-resume's snapshot agent, no on-disk ref).
  Registry entry (effect 'destructive', availableIn ['home','chat']); `clearedNotice`
  surfaces the sanitized old sessionId + `chat-resume`. TTY-interactive only — rejected
  under --json/plain so one machine stream stays one session lifecycle (ADR-0049).
- session:compacting — a new additive session event (amends ADR-0036) emitted at compact()
  start; drives a labeled "Summarizing…" moment; host-cleared when a manual /compact settles
  so the flag never latches into a later slash command.
- context-fullness footer (NN% ctx) via a pure @relavium/llm `contextWindowForModel` helper;
  skips a zero-input turn so a budget/abort never flashes 0% ctx.

Engine stays platform-free (/clear is host-only, session:compacting is a pure emit — no new
primitive); no vendor type crosses the @relavium/llm seam; no new dependency. Docs updated:
chat-session.md, commands.md, sse-event-schema.md, ADR-0036/0062 amendments (append-only),
phase-2.5 roadmap. Includes the Opus+Sonnet parallel-review fixes: compacting-latch clear,
0%-ctx guard, Home double-clear MCP-child guard, the ADR-0049 /clear-gate + teardownActive
both-live regression tests, and a pricing.test narrowing (no unsafe `as`).

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

Capture the Node.js upgrade findings without touching 2.5.F or any pin/config. As of
2026-07-05 the supported floor (Node 20.12) is on an EOL line that better-sqlite3 no longer
prebuilds, so a fresh CLI install on the declared floor already forces a C++ source build —
partially defeating ADR-0021. Raising the floor to >=22 restores prebuilt coverage and
unlocks ink 7 / node:sqlite / eslint 10 / vitest 5. Two decoupled changes are framed:
(A) dev/CI `.nvmrc` 22->24 (zero-risk, no ADR), and (B) a supported-floor bump (SemVer-major,
reopens ADR-0021 -> superseding ADR + tech-stack.md).

- docs/roadmap/phases/node-runtime-upgrade.md — the canonical analysis (current-version
  table, per-dependency verdicts, migration plans for A and B, verification, governance,
  open questions).
- docs/roadmap/deferred-tasks.md — a one-line pointer under "Decisions needed (maintainer
  call)" + the Last-updated stamp.

Analysis only — no code, pins, or config changed.

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

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
docs/reference/contracts/sse-event-schema.md (1)

263-272: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Event count is stale: union has 8 variants, text says "seven".

The SessionEvent union now lists 8 members (started, turn_started, turn_completed, cancelled, exported, compacting, compacted, trimmed), but line 278 still says "the seven session:* lifecycle/side events." Docs previously had 5, and 3 were added here — the narrative count should be updated to eight.

📝 Proposed fix
-So the **complete typed event stream for a session** is the seven `session:*` lifecycle/side events (the `SessionEvent` union above)
+So the **complete typed event stream for a session** is the eight `session:*` lifecycle/side events (the `SessionEvent` union above)

Also applies to: 278-278

🤖 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 `@docs/reference/contracts/sse-event-schema.md` around lines 263 - 272, The
SessionEvent union now has 8 variants, so update the surrounding prose in the
SessionEvent section to say eight rather than seven. Adjust the summary text
that describes the `session:*` lifecycle/side events to match the current union
members (`SessionStartedEvent`, `SessionTurnStartedEvent`,
`SessionTurnCompletedEvent`, `SessionCancelledEvent`, `SessionExportedEvent`,
`SessionCompactingEvent`, `SessionCompactedEvent`, `SessionTrimmedEvent`) and
keep the narrative consistent with the definitions in this block.
apps/cli/src/commands/chat.ts (1)

802-818: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Guard /compact like the other slash commands
built.session.compact('manual') can reject on unexpected compaction errors, and this path has no catch, so the rejection escapes handleSlashCommand instead of being surfaced as REPL output. Wrap it in try/catch/finally and clear store.clearCompacting() in finally so the spinner is reset even on failure.

🤖 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 `@apps/cli/src/commands/chat.ts` around lines 802 - 818, The `compactHistory`
slash-command handler currently awaits `built.session.compact('manual')` without
error handling, so unexpected rejections can escape instead of being shown in
the REPL. Update `compactHistory` in `handleSlashCommand` to use
`try/catch/finally`: keep the existing progress/output behavior, surface
failures via `emitOutput` in the catch path, and move `store.clearCompacting()`
into `finally` so the spinner state is always reset.

Source: Linters/SAST tools

🧹 Nitpick comments (2)
docs/roadmap/phases/node-runtime-upgrade.md (1)

3-9: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Blank line splits the blockquote (MD028).

Line 6 is blank inside the blockquote block (lines 3-9), which markdownlint flags and most renderers treat as two separate blockquotes with a visual gap rather than one continuous quote.

📝 Proposed fix
 > Status: **Analysis / deferred maintainer decision.** NOT scheduled into any phase; NOT part of
 > Phase 2.5.F. This note captures the findings so the decision isn't lost — acting on it is a
 > separate, governed change (see [§6 Governance](`#6-governance--what-acting-on-this-requires`)).
-
+>
 > Snapshot date: **2026-07-05.** Every version/EOL number below is a point-in-time fact from that
🤖 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 `@docs/roadmap/phases/node-runtime-upgrade.md` around lines 3 - 9, The
blockquote in node-runtime-upgrade.md is split by a blank line, triggering MD028
and rendering as separate quotes. Remove the empty line inside the quoted
“Status”/“Snapshot date” section so the entire note stays one continuous
blockquote, and keep the surrounding blockquote markers aligned with that
section.

Source: Linters/SAST tools

apps/cli/src/commands/chat.ts (1)

309-351: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate rebuild wiring between chatCommand and chatResumeCommand.

The rebuild closure (capturing boundAgent and calling buildFreshChatWiring with the same field set, including an identical onBudgetWarning callback) is copy-pasted verbatim between chatCommand (Lines 309-336) and chatResumeCommand (Lines 466-492). SonarCloud flags this exact duplication.

Consider extracting a shared factory, e.g. createClearRebuild(deps, boundAgent, opened, config, projectConfigDir, now, uuid, providers, mcpSecretResolver), called from both commands, so a future change to the rebuild contract can't silently diverge between the two entry points.

Also applies to: 466-510

🤖 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 `@apps/cli/src/commands/chat.ts` around lines 309 - 351, The rebuild wiring in
chatCommand and chatResumeCommand is duplicated, including the boundAgent
capture, buildFreshChatWiring call, and identical onBudgetWarning handling.
Extract that shared closure into a common helper/factory (for example, a
createClearRebuild-style function) and have both commands call it so the rebuild
contract stays in one place and cannot drift between chatCommand and
chatResumeCommand.

Source: Linters/SAST tools

🤖 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 `@apps/cli/src/commands/chat.test.ts`:
- Around line 516-519: The driveClear ChatDriver in chat.test.ts is marked async
without using await, which triggers the require-await lint error. Remove the
unnecessary async from driveClear and keep its behavior the same by returning
the clear result directly while still calling ctx.startSession() first. Use the
driveClear identifier and ChatDriver typing to locate and update this test
helper.

In `@apps/cli/src/commands/repl-commands.test.ts`:
- Around line 136-139: The REPL effect taxonomy test is missing `/trim` as a
write operation, so update the REPL command definition in REPL_COMMANDS_BY_NAME
(and any related command metadata in repl-commands.ts) to classify `/trim` as
write instead of read. Make sure the change is reflected consistently anywhere
command effects are asserted or derived, and keep the existing effects for
export, compact, and clear unchanged.

---

Outside diff comments:
In `@apps/cli/src/commands/chat.ts`:
- Around line 802-818: The `compactHistory` slash-command handler currently
awaits `built.session.compact('manual')` without error handling, so unexpected
rejections can escape instead of being shown in the REPL. Update
`compactHistory` in `handleSlashCommand` to use `try/catch/finally`: keep the
existing progress/output behavior, surface failures via `emitOutput` in the
catch path, and move `store.clearCompacting()` into `finally` so the spinner
state is always reset.

In `@docs/reference/contracts/sse-event-schema.md`:
- Around line 263-272: The SessionEvent union now has 8 variants, so update the
surrounding prose in the SessionEvent section to say eight rather than seven.
Adjust the summary text that describes the `session:*` lifecycle/side events to
match the current union members (`SessionStartedEvent`,
`SessionTurnStartedEvent`, `SessionTurnCompletedEvent`, `SessionCancelledEvent`,
`SessionExportedEvent`, `SessionCompactingEvent`, `SessionCompactedEvent`,
`SessionTrimmedEvent`) and keep the narrative consistent with the definitions in
this block.

---

Nitpick comments:
In `@apps/cli/src/commands/chat.ts`:
- Around line 309-351: The rebuild wiring in chatCommand and chatResumeCommand
is duplicated, including the boundAgent capture, buildFreshChatWiring call, and
identical onBudgetWarning handling. Extract that shared closure into a common
helper/factory (for example, a createClearRebuild-style function) and have both
commands call it so the rebuild contract stays in one place and cannot drift
between chatCommand and chatResumeCommand.

In `@docs/roadmap/phases/node-runtime-upgrade.md`:
- Around line 3-9: The blockquote in node-runtime-upgrade.md is split by a blank
line, triggering MD028 and rendering as separate quotes. Remove the empty line
inside the quoted “Status”/“Snapshot date” section so the entire note stays one
continuous blockquote, and keep the surrounding blockquote markers aligned with
that section.
🪄 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: 75b02b10-72bf-4eaf-a0f3-1519e3da9377

📥 Commits

Reviewing files that changed from the base of the PR and between 1598798 and 01d0724.

📒 Files selected for processing (33)
  • apps/cli/src/chat/repl-info.test.ts
  • apps/cli/src/chat/repl-info.ts
  • apps/cli/src/chat/session-host.ts
  • apps/cli/src/commands/chat.test.ts
  • apps/cli/src/commands/chat.ts
  • apps/cli/src/commands/repl-commands.test.ts
  • apps/cli/src/commands/repl-commands.ts
  • apps/cli/src/home/drive-home.tsx
  • apps/cli/src/render/tui/chat-ink.tsx
  • apps/cli/src/render/tui/chat-projection.test.ts
  • apps/cli/src/render/tui/chat-projection.ts
  • apps/cli/src/render/tui/chat-store.ts
  • apps/cli/src/render/tui/home-controller.test.ts
  • apps/cli/src/render/tui/home-controller.ts
  • apps/cli/src/render/tui/session-view-model.test.ts
  • apps/cli/src/render/tui/session-view-model.ts
  • docs/decisions/0036-run-loop-substrate-event-bus-and-execution-host.md
  • docs/decisions/0062-context-compaction-and-cli-history-commands.md
  • docs/reference/cli/chat-session.md
  • docs/reference/cli/commands.md
  • docs/reference/contracts/sse-event-schema.md
  • docs/roadmap/deferred-tasks.md
  • docs/roadmap/phases/node-runtime-upgrade.md
  • docs/roadmap/phases/phase-2.5-cli-consolidation.md
  • packages/core/src/engine/agent-session.test.ts
  • packages/core/src/engine/agent-session.ts
  • packages/core/src/engine/session-handle.ts
  • packages/llm/src/index.ts
  • packages/llm/src/pricing.test.ts
  • packages/llm/src/pricing.ts
  • packages/shared/src/constants.ts
  • packages/shared/src/run-event.test.ts
  • packages/shared/src/run-event.ts
✅ Files skipped from review due to trivial changes (7)
  • docs/decisions/0036-run-loop-substrate-event-bus-and-execution-host.md
  • packages/core/src/engine/session-handle.ts
  • docs/reference/cli/commands.md
  • docs/roadmap/deferred-tasks.md
  • apps/cli/src/render/tui/chat-projection.test.ts
  • docs/reference/cli/chat-session.md
  • docs/decisions/0062-context-compaction-and-cli-history-commands.md
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/llm/src/index.ts
  • apps/cli/src/chat/repl-info.ts
  • packages/shared/src/run-event.test.ts
  • docs/roadmap/phases/phase-2.5-cli-consolidation.md
  • packages/core/src/engine/agent-session.test.ts
  • packages/core/src/engine/agent-session.ts

Comment thread apps/cli/src/commands/chat.test.ts Outdated
Comment on lines +136 to +139
it('effects are sound: export + compact write, clear destructive, the rest read', () => {
expect(REPL_COMMANDS_BY_NAME.get('export')?.effect).toBe('write');
for (const name of ['help', 'exit', 'cancel', 'workflows', 'cost', 'doctor', 'mode']) {
expect(REPL_COMMANDS_BY_NAME.get('compact')?.effect).toBe('write'); // /compact spends tokens (ADR-0062)
expect(REPL_COMMANDS_BY_NAME.get('clear')?.effect).toBe('destructive'); // /clear ends the session (ADR-0062 §7)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect how `effect` is defined for trim/compact/clear in the REPL command registry.
rg -n "effect" apps/cli/src/commands/repl-commands.ts -A2 -B2

Repository: HodeTech/Relavium

Length of output: 3933


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repl-commands.test.ts (relevant section) =="
sed -n '120,155p' apps/cli/src/commands/repl-commands.test.ts

echo
echo "== effect consumers =="
rg -n "\.effect\b|effect:" apps/cli/src -g '*.ts' -g '*.tsx' -A2 -B2

echo
echo "== repl-commands.ts trim/compact/clear section =="
sed -n '175,210p' apps/cli/src/commands/repl-commands.ts

Repository: HodeTech/Relavium

Length of output: 16980


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== CommandEffect definition and consumers =="
rg -n "CommandEffect|\.effect\b" apps/cli/src -g '*.ts' -g '*.tsx' -A3 -B3

echo
echo "== repl-commands.test.ts around the effect assertions =="
sed -n '130,150p' apps/cli/src/commands/repl-commands.test.ts

Repository: HodeTech/Relavium

Length of output: 6197


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== CommandEffect docs and REPL command interface context =="
sed -n '1,90p' apps/cli/src/commands/manifest.ts
echo
sed -n '1,120p' apps/cli/src/commands/repl-commands.ts
echo
echo "== other mentions of trimHistory =="
rg -n "trimHistory|compactHistory|clearSession" apps/cli/src -g '*.ts' -g '*.tsx' -A2 -B2

Repository: HodeTech/Relavium

Length of output: 23267


Mark /trim as write in apps/cli/src/commands/repl-commands.ts:192 — it drops older messages from session history, so read doesn’t match the effect taxonomy.

🤖 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 `@apps/cli/src/commands/repl-commands.test.ts` around lines 136 - 139, The REPL
effect taxonomy test is missing `/trim` as a write operation, so update the REPL
command definition in REPL_COMMANDS_BY_NAME (and any related command metadata in
repl-commands.ts) to classify `/trim` as write instead of read. Make sure the
change is reflected consistently anywhere command effects are asserted or
derived, and keep the existing effects for export, compact, and clear unchanged.

…ompact error handling, rebuild dedup)

Fixes the two CI failures plus the PR-review findings on the 2.5.F diff.

CI failures:
- lint: the `driveClear` test helper was `async` with no `await` (require-await) — make it a
  plain arrow returning `Promise.resolve({ kind: 'clear' as const })`.
- test: the `replCommandList` slash-hint expected string was missing `/clear`.

Review findings:
- chat.ts `compactHistory`: wrap `compact('manual')` in try/catch/finally so an unclassified
  re-throw is surfaced (never crashes the REPL) and `store.clearCompacting()` always runs (the
  spinner moment is reset on every path — success, classified failure, or an unclassified throw).
- chat.ts: extract the duplicated `chat` + `chat-resume` `/clear` rebuild closures into a shared
  `createClearRebuild` factory so the rebuild contract lives in one place and cannot drift.
- chat-ink.tsx: extract the nested busy-line ternary into a `renderBusyLine` helper.
- sse-event-schema.md: "seven" -> "eight" session:* events (session:compacting was added).
- node-runtime-upgrade.md: merge the split Status/Snapshot blockquote into one (MD028).

Skipped (with reason): reclassifying the `/trim` effect read->write — ADR-0062 §7 explicitly
defines `/trim` as `read` (deterministic, no cost); changing it would contradict the accepted
ADR and belongs in a superseding ADR, not a silent code tweak.

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

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/reference/contracts/sse-event-schema.md (1)

274-274: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Avoid calling compaction result events “terminal”.

Line 274 first says these events are never stream terminals, then calls session:compacted / session:trimmed terminal. Use “completion event” or “end state” to avoid implementers closing SessionHandle.events.

Suggested wording
- and the moment ends on the terminal `session:compacted` (success) / `session:trimmed` `auto-fallback`
+ and the moment ends on the completion event `session:compacted` (success) / `session:trimmed` `auto-fallback`
🤖 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 `@docs/reference/contracts/sse-event-schema.md` at line 274, Update the wording
in the SSE event schema around session:compacting, session:compacted, and
session:trimmed so it does not describe the latter two as “terminal” events;
keep the distinction that they are side events and use “completion event” or
“end state” instead. Adjust the descriptive text in the SSE schema doc to
preserve the lifecycle behavior for the SessionHandle.events stream while
avoiding language that suggests closing the stream on session:compacted or
session:trimmed.
🤖 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.

Outside diff comments:
In `@docs/reference/contracts/sse-event-schema.md`:
- Line 274: Update the wording in the SSE event schema around
session:compacting, session:compacted, and session:trimmed so it does not
describe the latter two as “terminal” events; keep the distinction that they are
side events and use “completion event” or “end state” instead. Adjust the
descriptive text in the SSE schema doc to preserve the lifecycle behavior for
the SessionHandle.events stream while avoiding language that suggests closing
the stream on session:compacted or session:trimmed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0226adaa-ce15-4495-bed8-f74ecb9a6019

📥 Commits

Reviewing files that changed from the base of the PR and between 01d0724 and 645fb5f.

📒 Files selected for processing (6)
  • apps/cli/src/commands/chat.test.ts
  • apps/cli/src/commands/chat.ts
  • apps/cli/src/commands/repl-commands.test.ts
  • apps/cli/src/render/tui/chat-ink.tsx
  • docs/reference/contracts/sse-event-schema.md
  • docs/roadmap/phases/node-runtime-upgrade.md
✅ Files skipped from review due to trivial changes (1)
  • docs/roadmap/phases/node-runtime-upgrade.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/cli/src/commands/chat.test.ts
  • apps/cli/src/commands/repl-commands.test.ts

…intWidth 100)

The clearedNotice-sanitize call and the resume /clear expect(...).toBe(...) exceeded printWidth 100;
prettier wraps the call argument / the .toBe() argument respectively. Formatting-only, no behavior change.
Verified locally: prettier --check ., and pnpm turbo run lint typecheck test (23 tasks green, 3415 tests).

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

Copy link
Copy Markdown
Contributor Author

@coderabbitai review all

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

@cemililik Sure, I'll re-review the entire PR now.

✅ Action performed

Full review finished.

@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: 3

🧹 Nitpick comments (1)
packages/llm/src/adapters/gemini.ts (1)

1033-1043: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider a shared seam-defaults object to avoid triplicated boilerplate.

This exact contextLimit/managesOwnContext/estimateTokens block is duplicated verbatim across the Anthropic, OpenAI/DeepSeek, and Gemini adapters. Extracting a shared default into shared.ts and spreading it in each adapter would remove the duplication and prevent future drift (e.g. one adapter forgetting to update managesOwnContext when it eventually specializes it).

♻️ Proposed shared defaults
+// packages/llm/src/adapters/shared.ts
+export const compactionSeamDefaults = {
+  managesOwnContext(): boolean {
+    return false;
+  },
+  contextLimit: contextLimitFor,
+  estimateTokens: estimateRequestTokens,
+};
   async pollMediaJob(...): Promise<MediaJobStatus> {
       return geminiPollVideo(transport, jobId, key, signal);
     },
-    contextLimit(model: string): number | undefined {
-      return contextLimitFor(model);
-    },
-    managesOwnContext(): boolean {
-      return false;
-    },
-    estimateTokens(input: EstimateTokensInput): number {
-      return estimateRequestTokens(input);
-    },
+    ...compactionSeamDefaults,
   };
🤖 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/llm/src/adapters/gemini.ts` around lines 1033 - 1043, Extract the
duplicated adapter fallback methods into a shared seam-defaults object in
shared.ts and reuse it from Gemini, Anthropic, and OpenAI/DeepSeek instead of
repeating the same contextLimit, managesOwnContext, and estimateTokens
implementations. Keep the adapter-specific wrappers minimal by spreading the
shared defaults into each adapter object, and preserve any existing
model-specific overrides by layering them on top of the shared object.
🤖 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/core/src/engine/agent-session.ts`:
- Around line 562-568: The post-turn auto-compaction path in
`AgentSession.sendMessage` is still unguarded, so a failure from
`#maybeAutoCompact`/`compact('auto-threshold')` can reject an otherwise
successful `sendMessage()` after `session:turn_completed` has already been
emitted. Wrap the `pending` handling in `sendMessage` so the auto-compaction
call is best-effort and never throws outward, matching the surrounding
`#estimateTokens` and `managesOwnContext`/`contextLimit` guard behavior; if
needed, catch and swallow/log any compaction error locally while preserving the
completed turn result.

In `@packages/db/drizzle/0006_mean_adam_destine.sql`:
- Line 1: The committed Drizzle migration SQL is out of sync with the schema, so
regenerate the database artifacts instead of editing only the SQL file. Re-run
the db generation flow for the `@relavium/db` package using the schema in
packages/db/src/schema.ts, then commit the updated migration output and any
related generated artifacts so the migration matches the current schema state.

In `@packages/db/drizzle/meta/0006_snapshot.json`:
- Around line 1-2062: The committed Drizzle snapshot is out of sync with the
current schema, so regenerate the migration artifacts instead of editing this
snapshot manually. Run the database generation flow for `@relavium/db`, then
commit the updated snapshot and paired migration SQL so `schema.ts` and the
migration history match. Use the existing Drizzle migration snapshot files as
the source of truth for this fix.

---

Nitpick comments:
In `@packages/llm/src/adapters/gemini.ts`:
- Around line 1033-1043: Extract the duplicated adapter fallback methods into a
shared seam-defaults object in shared.ts and reuse it from Gemini, Anthropic,
and OpenAI/DeepSeek instead of repeating the same contextLimit,
managesOwnContext, and estimateTokens implementations. Keep the adapter-specific
wrappers minimal by spreading the shared defaults into each adapter object, and
preserve any existing model-specific overrides by layering them on top of the
shared object.
🪄 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: d3dbdfd3-fbb4-4429-add3-4066b836ccf9

📥 Commits

Reviewing files that changed from the base of the PR and between d84d6b8 and 06196d4.

📒 Files selected for processing (66)
  • CLAUDE.md
  • apps/cli/src/chat/persister.test.ts
  • apps/cli/src/chat/persister.ts
  • apps/cli/src/chat/repl-info.test.ts
  • apps/cli/src/chat/repl-info.ts
  • apps/cli/src/chat/session-host.test.ts
  • apps/cli/src/chat/session-host.ts
  • apps/cli/src/commands/chat.test.ts
  • apps/cli/src/commands/chat.ts
  • apps/cli/src/commands/repl-commands.test.ts
  • apps/cli/src/commands/repl-commands.ts
  • apps/cli/src/config/resolve.test.ts
  • apps/cli/src/config/resolve.ts
  • apps/cli/src/engine/media-wiring.test.ts
  • apps/cli/src/home/drive-home.tsx
  • apps/cli/src/render/tui/chat-ink.tsx
  • apps/cli/src/render/tui/chat-projection.test.ts
  • apps/cli/src/render/tui/chat-projection.ts
  • apps/cli/src/render/tui/chat-store.ts
  • apps/cli/src/render/tui/home-app.tsx
  • apps/cli/src/render/tui/home-controller.test.ts
  • apps/cli/src/render/tui/home-controller.ts
  • apps/cli/src/render/tui/palette-reducer.test.ts
  • apps/cli/src/render/tui/session-view-model.test.ts
  • apps/cli/src/render/tui/session-view-model.ts
  • docs/decisions/0036-run-loop-substrate-event-bus-and-execution-host.md
  • docs/decisions/0062-context-compaction-and-cli-history-commands.md
  • docs/decisions/README.md
  • docs/reference/cli/chat-session.md
  • docs/reference/cli/commands.md
  • docs/reference/contracts/agent-session-spec.md
  • docs/reference/contracts/config-spec.md
  • docs/reference/contracts/sse-event-schema.md
  • docs/reference/desktop/database-schema.md
  • docs/reference/shared-core/llm-provider-seam.md
  • docs/roadmap/current.md
  • docs/roadmap/deferred-tasks.md
  • docs/roadmap/phases/node-runtime-upgrade.md
  • docs/roadmap/phases/phase-2.5-cli-consolidation.md
  • packages/core/src/engine/agent-session.test.ts
  • packages/core/src/engine/agent-session.ts
  • packages/core/src/engine/session-handle.ts
  • packages/core/src/engine/session-resume.test.ts
  • packages/core/src/engine/session-resume.ts
  • packages/core/src/index.ts
  • packages/db/drizzle/0006_mean_adam_destine.sql
  • packages/db/drizzle/meta/0006_snapshot.json
  • packages/db/drizzle/meta/_journal.json
  • packages/db/src/schema.ts
  • packages/db/src/session-store.test.ts
  • packages/db/src/session-store.ts
  • packages/llm/src/adapters/anthropic.ts
  • packages/llm/src/adapters/gemini.ts
  • packages/llm/src/adapters/openai.ts
  • packages/llm/src/adapters/shared.test.ts
  • packages/llm/src/adapters/shared.ts
  • packages/llm/src/index.ts
  • packages/llm/src/pricing.test.ts
  • packages/llm/src/pricing.ts
  • packages/llm/src/types.ts
  • packages/shared/src/config.test.ts
  • packages/shared/src/config.ts
  • packages/shared/src/constants.ts
  • packages/shared/src/run-event.test.ts
  • packages/shared/src/run-event.ts
  • packages/shared/src/session.ts

Comment thread packages/core/src/engine/agent-session.ts
@@ -0,0 +1 @@
ALTER TABLE `session_messages` ADD `compaction_dropped_through_sequence` integer; No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Regenerate the DB migration artifacts.

CI already reports this migration is out of sync with packages/db/src/schema.ts, so the committed SQL will keep failing the db generation check until you rerun pnpm --filter @relavium/db db:generate.

🧰 Tools
🪛 GitHub Actions: CI / 2_lint · typecheck · test.txt

[error] 1-1: Migration out of sync. The committed @relavium/db migration is out of sync with src/schema.ts (or an upstream @relavium/shared enum the CHECKs derive from). Regenerate and commit: pnpm --filter @relavium/db db:generate

🪛 GitHub Actions: CI / lint · typecheck · test

[error] 1-1: db generation check failed: The committed @relavium/db migration is out of sync with src/schema.ts (or an upstream @relavium/shared enum the CHECKs derive from). Regenerate and commit with: pnpm --filter @relavium/db db:generate

🤖 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/drizzle/0006_mean_adam_destine.sql` at line 1, The committed
Drizzle migration SQL is out of sync with the schema, so regenerate the database
artifacts instead of editing only the SQL file. Re-run the db generation flow
for the `@relavium/db` package using the schema in packages/db/src/schema.ts, then
commit the updated migration output and any related generated artifacts so the
migration matches the current schema state.

Source: Pipeline failures

Comment on lines +1 to +2062
{
"version": "6",
"dialect": "sqlite",
"id": "213ad2ee-ef8d-4342-ba04-661ac99cb116",
"prevId": "8a2df4cb-e109-46f9-8b5f-398998fc272f",
"tables": {
"agent_sessions": {
"name": "agent_sessions",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"agent_id": {
"name": "agent_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"agent_slug": {
"name": "agent_slug",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"agent_snapshot": {
"name": "agent_snapshot",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"model_id": {
"name": "model_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"working_dir": {
"name": "working_dir",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"git_ref": {
"name": "git_ref",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"fs_scope_tier": {
"name": "fs_scope_tier",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'sandboxed'"
},
"status": {
"name": "status",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'active'"
},
"context_json": {
"name": "context_json",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'{}'"
},
"total_input_tokens": {
"name": "total_input_tokens",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
},
"total_output_tokens": {
"name": "total_output_tokens",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
},
"total_cost_microcents": {
"name": "total_cost_microcents",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
},
"exported_workflow_path": {
"name": "exported_workflow_path",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"deleted_at": {
"name": "deleted_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"idx_agent_sessions_status": {
"name": "idx_agent_sessions_status",
"columns": [
"status",
"\"updated_at\" desc"
],
"isUnique": false,
"where": "\"agent_sessions\".\"deleted_at\" is null"
},
"idx_agent_sessions_agent": {
"name": "idx_agent_sessions_agent",
"columns": [
"agent_id",
"\"created_at\" desc"
],
"isUnique": false,
"where": "\"agent_sessions\".\"agent_id\" is not null"
},
"idx_agent_sessions_updated": {
"name": "idx_agent_sessions_updated",
"columns": [
"\"updated_at\" desc",
"\"id\" desc"
],
"isUnique": false,
"where": "\"agent_sessions\".\"deleted_at\" is null"
}
},
"foreignKeys": {
"agent_sessions_agent_id_agents_id_fk": {
"name": "agent_sessions_agent_id_agents_id_fk",
"tableFrom": "agent_sessions",
"tableTo": "agents",
"columnsFrom": [
"agent_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"agent_sessions_model_id_model_catalog_id_fk": {
"name": "agent_sessions_model_id_model_catalog_id_fk",
"tableFrom": "agent_sessions",
"tableTo": "model_catalog",
"columnsFrom": [
"model_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {
"agent_sessions_fs_scope_tier_check": {
"name": "agent_sessions_fs_scope_tier_check",
"value": "\"agent_sessions\".\"fs_scope_tier\" in ('sandboxed', 'project', 'full')"
},
"agent_sessions_status_check": {
"name": "agent_sessions_status_check",
"value": "\"agent_sessions\".\"status\" in ('active', 'idle', 'exported', 'ended')"
}
}
},
"agents": {
"name": "agents",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"slug": {
"name": "slug",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"model_id": {
"name": "model_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"system_prompt": {
"name": "system_prompt",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "''"
},
"tools": {
"name": "tools",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'[]'"
},
"config": {
"name": "config",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'{}'"
},
"input_schema": {
"name": "input_schema",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"output_schema": {
"name": "output_schema",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"tags": {
"name": "tags",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'[]'"
},
"source_path": {
"name": "source_path",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"is_active": {
"name": "is_active",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "1"
},
"deleted_at": {
"name": "deleted_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"idx_agents_slug": {
"name": "idx_agents_slug",
"columns": [
"slug"
],
"isUnique": true,
"where": "\"agents\".\"deleted_at\" is null"
},
"idx_agents_model": {
"name": "idx_agents_model",
"columns": [
"model_id"
],
"isUnique": false
},
"idx_agents_active": {
"name": "idx_agents_active",
"columns": [
"is_active",
"\"created_at\" desc"
],
"isUnique": false,
"where": "\"agents\".\"deleted_at\" is null"
}
},
"foreignKeys": {
"agents_model_id_model_catalog_id_fk": {
"name": "agents_model_id_model_catalog_id_fk",
"tableFrom": "agents",
"tableTo": "model_catalog",
"columnsFrom": [
"model_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"llm_providers": {
"name": "llm_providers",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"display_name": {
"name": "display_name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"base_url": {
"name": "base_url",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"api_key_keychain_ref": {
"name": "api_key_keychain_ref",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"default_headers": {
"name": "default_headers",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'{}'"
},
"is_active": {
"name": "is_active",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "1"
},
"deleted_at": {
"name": "deleted_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"idx_llm_providers_name": {
"name": "idx_llm_providers_name",
"columns": [
"name"
],
"isUnique": true,
"where": "\"llm_providers\".\"deleted_at\" is null"
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"media_objects": {
"name": "media_objects",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"handle": {
"name": "handle",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"mime_type": {
"name": "mime_type",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"modality": {
"name": "modality",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"byte_length": {
"name": "byte_length",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"duration_ms": {
"name": "duration_ms",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"last_referenced_at": {
"name": "last_referenced_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"deleted_at": {
"name": "deleted_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"media_objects_handle_unique": {
"name": "media_objects_handle_unique",
"columns": [
"handle"
],
"isUnique": true
},
"idx_media_objects_gc": {
"name": "idx_media_objects_gc",
"columns": [
"last_referenced_at"
],
"isUnique": false,
"where": "\"media_objects\".\"deleted_at\" is null"
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {
"media_objects_modality_check": {
"name": "media_objects_modality_check",
"value": "\"media_objects\".\"modality\" in ('image', 'audio', 'video', 'document')"
}
}
},
"media_references": {
"name": "media_references",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"handle": {
"name": "handle",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"scope_kind": {
"name": "scope_kind",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"scope_id": {
"name": "scope_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"idx_media_references_unique": {
"name": "idx_media_references_unique",
"columns": [
"handle",
"scope_kind",
"scope_id"
],
"isUnique": true
},
"idx_media_references_scope": {
"name": "idx_media_references_scope",
"columns": [
"scope_kind",
"scope_id"
],
"isUnique": false
},
"idx_media_references_handle": {
"name": "idx_media_references_handle",
"columns": [
"handle"
],
"isUnique": false
}
},
"foreignKeys": {
"media_references_handle_media_objects_handle_fk": {
"name": "media_references_handle_media_objects_handle_fk",
"tableFrom": "media_references",
"tableTo": "media_objects",
"columnsFrom": [
"handle"
],
"columnsTo": [
"handle"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {
"media_references_scope_kind_check": {
"name": "media_references_scope_kind_check",
"value": "\"media_references\".\"scope_kind\" in ('run', 'node', 'session', 'workspace')"
}
}
},
"messages": {
"name": "messages",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"step_execution_id": {
"name": "step_execution_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"run_id": {
"name": "run_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"sequence_number": {
"name": "sequence_number",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"role": {
"name": "role",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"content": {
"name": "content",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"content_parts": {
"name": "content_parts",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"tool_calls": {
"name": "tool_calls",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"tool_call_id": {
"name": "tool_call_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"finish_reason": {
"name": "finish_reason",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"idx_messages_step": {
"name": "idx_messages_step",
"columns": [
"step_execution_id",
"sequence_number"
],
"isUnique": false
},
"idx_messages_run": {
"name": "idx_messages_run",
"columns": [
"run_id",
"created_at"
],
"isUnique": false
}
},
"foreignKeys": {
"messages_step_execution_id_step_executions_id_fk": {
"name": "messages_step_execution_id_step_executions_id_fk",
"tableFrom": "messages",
"tableTo": "step_executions",
"columnsFrom": [
"step_execution_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"model_catalog": {
"name": "model_catalog",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"provider_id": {
"name": "provider_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"model_id": {
"name": "model_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"display_name": {
"name": "display_name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"context_window_tokens": {
"name": "context_window_tokens",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"max_output_tokens": {
"name": "max_output_tokens",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"input_cost_per_mtok_microcents": {
"name": "input_cost_per_mtok_microcents",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
},
"output_cost_per_mtok_microcents": {
"name": "output_cost_per_mtok_microcents",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
},
"cached_input_cost_per_mtok_microcents": {
"name": "cached_input_cost_per_mtok_microcents",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
},
"media_image_cost_microcents": {
"name": "media_image_cost_microcents",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"media_audio_cost_microcents": {
"name": "media_audio_cost_microcents",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"media_video_cost_microcents": {
"name": "media_video_cost_microcents",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"media_surface": {
"name": "media_surface",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'chat'"
},
"supports_tool_calling": {
"name": "supports_tool_calling",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "0"
},
"supports_vision": {
"name": "supports_vision",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "0"
},
"supports_streaming": {
"name": "supports_streaming",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "1"
},
"supports_json_mode": {
"name": "supports_json_mode",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "0"
},
"capabilities": {
"name": "capabilities",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'{}'"
},
"deprecation_date": {
"name": "deprecation_date",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"is_active": {
"name": "is_active",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "1"
},
"deleted_at": {
"name": "deleted_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"idx_model_catalog_provider_model": {
"name": "idx_model_catalog_provider_model",
"columns": [
"provider_id",
"model_id"
],
"isUnique": true,
"where": "\"model_catalog\".\"deleted_at\" is null"
},
"idx_model_catalog_provider": {
"name": "idx_model_catalog_provider",
"columns": [
"provider_id"
],
"isUnique": false
},
"idx_model_catalog_active": {
"name": "idx_model_catalog_active",
"columns": [
"is_active"
],
"isUnique": false,
"where": "\"model_catalog\".\"deleted_at\" is null"
}
},
"foreignKeys": {
"model_catalog_provider_id_llm_providers_id_fk": {
"name": "model_catalog_provider_id_llm_providers_id_fk",
"tableFrom": "model_catalog",
"tableTo": "llm_providers",
"columnsFrom": [
"provider_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"run_costs": {
"name": "run_costs",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"run_id": {
"name": "run_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"node_id": {
"name": "node_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"model_id": {
"name": "model_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"input_tokens": {
"name": "input_tokens",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
},
"output_tokens": {
"name": "output_tokens",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
},
"cost_microcents": {
"name": "cost_microcents",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"idx_run_costs_run": {
"name": "idx_run_costs_run",
"columns": [
"run_id"
],
"isUnique": false
}
},
"foreignKeys": {
"run_costs_run_id_runs_id_fk": {
"name": "run_costs_run_id_runs_id_fk",
"tableFrom": "run_costs",
"tableTo": "runs",
"columnsFrom": [
"run_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"run_costs_model_id_model_catalog_id_fk": {
"name": "run_costs_model_id_model_catalog_id_fk",
"tableFrom": "run_costs",
"tableTo": "model_catalog",
"columnsFrom": [
"model_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"run_events": {
"name": "run_events",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"run_id": {
"name": "run_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"step_execution_id": {
"name": "step_execution_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"seq": {
"name": "seq",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"event_type": {
"name": "event_type",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"level": {
"name": "level",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'info'"
},
"node_id": {
"name": "node_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"payload_json": {
"name": "payload_json",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'{}'"
},
"ts": {
"name": "ts",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"idx_run_events_run_seq": {
"name": "idx_run_events_run_seq",
"columns": [
"run_id",
"seq"
],
"isUnique": true
},
"idx_run_events_step": {
"name": "idx_run_events_step",
"columns": [
"step_execution_id",
"ts"
],
"isUnique": false,
"where": "\"run_events\".\"step_execution_id\" is not null"
},
"idx_run_events_run_type": {
"name": "idx_run_events_run_type",
"columns": [
"run_id",
"event_type",
"ts"
],
"isUnique": false
}
},
"foreignKeys": {
"run_events_run_id_runs_id_fk": {
"name": "run_events_run_id_runs_id_fk",
"tableFrom": "run_events",
"tableTo": "runs",
"columnsFrom": [
"run_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"runs": {
"name": "runs",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"workflow_id": {
"name": "workflow_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"workflow_path": {
"name": "workflow_path",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"project_root": {
"name": "project_root",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"workflow_definition_snapshot": {
"name": "workflow_definition_snapshot",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"status": {
"name": "status",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'pending'"
},
"execution_mode": {
"name": "execution_mode",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'local'"
},
"trigger_type": {
"name": "trigger_type",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'manual'"
},
"trigger_metadata": {
"name": "trigger_metadata",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'{}'"
},
"input_json": {
"name": "input_json",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'{}'"
},
"output_json": {
"name": "output_json",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"error_json": {
"name": "error_json",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"started_at": {
"name": "started_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"completed_at": {
"name": "completed_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"total_input_tokens": {
"name": "total_input_tokens",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
},
"total_output_tokens": {
"name": "total_output_tokens",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
},
"total_cost_microcents": {
"name": "total_cost_microcents",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
},
"deleted_at": {
"name": "deleted_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"idx_runs_workflow": {
"name": "idx_runs_workflow",
"columns": [
"workflow_id",
"\"created_at\" desc"
],
"isUnique": false
},
"idx_runs_status": {
"name": "idx_runs_status",
"columns": [
"status",
"\"created_at\" desc"
],
"isUnique": false,
"where": "\"runs\".\"deleted_at\" is null"
},
"idx_runs_cost": {
"name": "idx_runs_cost",
"columns": [
"workflow_id",
"created_at",
"total_cost_microcents"
],
"isUnique": false,
"where": "\"runs\".\"deleted_at\" is null"
},
"idx_runs_created": {
"name": "idx_runs_created",
"columns": [
"\"created_at\" desc",
"\"id\" desc"
],
"isUnique": false,
"where": "\"runs\".\"deleted_at\" is null"
}
},
"foreignKeys": {
"runs_workflow_id_workflows_id_fk": {
"name": "runs_workflow_id_workflows_id_fk",
"tableFrom": "runs",
"tableTo": "workflows",
"columnsFrom": [
"workflow_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {
"runs_status_check": {
"name": "runs_status_check",
"value": "\"runs\".\"status\" in ('pending', 'running', 'paused', 'completed', 'failed', 'cancelled')"
},
"runs_execution_mode_check": {
"name": "runs_execution_mode_check",
"value": "\"runs\".\"execution_mode\" in ('local', 'cloud', 'managed')"
}
}
},
"session_messages": {
"name": "session_messages",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"session_id": {
"name": "session_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"sequence_number": {
"name": "sequence_number",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"role": {
"name": "role",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"content": {
"name": "content",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"content_parts": {
"name": "content_parts",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"tool_calls": {
"name": "tool_calls",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"tool_call_id": {
"name": "tool_call_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"finish_reason": {
"name": "finish_reason",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"model_id": {
"name": "model_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"input_tokens": {
"name": "input_tokens",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
},
"output_tokens": {
"name": "output_tokens",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
},
"cost_microcents": {
"name": "cost_microcents",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
},
"compaction_dropped_through_sequence": {
"name": "compaction_dropped_through_sequence",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"idx_session_messages_seq": {
"name": "idx_session_messages_seq",
"columns": [
"session_id",
"sequence_number"
],
"isUnique": true
},
"idx_session_messages_session": {
"name": "idx_session_messages_session",
"columns": [
"session_id",
"created_at"
],
"isUnique": false
}
},
"foreignKeys": {
"session_messages_session_id_agent_sessions_id_fk": {
"name": "session_messages_session_id_agent_sessions_id_fk",
"tableFrom": "session_messages",
"tableTo": "agent_sessions",
"columnsFrom": [
"session_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"session_messages_model_id_model_catalog_id_fk": {
"name": "session_messages_model_id_model_catalog_id_fk",
"tableFrom": "session_messages",
"tableTo": "model_catalog",
"columnsFrom": [
"model_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"step_executions": {
"name": "step_executions",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"run_id": {
"name": "run_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"node_id": {
"name": "node_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"node_type": {
"name": "node_type",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"agent_id": {
"name": "agent_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"agent_snapshot": {
"name": "agent_snapshot",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"model_id": {
"name": "model_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"attempt_number": {
"name": "attempt_number",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 1
},
"status": {
"name": "status",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'pending'"
},
"input_json": {
"name": "input_json",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'{}'"
},
"output_json": {
"name": "output_json",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"error_json": {
"name": "error_json",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"started_at": {
"name": "started_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"completed_at": {
"name": "completed_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"duration_ms": {
"name": "duration_ms",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"input_tokens": {
"name": "input_tokens",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
},
"output_tokens": {
"name": "output_tokens",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
},
"cached_tokens": {
"name": "cached_tokens",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
},
"cost_microcents": {
"name": "cost_microcents",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"idx_step_exec_run": {
"name": "idx_step_exec_run",
"columns": [
"run_id",
"created_at"
],
"isUnique": false
},
"idx_step_exec_run_node": {
"name": "idx_step_exec_run_node",
"columns": [
"run_id",
"node_id",
"attempt_number"
],
"isUnique": false
},
"idx_step_exec_agent": {
"name": "idx_step_exec_agent",
"columns": [
"agent_id",
"\"created_at\" desc"
],
"isUnique": false,
"where": "\"step_executions\".\"agent_id\" is not null"
},
"idx_step_exec_model": {
"name": "idx_step_exec_model",
"columns": [
"model_id",
"\"created_at\" desc"
],
"isUnique": false,
"where": "\"step_executions\".\"model_id\" is not null"
},
"idx_step_exec_cost": {
"name": "idx_step_exec_cost",
"columns": [
"model_id",
"created_at",
"cost_microcents"
],
"isUnique": false,
"where": "\"step_executions\".\"model_id\" is not null"
}
},
"foreignKeys": {
"step_executions_run_id_runs_id_fk": {
"name": "step_executions_run_id_runs_id_fk",
"tableFrom": "step_executions",
"tableTo": "runs",
"columnsFrom": [
"run_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"step_executions_agent_id_agents_id_fk": {
"name": "step_executions_agent_id_agents_id_fk",
"tableFrom": "step_executions",
"tableTo": "agents",
"columnsFrom": [
"agent_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"step_executions_model_id_model_catalog_id_fk": {
"name": "step_executions_model_id_model_catalog_id_fk",
"tableFrom": "step_executions",
"tableTo": "model_catalog",
"columnsFrom": [
"model_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {
"step_executions_status_check": {
"name": "step_executions_status_check",
"value": "\"step_executions\".\"status\" in ('pending', 'running', 'completed', 'failed', 'skipped')"
}
}
},
"workflows": {
"name": "workflows",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"slug": {
"name": "slug",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"definition": {
"name": "definition",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"input_schema": {
"name": "input_schema",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"tags": {
"name": "tags",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'[]'"
},
"source_path": {
"name": "source_path",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"is_active": {
"name": "is_active",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "1"
},
"deleted_at": {
"name": "deleted_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"idx_workflows_slug": {
"name": "idx_workflows_slug",
"columns": [
"slug"
],
"isUnique": true,
"where": "\"workflows\".\"deleted_at\" is null"
},
"idx_workflows_active": {
"name": "idx_workflows_active",
"columns": [
"is_active",
"\"updated_at\" desc"
],
"isUnique": false,
"where": "\"workflows\".\"deleted_at\" is null"
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
}
},
"views": {},
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"indexes": {
"idx_agent_sessions_status": {
"columns": {
"\"updated_at\" desc": {
"isExpression": true
}
}
},
"idx_agent_sessions_agent": {
"columns": {
"\"created_at\" desc": {
"isExpression": true
}
}
},
"idx_agent_sessions_updated": {
"columns": {
"\"updated_at\" desc": {
"isExpression": true
},
"\"id\" desc": {
"isExpression": true
}
}
},
"idx_agents_active": {
"columns": {
"\"created_at\" desc": {
"isExpression": true
}
}
},
"idx_runs_workflow": {
"columns": {
"\"created_at\" desc": {
"isExpression": true
}
}
},
"idx_runs_status": {
"columns": {
"\"created_at\" desc": {
"isExpression": true
}
}
},
"idx_runs_created": {
"columns": {
"\"created_at\" desc": {
"isExpression": true
},
"\"id\" desc": {
"isExpression": true
}
}
},
"idx_step_exec_agent": {
"columns": {
"\"created_at\" desc": {
"isExpression": true
}
}
},
"idx_step_exec_model": {
"columns": {
"\"created_at\" desc": {
"isExpression": true
}
}
},
"idx_workflows_active": {
"columns": {
"\"updated_at\" desc": {
"isExpression": true
}
}
}
}
}
} No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

CI-confirmed: migration out of sync with schema.ts — must regenerate before merge.

Both CI jobs fail db-generation drift checks, meaning this committed snapshot (and its paired migration SQL) does not match the current packages/db/src/schema.ts definitions.

Run pnpm --filter @relavium/db db:generate and commit the regenerated snapshot/migration.

🧰 Tools
🪛 GitHub Actions: CI / 2_lint · typecheck · test.txt

[error] 1-1: Migration out of sync. The committed @relavium/db migration is out of sync with src/schema.ts (or an upstream @relavium/shared enum the CHECKs derive from). Regenerate and commit: pnpm --filter @relavium/db db:generate

🪛 GitHub Actions: CI / lint · typecheck · test

[error] 1-1: db generation check failed: The committed @relavium/db migration is out of sync with src/schema.ts (or an upstream @relavium/shared enum the CHECKs derive from). Regenerate and commit with: pnpm --filter @relavium/db db:generate

🤖 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/drizzle/meta/0006_snapshot.json` around lines 1 - 2062, The
committed Drizzle snapshot is out of sync with the current schema, so regenerate
the migration artifacts instead of editing this snapshot manually. Run the
database generation flow for `@relavium/db`, then commit the updated snapshot and
paired migration SQL so `schema.ts` and the migration history match. Use the
existing Drizzle migration snapshot files as the source of truth for this fix.

Source: Pipeline failures

… adapter seam defaults

Two verified PR-review findings on the 2.5.F branch.

- **agent-session.ts (valid):** the post-turn auto-compaction was best-effort for its seam-method calls
  (managesOwnContext/contextLimit are try/caught) but NOT for `compact('auto-threshold')`, which RE-THROWS
  an unclassified error — so such a throw would reject an already-completed `sendMessage()` after
  `session:turn_completed` had fired. Guard the `compact()` call and treat an unclassified throw as a failure
  so the deterministic `/trim` fallback still bounds the next turn (ADR-0062 §5); the completed turn stands.
- **llm adapters (valid nitpick):** the three adapters (Gemini, Anthropic, OpenAI/DeepSeek) carried
  byte-identical `contextLimit` / `managesOwnContext` / `estimateTokens` wrappers. Extract them into a shared
  `CONTEXT_SEAM_DEFAULTS` in adapters/shared.ts (reusing the existing `contextLimitFor`/`estimateRequestTokens`)
  and spread it into each adapter; drop the now-unused imports. An adapter can still layer a specialised
  `estimateTokens` after the spread.

Skipped (with reason):
- **Drizzle 0006 migration + snapshot "out of sync":** INVALID. `drizzle-kit generate` reports "No schema
  changes, nothing to migrate" and leaves the tree clean — the committed migration + snapshot already match
  schema.ts. Nothing to regenerate (the bot flagged it speculatively without running drizzle-kit).

Verified locally: prettier --check + pnpm turbo run lint typecheck test — 23 tasks green, 3415 tests
(including the four adapter conformance suites and the auto-compaction engine tests).

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

sonarqubecloud Bot commented Jul 5, 2026

Copy link
Copy Markdown

@cemililik
cemililik merged commit 753e8a7 into main Jul 5, 2026
9 checks passed
cemililik added a commit that referenced this pull request Jul 5, 2026
PR #65 (ADR-0062 context commands) merged to main on 2026-07-05. Reconcile
the roadmap + agent guide to reflect the completed state:

- CLAUDE.md: replace the "next pickup is 2.5.F / G" status tail with 2.5.F
  ✅ Done — /clear (host-level fresh-session swap, TTY-interactive only),
  the session:compacting "Summarizing…" moment (amends ADR-0036), and the
  footer context-fullness indicator (contextWindowForModel); next is 2.5.G.
- docs/roadmap/current.md: same reconciliation on the experience-arm pointer.
- docs/roadmap/phases/phase-2.5-cli-consolidation.md: Status blockquote,
  the 2.5.F section header, and the closing callout now read Done/merged
  (incl. the Opus + Sonnet review round the merged PR carried).

Spine 2.5.A/B/C/E + experience arm 2.5.D/F complete; next 2.5.G, additive
lanes 2.5.H / I / J in parallel. Docs-only; no code or ADR change.

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