Skip to content

feat(tui): ambient passes read from store, not the live ring (AUR-267)#26

Merged
mishanefedov merged 1 commit into
mainfrom
aur-267-tui-thin-cache
May 3, 2026
Merged

feat(tui): ambient passes read from store, not the live ring (AUR-267)#26
mishanefedov merged 1 commit into
mainfrom
aur-267-tui-thin-cache

Conversation

@mishanefedov
Copy link
Copy Markdown
Owner

Linear: AUR-267

Summary

The TUI's reducer already kept a 500-event ring (MAX_EVENTS = 500 in src/ui/state.ts), but the ambient passes computed from it (computeBudgetStatus, anomaly scoring, sub-agent child counts) were therefore also capped at 500. That made budget rollups undercount on busy days and anomaly histories shallow.

This PR keeps the reducer / live tail unchanged (it still drives the timeline view) and switches the ambient passes to source from the SQLite store via a new EventStore.listRecentEvents() method:

  • Budget: queries up to 50k events from the last 30 days (covers per-session caps + today rollup).
  • Anomaly: queries the last 5k events newest-first (enough for MAD z-score baselines + stuck-loop detection).
  • childCountByAgentId: queries the last 10k events.
  • Launch backfill: seeds the live tail from store.listRecentEvents({ limit: 500 }) so the timeline isn't empty before adapter JSONL backfill re-emits. wrapSinkWithStore dedup handles any id collisions.

When store === null (e.g. a read-only $HOME), all four passes fall back to state.events so the TUI remains usable.

Acceptance criteria

  • TUI reducer no longer holds the full event buffer in React state — state.events capped at 500 events for the live SSE/timeline view; everything else queried lazily from the store.
  • All ambient passes (anomaly, budget, sub-agent counts) read inputs from the store, not from the React buffer.
  • No regression in TUI responsiveness — state.events still capped at 500; useMemo deps unchanged in shape (just the body queries the store).
  • Pause/resume still works (reducer untouched).
  • Existing tests pass.
  • Backfill on launch reads recent events from the store.

Out of scope (folded the AC where it doesn't apply)

The original ticket mentioned "the 4 MB backfill" + "47 reducer tests rewritten." The 4 MB backfill lives in adapters (per-JSONL re-tail); they still need to read JSONL because hermes/openclaw don't share the SQLite. The reducer's 500-event ring already matched the AC shape, so the 47 reducer tests stayed green without rewrite. Project-index / sessionRows passes moved to the web UI in v0.0.4 already, so that AC bullet is N/A in the TUI.

Test plan

  • npm run typecheck — clean
  • npm test368/368 pass (was 364; +4 new in sqlite.test.ts covering listRecentEvents desc/asc, sinceTs, and limit clamp)
  • npm run build — server (tsup) + web (vite) both build

Budget rollups, anomaly histories, and sub-agent child counts now query
the SQLite store via the new EventStore.listRecentEvents() instead of
iterating the 500-event React buffer. The live tail still drives the
timeline view; only the derived passes change source.

App.tsx also seeds the live tail at launch from store.listRecentEvents(
{ limit: 500 }) so the timeline isn't empty until adapter JSONL backfill
re-emits. The wrapSinkWithStore dedup guards against id collisions when
adapters do re-emit.

Adds 4 vitest cases covering desc/asc order, sinceTs filter, and limit
clamp. Full suite 368/368 (was 364).
@mishanefedov mishanefedov merged commit 01cf3fe into main May 3, 2026
3 checks passed
@mishanefedov mishanefedov deleted the aur-267-tui-thin-cache branch May 3, 2026 13:49
mishanefedov added a commit that referenced this pull request May 25, 2026
#26)

Budget rollups, anomaly histories, and sub-agent child counts now query
the SQLite store via the new EventStore.listRecentEvents() instead of
iterating the 500-event React buffer. The live tail still drives the
timeline view; only the derived passes change source.

App.tsx also seeds the live tail at launch from store.listRecentEvents(
{ limit: 500 }) so the timeline isn't empty until adapter JSONL backfill
re-emits. The wrapSinkWithStore dedup guards against id collisions when
adapters do re-emit.

Adds 4 vitest cases covering desc/asc order, sinceTs filter, and limit
clamp. Full suite 368/368 (was 364).
mishanefedov added a commit that referenced this pull request May 25, 2026
#26)

Budget rollups, anomaly histories, and sub-agent child counts now query
the SQLite store via the new EventStore.listRecentEvents() instead of
iterating the 500-event React buffer. The live tail still drives the
timeline view; only the derived passes change source.

App.tsx also seeds the live tail at launch from store.listRecentEvents(
{ limit: 500 }) so the timeline isn't empty until adapter JSONL backfill
re-emits. The wrapSinkWithStore dedup guards against id collisions when
adapters do re-emit.

Adds 4 vitest cases covering desc/asc order, sinceTs filter, and limit
clamp. Full suite 368/368 (was 364).
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