Skip to content

acpmux: chat multiplayer A: person author, provenance, answeredBy, queue.not_author - #25268

Merged
teamleaderleo merged 9 commits into
feat-cmux-nextfrom
lane/hb-mp-author
Oct 26, 2026
Merged

teamleaderleo merged 9 commits into
feat-cmux-nextfrom
lane/hb-mp-author

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator

Chat multiplayer, the daemon half, PR A of 2 (author, provenance, answeredBy, queue checks). PR B (hb-mp-presence) adds presence and typing.

The ask, word for word (subsession-card, relaying Leo): "A new ask for acpmux: multiplayer for regular agent chats. Leo, word for word (2026-10-26): "we can still do the regular chat multiplayer" and "we can just send some codexes to do whatever". The design doc is https://github.com/manaflow-ai/cmuxterm-hq/pull/4389 (plans/cmux-next/chat-multiplayer/DESIGN.md), under Codex review. My helper chat-multiplayer is building the webview, render-only behind an adapter. The daemon half is yours: 1. Provenance kind person_message {from, fromName, login, node}. It comes in as _meta.acpmux.author and is accepted only from unix-socket callers. Record it on user_message, queued and steer records, and keep it through peer forwarding. 2. answeredBy {person, name} on permission_decision, taken from author on permission_respond. A losing answer's not_found error carries data {answeredBy, outcome} (first answer wins). 3. An author check on queue_edit, queue_remove and steer_withdraw: refuse with queue.not_author. 4. Ephemeral presence, never stored in the log: _acpmux/typing {sessionId, on} (at most 2 per second, expires after 8 s); a _acpmux/presence {sessionId, participants:[{id,name,viewing,typing,self?}]} notification, and presence in the attach reply; viewing from _acpmux/view, per author. Person ids are tailnet:<login> until accounts are mapped. Codex xhigh review, then land, and reply with the PR number(s). Stop when they're merged."

This builds the contract hb-acpmux sent to the webview lane (#25253 renders it), items 1 to 4.

What changed

  • src/author.rs (new): Author {id, name, login, node} and author(params), which reads _meta.acpmux.author. id is tailnet:<login> or user_<id>, one line of 1 to 256 bytes; name is 1 to 64 characters; login and node are optional, one line of up to 256 bytes each. Unknown fields are dropped, and anything else gets author.value_refused.
  • Which origins may send author: the local unix socket, and a peer link (servedAs peer: the owner's own machine forwarding its unix socket or app), the same way fromAgent survives forwarding. Everything else is refused with author.origin_refused, on every method: Web, the app pane (LocalApp) and a peer forwarding for its own Web client (via: web). server/remote_guard.rs check_inner enforces this. mark_forwarded keeps author (it still drops provenance), and the hub that runs the chat stamps the provenance itself.
  • Trust: an author is only a label plus a restriction. It never satisfies the person rule (hub/person.rs). The one change there: a permission_respond for an ask already answered and still in the settled store passes the person check, but only to reach the not_found it then gets, which changes nothing. An allow that carries an author but no person proof is still permission.person_required, and a test covers that. It never unlocks a permission. Its only effect is to narrow who can change a queued prompt.
  • session/prompt: an author is recorded as _meta.provenance {kind: "person_message", from: id, fromName: name, login?, node?} on user_message, queued and steer records. A person's prompt is not an agent message: it has no agent sender, it doesn't skip the agent-pair person rule, and prompt_withdraw refuses it (withdraw.not_a_message).
  • _acpmux/queue_edit (every op except resume, including withdraw of an unread steer) and _acpmux/queue_remove refuse with queue.not_author unless the caller's author id equals the prompt's. No author on both sides counts as equal (the owner). An agent message counts as the owner's. An edit by the same author keeps the person_message provenance; it is written on queue_updated, so a restart keeps it too. An owner's edit of an agent message still drops it, as before.
  • permission_decision.answeredBy gains {id, name} from author on permission_respond, next to a paired device's {device, feedItem}. A losing answer's not_found (-32002) now carries data {answeredBy, outcome}. answeredBy is null when the owner won or the ask was cancelled.
  • docs/acpmux-schema.json documents all of the above.

Stop conditions (new state, no timer)

hub/permission_settled.rs keeps each chat's settled answers by permission id so a later answer can be told who won. No task or timer is involved. An entry is dropped once it is 10 minutes old (checked on every write and read), when it falls out of the newest 64 for its chat, or when the chat goes away. SteerNow.waiting now maps each unread steer to its author (a HashSet before); entries are removed exactly as before, when the agent answers the steer.

Tests (red at 8a37393, green at the head)

  • prompt_provenance::an_author_is_taken_from_the_unix_socket_and_a_forwarding_peer_only: accepted from unix and Peer; refused from Web, LocalApp and Peer via: web.
  • peer_integration::a_persons_prompt_through_a_peer_is_recorded_as_theirs_on_the_owning_daemon: a prompt forwarded through a peer is still recorded with its author.
  • prompt_provenance::a_persons_queued_prompt_and_steer_record_who_sent_them: covers user_message, queued and steer records.
  • queue_restore::a_persons_queued_prompt_stays_theirs_across_a_crash: provenance and the author check survive a SIGKILL restart, through the author's own edit.
  • prompt_provenance::a_losing_answer_is_told_who_answered_first: data.answeredBy and outcome; null for the owner; no allow without the person's proof.
  • prompt_provenance::only_the_sender_changes_a_waiting_prompt and steer_now::only_the_sender_takes_back_a_persons_steer: not_author for another author and for the owner on a person's prompt; allowed for the same author, and for the owner on the owner's own prompt or an agent's message.
  • Review findings, each with a test: a person's prompt takes no supersede key, new or restored (prompt_provenance::a_persons_prompt_neither_supersedes_nor_is_superseded, author::tests::a_restored_persons_prompt_takes_no_supersede_key); a group answer and a Stop are recorded in the settled store under the same lock (permission_groups::an_answer_after_a_group_answer_is_told_the_group_won, prompt_provenance::an_answer_after_a_stop_is_told_the_ask_was_cancelled).
  • Unit tests: author::tests and permission_settled::tests.
  • Full acpmux run on fast-checks: fmt, clippy -D warnings, lib, hub_integration, durable_agents, peer_integration all green.

Size

About 920 changed lines: roughly 470 are source and the rest are tests and the schema doc. claude-sr2 cleared this size as long as the hand-written part stays under ~800.

Shared with PR B

src/author.rs (crate::author) is shared with hb-mp-presence's #25285, which carries an identical copy. Whichever PR lands second drops its copy.

No capture: daemon only; the webview lane draws it.

Agent session: big-red
🤖 Generated with Claude Code

@teamleaderleo
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 26, 2026 02:34 — with GitHub Actions Inactive
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Owned by hb-acpmux on big-red · session

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review 307f6da: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr25268 -f ref=lane/hb-mp-author -f platform=macos -f backend_mode=local

red: cmux-next checks (god files, concurrency, crash safety, l10n); no finished base run on 943bb82 to compare; do not merge until green; see lane-rules

@github-actions github-actions Bot added the risk:high Risk tier (docs/ci/risk-tiers.md) label Oct 26, 2026
@blacksmith-sh

blacksmith-sh Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Found 9 test failures on Blacksmith runners:

Failures

Test View Logs
/tmp/tmp67ir4aj2/summary-fail.json (live):/summary-fail.json (live): View Logs
/tmp/tmprxjmtlz_/summary-missing.json (live):/summary-missing.json (live): View Logs
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows/
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows
View Logs
[new_workspaces] the window draws no frames/[new_workspaces] the window draws no frames View Logs
[tabs] debug.tab_switch timed 0 of 40 switches/
[tabs] debug.tab_switch timed 0 of 40 switches
View Logs
[tabs] the window draws no frames/[tabs] the window draws no frames View Logs
bench OTHER_SWIFT_FLAGS is 'OTHER_SWIFT_FLAGS=$(inherited) -DCMUX_BENCH_HOOKS -DCMUX_BL
OCKING_CHECKS'/
bench OTHER_SWIFT_FLAGS is 'OTHER_SWIFT_FLAGS=$(inherited) -DCMUX_BENCH_HOOKS -DCMUX_BL
OCKING_CHECKS'
View Logs
bench OTHER_SWIFT_FLAGS is 'OTHER_SWIFT_FLAGS=$(inherited) -DCMUX_BENCH_HOOKS -DCMUX_BL
OCKING_CHECKS'/
bench OTHER_SWIFT_FLAGS is 'OTHER_SWIFT_FLAGS=$(inherited) -DCMUX_BENCH_HOOKS -DCMUX_BL
OCKING_CHECKS'
View Logs
convention violations found (ERROR lines above)./
convention violations found (ERROR lines above).
View Logs

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

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Bead cx-o0h0d created for this PR by the cmux coordinator.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

cmux-next fast gates / post cmux-next/fast-gates is red on 19 of 20 fresh feat-cmux-next PR heads in the last 2:00:00, so it is most likely a base red, not this PR's. The base-reds lane has it; a base merge picks up the fix once it lands.

teamleaderleo and others added 4 commits October 26, 2026 03:15
…swers

Red first: the author is not read yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Chat multiplayer, the daemon half (hq#4389): _meta.acpmux.author from the unix
socket or a forwarding peer is stamped as a person_message provenance on
user_message, queued and steer records, kept by the same author's edit and
across a restart; named in answeredBy {id, name} on permission_decision, and
told to a losing answer in not_found data {answeredBy, outcome}; and checked by
queue_edit, queue_remove and steer withdraw (queue.not_author).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…old to a later one

Review round 1: supersede.rs matches senders by agent name, so every person
counted as the owner; and a group answer removed the asks before the settled
store knew of them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cursor

cursor Bot commented Oct 26, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

@teamleaderleo
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 26, 2026 03:26 — with GitHub Actions Inactive
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

cmux-next guards / nightly-guards / workflow-guard-tests / release-notary is red on 11 of 16 fresh feat-cmux-next PR heads in the last 2:00:00, so it is most likely a base red, not this PR's. The base-reds lane has it; a base merge picks up the fix once it lands.

Review round 2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…efuse an author beside a provenance

Review: the author check snapshot left a gap before the change, and a prompt
naming both an author and an agent provenance ran as the agent's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…a peer

Review: the forward dropped the provenance and kept the author.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teamleaderleo
teamleaderleo merged commit d41c6b1 into feat-cmux-next Oct 26, 2026
117 of 124 checks passed
@teamleaderleo
teamleaderleo deleted the lane/hb-mp-author branch October 26, 2026 04:49
teamleaderleo added a commit that referenced this pull request Oct 26, 2026
…hange

acpmux now records a person author on queued prompts (#25268) and refuses
another author's queue edit with queue.not_author. The queue entry keeps the
author from its queued or queue_updated record and through acpmux's list; its
row shows an inline author chip and offers no Steer, Delete, edit, move or
menu, and its keys only walk.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-847de1e22cadc71cd076
@github-actions

Copy link
Copy Markdown
Contributor

Shipped in:

teamleaderleo added a commit that referenced this pull request Oct 26, 2026
…o change (#25357)

* cmux NEXT chat: shared chat faces (author, presence, typing, answered by), render only

Leo, 2026-10-26: "we can still do the regular chat multiplayer".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16f9b-2fcb-7c72-b06e-af8fce00adcb&host=cosmic-gray

* shared chat faces: Chief adapter, gallery Chief variant, a11y and coverage fixes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16f9b-2fcb-7c72-b06e-af8fce00adcb&host=cosmic-gray

* shared chat faces: format, covers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16f9b-2fcb-7c72-b06e-af8fce00adcb&host=cosmic-gray

* shared chat faces: adapter reads hb-acpmux's final shapes (answeredBy {id,name}, own id instead of self)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16f9b-2fcb-7c72-b06e-af8fce00adcb&host=cosmic-gray

* shared chat faces: typing line on the composer's edge; one letter on a small face

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16f9b-2fcb-7c72-b06e-af8fce00adcb&host=cosmic-gray

* shared chat faces: names lose hidden characters and are bounded in both adapters; ids stay whole

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16f9b-2fcb-7c72-b06e-af8fce00adcb&host=cosmic-gray

* shared chat faces: format

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16f9b-2fcb-7c72-b06e-af8fce00adcb&host=cosmic-gray

* shared chat faces: the Chief adapter moves to the Home mount PR (size)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a16f9b-2fcb-7c72-b06e-af8fce00adcb&host=cosmic-gray

* multiplayer: a small face's one letter is a whole grapheme

initials(name).slice(0, 1) cut the joined string by UTF-16 units, so a name
starting with an astral character drew a lone surrogate. initials takes the
count and slices graphemes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-847de1e22cadc71cd076

* multiplayer: a teammate's queued prompt names them and is theirs to change

acpmux now records a person author on queued prompts (#25268) and refuses
another author's queue edit with queue.not_author. The queue entry keeps the
author from its queued or queue_updated record and through acpmux's list; its
row shows an inline author chip and offers no Steer, Delete, edit, move or
menu, and its keys only walk.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-847de1e22cadc71cd076

* fmt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-847de1e22cadc71cd076

* multiplayer: gallery variants for a teammate's queued prompt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-847de1e22cadc71cd076

* queue author: no move passes a teammate's prompt; reducer and menu paths tested

A move shifts every row it passes, so Alt+arrows and the menu's moves now
stop at a teammate's prompt (the menu shows them disabled). queueAuthor.test.ts
drives the pane through queued, queue_updated, queue_order, acpmux's list and
the re-read once a sender's chat is listed, and checks the author each time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-847de1e22cadc71cd076

* multiplayer: a name loses every invisible format character, Unicode tags too

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-847de1e22cadc71cd076

* queue author: kept by prompt id, so an attach's page that starts after the queued record keeps it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-847de1e22cadc71cd076

* multiplayer: spacing and sizes on the design scale

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-847de1e22cadc71cd076

* tests: formatted

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-847de1e22cadc71cd076

* Keep a teammate's queue author through a kept-tail redraw

A switch back to a chat (or a new tab) draws it from its kept tail before
the attach lands. When the authored queued record had fallen out of that
tail, the author-less session list won and the row became editable. The
kept queue's authors now seed the author cache.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-847de1e22cadc71cd076

* Queue author: an open edit closes on a teammate's row; a cold open takes the listed author

An edit opened while the row named no author closes, unsaved, once the
authored record arrives; its text goes back to the composer, and save
refuses a teammate's prompt. A cold open (the authored record older than
the attach page) takes the author acpmux's queue list names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-847de1e22cadc71cd076

* Queue author: a listed null author is the owner's; a native menu picked after a row turned a teammate's does nothing

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-847de1e22cadc71cd076

* Queue: an edit pulled back after its images load rechecks the row is still ours

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-847de1e22cadc71cd076

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

This branch had an error being deployed

2 failed and 1 inactive deployments
sentry-dsyms — 37582265 Deployed Oct 26, 2026 by teamleaderleo via upload cmux-tui dSYMs to Sentry #54917
artifacts — 37582265 Deployed Oct 26, 2026 by teamleaderleo via publish to R2 #54917
cmux-tui-merge-tree — 37582265 Deployed Oct 26, 2026 by teamleaderleo via publish the merge tree #6134
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:high Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant