acpmux: agent-to-agent messages show their sender; sub-sessions show in the transcript and summary

Merged
TE
15d ago
+5187-155

Agent-to-agent work in acpmux is now visible in the agent pane, and a lane's acpmux send report draws as a message from that lane, not as the person's prompt.

What changes

  • Who sent a prompt. When a connection proves it runs inside an acpmux session (the caller token acpmux puts in that session's env) and prompts a different session without a provenance of its own, the daemon records {kind: "session_message", from, fromName, to} on the user_message and on its queued record. The pane draws it as "Received message from ›", and the name opens that chat. The person's connections, prompts to the sender's own session, the acpmux TUI and a shell's acpmux send stay the person's. A provenance the sender gives is still checked and kept as before. This is the daemon-side fix the #22761 review asked for in place of a client-name guess.
  • Sub-session messages. A parent's send_message waits behind the child's running turn by default (deliver: after_turn). next_step is opt-in and delivers into the running turn when the child's agent can take a message mid-turn. The reply says which happened, with the queue's length.
  • What a child waits on. get_session and the summary say what a sub-session waits on the person for (a question or a permission), plus its plan step and latest tool.
  • Where the user sees the child. start_session's reply says the child is listed under the parent chat.
  • The child's chat names its sender (parent, sub-session, ancestor or descendant), with the sender's short name.
  • Transcript and summary. Sub-session tool calls draw as cards (started, sent, waiting, reply). The summary lists sub-sessions after subagents, and a row opens the child's chat.

Tests

  • Failing first: cmux-tui/crates/acpmux/tests/subsessions_delivery.rs (e02cbe7, a parent's message waits behind a working child's turn) and tests/hub_integration/prompt_provenance.rs a_prompt_sent_from_inside_another_session_records_that_session_as_its_sender (bf479a0). Both are expected to fail on base and pass on the head.
  • Webview: incomingMessage.test.ts, subsessionCalls.test.ts, subagents/subsessions.test.ts.

Observed / Not run

  • Observed locally: scripts/web-lint.sh passed, prepush-gate.py found no new violations, and cargo fmt is clean.
  • Not run: the Rust tests and the webview tests. Agents don't compile or run tests on Leo's Mac, so CI and the fleet are the first runs. The red-on-base and green-on-head results for the two failing-first tests come from CI.
  • Not done: the Swift side (an open-tab action for a child chat, sidebar nesting). The Swift app also doesn't read session_message yet, so its own transcript still shows such a prompt as the person's.

No capture: the visible change is the agent pane's webview (sub-session cards, "Received message from …", the summary's sub-session rows), which the gallery advisory run captures; the only native path is build-agent-pane-web.sh adding the cards' stylesheet to the pane bundle.

Changelog

  • Changed: Messages that agents send each other (sub-session messages and acpmux send from inside a session) show as "Received message from …" from the sending chat. Sub-sessions show as cards in the transcript and in the summary.

Agent session: cosmic-gray

🤖 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

Acpmux now shows agent-to-agent messages with their sender in the agent pane, and sub-sessions appear in the transcript and summary instead of reading as the person's own prompts.

  • A prompt sent from inside another session with no provenance of its own is recorded as session_message and drawn as "Received message from "; sub-session family messages draw as coming from the sending chat, and the pair guard counts these.
  • send_message queues behind the target's running turn by default (deliver: after_turn); deliver: next_step is opt-in, and a child's next_step to its parent waits for the turn to end since a steer can cancel the parent's tool call. The reply says which happened and the queue length.
  • Running turns expose their current activity (latest tool title, plan step), and session summaries, session rows and get_session report what a sub-session waits on the person for (question or permission); an ancestor may answer a question when the person enables that. These fields are masked for peers and in rows handed to a parent's agent and stay optional in the protocol spec, as schemars emits them; session rows carry the summary's whole field set, watching included.
  • Sub-session tool calls draw as cards in the transcript, including a running call in a live turn as its own card so the working line never names it, and a settled run holding a sub-session card stays unfolded. The summary lists sub-sessions under subagents; names open the child's chat.

Not included: Swift-side support (open-tab action, sidebar nesting); the Swift app still shows session_message prompts as the person's.