acpmux: a person's Stop of a chat on a peer holds the other agents' messages there
Leo's asks:
i literally can't stop you i clicked the stop button and it didnt do anything so this is still busted didnt we say we were going to fix this in acpmux?
this is so awkward the stop button doesnt really work and resume doesnt really appear!?
What was wrong
The person's Stop on a local chat holds the other agents' messages queued behind the stopped turn (hub/cancel.rs person_stop, merged in #27685 and before it). A chat that runs on an ssh peer didn't get that. The daemon forwarded the Stop as a bare session/cancel, and the peer daemon's link is never the person on the peer (hub/person.rs), so the peer ran a plain cancel. The stopped turn ended and the next agent message started a turn right away. To the person, Stop looked like it did nothing.
The change
- This daemon forwards the person's Stop with
_meta.acpmux.personStop: true. It covers both the notification and the request forms (requests.rs,peer_forward.rs). - Only a peer that lists the new
personStopfeature in itsinitializegets the field (peer.rs). This is the same capability check the other peer features use. An older peer gets the plain cancel it always took: no error, and its behavior is the same as before. - A peer accepts
personStoponly from a peer daemon's own link:PeerTokenauth,Control::Peer, not forwarded for its Web client (via: web) or for a paired machine (via: device). It then runs the sameperson_stopit runs for its own person, so the hold and the escalation (#27685) behave the same as on a local chat (server/requests/person_stop.rs). - Any
personStopa client writes into its own cancel is dropped before the forward. Only this daemon's word that its person pressed Stop goes through.
The field can only hold agent messages back, which narrows what runs. The cancel itself is something the link could already send.
peer.rs is over its god-file budget on base. The handshake's feature reads now share one set helper, so the file doesn't grow.
Tests
cmux-tui/crates/acpmux/tests/peer_person_stop.rs:
- Two real daemons. B runs an
until-cancelturn with an agent message queued behind it. A's person presses Stop onb/lane, as a notification and as a request. B logsqueue_pausedwithreason: "stop"holding the agent message, and that message is still queued and starts no turn after the stopped one ends. - A fake peer records what A forwards. A peer that lists
personStopgets the field; one that doesn't gets a plain cancel. A non-person client's Stop never carries it, even when the client writes the field itself.
Command: cargo test -p acpmux --test peer_person_stop (cmux-ci, Linux 32 vCPU).
- Red at the test-only commit ffe523d4a26c: all 3 fail ("the peer took the person's Stop as a plain cancel: no hold of the agent message"; the forwarded cancel carried no
personStop): https://github.com/manaflow-ai/cmux/actions/runs/41024570975 - Green at head 24c837b119ee: the 3 new tests, plus
session_control_peer_cli,remote_guard,peer_long_turn,peer_degraded, the lib tests matching peer/cancel/person (260 passed), andcargo clippy -p acpmux --all-targets -D warnings: https://github.com/manaflow-ai/cmux/actions/runs/41024952631
Observed / Not run
- Observed: the two cmux-ci runs above.
- Not run: a live Mac app against a real ssh peer. This is daemon-only; the pane already sends the same
session/cancel.
Review (gpt-6.1-sol, high): SHIP at 24c837b119ee0150d9296b230fa1ed0817d2b625. No findings; it noted the fixed 500 ms check in the e2e test as a minor robustness point.
The owner Stop of a chat started on the peer itself (remote_guard.rs check_cancel_ownership refuses it today) is a separate change on its own PR.
risk: normal. Daemon-only, and the field can only narrow what runs.
Changelog
- Fixed: Stop on a chat that runs on another machine now holds the other agents' queued messages, as it does for a chat on this Mac.
Agent session: agent session
🤖 Generated with Claude Code