Skip to content

acpmux: every chat's inbox; another agent's plain send is never a turn of its own (A1) - #27686

Merged
teamleaderleo merged 18 commits into
feat-cmux-nextfrom
lane/hb-agent-inbox
Oct 30, 2026
Merged

teamleaderleo merged 18 commits into
feat-cmux-nextfrom
lane/hb-agent-inbox

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Leo, 2026-10-29:

  • "What are we gonna do with these 200 fing messages? You never end a fing turn. As soon as it comes in, it fing, like, occupies you for another fing turn."
  • "im the only one that steers... shouldn't some of these messages like... you can turn on steer or queue or whatever? idk what the best situation is... didnt we think of a forum-type system earlier? idk how swarms and dynamic workflows are best done..."
  • "i feel like messages should kinda act as pseudo threads within threads/side chats?"
  • "until the acpmux fixes get into the app your turns almost never end ... what is the actual solution? forum or whatever claude code does?"
  • Earlier: "are these messages literally not getting to you ... i literally can't stop you i clicked the stop button and it didnt do anything ... otherwise i have to manually just click steer on all of them unless you come up with better solulus"

A coordinator session had 319 other agents' messages queued as 319 turns, behind a turn that the person's steers kept going.

The coordinator's design, from those words: agent messages never become turns; only the person's prompts are turns.

Default change. Before this PR, a plain send from another agent started a turn in an idle chat, or queued or steered into a busy one. Now it goes into the chat's inbox and starts no turn. It wakes an idle chat (one debounced turn) only when it is:

  • a blocker;
  • a report of work the chat gave out;
  • the chat's first work;
  • an action from the chat's parent or delegator.

Any other action, and every FYI, waits for the chat's next turn, or up to 10 minutes in an idle chat. Config agentInbox: false restores the old behaviour.

What

Every chat gets an inbox (hub/inbox.rs). Another agent's plain send never becomes a turn or a steer of its own.

  • What goes in (this PR, A1): a plain send from another agent on this host: acpmux send and the MCP send tool. The one routing predicate is inbox::plain_send, which has a unit test.

  • Still on the old queue path; A2 ports them as a follow-up:

    • sends with a --supersede key;
    • steer-only sends;
    • team messages;
    • peer-daemon and mesh-relay forwards;
    • lane reports;
    • task cards;
    • a paused agent pair's releases.

    Their tests are unchanged and pin agentInbox: false where they send plain messages to test the queue path.

  • Rollback switch: config agentInbox: false puts plain sends back on the queue without a new build.

  • Storage. A text-only agent message goes into the receiver's inbox, kept apart from its queue. It is an inbox record in the log, so a restart keeps it.

  • Threads. A message's thread is, in order of preference:

    • the --pr/--thread its sender gave (_meta.acpmux.pr/topic);
    • otherwise the sender plus the PR its first line names;
    • otherwise the sender alone.
  • Notices. The agent hears of new messages as ONE notice (provenance inbox_notice), the way Claude Code reports background work. The notice has one line per thread with new messages: the count, the sender(s), and the newest message's first line cut to 120 characters. Blockers come first.

    • Running turn: the notice is a steer read at the next tool step. Only one is in flight at a time, so a turn takes at most one per step. The turn cap (acpmux: turn cap, so a session taking many agents' messages can end its turn #27520) still holds.

    • A prompt waits in the queue: no notice steers in ahead of it; the notice goes into that prompt's own turn (fix(acpmux): queue automatic messages after the original steering window #27566's case).

    • Idle chat: it wakes as ONE turn, 2 s after the first message that may wake it, so a burst is one turn. A message wakes it only if it is:

      • a blocker (block/question, alias blocker);
      • an action (result, the default; alias action) from the session that started the chat or gave it its first work;
      • a lane's report or a task's result (work the chat gave out);
      • or the chat's first work (it has had no turn yet).

      An FYI never wakes a chat. Anything left waits at most 10 minutes before a notice brings it.

  • Replies. A sender waiting on session/prompt (a waited acpmux send) gets the answer of the steer or turn that carried its message. If the chat closes first, the sender gets the closing error. A chat that is closing or closed takes messages as it did before the inbox. A notice that went in logs its answer, or its error if it failed after it went in (an inbox_answered record). A resend of one of its messages gets that same answer and is not carried again: before or after a restart, and after the in-memory inbox let go of the message (it keeps 500 and only lets go of answered, read ones).

  • Durability. Messages are marked noticed (an inbox_noticed record) only once the notice went in, meaning the agent's writer confirmed its prompt, for a steer and for a turn of its own. If a Stop or a failure comes before that, they stay new. Only one notice is in flight: a turn of its own until its prompt is written, a steer until the agent took it at its tool step. A steer notice checks again for a waiting prompt and the turn cap while it holds the input handoff, so nothing is written between that check and its own write. A refused notice is tried again after 30 s, or sooner when a turn starts or ends. A closed chat's threads survive a restart.

  • Withdraw (_acpmux/prompt_withdraw): an inbox item that no notice has carried yet is unsent (an inbox_removed record, which a restart honours). A resend of it answers withdrawn and never puts it back. A withdraw of a message whose notice is on its way but not yet written waits up to 10 s for it: unsent if the notice did not go in, retracted if it did. Once a notice has carried it, the withdraw answers retracted with via: "inbox", and the retraction goes into the item's thread.

  • Reading and answering.

    • The MCP tool inbox_read (_acpmux/inbox_thread) reads a thread whole and marks it read; _acpmux/inbox_threads lists the threads.
    • The MCP tool inbox_reply (_acpmux/inbox_reply) answers into the sender's inbox under the same thread, so it never becomes a turn for them.
    • CLI: acpmux session inbox NAME [--thread T] and acpmux send --pr N | --thread T.
  • Not inbox items; they run as before: images, uploads, slash commands, sub-session family messages, Web prompts.

  • Wire contract (for the GUI, which is a separate PR):

    • _acpmux/inbox_threads {sessionId} → {threads: [{threadId, key, label, from[], unread, new, count, newestAt, newestPreview, class}]}, newest first;
    • _acpmux/inbox_thread {sessionId, threadId, markRead?} → {threadId, label, messages: [{promptId, from, fromSession, class, kind, at, text, read}]};
    • _acpmux/inbox_reply {sessionId, threadId, text};
    • log records inbox, inbox_noticed, inbox_answered, inbox_read, inbox_removed; the notice prompt's provenance is {kind: "inbox_notice", noticeId, threads}.
    • Documented in docs/acpmux-schema.json; spec/acpmux-protocol.json lists the methods.
  • Not the forum: base's hub/forum.rs is rules announcements, not a message store, so the inbox does not reuse it.

No capture: no visible change. The only webview file is the generated protocol method list (methods.ts, three untyped methods), and the GUI for threads is a separate PR.

Tests

tests/hub_integration/agent_inbox.rs:

  • 300 agent messages arrive as one notice at the next tool step, with no extra turns;
  • an idle chat wakes only for a blocker or for its first work, as one turn, and a waited send gets that turn's reply;
  • a queued prompt never waits behind agent messages (the starvation case from fix(acpmux): queue automatic messages after the original steering window #27566);
  • a withdraw unsends an inbox item until a notice carries it (a resend then answers withdrawn), then retracts it into its thread without a turn; a resend of a carried message gets its notice's answer;
  • a chat that closes answers its waiting senders with an error and keeps its threads readable;
  • threads are read whole and marked read, another session's read is refused, and a reply lands in the sender's inbox under the same thread without waking it.

Red: https://github.com/manaflow-ai/cmux/actions/runs/40973826419 (3 of 3 fail). Green at the head: https://github.com/manaflow-ai/cmux/actions/runs/41053769339 (fmt, protocol IR, clippy, lib tests, every integration test binary; only the base reds below).

Base reds, each compared per test against base 7365192, where it fails the same way:

  • hermetic_daemon_guard::every_test_binary_runs_in_the_hermetic_environment;
  • send_async::overlapping_direct_and_queued_sends_of_one_prompt_id_record_one_outgoing_copy, a peer-link send, so on the queue path. It is flaky: 6 of 8 fail on base, 1 of 8 at the head;
  • GitHub checks: cmux-next "No literal state roots" (help_examples/session.rs:179), release-notary's What's New highlights (chief-name-in-agent-chats.md), the test-execution registry (test_cmux_tui_owned_mini_caches.py), and the resource API boundary (crates/cmux-tui/src/cli.rs, which this PR does not touch).

Earlier runs against older bases also showed the remote_guard_modes, send_relay_cli, peer_state_origin, auto_resume::retry and transcript_events reds; base has fixed them since. The intent-review warning, the gallery advisory and the PR-checks link jobs failed on GitHub's installation API rate limit.

Merge with base. Base's acpmux session inbox NAME --digest [--dry-run] (the queue digest) and this PR's --thread [--mark-read] share one subcommand: --digest/--dry-run drain the queue, and --thread reads inbox threads; the two conflict. The digest drains exactly the kinds that still queue until A2. Base's new queue tests (inbox digest, stop, session queue CLI, peer person stop) pin agentInbox: false. recover_message_events also scans inbox records, so a message the inbox took has its message projection repaired after a restart, as a queued one does. finish_turn keeps inbox_turn_ended on base's new Stop-before-prompt path. The inbox flags are flattened clap Args, because inline they overflowed libtest's 2 MiB thread in a debug build.

Risk

High, for every chat: other agents' messages no longer steer in or queue as turns.

  • An action from a session that neither started the chat nor gave it its first work waits for the chat's next turn, or for up to 10 minutes if the chat is idle.
  • A lane that relies on a peer's result to wake it must be the one that delegated the work, or the peer must send it as a blocker.

Agent session: agent session

🤖 Generated with Claude Code

…f their own (red)

300 agent messages waiting while the agent reads the one before them go
in as one notice at its next tool step; an idle chat wakes for a blocker
or its first work only, as one turn; a prompt queued behind a running
turn never waits behind agent messages (#27566's case). The fake agent's
FAKE_STEER_STEP answers a steer at the test's next tool step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-1288bbb2b5d2f5c6b66a
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Owned by an agent session · session

…n of its own

A plain send from another agent (acpmux send, the MCP send tool) goes into the
receiving chat's inbox, in a thread (--pr / --thread, else the sender and the PR
its first line names, else the sender). The agent hears of new messages as ONE
notice at its next tool step; an idle chat wakes as one debounced turn only for
a blocker, a report of work it gave, its first work, or an action from its
parent or delegator; an FYI never wakes it, and anything left waits at most 10
minutes. MCP inbox_read / inbox_reply and _acpmux/inbox_threads, inbox_thread
and inbox_reply read and answer threads. Withdraw unsends an inbox item until a
notice carried it, then retracts it into its thread. agentInbox: false puts
plain sends back on the queue.

Supersede-keyed, steer-only and team sends, peer and mesh forwards, lane
reports, task cards and pair releases keep the queue path for now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-1288bbb2b5d2f5c6b66a
…nt-inbox

# Conflicts:
#	cmux-tui/crates/acpmux/docs/acpmux-schema.json
@github-actions github-actions Bot added risk:normal Risk tier (docs/ci/risk-tiers.md) and removed risk:low Risk tier (docs/ci/risk-tiers.md) labels Oct 30, 2026
@blacksmith-sh

blacksmith-sh Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Found 12 test failures on Blacksmith runners:

Failures

Test View Logs
/tmp/tmp31ndv3tw/summary-missing.json (live):/summary-missing.json (live): View Logs
/tmp/tmp84tubrka/summary-fail.json (live):/summary-fail.json (live): View Logs
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows/
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows
View Logs
[new_workspaces] the window draws no frames/[new_workspaces] the window draws no frames View Logs
[tabs] debug.tab_switch timed 0 of 40 switches/
[tabs] debug.tab_switch timed 0 of 40 switches
View Logs
[tabs] the window draws no frames/[tabs] the window draws no frames View Logs
main.DeveloperIdAppTests.test_download_prints_the_active_profile_only/
test_download_prints_the_active_profile_only
View Logs
stubs: /System/Library/Frameworks/Security.framework/Versions/A/Security has no stub/
Security has no stub
View Logs
stubs: CoreFoundation.tbd: install-name /System/Library/Frameworks/
CoreFoundation.framework/Versions/A/CoreFoundation, missing 1: ['_CFRetain']/
CoreFoundation, missing 1: ['_CFRetain']
View Logs
stubs: CoreFoundation.tbd: install-name /System/Library/Frameworks/
CoreFoundation.framework/Versions/A/CoreFoundation, missing 1: ['_log2']/
CoreFoundation, missing 1: ['_log2']
View Logs

...and 2 more test failures. View all on Blacksmith

Fix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

teamleaderleo and others added 3 commits October 29, 2026 18:15
… back off refusals

- A chat closing or closed takes an agent's message as before the inbox; one its inbox
  holds, or a notice in flight carries, is answered with the closing error.
- A notice turn's messages are noticed (logged) once its prompt is written, not at its
  turn's start; a Stop or failure before that leaves them new.
- A refused steer backs off REFUSED_RETRY_MS within a turn; a turn's start or end clears it.
- A resend of a noticed item says duplicate/noticed; a closed chat's inbox restores and the
  inbox methods wait for the chat's log load.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-1288bbb2b5d2f5c6b66a
…s the inbox record

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-1288bbb2b5d2f5c6b66a
@teamleaderleo teamleaderleo changed the title acpmux: other agents' messages go into an inbox, never a turn of their own acpmux: every chat's inbox; another agent's plain send is never a turn of its own (A1) Oct 30, 2026
@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review 201052a: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr27686 -f ref=lane/hb-agent-inbox -f platform=macos -f backend_mode=local

Passes: CI passes on 69e370e34e.

CI passes on 69e370e34e (run 41053747733 attempt 1).

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's; yours means the failing file is one this PR changes, also red on main that main's latest full suite fails the same way, seen on other PRs that it failed on another pull request's run lately.

web and size checks: green

Gallery before/after for 201052a: 16 states rendered, 0 images differ, 2455 more touched states not rendered. Images are in the run's artifact, not on this public PR.

gh run download 41050455101 -R manaflow-ai/cmux -n gallery-advisory-27686-201052ab8c5964cf45f44612c3f1484a4eb4b5d6 (then read gallery-review.json)

States
State Theme Status Diff Picked by
agent-pane.auth-health/signed-in Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.auth-health/signed-in Apple System Colors unchanged 0% imports methods.ts (2 hops)
agent-pane.auth-health/signed-out Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.auth-health/signed-out Apple System Colors unchanged 0% imports methods.ts (2 hops)
agent-pane.auth-health/expired Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.auth-health/expired Apple System Colors unchanged 0% imports methods.ts (2 hops)
agent-pane.auth-health/unknown Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.auth-health/unknown Apple System Colors unchanged 0% imports methods.ts (2 hops)
agent-pane.chat-width/standard Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.chat-width/standard Apple System Colors unchanged 0% imports methods.ts (2 hops)
agent-pane.chat-width/wide Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.chat-width/wide Apple System Colors unchanged 0% imports methods.ts (2 hops)
agent-pane.chat-width/wide-blocks Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.chat-width/wide-blocks Apple System Colors unchanged 0% imports methods.ts (2 hops)
agent-pane.composer-edge/short-edge Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.composer-edge/short-edge Apple System Colors unchanged 0% imports methods.ts (2 hops)
agent-pane.composer-edge/wide-edge Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.composer-edge/wide-edge Apple System Colors unchanged 0% imports methods.ts (2 hops)
agent-pane.composer-edge/no-edge Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.composer-edge/no-edge Apple System Colors unchanged 0% imports methods.ts (2 hops)
agent-pane.composer-floats/scrolled-to-end Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.composer-floats/scrolled-to-end Apple System Colors unchanged 0% imports methods.ts (2 hops)
agent-pane.composer-floats/composer-grown Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.composer-floats/composer-grown Apple System Colors unchanged 0% imports methods.ts (2 hops)
agent-pane.continuation-spacing/shipped Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.continuation-spacing/shipped Apple System Colors unchanged 0% imports methods.ts (2 hops)
agent-pane.continuation-spacing/two-thirds Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.continuation-spacing/two-thirds Apple System Colors unchanged 0% imports methods.ts (2 hops)
agent-pane.continuation-spacing/half-tucked Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.continuation-spacing/half-tucked Apple System Colors unchanged 0% imports methods.ts (2 hops)
agent-pane.continuation-spacing/before Apple System Colors Light unchanged 0% imports methods.ts (2 hops)
agent-pane.continuation-spacing/before Apple System Colors unchanged 0% imports methods.ts (2 hops)

Pick other states with a Gallery: entry.id, entry.id/variant line in the PR body.

- A notice turn counts as delivered once the harness writer confirmed its prompt
  (confirmed write), and each notice keeps its own landing/written mark.
- inbox_review arms or disarms the wake timer under the lock it decided under, so an
  older review never aborts a newer timer.
- trim never drops a message a notice in flight or landing carries.
- A notice checks the queue and the turn cap again at dispatch (notice_held).
- A chat closing meanwhile answers a notice's senders with the closing error.
- A resend of a noticed item answers with its notice's logged outcome.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-1288bbb2b5d2f5c6b66a
…nt-inbox

# Conflicts:
#	cmux-tui/crates/acpmux/src/hub/turns.rs
@cursor

cursor Bot commented Oct 30, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

@teamleaderleo teamleaderleo left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Review (codex gpt-6.1-sol high fast, independent review): SHIP at 69e370e

Sol reviewed this head because Astra's subrouter account hit its usage limit partway through the review. It covers the third merge with base. The one conflict is at the end of finish_turn: inbox_turn_ended now runs before base's new stopped_early return. A notice turn that base's Stop ends before its prompt was written still leaves its messages new, because notice_done gates on Inbox::written.

Earlier rounds:

  • Round 9, Astra: SHIP at 201052a (base's peer_person_stop pin and a clean merge).
  • Round 8, Astra: SHIP at b3e26bf (the second merge, message_events scanning inbox records).
  • Round 7, Astra: SHIP at f54c0d1 (the first merge and the CLI flags).
  • Round 6, Astra: SHIP at bf8dae1 (the inbox logic).

CI at this head: https://github.com/manaflow-ai/cmux/actions/runs/41053769339 ran fmt, protocol IR, clippy, lib tests (2193 passed) and every integration test binary. Codegen is clean. Every red fails the same way on base 7365192 (listed in the PR body), or failed on GitHub's API rate limit.

@github-actions

Copy link
Copy Markdown
Contributor

Shipped in:

@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

@lawrencecchen

Copy link
Copy Markdown
Contributor

Base red from this PR on feat-cmux-next: plain_kind also took a cmux agent message (provenance agent_message with a msgId, sent by the cmux daemon's pump) into the inbox, so 5 agents_chat_tools tests and agent_message_reply_in_place fail (reply in place refused, a send to a chat that cannot run says delivered, an idle chat runs no turn). Fix: #28416 keeps only that kind out; acpmux send/--from and the MCP send tool still go to the inbox.

This branch was previously deployed

3 inactive deployments
artifacts — 69e370e3 Deployed Oct 30, 2026 by teamleaderleo via add the Windows daemon to the tree #82958
ci-routing-agent — 69e370e3 Deployed Oct 30, 2026 by github-actions[bot] via cmux-next macOS placement #49610
sentry-dsyms — 69e370e3 Deployed Oct 30, 2026 by teamleaderleo via upload cmux-tui dSYMs to Sentry #82958
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:normal Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants