feat(2.5.H): reasoning rendering, live-turn feedback & actionable error taxonomy (EA6)#67
Conversation
Add the reasoning ("thinking") counterpart of `agent:token`: a dual-envelope
`agent:reasoning` stream event the correlation-agnostic agent turn core emits
per `reasoning_delta` chunk. Pure host-emit — the @relavium/llm seam already
carries the reasoning chunks (ADR-0030); the turn core previously only
ACCUMULATED reasoning deltas with no event of their own. The seam and the
engine architecture are unchanged.
- @relavium/shared: AgentReasoningEventSchema + type, added to the RunEvent
union, RUN_EVENT_TYPES (22→23), and the dualBase family; drift-pin matrix +
dual-envelope test extended.
- @relavium/core: 'agent:reasoning' in InNodeEventType; foldReasoningChunk emits
it (mirrors the agent:token emit) carrying text + active model, never the
ephemeral same-provider signature (ADR-0030); the session sink carries it
(not run-only, unlike agent:file_patch_proposed). Engine + sink tests.
- docs: sse-event-schema.md row + Selected-definition + session-stream notes;
ADR-0036 append-only amendment note (EA6 amends the event substrate; no new
top-level ADR — an additive event in the shared union).
Toolchain: lint + typecheck + test + build green (24 tasks); format:check clean;
seam-hold clean.
Refs: ADR-0036, phase-2.5-cli-consolidation.md (2.5.H, EA6)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ore throttle Fix the review-caught gaps in the agent:reasoning (EA6) landing: - BLOCKER: the engine's #nodeEmit run-path translation point switched on event.type with no default, so agent:reasoning (added to InNodeEventType) fell through and was SILENTLY DROPPED on the workflow-run path — the dual-envelope contract was only half-delivered (the session path emitted it, a run did not). Add the pass-through case AND an exhaustiveness guard (`const exhaustive: never`) so a future in-node event is a compile error here, never another silent drop. New engine.test.ts regression asserts it reaches the bus with runId. - MEDIUM: agent:reasoning was missing from HIGH_FREQUENCY_EVENTS in chat-store + run-store, so each reasoning delta flushed an immediate repaint (a live thrash on the session path now — reasoning models emit many thinking tokens). Coalesce it like agent:token. - LOW: add it to the db STREAMING_EVENT_TYPES firehose set (defensive; never persisted); broaden the agent-turn.ts model-attribution NOTE (both mid-stream events share the pre-content-failover edge) + the deferred-task entry; fix the stale "four dual events" enumerations (agent-session.ts, node-executor.ts, agent-runner.md). - Tests: run-path bus assertion (would have caught the BLOCKER) + a redacted-block zero-emit test. Toolchain: lint + typecheck + test + build green (media-gc flake passed on retry); format:check clean. Refs: ADR-0036, phase-2.5-cli-consolidation.md (2.5.H, EA6) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…+ doc consistency Close the review-caught gaps (0 blocker/high; 2 medium + 1 low): - MEDIUM: sse-event-schema.md had a self-contradictory paragraph — its first sentence listed the reused in-turn events WITHOUT agent:reasoning while a later sentence in the same paragraph included it. Add it to the first list so the canonical home is internally consistent. - MEDIUM: agent:reasoning was the one HIGH_FREQUENCY_EVENTS member with no coalescing regression test in either store. Add a chat-store test (mirrors the cost:updated test) + extend the run-store family test — so dropping it from either set fails a test (the untested-drift class the BLOCKER post-mortem flags). - LOW: run-history-store.test.ts claimed "ALL THREE excluded streaming types" but the set now has four; persist + assert the 4th (agent:reasoning) so the "fails on drift/misspelling" guarantee holds for it too. Toolchain: shared/core/db/cli green (media-gc CAS-orphan flake confirmed pre-existing + unrelated — non-deterministic across repeated isolated runs, touches no event code); format:check clean. Refs: ADR-0036, phase-2.5-cli-consolidation.md (2.5.H, EA6) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… attribution
Additive `relavium chat` UX (host-only, no engine change):
- Live-turn timer: before the first token streams, the busy line shows
`⠋ Working… {elapsed} · Esc to stop` (whole-second `formatElapsed`, floored so it
doesn't jitter per frame) — a running turn never reads as a frozen bare spinner.
`nowMs` is threaded to `ChatView` from both `ChatApp` (Date.now()) and the Home.
- Elision marker: the live token buffer already sliced its head at
MAX_LIVE_TOKEN_CHARS but dropped it SILENTLY; now the view-model tracks
`liveTokensTruncated` (reset with the buffer) and the render shows a leading `…`,
so the scroll-out is visible. `appendBounded` reports the elision (reused by Step
2b for reasoning).
- Per-attempt model attribution: the completed-turn summary shows `via {model}` when
the committed model (from the accurate per-attempt `cost:updated.model`, EA2)
differs from the bound model — a within-turn failover — and is omitted otherwise
(the footer already shows the bound model), so a plain turn is unchanged.
Tests: view-model truncation-flag + reset + failover-attribution; formatElapsed;
formatTurnSummary `via {model}` + sanitization. lint/typecheck/test/build green;
format:check clean.
Refs: phase-2.5-cli-consolidation.md (2.5.H)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-line tests
Fix the review-caught HIGH + close the coverage gap:
- HIGH: the live-turn timer was frozen at 0s in the bare-Home in-process chat.
`nowMs` was computed once per RootApp render (which only re-renders on controller
state changes), then handed to the per-frame ChatRegion as a frozen number — so
during the pre-first-token wait the elapsed stuck at 0s (worse than no timer).
Thread the clock as a FUNCTION into ChatRegion (which re-renders every frame while
a turn streams, via its own store subscription) and read it there. Standalone
`relavium chat` was already correct (ChatApp reads Date.now() in its own render).
- MEDIUM: the busy-line branch matrix was inline JSX with zero test coverage.
Extract a pure `formatBusyLine({...}) → { text, dim }` into chat-projection.ts and
unit-test every branch (compaction / shell / pre-token Working…+elapsed / streaming
content + elision adjacency / sanitization). The render now only maps `dim` → <Text>.
- LOW: guard formatElapsed against a non-finite input (symmetry with spinnerFrame);
document the last-cost:updated "committed model" engine-ordering dependency + add a
multi-attempt attribution test (cost A → cost B → `via B`).
Deferred to 2b (holistic busy-region redesign for the reasoning panel): keeping a
compact abort hint visible during token streaming.
Toolchain: lint/typecheck/test/build green (media-gc CAS-orphan flake CONFIRMED
pre-existing — fails 3/3 on the pre-2.5.H base commit, touches no file in my diff);
format:check clean.
Refs: phase-2.5-cli-consolidation.md (2.5.H)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…+ test gaps Close the review-caught HIGH + the smaller gaps: - HIGH: the elision marker vanished the instant a turn completed. reduceTurnCompleted built the transcript entry from raw liveTokens (no `…`), and the live busy-line marker (formatBusyLine, in-flight only) never applied to it — so once the turn landed in ink <Static> (the terminal's PERMANENT scrollback) it rendered verbatim, reverting to the silent head-loss the feature exists to surface. Bake `…` into the FINALIZED display text when the final segment truncated (guarded on the RAW text so no spurious empty entry), fully reducer-testable. The DURABLE record (persister → resume/export) is untouched + full — only the bounded live echo is short. - MEDIUM: formatElapsed's non-finite guard shipped untested — add NaN/Infinity → "0s". - LOW: pin formatBusyLine's compaction-over-shell priority. - MEDIUM (tracked, not silently deferred): the Home live-timer fix has no regression test — the CLI has no ink component-render harness by design (pure-logic testing). Filed as a deferred task (a render-layer harness is a first-of-its-kind devDep/ADR decision, out of 2.5.H scope) alongside the streaming-abort-hint follow-up. Toolchain: lint/typecheck/test/build green (1560 CLI tests; media-gc CAS-orphan flake pre-existing — fails 3/3 on the pre-2.5.H base commit); format:check clean. Refs: phase-2.5-cli-consolidation.md (2.5.H), deferred-tasks.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nking + Ctrl+T) Render the streamed reasoning (agent:reasoning, EA6) in the chat TUI: - View-model: fold agent:reasoning into a bounded `liveReasoning` buffer (+ its `liveReasoningTruncated` elision flag). Unlike liveTokens it is reset only per TURN (started/completed/cancelled), NOT per tool call, so the panel accumulates the whole turn's thinking across tool rounds. - A collapsible "thinking" panel (pure `formatReasoningPanel`) — default COLLAPSED (a dim `✻ Reasoning · Ctrl+T show` header); expanded shows the sanitized reasoning body (newline-preserving) with a leading `…` elision marker. The busy line labels the pre-token phase "Thinking…" (vs "Working…") when reasoning is streaming. - Toggle both ways (the maintainer's choice): a chat-only `/thinking` slash command (a new `toggleReasoning` ReplCommandContext capability, wired in chat.ts's replCtx, inert in the bare-Home ctx) + a `Ctrl+T` keybind (a new `toggle-reasoning` ChatKeyAction, decided before the running-swallow so it works mid-turn). Both flip the store's new `reasoningVisible` flag; wired in BOTH surfaces (chat + Home). - Home parity: ChatRegion threads reasoningVisible; the controller routes Ctrl+T to the store toggle. Docs: commands.md + chat-session.md (the `/thinking` command, the Ctrl+T keymap row, and a "Reasoning & live-turn feedback" section). Tests: reasoning fold + per-turn reset + cross-tool accumulation; store toggle; formatReasoningPanel + the Thinking… label; the Ctrl+T key action; the Home integration; the /thinking→toggleReasoning dispatch. lint/typecheck/test/build green (media-gc flake pre-existing); format clean. Refs: phase-2.5-cli-consolidation.md (2.5.H, EA6) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address the review-caught polish (all LOW; Opus verdict was "ship it"):
- The pre-token busy line read "Thinking… {elapsed}" even while a tool was
executing (reasoning is turn-scoped and liveTokens resets on a tool call, so the
empty-tokens gap during a tool round showed the thinking label). Now the label is
"Thinking…" only when reasoning has streamed AND no tool call is unresolved — during
a tool round it falls back to "Working…", so the timer never claims the model is
thinking while a tool runs.
- Tests: a sticky-truncation-across-deltas test for liveReasoning; a `/thinking`
end-to-end dispatch test (through the real chat command → replCtx.toggleReasoning →
the shown/hidden notice) closing the one untested wiring link.
- Deferred (tracked, not silently): bound the expanded reasoning panel by rendered
LINES not chars (live-region flicker on a short terminal); allow Ctrl+T/`/thinking`
during a pending approval (deliberately left fail-closed — the approval floor is
security-sensitive, ADR-0057).
Toolchain: lint/typecheck/test/build green (media-gc CAS-orphan flake pre-existing);
format:check clean.
Refs: phase-2.5-cli-consolidation.md (2.5.H), deferred-tasks.md
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…reasoningLabelActive Close the two MEDIUMs (same root) + the doc LOW (Sonnet verdict: 0 blocker/high): - The Opus-fix's "Thinking… only when no tool is executing" derivation lived inline in ChatView (untestable without an ink harness) and left formatBusyLine's `hasReasoning` param doc describing the PRE-fix semantics (the caller now passes the narrower tool-gated value). Extract a pure, exported `reasoningLabelActive(hasReasoning, liveToolCalls)` in chat-projection.ts, unit-tested with the resolved/unresolved/mixed/ empty/no-reasoning cases; rename formatBusyLine's param to `reasoningActive` with an accurate doc. The inline glue is now a one-line call to the tested helper. - Documented the LOW bound-interaction note (liveToolCalls is capped) on the helper. - Docs: chat-session.md now notes the "Working…" fallback also applies while a tool call is unresolved (not only when there is no reasoning). Left as-is (Sonnet LOW, deliberate/acceptable): the turn-sticky "Thinking…" heuristic in the resolved-tool→compose gap (a reasonable approximation, not a defect); the approval-gated toggle + line-bounded panel remain tracked in deferred-tasks. Toolchain: lint/typecheck/test/build green (1577 CLI tests, no flake this run); format:check clean. Refs: phase-2.5-cli-consolidation.md (2.5.H) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rvives) A failed turn is never a terminal (only session:cancelled ends a session), so a failure always leaves the REPL live. Beyond the closed ErrorCode, the chat now renders a one-line, secret-free recovery hint for each operational class that names the next step + makes "the session is still active" explicit — extending the "say so plainly" philosophy from the 2.5.A capability gap to the transport/quota/limit classes: - provider_rate_limit / provider_unavailable → retried w/ backoff + failover, resend. - provider_auth → check the key or UNLOCK the OS keychain (if locked mid-session). - context-overflow → surfaces as `validation`; a keyword heuristic on the (never- displayed) provider message distinguishes it from a shape error and suggests /compact·/trim (the maintainer-chosen detection; a SECONDARY net — 2.5.F auto- compaction pre-empts most overflows). - tool_failed (incl. an MCP-server timeout), budget_exceeded, run_timeout, turn_limit, content_filter, internal → each an actionable one-liner. The hint is ALWAYS a static host string — the provider message is read only to pick the hint (the overflow heuristic), NEVER echoed, so no provider text/secret reaches the terminal (tested). Rendered on the interactive TUI (a yellow line under the summary) + the plain non-TTY driver (under `[turn failed: <code>]`); --json stays the structured stream. `cancelled` (user-initiated) / `sandbox_error` (not chat-reachable) get no hint. Docs: chat-session.md "Actionable error recovery" section. Tests: errorRecoveryHint per class + the overflow heuristic + the no-echo security assertion; the plain-printer hint (+ secret-free). lint/typecheck/test/build green; format + seam clean. Refs: phase-2.5-cli-consolidation.md (2.5.H) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…+ drift-guard Address the review-caught accuracy debt (0 HIGH; 4 MEDIUM, all one-line string edits with real user impact; the no-echo security crux was verified clean): - tool_failed: the hint over-claimed "retried within budget" (there is NO session- level tool retry) and told the user to "try again" — unsafe for the non-recoverable (side-effecting) failure that actually reaches a completed turn, and contradicting the summary line's own path/target framing. Reframed to complement the summary: "fix the path or target (or check the MCP server) and resend" (no blind retry). - turn_limit: had TWO producers — the per-turn tool-call ceiling AND the session HARD round cap (agent-session.ts) — and "send another message" is re-blocked by the latter. Now covers both (`/clear` if the hard cap is hit). - internal: pointed at a "correlation id from the logs" that the session path never produces (correlationId is WorkflowEngine-only, ADR-0036). Dropped that clause. - context-overflow heuristic: added Anthropic's "…exceed context limit" phrasing (a real false-negative), and dropped the false-positive-prone bare "exceeds the maximum" / "token limit" markers (a param 400 like "temperature exceeds the maximum" would mis-suggest /compact). Added a false-positive-boundary test. - run_timeout: dropped its hint — it is WorkflowEngine-only (a session-path provider timeout classifies as provider_unavailable), so it's as unreachable as sandbox_error was already excluded (consistent rationale). - tool_unavailable: added a resend affordance for parity. - Test drift-guard: derive the taxonomy check from `ERROR_CODES` (partitioned hint / no-hint / message-conditional), so a NEW ErrorCode forces a decision. Docs: chat-session.md tool_failed bullet corrected. lint/typecheck/test/build green (1582 CLI tests); format clean. Refs: phase-2.5-cli-consolidation.md (2.5.H) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…l_unavailable Close the two review-caught HIGHs + the docs/LOW gaps: - HIGH: the session-continuity recovery hints leaked into `relavium agent run`'s PLAIN output — a one-shot that cancels its session in `finally` right after. So "the session is still active", `/compact`, resend (a REPL-only affordance) were all FALSE there. Gate the hint via `makePlainPrinter(io, recoveryHints = true)`: the chat REPL (`drivePlain`) keeps it (session survives); the one-shot `agent run` passes `false` → only `[turn failed: <code>]`. A plain-mode agent-run failed-turn test + a makePlainPrinter suppression test lock it. - HIGH: my own step-3-Opus fix over-reached — `tool_unavailable`'s hint became "resend and the model will answer without it", an unconditional promise that re-fails identically (the capability arm isn't wired for the whole session; it's not a `/mode` toggle). Softened to the same no-blind-retry standard the sibling tool_failed fix set (rephrase to avoid it, or wire it + fresh session). - MEDIUM/LOW: docs now enumerate tool_denied/tool_unavailable + the one-shot suppression; reorder tool_failed so "session is still active" isn't leading (parity with its 8 siblings); drop the dead `context_length` underscore marker (the heuristic reads the message, not the code); tighten the `cancelled` rationale (it's structurally unreachable — settles as `aborted` with no error); make `internal`'s tail concrete (`/doctor`, not a dangling "report it"); add a no-hint plain-printer test. Toolchain: lint/typecheck/test/build green (1585 CLI tests); format clean. Refs: phase-2.5-cli-consolidation.md (2.5.H) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…5-3 reached Reconcile the canonical status surfaces now that 2.5.H (reasoning rendering + live-turn feedback + the actionable error taxonomy, behind EA6 amending ADR-0036) has landed across four opus+Sonnet-reviewed steps: - phase-2.5-cli-consolidation.md: a ✅-Done status block on the 2.5.H workstream, the top-of-file status line, the milestone table (M2.5-3 reached with 2.5.E), and the EA6 "Required ADRs" note (landed). - current.md + CLAUDE.md: the additive-lane mention updated to 2.5.H ✅ Done (detail in current.md per the one-canonical-home rule); the stale "2.5.H / I / J" triple is gone (2.5.I / J remain). (The reference homes — sse-event-schema.md, chat-session.md, commands.md, ADR-0036's append-only note, deferred-tasks.md — were updated in their own step commits.) format:check + lint:fence-check clean. Refs: phase-2.5-cli-consolidation.md (2.5.H, EA6), ADR-0036 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… optional param The full-repo lint gate flagged `message?: string | undefined` (the `?` already implies `undefined`) — @typescript-eslint/no-duplicate-type-constituents. Simplify to `message?: string`; behaviorally identical (a function-parameter optional, not an exactOptionalPropertyTypes object property). lint/typecheck/test/build all green. Refs: phase-2.5-cli-consolidation.md (2.5.H) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Sorry @cemililik, your pull request is larger than the review limit of 150000 diff characters
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
There was a problem hiding this comment.
Code Review
This pull request implements Phase 2.5.H, introducing the agent:reasoning dual-envelope stream event to support live rendering of model reasoning. Key changes include a collapsible 'thinking' panel in the TUI (toggled via /thinking or Ctrl+T), live-turn feedback with an elapsed timer, visible … elision markers for truncated buffers, and per-attempt model attribution for within-turn failovers. Additionally, it adds an actionable, secret-free error recovery hint system for failed turns (e.g., suggesting /compact or /trim on context overflows). I have no feedback to provide as there are no review comments.
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.
2.5.H landed on main (PR #68, development→main). Add the workstream PR reference (#67 — the 2.5.H feature PR to development, matching how 2.5.A–G cite their feature PR; #68 is the dev→main release mechanism, like #66) to the 2.5.H status surfaces: phase-2.5-cli-consolidation.md (top status + the 2.5.H block header + Status line), current.md, and CLAUDE.md. Date-only; no other change. format:check + lint:fence-check clean. Refs: phase-2.5-cli-consolidation.md (2.5.H) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>



Phase 2.5.H — Reasoning rendering, live-turn feedback & actionable error taxonomy
The additive 2.5.H lane. Makes the reasoning the
@relavium/llmseam already carries (ADR-0030) visible in the chat TUI, gives every in-flight turn live latency + honest truncation + failover attribution, and turns each failed-turnErrorCodeinto an actionable, session-survives recovery hint. The@relavium/llmseam is not modified; the engine change is one additive event.Behind EA6 — a new dual-envelope
agent:reasoningstream event that amends ADR-0036 (the event substrate) via an append-only note. No new top-level ADR (pre-authorized by the roadmap 2.5.H section + the EA appendix).Base:
development(= the pre-2.5.H tip20027fc), so this PR is exactly the 14 2.5.H commits.Delivered (4 steps, each with an opus → Sonnet review round)
Step 1 —
agent:reasoning(EA6) ·@relavium/shared+@relavium/core+ docsA dual-envelope stream event (the reasoning counterpart of
agent:token), emitted by the correlation-agnostic turn core perreasoning_deltachunk — pure host-emit. Added to the shared union +RUN_EVENT_TYPES(22→23) + the drift-pin; carried on both the run path (engine#nodeEmit, now made compile-exhaustive) and the session sink; never the ephemeral same-providersignature(ADR-0030). Amends ADR-0036 + updatessse-event-schema.md.Step 2a — Live-turn feedback + elision + attribution ·
apps/cli(host-only)Thinking… / Working… {elapsed}s · Esc to stop— a whole-secondformatElapsedtimer so a running turn is never a frozen spinner (works standalone and in the Home).…elision marker when a bounded live buffer's head scrolls out — fixing the pre-existing silent head-drop, baked into the finalized transcript entry too (so scrollback shows it).via {model}) on a within-turn failover, from the accuratecost:updated.model.Step 2b — Collapsible "thinking" panel ·
apps/cliReduces
agent:reasoninginto a per-turn buffer; a collapsible panel (default collapsed), toggled by/thinkingandCtrl+T(both surfaces, works mid-turn). The pre-token label reads "Thinking…" only while the model is plausibly reasoning (not while a tool executes).Step 3 — Actionable error taxonomy ·
apps/cliA static, secret-free per-
ErrorCoderecovery hint that makes "the session is still active" explicit — provider rate-limit / unavailability (retried + failover), provider_auth (check the key / unlock the keychain), a context-overflow message heuristic →/compact·/trim, tool_failed (incl. an unreachable MCP server), budget / turn-cap / content-filter / internal. Rendered on the TUI (yellow, under the summary) + the plain chat driver; suppressed on the one-shotagent run(its session ends immediately);--jsonunchanged.Design decisions (confirmed up front)
/thinkingslash + aCtrl+Tkeybind (both surfaces).--json:agent:reasoningis part of the session stream (dual-envelope, likeagent:token); consumers filter ontype.Security posture
signature(ADR-0030) never reaches the event, a log, or the transcript (traced end-to-end).messageis read for the heuristic but never echoed — every hint is a static host string (tested with a secret-ish substring on both surfaces).@relavium/llmseam holds;packages/corestays platform-free; no new dependency.Review cadence & fixes
Every step went through an aggressive, multi-dimensional opus review → fix → Sonnet review → fix. ~15 verified findings fixed, including:
#nodeEmithad no case) — fixed + an exhaustiveness guard so the next in-node event can't silently drop.0s(anowMscaptured at the wrong component boundary) — the clock is now read per-frame inChatRegion.<Static>scrollback — now baked into the finalized entry.agent runplain output (where "session is still active" //compactare false) — gated + suppressed.tool_unavailable(an over-promised resend) — reverted to the no-blind-retry standard.ERROR_CODES.Deferred (tracked in
deferred-tasks.md, none blocking)Ctrl+T//thinkingduring a pending approval (deliberately fail-closed for now — the approval floor is security-sensitive).CI / tests
pnpm turbo run lint typecheck test build— 24/24 tasks green;lint:fence-check,lint:engine-deps,format:checkclean. New tests across@relavium/shared,@relavium/core,@relavium/db, andapps/cli(view-model, store, projection, key reducer, command registry, plain printer, agent-run).🤖 Generated with Claude Code