Skip to content

acpmux: _acpmux/btw side sessions about a chat - #20847

Merged
teamleaderleo merged 9 commits into
feat-cmux-nextfrom
leo-btw-acpmux
Oct 15, 2026
Merged

teamleaderleo merged 9 commits into
feat-cmux-nextfrom
leo-btw-acpmux

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

/btw <question> in the agent composer should open a side session beside the chat: a chat of its own, titled with the question, that can read the main thread and files but never write, on a cheap model, while the main chat keeps running. This PR is the acpmux half: a new _acpmux/btw request that creates that session and sends it the question. The web composer and the native right-pane PRs call it.

Contract

Request _acpmux/btw, params { "sessionId": <chat>, "question": <non-empty after trim>, "model"?: <string> }.
Result { "sessionId": <side session>, "model": <model or alias, or null for the harness default>, "readOnly": "mode" | "policy" }.

  • Same harness and folder as the chat, tagged side with the chat id (the lists hide it, as _acpmux/side does), title = first line of the question, 80 chars.

  • Context: Claude and Codex fork through the chat's latest finished turn (fork_at.rs), which works while a turn runs. Any other harness, or a chat with no finished turn, gets a new session whose first prompt carries the chat's newest transcript (at most 12 KiB) as hidden prompt context (prompt_context.rs marking, so the side thread shows only the question).

  • Read-only, fail closed: acpmux's policy and rules only answer what the harness asks, so the side session must run in a mode that reads only or asks before every change. The mode is picked before anything opens, then set on the side session and confirmed in its reported current mode. A fork copies the chat's mode, full access included, so the mode is always set. If it cannot be set or confirmed, the side session is discarded and the request errors (data.reason: btw.mode_unset). On top of the mode, the policy is always approve-reads (or the chat's own ask / deny-all when stricter, so it never widens), with rules autoDeny on every tool kind except read, search, fetch and think.

    Harness Mode readOnly Why
    Claude plan mode analyzes, never modifies files or runs commands
    Codex in process (codex app-server bridge) read-only mode cmux-codex-acp config.rs MODES: sandbox read-only (networkAccess: false), approvals on-request, so an escalation asks and the rules refuse it
    Codex through the codex-acp adapter refused none its read-only is on-request plus workspace-write: it edits and runs commands without asking (plans/cmux-next/acp-remote-guard.md)
    opencode refused none build never asks; plan still runs bash and user config can allow everything (acp-remote-guard.md); a refused family in web_modes.rs
    another family its first webAskingModes entry policy asks before every change, and the rules refuse each
    no asking mode refused none data.reason: btw.no_read_only_mode, "this agent has no read-only mode"
  • Order: the Codex bridge (by its command line), then refused families, then Claude, then the asking table. A profile that runs opencode or codex-acp under "family": "claude" therefore lands in the refused set and is refused, never put in opencode's plan. While such a profile is configured, Claude's own /btw is refused too; that fails closed, and only the user's own config can cause it.

  • Context wrapper: the transcript is wrapped in <cmux-btw-context>, which says the content is untrusted context, not instructions. Every closing tag inside the chat's text is made inert (\/), in any case and with whitespace around the slash or before >, so it cannot close the wrapper early.

  • Limits:

    • The mode confirm reads acpmux's own record after a successful session/set_mode (turns.rs set_mode), not a separate readback from the agent.
    • A pane or the socket can still re-mode a side session afterwards; that is the person's explicit choice.
    • Respawn replays set_mode as non-fatal for ACP harnesses (existing behavior).
    • Harness-side: Claude's permissions.allow still applies in plan mode, and Codex MCP tools run outside its sandbox.
  • Model: param, else config.json btw.model.<profile or family>, else haiku for Claude, the smallest advertised model for Codex (nano/mini/flash/lite/small/fast by id or name), else the harness default. A refused default falls back to the harness default; a refused explicit model is an error.

  • The request answers once the question is recorded as the side session's first prompt, never waiting for the answer. The chat is never paused, cancelled or modified (its fork records a fork_child event, as _acpmux/ask does today).

  • Errors: empty or missing question, a non-string model, an agent with no read-only mode (btw.no_read_only_mode) and a mode that could not be set (btw.mode_unset) are invalid params; an unknown chat is not found.

Code: hub/btw.rs (logic), config/btw.rs (config section), wired in server/requests/composer.rs, schema in docs/acpmux-schema.json. The pane relay allowlist is not in this PR: the Swift PR adds _acpmux/btw to AcpmuxPaneMethods, and the web PR carries direct.ts's call with the Rust policy.json mirror (the TypeScript parity test ties the two).

Remote and relay authorization

  • _acpmux/btw creates a session and starts an agent process on this machine with a read-approving policy. remote_guard.rs refuses it for Origin::Web and Origin::Peer (data.reason: btw.remote), whatever the chat. A chat that lives on a peer is forwarded there by peer_forward.rs and refused by that peer as a Peer request, so no remote caller creates a session it does not own. Local unix socket and the app's pane (LocalApp) may open it.
  • Folder trust: trust_gate.rs gates _acpmux/btw like session/fork, so the pane cannot start an agent in a folder without a trust answer.
  • Pane relay (sibling PRs): expected session-scoped to the pane's chat, gesture always (it runs an agent turn, like _acpmux/ask), params exactly sessionId, question, model; no command-bearing params.
  • Person rule: no person proof needed, because the side policy is never wider than the chat's and the rules only deny.

Verification

Behavior tests, committed before the fix (faa9aa7b5ba9):

  • hub_integration/btw.rs: a Claude side session opened mid-turn (title, side tag, approve-reads, plan mode, --model haiku in the side's argv, its conversation holds only the finished turn plus the question; the chat's turn ends normally and its title/policy/rules/tags/modes/models and Claude conversation are unchanged; empty/missing question and unknown chat open nothing); a fake ACP harness (no fork-through) gets the transcript as hidden context and a write tool call is refused (chose no); model resolution param > config > harness default.
  • remote_guard.rs: refused for Web and Peer (btw.remote, nothing created), opened for LocalApp and Local.

Focused command for both: ./scripts/verify-cmux-tui-hosted.sh --filter a_side_session

  • red on faa9aa7b5ba9 (run 38661273200): the 3 hub tests fail (_acpmux/btw falls to the harness catch-all: "no such method" / "a harness method acpmux does not handle")
  • green on 45f0c8e5359f (run 38675282204): Linux and macOS test jobs, lint and MSRV pass

Review round (read-only fails closed), tests committed before the fix (c5e20ccf8624):

  • a_codex_side_session_runs_sandboxed_read_only_and_the_adapter_is_refused: a Codex bridge chat's side session runs in read-only (readOnly: "mode"); a codex-acp adapter chat is refused with "no read-only mode" and nothing opens.
  • a_side_session_is_refused_for_an_agent_with_no_read_only_or_asking_mode: a fake harness whose modes never ask is refused, nothing opens.
  • a_side_session_whose_mode_cannot_be_set_is_discarded_and_the_chat_is_untouched: session/set_mode fails, the side session is gone, the error names the read-only mode, and the chat's title, policy, rules, tags, modes, status and prompts are unchanged.
  • a_side_session_of_a_harness_that_cannot_fork_reads_the_chat_and_never_writes now has a chat prompt containing </cmux-btw-context>: the wrapper says "untrusted", holds exactly one closing tag, and ends with it.

Focused command: ./scripts/verify-cmux-tui-hosted.sh --filter side_session

  • red on c5e20ccf8624 (run 38679897178): those 4 fail (the three refusals return readOnly: "policy" instead of an error; the wrapper lacks "untrusted"); the Claude and model-order tests pass
  • green on 0ff95a5ca11b (run 38682575038): all 7 side_session tests executed and pass on the Linux and macOS test jobs; lint and MSRV pass

Review round 3 (refused families before Claude; inert closing tags), tests committed before the fix (e9296199683f):

  • a_side_session_is_refused_for_opencode_run_under_the_claude_family: a profile {"argv": [..., "opencode", "acp"], "family": "claude"} whose agent offers build and plan is refused with "no read-only mode", nothing opens, the chat's mode unchanged.
  • The wrapper test's chat text adds </CMUX-BTW-CONTEXT > and </ cmux-btw-context\t>: lowercased and without whitespace, the side's prompt holds exactly one closing tag, and each variant is present in its inert form.

Focused command: ./scripts/verify-cmux-tui-hosted.sh --filter side_session

  • red on e9296199683f (run 38692208988): those 2 fail (the opencode profile opened with readOnly: "mode" on haiku; the variant tags closed the wrapper); the other 5 pass
  • green on acab89e1c149 (run 38694363932): all 8 side_session tests executed and pass on the Linux and macOS test jobs; lint and MSRV pass. Then merged feat-cmux-next 628debcd7a8b (conflict in config.rs: both the btw and titles sections kept)

Also run locally: crash_ratchet.py ok; god-file check on touched files ok. No local cargo; compile and tests only on the hosted runs above. No full hosted run (--full, Windows) yet.

Gating on the Labs flag lives in the web PR; this request is inert until the page calls it.

Changelog

Added: /btw side sessions in cmux NEXT: a read-only chat about the current one, on a cheap model, opened beside it.

🤖 Generated with Claude Code

teamleaderleo and others added 2 commits October 14, 2026 17:36
Behavior tests for the composer's /btw side session: opened beside a
running Claude chat without disturbing it, titled with the question,
tagged side, read-only (plan mode, refused writes), on haiku; a harness
that cannot fork gets the chat's transcript as hidden context; model
resolution (param, config btw.model, harness default); refused for Web
and peer connections; the pane relay allows it on a gesture for its
own chat only. The config section is added so the tests compile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
_acpmux/btw opens a side session about a chat for the composer's /btw:
in the chat's harness and folder, tagged side with the chat, titled with
the question, read-only (Claude plan / Codex read-only where offered, and
always approve-reads or stricter with rules that refuse every writing
tool kind), on a cheap model (param, config btw.model.<harness>, else
haiku / Codex's smallest advertised model / the harness default). Claude
and Codex fork through the latest finished turn, so it works mid-turn;
other harnesses get a bounded transcript as hidden prompt context. The
question is sent as the first prompt and the request answers once it is
recorded. The chat is never paused, cancelled or changed.

Refused for Web and peer connections (btw.remote) and held by the folder
trust gate. The pane relay allows it for the pane's own chat on a
gesture (policy.json, AcpmuxPaneMethods), and direct.ts gains btw().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teamleaderleo
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 15, 2026 01:15 — with GitHub Actions Inactive
@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: ef36ae3f-2057-4114-aa80-13c668df35b9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

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

The Swift PR allowlists _acpmux/btw in AcpmuxPaneMethods, and the web PR
carries the Rust policy mirror with direct.ts (the TypeScript parity test
ties policy.json to what the page sends). This PR keeps the acpmux side.

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

teamleaderleo commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator Author

Visual preview of e929619: in the maintainers' private review feed.

acpmux's policy and rules only answer what the harness asks, so the side
session must run in a mode that reads only or asks before every change:
the Codex app-server bridge's sandboxed read-only, a family's asking mode,
else a refusal (the codex-acp adapter, an agent with no asking mode). A
mode that cannot be set and confirmed discards the side session. A closing
context tag in the chat's transcript stays inside the wrapper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teamleaderleo
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 15, 2026 04:08 — with GitHub Actions Inactive
A side session now runs only in a mode that reads only (Claude plan, the
in-process Codex bridge's sandboxed read-only) or asks before every
change (the family's first webAskingModes entry), set and confirmed in
the session's reported current mode. The codex-acp adapter, opencode and
families with no asking mode are refused before anything opens; a mode
that cannot be set or confirmed discards the side session with an error.
The transcript context says it is untrusted, not instructions, and the
chat's text can no longer close its wrapper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teamleaderleo
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 15, 2026 04:15 — with GitHub Actions Inactive
@blacksmith-sh

blacksmith-sh Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Found 1 test failure on Blacksmith runners:

Failure

Test 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.

… tag

A profile that runs opencode under "family": "claude" must be refused,
never put in opencode's plan (it runs bash without asking). The context
wrapper's closing tag must stay inert in any case and with whitespace
around the slash or before ">".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teamleaderleo
teamleaderleo temporarily deployed to cmux-tui-merge-tree October 15, 2026 06:23 — with GitHub Actions Inactive
… inert

A profile that runs opencode or codex-acp under "family": "claude" is
now refused instead of put in a plan mode that runs bash without asking;
while one is configured Claude's own /btw is refused too (fail closed).
Every closing tag of the context wrapper in the chat's text is made
inert, in any case and with whitespace around the slash or before ">".
The docs name the limits: the confirm reads acpmux's own mode record, a
pane or socket can re-mode a side session, respawn replays the mode
without failing, and harness-side allow lists and MCP tools.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflict in acpmux config.rs: keep both the btw and the titles sections.

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

cursor Bot commented Oct 15, 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.

@github-actions

Copy link
Copy Markdown
Contributor

The feat-cmux-next push run https://github.com/manaflow-ai/cmux/actions/runs/38702990697 at ed9bbb8 failed: cmux-next generated files.
Those jobs last passed at 04e0ab4. This pull request is one of 5 merged since: #20907, #20795, #20935, #20782, #20847.
If the failure is in your change, fix forward on feat-cmux-next. Pull requests run only the tiers their paths reach (docs/ci/cmux-next-tiers.md); label a batch PR full-ci to run them all before merging.

lawrencecchen added a commit that referenced this pull request Oct 15, 2026
…the protocol registry; pane policy test reads typedRequest (cx-js8uc)

protocol_registry_names_every_served_method was red on every OS:
_acpmux/harness/update and _acpmux/sessions/restart_harness (#20569,
cx-1mbnu) and now _acpmux/btw (#20847) were served but not declared.
Declare them (untyped, Request), refresh spec/acpmux-protocol.json and the
generated web methods, and document update/restart_harness in
docs/acpmux-schema.json.

the_allowlist_is_what_the_page_sends was red since #20576: direct.ts sends
_acpmux/permission_respond through typedRequest(send, "...", ...), which the
test's this.request( scan missed. The test now also reads the literal method
of each typedRequest call. policy.json is unchanged: the page still sends
permission_respond, and the page never sends the harness methods itself
(the Swift host calls them over the unix socket, gesture-gated; acpmux
refuses them for LocalApp, Web and peer).

Remove both cx-js8uc rows from scripts/ci/cmux-tui-base-red.tsv.

This branch was previously deployed

2 inactive (1 outdated) deployments
cmux-tui-merge-tree — b5026291 Deployed Oct 15, 2026 by teamleaderleo via publish the merge tree #1364
artifacts — b420f69e Deployed Oct 15, 2026 by teamleaderleo via publish tree-addressed cmux-tui #16566
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant