acpmux: _acpmux/queue_text, and the sender session and harness on queued agent messages
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." and "you can't you just deal with this? Like ACPmux can't be this stuff. How do swarms work?"
For the queued-messages UI, as quoted by its lane: "If I expand it, I expect to see the whole thing" and "why are some of these icons still only placeholders?"
This PR adds the two daemon fields that UI needs.
1. A queued prompt's whole text: _acpmux/queue_text
Each queue row (the session summary's queue, and _acpmux/composers) carries only a 200-character prompt preview. So a queued agent message expanded after a reload showed cut text.
No existing read returns one queued prompt by id. _acpmux/events has the queued record with the full text, but only by kind (kinds: ["queued"]), so a client would have to page the whole log to find one prompt.
New method: _acpmux/queue_text {sessionId, promptId} returns {sessionId, promptId, turnId, text, prompt, fromAgent, fromSessionId, fromHarness}.
- It reads from the live queue only.
- Once the prompt has left the queue it answers
queue.not_found(-32002,data.reason). After that the text is in the prompt'suser_messagerecord (it ran) or itsqueuedrecord (it left another way). - Authorization is the same as
_acpmux/events: it is a read for anyone who may read the session's log, which already holds the same text. It is on the read-only pairing allow list. A peer's session is forwarded as usual. - Capability
queue-text-v1. - Full text is NOT added to every row. A coordinator's queue may hold hundreds of messages of up to 64 KB each.
How this relates to draft #26855 (_acpmux/queue_get): that PR is the owner's approval read for a guest's held prompt. It is owner-only, behind the Web-control check, returns revision, and is stacked on the guest-approval work. This PR is a display read with log-read authorization. Neither replaces the other. If both land, queue_get could reuse Hub::queued_text.
2. Sender session and harness: fromSessionId, fromHarness
An agent message's provenance now carries fromSessionId and fromHarness (the sending session's harness family: claude, codex, ...). The daemon stamps them only when it bound them itself:
- A local proven caller (its caller token):
acpmux sendfrom inside a session, including--no-waitthrough the outbox, which proves the caller again. - A lane's report to its parent.
- A peer link (
Origin::Peer): the forwarding daemon sets them from its own proven caller, inpeer_forward.rsname_sender. - The mesh relay: the hub message's
from.sessionId, which the sending daemon set from its proven caller. Session id only: the hub'sMeshSenderschema (cmux-platformopenapi.json) has no harness field andadditionalProperties: false, so sending one would get the message refused. Adding a harness to the hub is a follow-up there.
Unproven values are dropped, never passed through:
- From any connection other than a peer link, a client's
fromSessionIdorfromHarnessis ignored. - A forwarding daemon removes the client's values before it sets its own.
- A peer prompt marked
via(forwarded for that peer's Web, app or paired-device client) names no sender. - Malformed values are dropped one by one, never refused.
- A value bound from a caller token replaces any claimed value.
Where they show up:
_meta.provenanceon thequeuedanduser_messagerecords.- Queue rows in the session summary and in
_acpmux/composers. _acpmux/queue_text.
They survive a restart (the provenance is restored with the queue) and an edit drops them along with the provenance.
These are display fields only. Nothing makes an authorization decision from them.
acpmux send --no-wait (the outbox) now gets a proper sender too:
- The outbox already records the caller its connection proved when it took the message. It is saved in
outbox.json, so a retry after a restart keeps it, and it proves that caller again on delivery. Its prompts therefore get the proven session'ssession_messageprovenance withfromSessionIdandfromHarness.tests/send_async.rsa_no_wait_send_from_a_session_names_its_proven_sendernow holds this, including that claimed values are ignored. hub/prompt_line.rscountsacpmux-outbox#ā¦as a shell send. A--no-waitmessage that has no proven sender but starts with a[name] wordsline therefore reads as that name, for display only, the same asacpmux-cli#andacpmux-peer#. (A sub-session's note to its parent was showing as a plain box of raw text.)
Not covered: sub-session messages and task briefs. Their hub-set provenance already names the sender's session in from.
Docs: docs/acpmux-schema.json, spec/acpmux-protocol.json, README, and the regenerated web protocol types.
The spec also gains _acpmux/mesh_control. The registry already lists it, but the committed IR did not, so protocol_ir_is_current was red on base.
Overlap
- #27038 (persist session message events) adds a separate peer-carried
messageSender {name, sessionId, host}for the durable_acpmux/messagerecord and edits the samename_senderfunction. The two changes are additive. Whichever lands second merges base. hub/queue.rsanddigest.rsare untouched.lane_reports.rsgets 6 additive lines.
Stack size
durable_agents was already flaky on base because a debug daemon overflowed tokio's 2 MiB worker stack. Adding to the session/prompt arm made it worse, so the sender stamping and the queue_text arm now live in their own functions outside dispatch_request. #27643 fixes the stack itself and lands before this PR.
Risk
risk:low. This adds a new read-only method and new display-only fields on existing records. Nothing changes who may do what. The remaining risk is that a peer daemon older than this one passes a local unix client's claimed values through unchanged. That is the same trust fromAgent already has, and it is still display only.
Tests
Red first: https://github.com/manaflow-ai/cmux/actions/runs/40938145206 (at 7db3d020691). The tests-only commit fails 3 hub_integration tests and 1 mesh_relay test on assertions. The fix is green: https://github.com/manaflow-ai/cmux/actions/runs/40940033703 (at 6d92b119988: the lib tests including protocol_ir_is_current, hub_integration prompt_provenance::, mesh_relay, lane_reports, and clippy -D warnings).
prompt_provenance::a_queued_agent_message_reads_whole_and_names_its_senders_session_and_harnessprompt_provenance::a_senders_session_and_harness_come_from_a_proven_caller_or_a_peer_link_onlyprompt_provenance::a_prompt_sent_from_inside_another_session_records_that_session_as_its_sender(updated)mesh_relay::a_relayed_message_names_its_sending_session_for_display- unit tests
prompt_provenance::sender_tests
Capture
No capture: daemon protocol only. The only webviews files touched are the generated protocol types, so nothing in the UI changes; the UI lane shows these fields.
Changelog
none
Agent session: agent session
š¤ Generated with Claude Code