Skip to content

feat(coordination): reconciled agent-keyed inbox (de-dupe, segregate, retention) + exposé flicker fix - #171

Merged
TraderSamwise merged 5 commits into
masterfrom
chore/tui-next-26
Jun 19, 2026
Merged

feat(coordination): reconciled agent-keyed inbox (de-dupe, segregate, retention) + exposé flicker fix#171
TraderSamwise merged 5 commits into
masterfrom
chore/tui-next-26

Conversation

@TraderSamwise

@TraderSamwise TraderSamwise commented Jun 19, 2026

Copy link
Copy Markdown
Owner

Summary

Coordination information-layer redesign (data/IA only — styling deliberately deferred), plus a follow-on exposé flicker fix.

Coordination (3 phases)

The Coordination screen rendered the raw notification + thread stores as two parallel firehoses, so it cluttered with dead "missing" agents, duplicated every needs-input notice across Inbox and Threads, and ordered by time rather than what needs you. This reconciles the persisted log against live agent state.

  • Phase 1 — join model + thread de-dupe (8d38352): new pure src/coordination-model.ts joins live sessions/teammates/services × notifications × threads into agent-keyed, urgency-sorted items, each carrying reachability (live/offline/missing/none), an actionable flag, and a stale heuristic (live agent whose label moved on from an unread needs-input notice — display-only, never a write). Notification-tagged threads are excluded from buildThreadEntries, so the Threads section stops mirroring the Inbox.
  • Phase 2 — order + segregate (6a39eca): refreshNotificationEntries drives the Inbox from the model — actionable rows first, a handled · unreachable divider, then the de-emphasized tail; per-row reachability/stale tags. Kept 1:1 with notifications so nav/actions/detail are unchanged.
  • Phase 3 — retention + size cap (2fd0c93): new top-level inbox config (cleanupEnabled/retentionDays/cleanupIntervalMs/maxSize, default 14d/daily/10), pure src/inbox-cleanup.ts (plan/run), and a scheduler wired exactly like graveyard cleanup. Archives read+aged notifications and trims overflow beyond maxSize — read-then-oldest, never an unread actionable row.

Design invariant (deliberate): notification read/cleared state tracks the user's relationship to a request (seen / handled / aged out), not an agent process-lifecycle event — so a restart/resume never silently marks a request read.

Exposé

  • 70db4d2 — fix periodic-refresh flicker: repaint only the opaque tiles on the capture-refresh tick (the static dimmed backdrop + panel frame are painted once), and skip the repaint entirely when nothing changed. The full-screen blank-and-repaint that flickered on terminals ignoring synchronized-output mode 2026 is gone.

Test plan

  • yarn typecheck, yarn lint, full yarn vitest (1451 passing), yarn build — all clean.
  • Each phase passed an independent plan-audit and implementation-audit (Explore agents) before commit.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added configurable inbox cleanup with retention and inbox size caps, running periodically in the background, with dry-run support and protection for actionable unread items.
    • Introduced a coordination inbox model to reconcile notification/session state, derive reachability/actionable flags, and prioritize actionable “live” items first.
  • Improvements
    • Updated inbox UI to render counts and row styling using per-notification metadata (actionable, unreachable, stale).
    • Prevented notification exchange threads from appearing in merged coordination data.
    • Optimized terminal rendering to repaint incrementally when possible.

test and others added 4 commits June 19, 2026 08:37
The periodic capture refresh repainted the full screen (dimmed backdrop + panel
frame + tiles) every tick, which flickered visibly because the tmux popup does
not honor synchronized-output mode 2026 — the repaint showed progressively
(blank → backdrop → complete). It also repainted when nothing had changed.

refreshCaptures now reports whether any capture changed, so an idle exposé does
not repaint at all. The backdrop and panel frame are static (they depend only on
size), so render(full=false) repaints just the opaque tiles in place; the full
backdrop+frame is repainted only on initial paint, resize, tile-count change, or
zoom. Navigation also repaints tiles-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add pure src/coordination-model.ts: buildCoordinationModel joins live
sessions/teammates/services against the notification log and genuine threads
into agent-keyed, urgency-sorted items. Each item carries reachability
(live/offline/missing/none), a stale heuristic (live agent whose label moved on
from an unread needs-input notice — display-only, never a write), an actionable
flag, and per-agent notification rollup. Dormant until the view is wired (next
phase).

Also exclude notification-tagged threads from buildThreadEntries so the
Coordination Threads section stops mirroring the Inbox (notifications are stored
as exchange threads tagged `notification`).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
refreshNotificationEntries now reconciles the notification log against live
agent state via buildCoordinationModel, then flattens the urgency-sorted,
agent-keyed items back to a 1:1 notification list (nav/actions/detail
unchanged). A parallel notificationRowMeta carries per-row reachability /
stale / actionable. showCoordination builds threadEntries before the refresh
so the model can annotate on first paint.

The inbox now renders actionable rows first, a "handled · unreachable"
divider, then the de-emphasized tail; each row tags reachability
(live/offline/missing) and a stale marker (live agent that moved on from an
unread needs-input notice). Header shows "(N of M)". No visual rollup yet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a notification-inbox cleanup that keeps the inbox bounded without ever
dropping a request the user still owes attention. New top-level `inbox` config
(cleanupEnabled/retentionDays/cleanupIntervalMs/maxSize, default 14d/daily/10),
a pure src/inbox-cleanup.ts (buildInboxCleanupPlan/runInboxCleanup) that
archives read/handled notifications past the retention window and trims overflow
beyond maxSize — evicting read-then-oldest and never a protected (unread
actionable) row — and a scheduler (cleanupInbox/start/stopInboxCleanup) wired
exactly like graveyard cleanup (start + immediate run on project-service launch,
stop on teardown).

cleanupInbox protects the unread notifications of agents the live coordination
model still considers actionable, so missing/stale rows can be capped while
live work is never archived. Archival uses clearNotifications (recoverable via
includeCleared), consistent with the corrected invariant: read state tracks the
user's relationship to the request, not a process lifecycle event.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
app Ready Ready Preview, Comment Jun 19, 2026 1:53am

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 21f9f809-bab5-448d-8d19-16f6f010bbcc

📥 Commits

Reviewing files that changed from the base of the PR and between 2fd0c93 and caceb63.

📒 Files selected for processing (4)
  • src/coordination-model.test.ts
  • src/coordination-model.ts
  • src/tmux/expose.ts
  • src/tui/screens/subscreen-renderers.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/coordination-model.test.ts
  • src/coordination-model.ts
  • src/tmux/expose.ts
  • src/tui/screens/subscreen-renderers.ts

📝 Walkthrough

Walkthrough

Introduces a CoordinationModel that orders inbox notifications by reachability and urgency, an InboxCleanup subsystem that plans and executes retention/overflow-based notification eviction, and wires both into the Multiplexer lifecycle. Notification threads are filtered from workflow coordination entries. TUI inbox rendering is updated with per-row metadata. The tmux expose module gains an incremental repaint path.

Changes

Coordination Inbox and Cleanup

Layer / File(s) Summary
InboxConfig schema and defaults
src/config.ts, src/config.test.ts
Exports InboxConfig interface, adds inbox property to AimuxConfig, sets DEFAULT_CONFIG inbox defaults, and asserts those defaults in a new test.
Coordination model types and buildCoordinationModel
src/coordination-model.ts, src/coordination-model.test.ts
Defines all exported coordination model types, implements resolveReachability, item construction with urgency scoring, buildCoordinationModel sorting/filtering, and full test coverage for ordering, reachability, deduplication, and enrichment.
Filter notification threads from coordination entries
src/notifications.ts, src/workflow.ts, src/workflow.test.ts
Exports NOTIFICATION_TAG, applies it in buildThreadEntries() to exclude inbox notification exchange threads from coordination workflow entries, and tests the exclusion.
Inbox cleanup plan/run module
src/inbox-cleanup.ts, src/inbox-cleanup.test.ts
Defines InboxCleanup exported interfaces and operation hooks, implements buildInboxCleanupPlan (aging + overflow with protected-ID logic), runInboxCleanup (dry-run and live), and full plan/run test suite.
Notification refresh with coordination model and NotificationRowMeta
src/multiplexer/notifications.ts, src/multiplexer/index.ts, src/multiplexer/coordination.ts, src/multiplexer/notifications.test.ts
Exports NotificationRowMeta, replaces listNotifications with coordination-model-driven refresh populating notificationRowMeta 1:1 with entries, extends ensureNotificationState, adds Multiplexer private fields, reorders showCoordination setup, and tests inbox ordering.
Inbox cleanup lifecycle wiring
src/multiplexer/persistence-methods.ts, src/multiplexer/index.ts, src/multiplexer/session-launch.ts, src/multiplexer/runtime-lifecycle-methods.ts
Adds cleanupInbox/startInboxCleanup/stopInboxCleanup persistence methods, normalizeInboxCleanupIntervalMs, actionableUnreadNotificationIds, cleanup interval/running-state Multiplexer fields, guarded session-launch invocation, and stopInboxCleanup in teardown.
TUI inbox rendering with per-row metadata
src/tui/screens/subscreen-renderers.ts
Updates inbox list rendering to use ctx.notificationRowMeta for actionable counts, divider between actionable and non-actionable rows, status dot selection, title tone, reachability labeling, and stale indicator.

tmux Incremental Render Optimization

Layer / File(s) Summary
tmux expose incremental tile render
src/tmux/expose.ts
Rewrites refreshCaptures to return a change-detection boolean, introduces static-frame bookkeeping (staticSize, staticVisibleCount), splits render() into full vs tile-only paths, and updates startup, keyboard navigation, and periodic refresh to use render(false) when only tiles change.

Sequence Diagram(s)

sequenceDiagram
  participant SessionLaunch as runProjectService
  participant PersistenceMethods as persistenceMethods
  participant InboxCleanup as inbox-cleanup
  participant CoordModel as buildCoordinationModel
  participant Notifications as refreshNotificationEntries

  SessionLaunch->>PersistenceMethods: startInboxCleanup()
  PersistenceMethods->>PersistenceMethods: loadConfig → check cleanupEnabled, normalizeInterval
  loop Every cleanupIntervalMs
    PersistenceMethods->>CoordModel: buildCoordinationModel(notifications, sessions)
    CoordModel-->>PersistenceMethods: actionable item ids (protected set)
    PersistenceMethods->>InboxCleanup: buildInboxCleanupPlan(config, protectedIds)
    InboxCleanup-->>PersistenceMethods: InboxCleanupPlan (aged + overflow targets)
    PersistenceMethods->>InboxCleanup: runInboxCleanup(plan)
    InboxCleanup-->>PersistenceMethods: InboxCleanupRunResult
    PersistenceMethods->>Notifications: refreshNotificationEntries(host)
    Notifications->>CoordModel: buildCoordinationModel(...)
    CoordModel-->>Notifications: CoordinationModel (sorted items)
    Notifications-->>PersistenceMethods: updated notificationEntries + notificationRowMeta
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • TraderSamwise/aimux#157: Modifies refreshNotificationEntries and the coordination screen transition, which this PR directly extends by rebuilding that refresh around buildCoordinationModel and NotificationRowMeta.

Poem

🐰 Hop through the inbox, old threads swept away,
A coordination model sorts what needs the day.
Live sessions bounce first, the ghosts drift below,
A cleanup plan trims what's too old in the row.
The tmux tiles blink just when captures have changed —
No wasted redraws, every pixel arranged! 🌸

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main changes: a new coordination-model inbox with de-duplication/segregation/retention features plus a display flicker fix.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/tui-next-26

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/workflow.test.ts (1)

11-13: ⚡ Quick win

Assert exclusion by thread tag instead of title text.

Line 73 is tied to display text, but the production filter is tag-based. Asserting on tags makes this test resilient to title-format changes.

Proposed test hardening
 import { addNotification } from "./notifications.js";
+import { NOTIFICATION_TAG } from "./notifications.js";
 import { buildCoordinationThreadEntries, buildWorkflowEntries, filterWorkflowEntries } from "./workflow.js";
@@
-    expect(entries.some((entry) => entry.thread.title.includes("[Needs input]"))).toBe(false);
+    expect(entries.some((entry) => (entry.thread.tags ?? []).includes(NOTIFICATION_TAG))).toBe(false);
     expect(entries.some((entry) => entry.thread.kind === "task")).toBe(true);

Also applies to: 73-74

🤖 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 `@src/workflow.test.ts` around lines 11 - 13, The test assertions at lines
73-74 are currently checking against display text or title format, but the
production filter implementation uses thread tags for filtering. Update these
assertions to check the thread tags property instead of title text to ensure the
test aligns with how the production code actually filters entries and to make
the test resilient to future changes in title formatting.
🤖 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 `@src/coordination-model.ts`:
- Around line 18-24: The liveLabel variable is undefined because the semantic
field is never populated when DashboardSession objects are constructed. In the
resolveReachability function, liveLabel is extracted from
session.semantic?.user?.label at line 100, but since semantic is undefined, this
always results in undefined. This causes the stale detection check at line 143
to fail. Fix this by adding a fallback in the code that reads liveLabel: when
session.semantic?.user?.label is unavailable, use the top-level label field
(which exists on DashboardSession at line 18) as the fallback value. This
ensures liveLabel has a value for the stale check to work correctly, allowing
unread needs-input notifications to be properly marked stale and preventing
stale entries from being retained in cleanup operations.

In `@src/tmux/expose.ts`:
- Around line 611-615: The periodic refresh timer in the scheduleRefresh
function only calls render(false) when refreshCaptures() returns true, which
means resize events are missed if captures remain unchanged. To fix this, modify
the logic in the timer callback within scheduleRefresh to also track resize
events separately from capture changes, and call render(false) whenever either
captures have changed OR a resize has occurred, ensuring the frame and layout
are updated even when content stays identical.

---

Nitpick comments:
In `@src/workflow.test.ts`:
- Around line 11-13: The test assertions at lines 73-74 are currently checking
against display text or title format, but the production filter implementation
uses thread tags for filtering. Update these assertions to check the thread tags
property instead of title text to ensure the test aligns with how the production
code actually filters entries and to make the test resilient to future changes
in title formatting.
🪄 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: 52ffaab3-8451-4778-ae33-df676040f4b7

📥 Commits

Reviewing files that changed from the base of the PR and between 3515bdb and 2fd0c93.

📒 Files selected for processing (18)
  • src/config.test.ts
  • src/config.ts
  • src/coordination-model.test.ts
  • src/coordination-model.ts
  • src/inbox-cleanup.test.ts
  • src/inbox-cleanup.ts
  • src/multiplexer/coordination.ts
  • src/multiplexer/index.ts
  • src/multiplexer/notifications.test.ts
  • src/multiplexer/notifications.ts
  • src/multiplexer/persistence-methods.ts
  • src/multiplexer/runtime-lifecycle-methods.ts
  • src/multiplexer/session-launch.ts
  • src/notifications.ts
  • src/tmux/expose.ts
  • src/tui/screens/subscreen-renderers.ts
  • src/workflow.test.ts
  • src/workflow.ts

Comment thread src/coordination-model.ts
Comment thread src/tmux/expose.ts
…ize repaint

Review fixes:
- Group sessionless notifications by dedupeKey (falling back to id) so repeated
  project-level alerts collapse into one inbox item instead of duplicate rows.
- Copy the unread array before sorting for latestUnread (avoid mutating a derived
  array that a future refactor could alias).
- Only emit the "handled · unreachable" divider once an actionable row precedes
  it, so an all-unreachable inbox has no leading divider.
- Exposé periodic refresh now repaints on a terminal resize as well as on changed
  captures (no SIGWINCH handler), so an idle popup reflows on resize.

Co-Authored-By: Claude Opus 4.8 <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