acpmux: other agents' messages never go into the person's own turn; they run as a turn of their own after it

Merged
TE
5d ago
+283-32

Leo: "why do the x messages from y sessions seem to always piggyback on my messages instead of going on their own"

What was wrong

  • Inbox notices. When the person's prompt started a turn, inbox_prompt_written ran inbox_review, which saw a running turn that steers and steered the pending notice into it right away. In one coordinator log that happened 57 times, each notice written 1 ms after the person's prompt. So "[inbox] New messages from other agents…" landed in the person's turn, right under their words.
  • Other agents' steers. Messages that aren't inbox items (peer and mesh-relay forwards, team messages, cmux agent message) steered into the person's turn at its next tool step. Claude Code's adapter batched them as "N messages from M sessions arrived during this step", the line Leo is quoting.

What changes

  • TurnInfo::persons records whether the person's own prompt started the turn (inbox::persons_prompt: no agent sender, and not a notice, an inbox digest or the turn cap's notice). It is set in run_prompt_joined. Carried turns, restored host turns and test turns default to false, which is the old behaviour.
  • Inbox (hub/inbox.rs): inbox_review and notice_held never steer a notice into the person's turn. Messages held back that way set Inbox::held_by_person, so they go in as one turn of their own as soon as the chat is idle, whatever their class. Before, an FYI or an action from an unrelated lane would have waited the 10-minute flush. The flag clears when a notice goes.
  • Other agents' steers (hub/turns.rs, checked again as the steer is written in steer_end.rs): another agent's steer queues behind the person's turn instead of going in, and its interrupt stops nothing. A steer-only one is refused with steer.unavailable / personsTurn. The person's own steers, Send now, and an inbox digest the agent asked to drain still go in.
  • The person's prompt runs exactly as written. The batch runs as its own turn after it, so the transcript draws it as its own inbox row (InboxNoticeCard), never inside the person's turn. No web change was needed. Older logs stay as they are.

Tests (tests/hub_integration/agent_inbox.rs)

  • New: a_notice_never_goes_into_the_persons_turn_and_runs_on_its_own_after_it (fails on base: the notice steers in at the turn's start).
  • New: another_agents_steer_waits_for_the_persons_turn (fails on base: the steer goes in).
  • a_queued_prompt_never_waits_behind_agent_messages (#27566's case) used to assert the old behaviour, that the notice goes into the queued person's turn. It now asserts that the notice runs as its own turn after that turn and that the person's words are unchanged.
  • three_hundred_agent_messages_go_in_as_one_notice_at_the_next_tool_step and another_agents_steer_goes_in_when_no_prompt_waits now run under a turn started by another agent's first work, since steering into a running turn is still how a non-person turn takes notices.

Risk: this changes delivery (a state machine). A coordinator no longer reads lane messages in the middle of a long turn Leo started; it reads them right after. That is the ask.

Agent session: agent session

🤖 Generated with Claude Code


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


Summary by cubic

Other agents' messages no longer piggyback on the person's turn. Inbox notices used to steer into the person's turn the moment its prompt was written, and other agents' steers went in at its next tool step. A turn the person's own prompt started now takes neither; both wait for the turn's end and run as a turn of their own, immediately.

  • TurnInfo::persons records whether the person's own prompt started the turn. A retry, a continuation, and other acpmux-owned prompts are not the person's; carried, restored, and test turns default to false.
  • This applies only while the agent inbox is on; with agentInbox off, agents' sends steer in as before.
  • turn_started now logs persons, so a restart and a new harness keep the turn the person's own.
  • Notices held back by the person's turn are due as soon as the chat is idle, whatever their class, instead of waiting the 10-minute flush; messages already waiting as that turn starts are held back the same way. Held-back messages never steer into a turn carried on from it, and a Stop that cuts the turn still releases them.
  • Another agent's steer-only prompt is refused with steer.unavailable / personsTurn; its interrupt stops nothing.
  • The person's own steers, Send now, agent-requested inbox digests, and a sub-session family message from the turn's own chat still go in.
  • Delivery changes: a coordinator reads lane messages right after the person's turn ends, not in its middle.