Skip to content

Hard cut coordination exchange paths#33

Merged
TraderSamwise merged 7 commits into
masterfrom
phase/communication-exchange-hard-cut
May 26, 2026
Merged

Hard cut coordination exchange paths#33
TraderSamwise merged 7 commits into
masterfrom
phase/communication-exchange-hard-cut

Conversation

@TraderSamwise

@TraderSamwise TraderSamwise commented May 26, 2026

Copy link
Copy Markdown
Owner

Summary

  • route threads/tasks/handoffs/inbox/notification compatibility state through runtime-exchange.yaml
  • wire dashboard, metadata server, app notification APIs, tmux inbox popup, and debug-state to exchange-backed coordination state
  • remove legacy notifications.json path and avoid .aimux task/thread write authorities

Verification

  • yarn verify
  • yarn build
  • cd app && yarn test lib/api.test.ts
  • subagent review loop: clean/trivial

Summary by CodeRabbit

  • New Features

    • New inbox endpoints and UI flows for viewing, marking read, and clearing inbox items; dashboard and popup now use inbox APIs.
    • Notifications, threads, tasks, and handoffs unified on a runtime-backed exchange for consistent state.
  • Bug Fixes

    • More reliable handoff/task recipient handling and fallback logic.
    • Improved unread/read/clear behavior and syncing across UI panels.
  • Documentation

    • Updated agent coordination guidance to prefer runtime-backed commands and avoid writing legacy task/thread files.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR migrates AIMUX state (notifications, threads, tasks) from file-backed JSON into a runtime-exchange store, implements deriveRuntimeExchangeIndexes, adds inbox/read/clear HTTP endpoints, updates server/client/UI/watcher/CLI code to use the runtime exchange, and updates agent documentation to prefer runtime-exchange-backed handoff/task/thread flows.

Changes

Runtime Exchange State Storage and API Integration

Layer / File(s) Summary
Path Configuration and Contract Changes
src/paths.ts
Removes notificationsPath, drops getNotificationsPath(), stops creating/migrating tasks/threads in local shared dirs.
Runtime Exchange Index Derivation
src/runtime-core/exchange-derived.ts
New deriveRuntimeExchangeIndexes() computes handoffs, reviews, waits, and inbox entries and preserves acknowledged inbox items when unchanged.
Thread and Message Management Migration
src/threads.ts, src/threads.test.ts
Threads/messages now read/write via runtime exchange store; adds unique() and threadUpdatedForMessage() and updates tests to assert derived inbox/waits/handoffs.
Task Management Migration and Review Status
src/tasks.ts, src/tasks.test.ts
Tasks mapped to runtime exchange tasks; TaskStatus originates from runtime type; adds review-status types; writeTask updates runtime store synchronously; tests adjusted to use runtime store.
Notification State and Lifecycle Management
src/notifications.ts, src/notifications.test.ts
Replaces file-backed notifications with runtime-exchange-backed threads/messages; derives NotificationRecord from tagged threads; read/upsert/list/read/clear operate on runtime inbox and message metadata.
Debug State Integration with Runtime Exchange
src/debug-state.ts, src/debug-state.test.ts
Reads runtimeExchange for debug reports; filterNotifications() derives records from exchange threads tagged notification; tests updated to emit runtime-exchange YAML.
Metadata Server: Inbox Endpoints and Request Handling
src/metadata-server.ts, src/metadata-server.test.ts
Adds GET /inbox and POST /inbox/read & /inbox/clear backed by runtime store, optionalStringOrFirst helper, and normalizes to parsing for handoff/tasks; tests cover inbox flows.
Metadata Watchers and CLI Handoff Updates
src/builtin-metadata-watchers.ts, src/builtin-metadata-watchers.test.ts, src/main.ts
Watchers monitor dirname(runtimeExchangePath) not tasks dir; tests drop pre-creating tasks dir; CLI handoff fallback builds to from --to or [assignee,tool].
Dashboard Orchestration and Multiplexer Integration
src/multiplexer/dashboard-interaction.ts, src/multiplexer/dashboard-interaction.test.ts, src/orchestration-actions.test.ts
Handoff postings now target /handoff; task assign uses description field; tests assert posted payloads and runtime-store persisted handoff status/acceptedBy/completedBy.
Multiplexer Notification Panel and Screen UI
src/multiplexer/notifications.ts, src/multiplexer/notifications.test.ts
Notification UI loads entries from runtime inbox, marks entries done via runtime inbox API, and tests use exchange-backed helpers to assert unread counts.
Tmux Inbox Popup Integration
src/tmux/inbox-popup.ts
Popup calls /inbox and `/inbox/read
Documentation and Agent Coordination Guidance
AGENTS.md, docs/runtime-authority-dead-paths.md, docs/runtime-core-hard-cut-roadmap.md, src/session-bootstrap.ts
Docs and agent instructions direct agents to use aimux CLI/API (task/handoff/thread) backed by runtime exchange and discourage proactive writes to .aimux/plans/.aimux/status for simple interactions.
API Client Notification Helper Updates
app/lib/api.ts
listNotifications() formatting change; clearNotifications() awaits response and normalizes cleared from cleared

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

  • TraderSamwise/aimux#29: Documentation edits around runtime-core hard-cut authority guidance touching the same runtime-authority docs.
  • TraderSamwise/aimux#16: Notification API and UI work that aligns with the notification/inbox changes here.
  • TraderSamwise/aimux#30: RuntimeExchangeStore and path helpers that are used by this PR’s runtime-exchange persistence changes.

"I'm a rabbit in the code-laden glen,
hopping through threads and tasks with a pen.
I nibble old JSON and build an exchange,
inboxes tidy, migrations in range.
Handoff hops done — 🐇✨"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.99% 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 'Hard cut coordination exchange paths' clearly and concisely summarizes the main change: migrating coordination state (threads, tasks, handoffs, inbox, notifications) from legacy file-based paths to the runtime exchange.
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 phase/communication-exchange-hard-cut

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

Caution

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

⚠️ Outside diff range comments (1)
src/paths.ts (1)

6-6: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update the comment to reflect that tasks and threads are no longer actively managed.

Line 6 still lists "tasks" and "threads" as agent-facing shared artifacts, but lines 339 and 360-363 show they are no longer created or migrated. According to the PR objectives, these are now legacy import-only paths.

📝 Proposed fix
- *                (config, team, plans, context, history, tasks, status, threads)
+ *                (config, team, plans, context, history, status)

Add a note about legacy paths if needed:

+ * Legacy paths (import-only): tasks, threads
🤖 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/paths.ts` at line 6, Update the top-of-file comment in src/paths.ts that
lists agent-facing shared artifacts to remove or mark "tasks" and "threads" as
legacy/import-only (they are no longer created or migrated) so the comment
matches current behavior; locate the comment block that enumerates "(config,
team, plans, context, history, tasks, status, threads)" and either remove
"tasks" and "threads" from the active list or append a short note like "tasks,
threads — legacy/import-only" to make intent explicit.
🧹 Nitpick comments (4)
src/tasks.ts (1)

66-77: 💤 Low value

Mutating the input task object is a side effect.

Lines 75-76 mutate the passed-in task object. This could surprise callers who don't expect their reference to be modified. Consider returning a new object or documenting this behavior explicitly.

🤖 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/tasks.ts` around lines 66 - 77, The writeTask function currently mutates
the input Task (task.updatedAt and task.reviewStatus); instead, create a new
Task object (e.g., newTask) based on the incoming task, set updatedAt and
normalized reviewStatus on that copy, and use newTask when calling
toExchangeTask and when persisting; leave the original task untouched. Update
references to task in writeTask to use the new copy and keep calls to nowIso(),
normalizeReviewStatus(), toExchangeTask(), and
createRuntimeExchangeStore().update(...) unchanged except for passing the
newTask.
src/tasks.test.ts (1)

137-158: 💤 Low value

Test setup is convoluted due to writeTask always overwriting updatedAt.

The test writes a task, then immediately overwrites the exchange store to set a past updatedAt. This works but is fragile. Consider adding a test helper or exposing a way to write tasks with explicit timestamps for testing scenarios.

🤖 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/tasks.test.ts` around lines 137 - 158, The test is brittle because
writeTask always sets/overwrites updatedAt so the test hacks the exchange store
to backdate a task; change writeTask (or add a new exported helper e.g.,
writeTaskWithTimestamp) to accept an optional updatedAt timestamp (or full Task
object) so tests can persist tasks with explicit timestamps, update the test to
call the new API instead of mutating createRuntimeExchangeStore, and ensure
cleanupTasks, readTask and readAllTasks behavior remains unchanged.
src/notifications.ts (1)

227-254: ⚡ Quick win

Repeated store.read() calls inside mapping loop.

Lines 238-245 call store.read() for each record in the loop. This reads and parses the exchange file repeatedly, which is inefficient. Read the exchange once before the loop.

♻️ Proposed fix
 export function markNotificationsRead(opts?: { id?: string; sessionId?: string }): number {
   const store = createRuntimeExchangeStore();
-  const records = notificationRecords(store.read()).filter((record) => {
+  const exchange = store.read();
+  const records = notificationRecords(exchange).filter((record) => {
     if (record.cleared || !record.unread) return false;
     if (opts?.id && record.id !== opts.id) return false;
     if (opts?.sessionId && record.sessionId !== opts.sessionId) return false;
     return true;
   });
   if (records.length === 0) return 0;
   const threadIds = new Set(
     records
       .map((record) => {
-        const exchange = store.read();
         return exchange.threads.find((thread) => {
           const message = threadLatestMessage(exchange, thread.id);
           return message && notificationRecord(exchange, thread, message).id === record.id;
         })?.id;
       })
       .filter((id): id is string => Boolean(id)),
   );
🤖 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/notifications.ts` around lines 227 - 254, In markNotificationsRead, avoid
calling store.read() inside the records.map loop; instead call const exchange =
store.read() once before building threadIds and reuse that exchange when calling
threadLatestMessage(exchange, thread.id) and notificationRecord(exchange,
thread, message) so the mapping that produces threadIds uses the single cached
exchange value; update the code where threadIds is computed to reference this
cached exchange and keep the rest of the function behavior unchanged.
src/runtime-core/exchange-derived.ts (1)

12-14: 💤 Low value

unique() helper duplicated across files.

This same unique() function is also defined in src/threads.ts (lines 28-30). Consider extracting to a shared utility module to avoid duplication.

🤖 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/runtime-core/exchange-derived.ts` around lines 12 - 14, The helper
function unique is duplicated (the function named unique exists in this module
and the threads module); extract it into a single exported utility (e.g., create
a shared util function unique(values: Array<string | undefined>): string[]) in a
common utilities module, export it, update this file to import and use unique
instead of the local definition, and remove the duplicate definition from the
other module so both consumers import the shared unique implementation.
🤖 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/builtin-metadata-watchers.ts`:
- Line 100: The DirectoryWatcher is created over
dirname(getRuntimeExchangePath()) so its callback fires for unrelated .aimux
changes and metadata.log(...) (in the taskWatcher callback) appends duplicate
task lines; change the watcher to either watch the specific runtime-exchange
file (use getRuntimeExchangePath() instead of dirname(...)) or, inside the
taskWatcher callback, inspect the reported changed path and only call
metadata.log when it equals the runtime exchange file (or when the task content
actually changed), and additionally guard metadata.log to avoid appending an
identical last entry (compare new task line to the last logged line) so
duplicates are not recorded.

In `@src/multiplexer/notifications.ts`:
- Around line 88-90: The current notificationTargetSessionId function returns
undefined when hasNotificationTarget(host, participantId) is false, which drops
the sessionId and prevents the missing-target flow; change
notificationTargetSessionId to always return the participantId (preserve the
sessionId) even if hasNotificationTarget(...) is false so the missing-target
handling can run, and apply the same change to the other analogous helper blocks
mentioned (the similar checks at lines 113-116 and 131-134) so they also
preserve sessionId rather than returning undefined.

---

Outside diff comments:
In `@src/paths.ts`:
- Line 6: Update the top-of-file comment in src/paths.ts that lists agent-facing
shared artifacts to remove or mark "tasks" and "threads" as legacy/import-only
(they are no longer created or migrated) so the comment matches current
behavior; locate the comment block that enumerates "(config, team, plans,
context, history, tasks, status, threads)" and either remove "tasks" and
"threads" from the active list or append a short note like "tasks, threads —
legacy/import-only" to make intent explicit.

---

Nitpick comments:
In `@src/notifications.ts`:
- Around line 227-254: In markNotificationsRead, avoid calling store.read()
inside the records.map loop; instead call const exchange = store.read() once
before building threadIds and reuse that exchange when calling
threadLatestMessage(exchange, thread.id) and notificationRecord(exchange,
thread, message) so the mapping that produces threadIds uses the single cached
exchange value; update the code where threadIds is computed to reference this
cached exchange and keep the rest of the function behavior unchanged.

In `@src/runtime-core/exchange-derived.ts`:
- Around line 12-14: The helper function unique is duplicated (the function
named unique exists in this module and the threads module); extract it into a
single exported utility (e.g., create a shared util function unique(values:
Array<string | undefined>): string[]) in a common utilities module, export it,
update this file to import and use unique instead of the local definition, and
remove the duplicate definition from the other module so both consumers import
the shared unique implementation.

In `@src/tasks.test.ts`:
- Around line 137-158: The test is brittle because writeTask always
sets/overwrites updatedAt so the test hacks the exchange store to backdate a
task; change writeTask (or add a new exported helper e.g.,
writeTaskWithTimestamp) to accept an optional updatedAt timestamp (or full Task
object) so tests can persist tasks with explicit timestamps, update the test to
call the new API instead of mutating createRuntimeExchangeStore, and ensure
cleanupTasks, readTask and readAllTasks behavior remains unchanged.

In `@src/tasks.ts`:
- Around line 66-77: The writeTask function currently mutates the input Task
(task.updatedAt and task.reviewStatus); instead, create a new Task object (e.g.,
newTask) based on the incoming task, set updatedAt and normalized reviewStatus
on that copy, and use newTask when calling toExchangeTask and when persisting;
leave the original task untouched. Update references to task in writeTask to use
the new copy and keep calls to nowIso(), normalizeReviewStatus(),
toExchangeTask(), and createRuntimeExchangeStore().update(...) unchanged except
for passing the newTask.
🪄 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 Plus

Run ID: 89d6c2c6-950e-4f73-ba6b-40536f1159db

📥 Commits

Reviewing files that changed from the base of the PR and between a47e2d5 and 0f0dbf2.

📒 Files selected for processing (26)
  • AGENTS.md
  • app/lib/api.ts
  • docs/runtime-authority-dead-paths.md
  • docs/runtime-core-hard-cut-roadmap.md
  • src/builtin-metadata-watchers.test.ts
  • src/builtin-metadata-watchers.ts
  • src/debug-state.test.ts
  • src/debug-state.ts
  • src/main.ts
  • src/metadata-server.test.ts
  • src/metadata-server.ts
  • src/multiplexer/dashboard-interaction.test.ts
  • src/multiplexer/dashboard-interaction.ts
  • src/multiplexer/notifications.test.ts
  • src/multiplexer/notifications.ts
  • src/notifications.ts
  • src/orchestration-actions.test.ts
  • src/paths.ts
  • src/runtime-core/exchange-derived.ts
  • src/session-bootstrap.test.ts
  • src/session-bootstrap.ts
  • src/tasks.test.ts
  • src/tasks.ts
  • src/threads.test.ts
  • src/threads.ts
  • src/tmux/inbox-popup.ts

Comment thread src/builtin-metadata-watchers.ts
Comment thread src/multiplexer/notifications.ts

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

🧹 Nitpick comments (1)
src/builtin-metadata-watchers.ts (1)

100-100: 💤 Low value

Consider watching the runtime exchange file directly instead of its parent directory.

The current implementation watches dirname(getRuntimeExchangePath()), which triggers the callback for any file change in that directory. While the fix at line 119 prevents duplicate logs, each trigger still calls readAllTasks(), rebuilds the latestBySession map, and compares all sessions—even when unrelated files in the directory change.

Watching getRuntimeExchangePath() directly would avoid unnecessary processing.

⚡ Proposed optimization
-  const taskWatcher = new DirectoryWatcher(dirname(getRuntimeExchangePath()), () => {
+  const taskWatcher = new DirectoryWatcher(getRuntimeExchangePath(), () => {

Note: Node.js fs.watch() supports watching individual files.

🤖 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/builtin-metadata-watchers.ts` at line 100, The DirectoryWatcher is
currently constructed with dirname(getRuntimeExchangePath()), causing callbacks
for any file in that directory; change the watcher to target the runtime
exchange file itself by passing getRuntimeExchangePath() (e.g., update the
taskWatcher creation to use getRuntimeExchangePath()), so the callback only
fires for changes to that file; keep the existing callback body (readAllTasks(),
latestBySession comparison) but verify DirectoryWatcher supports file paths and
handle potential file-deletion/rename edge cases inside the same callback if
needed.
🤖 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.

Nitpick comments:
In `@src/builtin-metadata-watchers.ts`:
- Line 100: The DirectoryWatcher is currently constructed with
dirname(getRuntimeExchangePath()), causing callbacks for any file in that
directory; change the watcher to target the runtime exchange file itself by
passing getRuntimeExchangePath() (e.g., update the taskWatcher creation to use
getRuntimeExchangePath()), so the callback only fires for changes to that file;
keep the existing callback body (readAllTasks(), latestBySession comparison) but
verify DirectoryWatcher supports file paths and handle potential
file-deletion/rename edge cases inside the same callback if needed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f292360e-00bc-444f-b183-06b005ca4e54

📥 Commits

Reviewing files that changed from the base of the PR and between 0f0dbf2 and 14a2559.

📒 Files selected for processing (1)
  • src/builtin-metadata-watchers.ts

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