acpmux: send --class block|question|result|fyi

Merged
TE
5d ago
+670-13

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?"

What

First piece of the coordinator inbox. A message now carries its class: what it asks of the session it is sent to. The coordinator inbox mode (follow-up PR) uses the class to decide which messages wake a coordinator (blocks and questions) and which wait for its inbox digest (results and FYIs).

  • acpmux send --class block|question|result|fyi, and class on the acpmux MCP send tool, set _meta.acpmux.class.
  • The daemon accepts only these four values. Anything else is refused as invalid params, with data.reason class.value_refused. The MCP tool refuses it before sending.
  • New prompt_class.rs holds PromptClass and declared(), the one definition shared with the inbox-digest renderer.
  • Only another agent's message keeps a class (digest.rs agent_sender):
    • with no class it counts as result;
    • a lane's report (lane_reports.rs) and a lane's task result card (tasks.rs) count as result;
    • another session's message (session_message provenance) keeps its sender, and so its class, when a paused agent pair releases it (agent_pairs.rs).
    • the person's own prompt has none, whatever it sends.
  • The class is stored on the queued prompt (PromptRun.class) and written to its queued record, so a restart rebuilds it. A record from before this change reads as result for an agent's message.
  • A send that only the mesh relay reaches refuses any class but result (mesh.no_class): the relay cannot carry it. The class is checked before a peer's prompt is forwarded and when _acpmux/send_queue takes a send.
  • A queue_edit update makes the prompt the person's, so the class is dropped along with fromAgent.
  • Queue rows show it as class: SessionSummary.queue (QueuedPromptSummary) and the _acpmux/composers queue. Each message in agent_digest provenance names its class.
  • docs/acpmux-schema.json, spec/acpmux-protocol.json and the generated web types are updated.
    • The spec bless also adds _acpmux/mesh_control, which base's committed IR was missing.

Tests

  • Red first: commit 97169ec (tests only) fails 3/3 of hub_integration prompt_class::* (fleet job 66f78bcb3b8a9298f05aec66, after Actions run 40935272800 was cancelled for queueing).
  • Green at the fix (run 40935684163, then a fmt-only amend):
    • acpmux --lib: 1912 passed;
    • hub_integration: 546 passed, 2 failed, both base reds (below);
    • clippy -D warnings clean.
  • Review fixes, red first again:
    • round 1: the mesh relay, pre-forward validation, agent-pair holds keeping the class (fbbf5c4; run 40938537656);
    • round 2: a released agent-pair message and a task result card lost their sender, so their class. Tests-only commit d8a77c3 fails all 3 new checks on the missing fromAgent (run 40942026978); the fix 8603302 passes them (run 40942124836: --lib 1914 passed, hub_integration 548 passed with the same 2 base reds, mesh_relay 16 passed, fmt and clippy clean).
  • Load flakes seen once in run 40938537656 (stop::a_cancel_ends... at 229 ms against a 200 ms budget, held_queue::agent_messages_joined_to_a_send_whose_agent_cannot_start_are_not_held timing out) pass 5 of 5 alone at both the head and base (run 40939960419).
  • New tests:
    • hub_integration/prompt_class.rs: rows, the composers queue, queued records, an edit dropping the class, refusal of other values, and the class surviving a restart;
    • an acpmux_mcp send test;
    • a CLI parse test and a PromptId::params test;
    • a lane report queues as result;
    • agent_pairs::a_released_message_keeps_its_sender_and_its_class and tasks::a_result_card_is_the_lanes_message_and_a_result;
    • unit tests in prompt_class.rs.
  • Base reds, the same test failing with the same error at base plus the tests-only commit, 3 runs out of 3 (run for source 7fd0a4a9):
    • auto_resume::retry::stop_before_a_retrys_turn_starts_prevents_it: auto_resume_retry.rs:222, "the turn was stopped before this prompt reached the agent";
    • transcript_events::orchestration_fields_reach_subagent_events_and_session_summary: transcript_events.rs:721, Null != 1234.

No capture: the only webviews change is the generated protocol types (a new optional class field on queue rows); no component reads it, so nothing on screen changes.

Risk

Low. The change is additive: one new optional wire field, and one new field in queue rows that defaults to null for a peer on an older acpmux. Delivery is unchanged: nothing reads the class to steer, queue or start a turn yet.

The one new refusal: a session/prompt whose _meta.acpmux.class is not one of the four values. No client sent that field before this change.

Agent session: agent session

šŸ¤– Generated with Claude Code