Use your existing Muse Code subscription in Zed, JetBrains IDEs, and any other ACP client.
muse-acp is a bridge between editors that speak the
Agent Client Protocol (ACP) and
Muse Code. It talks to Muse over its
native Muse Session Protocol
(MSP), so your subscription keeps Muse's own session engine, tools,
authentication, and approval flow.
The adapter is one small Rust binary with no runtime dependencies. It supports ACP v1 and v2, and installs on macOS, Linux, and Windows.
Client implementers should start with the
ACP extension contract. It lists every negotiated
Muse method, _meta shape, capability, fallback, and the ACP standard fields
used where they exist.
Toward the editor, the adapter speaks ACP through the official Rust SDK, the
agent-client-protocol
crate. The SDK owns the stdio transport, JSON-RPC, ACP v1/v2 version
negotiation, request parsing into its typed schema, the serialization of
every response, notification and request the adapter sends, and the
$/cancel_request withdrawal of the adapter's own requests. Only Muse
extensions that no ACP schema defines (the _session/* methods, _meta
members, and JetBrains AIR updates) are the adapter's own JSON.
Toward Muse, the adapter speaks MSP through
muse-msp-client, a dependency-free
synchronous client that launches a configured muse serve, performs the
handshake, correlates requests, routes server requests, and shuts the host
down within a bounded time. Tools that need Muse sessions without ACP can use
it directly.
muse-acpis an independent community project. Muse Code and Muse Spark are products of Meta Platforms, Inc. This project is not affiliated with, endorsed by, or supported by Meta.
Muse Code's own :auto-review permission profile is a TUI feature. Under
muse serve, a saved :auto-review session falls back to asking you for
everything, so editor users choose between clicking through every prompt or
switching to allowAll and giving up review entirely. muse-acp gives you
the middle path: a per-session Auto-review selector that sends every
permission request to a reviewer agent instead of to you.
The reviewer is a second Muse model session on its own memory-only, read-only
host (--no-session-log), so reviews are never saved and never touch your
files. It gets the same kind of input Codex's guardian gets: a fixed safety
policy, your trusted instructions, recent evidence, and the exact approval
request. It ends its answer with one verdict line, ALLOW: <why> or
DENY: <why>. Low and medium risk actions proceed; critical risk is denied;
high risk proceeds only when your instructions authorize it. A reply without
a verdict is asked again up to three times; a reviewer failure, or still no
verdict, denies the action rather than letting it run. Denials are shown in the editor transcript with the specific
reason. Stderr logs the stable approval id and outcome; reviewer rationale is
kept in the editor transcript rather than copied into routine logs.
flowchart TD
A[Muse sends a permission request] --> B{Auto-review on?}
B -- No --> P[Editor permission prompt]
B -- Yes --> R[Reviewer agent: policy + instructions + action]
R --> D{allow or deny?}
D -- allow --> E[approval/decide with an allowing choice]
D -- deny --> F[approval/decide with a rejecting choice + rationale]
Auto-review is off by default. Turn on the Auto-review toggle (ACP v2,
or ACP v1 clients that advertise
clientCapabilities.session.configOptions.boolean) or select On in the
Auto-review selector (other ACP v1 clients), next to Approval Mode in
clients that render ACP configOptions, and it applies to that session only. See the
Auto-review guide for the policy, the reviewer's
inputs, the decision thresholds, and the failure behavior.
- Muse Code installed, authenticated, and available as
museonPATH. - A stdio ACP client: Zed, a JetBrains IDE with AI Assistant, or another ACP client such as micro-acp.
- Node.js 22+ if you install from npm.
- Rust 1.88+ if you build from source.
Install Muse and log in before installing the adapter:
curl -fsSL https://dev.meta.ai/install.sh | sh
muse loginOn macOS you can also use Homebrew:
brew install --cask muse-codeConfirm muse --version works, then continue.
With Node.js 22 or later, install from npm:
npm install -g @brokkai/muse-acp
muse-acp --selftestOr run it without a global install:
npx --yes @brokkai/muse-acp --selftestThe npm package bundles the native binaries for every supported platform, so it needs no install scripts or separate downloads.
The package is the scoped
@brokkai/muse-acp. The unscopedmuse-acppackage on npm is a different, unrelated project, sonpm install -g muse-acpinstalls someone else's adapter.
On Linux and macOS you can install the latest release instead:
curl --proto '=https' --tlsv1.2 -LsSf \
https://github.com/BrokkAi/muse-acp/releases/latest/download/install.sh | shThe installer detects your platform, verifies the archive's SHA-256 checksum,
and installs muse-acp to ~/.local/bin. Pin a version or choose another
absolute destination with environment variables on sh:
curl --proto '=https' --tlsv1.2 -LsSf \
https://github.com/BrokkAi/muse-acp/releases/latest/download/install.sh \
| MUSE_ACP_INSTALL_DIR="$HOME/bin" MUSE_ACP_VERSION=vX.Y.Z shOn Windows x86_64 or arm64, run the PowerShell installer for the MSVC release:
irm https://github.com/BrokkAi/muse-acp/releases/latest/download/install.ps1 | iexIt picks the build for the machine's architecture, even from an emulated x64
PowerShell on arm64, verifies the ZIP's SHA-256 checksum, and installs
muse-acp.exe to $env:LOCALAPPDATA\Programs\muse-acp, leaving PATH
untouched. Set MUSE_ACP_VERSION or MUSE_ACP_INSTALL_DIR to pin a version or
choose another absolute directory, then add the reported directory to your user
PATH.
From a checkout, build and install with Cargo:
cargo install --path .muse-acp install # Zed
muse-acp install-intellij # IntelliJ IDEA and other JetBrains IDEsBoth commands are safe to re-run: they preserve existing agent entries, write a
.bak backup, and replace the settings file atomically. Use --dry-run to
preview an edit.
On Unix, a newly created settings file is owner-only (0600). When an existing
file is replaced, its permissions are preserved so values such as --env
secrets do not become more broadly readable.
Any other ACP client can launch muse-acp directly over stdio.
muse-acp install registers the adapter in Zed's settings file,
~/.config/zed/settings.json (%APPDATA%\Zed\settings.json on Windows):
{
"agent_servers": {
"muse-acp": {
"type": "custom",
"command": "muse-acp",
"args": [],
"env": {}
}
}
}On Windows it records the full path to the running muse-acp.exe instead,
because the PowerShell installer leaves PATH untouched.
muse-acp install-intellij writes ~/.jetbrains/acp.json, recording the full
path to the running binary as JetBrains requires:
{
"agent_servers": {
"muse-acp": {
"command": "/Users/you/.local/bin/muse-acp",
"args": [],
"env": {}
}
}
}Then open AI Chat and select muse-acp.
install and install-intellij accept:
muse-acp install --command /absolute/path/to/muse-acp
muse-acp install --env MUSE_CLI=/absolute/path/to/muse
muse-acp install --settings /path/to/settings.json --dry-run
muse-acp uninstall
muse-acp uninstall-intellijRun muse-acp help for the full option list.
The adapter reads its settings from the environment. Export them in your shell, or set them in the client's agent entry so the editor passes them to the adapter.
Auto-review is the exception: it is a per-session selector, not an environment variable. See Auto-review.
| Variable | Default | Purpose |
|---|---|---|
MUSE_CLI |
muse |
Muse host binary to launch. Use an absolute path when the editor's PATH differs from your shell's. On Windows the default also finds the muse.cmd launcher that the Muse installer puts on PATH. When PATH has no muse, the default falls back to the Muse installer's location (MUSE_INSTALL_DIR, else ~/.local/bin, or %LOCALAPPDATA%\Programs\muse on Windows). |
MUSE_SERVE_ARGS |
none | Extra host-lifetime flags for muse serve (see muse serve --help). Split on whitespace; no shell quoting or expansion. --join (serve through the machine's shared session daemon) applies to the main host only: a joined host takes the daemon's write and shell limits, so the read-only and reviewer hosts drop it. The session hosts already pass --trust-workspace (see Workspace trust); it is not repeated, and --per-session, which muse serve refuses beside it, leaves trust to that host. |
MUSE_APPROVAL_MODE |
host default | Force an approval posture: allowAll, promptUnmatched, onRequest, or denyUnmatched. promptUnmatched sends every unmatched tool call through session/request_permission. |
MUSE_COMMAND_TIMEOUT_MS |
method-specific | Override the host admission-ack deadline, in milliseconds. |
MUSE_REVIEW_TIMEOUT_MS |
180000 |
Auto-review deadline: a reviewer turn that never finishes is denied after this long, 100–600000 ms. Twice Codex's 90-second guardian review timeout, because each review here is one cold turn rather than a warm pooled reviewer. |
MUSE_SHUTDOWN_TIMEOUT_MS |
8000 |
Shutdown deadline, 100–60000 ms. |
MUSE_TOOL_OUTPUT_LIMIT |
8000 |
Editor-facing tool output bound, in characters (minimum 200). |
MUSE_LOG |
normal |
Set to debug for per-method protocol tracing (no payloads). |
MUSE_SHOW_REMINDERS |
off | Set to 1, true, yes, or on to render each background reminder check as its own transcript card instead of one Background checks card. |
MUSE_EDITOR_TOOLS |
on | Set to 0, false, no, or off to stop giving Muse the editor tools on ACP v1 clients that advertise fs or terminal. |
MUSE_ALLOW_UNSCOPED_READS |
off | Dangerous. Set to 1, true, yes, or on to allow local reads outside the approved workspace roots. |
Without MUSE_COMMAND_TIMEOUT_MS, admission deadlines are 30 seconds for the
handshake, queries, and approval or input decisions; 180 seconds for session
start, resume, and read and for view paging; and 60 seconds for other methods.
These bound how long the adapter waits for a command response, not how long a
model turn may run.
The editor's Mode selector chooses what Muse may change in a session:
- Default — Muse edits files and runs commands as the approval mode allows.
- Read-only — Muse can read, search, and answer, but cannot write files or run shell commands.
- Plan — read-only, and each turn tells Muse to investigate and propose a
plan instead of implementing it. Only changing the mode leaves Plan. A bare
/planswitches to Plan without starting a turn;/plan <text>still runs Muse's plan skill in the current mode.
Read-only and Plan are enforced by Muse, not by the adapter: those sessions
run on a second muse serve that muse-acp starts with --disable-write --disable-shell the first time it is needed. Through Muse 1.4.2, Muse
answers a write with "tool policy denied filesystem write"; from 1.4.3 it
does not offer write or shell tools in those sessions at all. MCP tools are
not covered by those flags. Client-supplied MCP servers remain available in
Read-only and Plan, and those servers can write files, run commands, or use
the network. Read-only and Plan are therefore not a sandbox for MCP tools and
do not guarantee that no external tool has side effects. Their calls still go
through the host's approval policy, so use promptUnmatched or
denyUnmatched when that approval boundary must be enforced.
Muse lets only one host hold a session, and releases it only when that host exits. Changing the mode of an open session therefore restarts the host that holds it, and every other session on that host reconnects by itself. The change is refused while anything runs on that host, in this thread or another: a turn, a background tool, a subagent, or a workflow. Try again when it finishes.
The adapter remembers each session's mode in
$XDG_STATE_HOME/muse-acp/session-modes.json (Windows:
%LOCALAPPDATA%\muse-acp\session-modes.json), so a session you load or
resume later comes back in the same mode. A fork keeps its source's mode.
The approval policy has its own Approval Mode selector (approval_mode).
Clients that still send an approval mode such as promptUnmatched to the
mode selector, or to session/set_mode, keep setting the approval mode.
If MUSE_APPROVAL_MODE is unset, the adapter inherits the host's saved
default and logs a warning when that effective mode is allowAll. In
allowAll, Muse runs shell commands, file writes, network access, and MCP
tools without a permission request, so neither the editor prompt nor
Auto-review sees the action. That posture is appropriate only when the session
should have unrestricted host access. Harness deployments should set
MUSE_APPROVAL_MODE=promptUnmatched (or denyUnmatched) explicitly.
If Muse is saved with the :auto-review permission profile, muse serve
cannot start its automated reviewer. The adapter gives its own muse serve
child a private, temporary settings view that uses :ask-me, so approvals come
to your editor. Your saved Muse settings, editor launcher, and session data are
left untouched, and the temporary view is removed when the host exits. Other
permission profiles are passed through unchanged.
On Muse 1.4.8+, an ACP session/load or session/resume request can select a
launch profile with
{"_meta":{"muse":{"permissionProfile":"<profile-id>"}}}. The adapter
forwards it to the host's resume step; omitting it restores retained authority
only. The id must already be admitted by the host's permission-profile
catalog; the request selects a profile and never creates one. The value must
be a non-empty string, and malformed metadata fails locally with invalid
params before the host is called. Unknown or unavailable ids keep the host's
typed commandRejected refusal. session/fork never carries the member.
The view links the rest of your configuration instead of copying it, so
credentials are never copied. Files Muse creates while it runs, such as the
credential from your first login or your first workspace trust, are written
to your real configuration too. On Windows it needs neither Developer Mode nor
administrator rights: folders are linked with directory junctions, and files
with symbolic links when Windows allows them, otherwise with hard links. When
Muse replaces a hard-linked file, such as a refreshed auth.json, or creates
a new one, the adapter moves the new file to your configuration when the host
exits, unless your configuration also changed in the meantime. If the editor
stops the agent before it can, the next launch finishes the job. With hard
links, a login you complete in a terminal while the agent runs reaches
muse serve with your next prompt or new session, or when the editor
authenticates after Log in with Muse. If the view cannot
be built, for example because the temporary folder is on a different drive
from your configuration, muse serve starts with your saved settings, and the
error the editor shows if Muse then refuses the profile says why.
- Sessions — new, load, resume, list, close, and fork, with durable Muse
session IDs that survive adapter and host restarts. On Muse 1.4.11+ closing
a session also releases it in Muse (
session/leave), so it stops holding host resources; it stays listed and a resume reloads it. An ACP v2 resume replays history only forreplayFrom: {"type": "start"}; an omitted ornullcursor reconnects without replay, and an unsupported or malformed cursor is refused with-32602before Muse is asked. Every successful v2 resume sends the currentidle,running, orrequires_actionstate after any replay and before its response. Delete is available on Muse 1.4.1 and 1.4.2 with durable session logs; Muse 1.4.3 no longer serves it, so the adapter does not offer it there. Muse deletes only sessions it can prove the running host owns, so a session from an earlier editor run is kept and the editor is told why instead of being pretended. Deleting a session that never existed succeeds silently when Muse's listing filters can prove the absence (1.4.2); without them the refusal is reported. - Turns — streamed text and tool updates, queued concurrent prompts,
cancellation with a terminal event, and exact-turn steering over the
_session/steeringextension (ACP v1 and v2). A declined retract keeps the prompt and turn targetable until Muse's interrupt terminal arrives; accepted or legacy retracts settle the prompt as cancelled immediately. On ACP v2 every accepted prompt, local commands included, is answered with the inserted user message'smessageId, and theuser_messageupdate reuses it. A local command handled from idle reportsrunningbefore itsidle(end_turn); a/feedbackform stopped before the prompt is inserted answers-32800. - Approvals — Muse approval requests surfaced as
session/request_permission, with a deny-safe fallback that explains failed or unusable editor replies in the transcript, plus optional Auto-review for workspace-local file access. On Muse 1.4.9+ an approval held by Muse's native reviewer appears in reversiblesession_info_update._meta.muse.approvalReviewingmetadata, never as a permission request. Each row carriessourceSessionId,approvalId,title, andkind; the value becomesnullwhen no native reviews remain. The hint clears when the host releases the approval, resolves it, or ends the owning turn. Clients that ignore_metasee no change and still receive the ordinary permission request after release. - MCP servers — stdio and HTTP MCP servers attached by the editor load into the Muse session on Muse 1.3.0 and newer; see Client-provided MCP servers.
- Editor file access (ACP v1) — when an ACP v1 editor advertises
fs.readTextFileorfs.writeTextFile, Muse gets tools that read and write files through the editor (fs/read_text_file,fs/write_text_file), so the model sees unsaved buffers and the editor tracks its writes; see Editor file access. - Editor terminals (ACP v1) — when an ACP v1 editor advertises
terminal, Muse gets a tool that runs a command in an editor terminal (terminal/create), shown live on the tool card; see Editor terminals. - Questions — Muse
userInput/requestedbridged to ACPelicitation/createforms when the client advertises form support; otherwise the host falls back to auto-cancel. Each field'stitleis the question's header and the form message is the question text. Options are titledoneOf/anyOfentries that keep Muse's optiondescription, with any option preview under the entry's_meta.muse.preview. Option-bearing forms include an Explain instead choice and an inline clarification field; choosing it sendsuserInput/clarifyinstead of a structured answer. Multi-question prompts are asked one question per form so each confirmed answer is held by the adapter: stopping mid-way (or declining a later question) sendsuserInput/interruptwith the answers already given, which settles the prompt and stops the turn instead of losing them. A question with no confirmed answer stops through the ordinary turn interrupt and is declined throughuserInput/cancelas before. - Modes — Default, Read-only, and Plan, where Muse itself refuses file writes and shell commands; see Read-only and plan modes.
- Configuration — mode, approval mode, auto-review, model, and reasoning
effort exposed as ACP
configOptionsselectors, refreshed from Muse on create, load, resume, and config change. The reasoning selector starts atMuse defaultand sends no override until you pick a tier; on Muse 1.4.1+ it offers exactly the tiers the selected model serves, with Muse's own descriptions, and follows a model change. The adapter remembers the last tier picked per model in$XDG_STATE_HOME/muse-acp/reasoning-efforts.json(Windows:%LOCALAPPDATA%\muse-acp\reasoning-efforts.json), so a new session starts at the last effort the way it already starts at the last model. PickingMuse defaultforgets the remembered tier. - Skills — Muse's skill catalog drives ACP
available_commands_update. Each skill command carries the skill's name as_meta.muse.title: the name the skill declares inSKILL.md(metadata.display-name, Muse 1.4.8+), or Muse's display name when it declares none. Slash prompts such as/planuse native skill turn parts;/compactinvokes Muse's native compaction. - Goals —
/goal <objective>sets the session goal,/goal edit <objective>replaces it, and/goal pause,/goal resume,/goal clearmanage it, mapping onto the hostgoal/*methods. When a command starts a goal turn, the prompt stays open until that turn ends, and Stop interrupts it (Muse then pauses the goal). Stop also reaches goal turns Muse starts on its own. Objectives may include @-mentions. Goal state still streams back throughsession/goalChangeddisplay metadata. - Session names —
/rename <name>renames the session through the host'ssession/rename; the new title arrives throughsession/nameChangedas an ACPsession_info_update. When the host proves how the name was set (Muse 1.4.7+), the update's_meta.muse.nameOriginsaysautomaticorcustom; unknown provenance is omitted, never guessed. When Muse retains a generated short title (1.4.8+), the same update carries it as_meta.muse.generatedName; it is always the host's value and is never inferred from the canonical name. The adapter requests the host'ssessionRenamecapability at startup. Muse 1.4.8 added it to the grantable registry; a host that withholds the grant is answered with the typedcapabilityRequiredrefusal, while hosts that predate the entry keep servingsession/renamewithout it. - Workflow children — workflow cards list each child with its id, and
/workflow-child skip <childId>or/workflow-child retry <childId>controls one child of a running workflow through the host'sworkflow/childControl. A bare/workflow-childlists the children you can control. - Workflow runs —
/workflow pause [<runId>]and/workflow resume [<runId>]pause and resume a live run through the host'sworkflow/pauseandworkflow/resume. A single live run may be implied; a bare/workflowlists the live runs with their pause state. A paused card is titled(paused), and a resumed launch arrives as a new item naming the run it continued. - Away recap —
/recapasks Muse for a while-you-were-away summary. The prompt stays open until the host'srecap/completedsettles it (the host enforces a 45-second timeout, reported as a failure), the summary arrives as agent text, and Stop sendsrecap/cancelinstead of waiting it out. - Skills —
/skill-activation <selector> on|userInvocableOnly|off [user|project|bundled|plugin]writes one skill's activation through the host'sskill/setActivation(the scope defaults touser), and/skill-import <claude|codex> [dry-run] [force]imports foreign skills throughskill/importand reports the host's receipt. The command catalog refreshes throughskill/changedafter either write. - Side chats — the opt-in
_session/sideChatextension opens a side chat from a loaded session (Muse'ssession/sideChat) and returns the new session id plus itssideFromprovenance. The side is an ordinary host session: attach it withsession/resume/session/load, and its parent link arrives insession_info_update._meta.muse.sideFrom. - Plugins — the opt-in
_session/pluginsextension reads the session's installed plugin rows (plugin/list), and/plugin install <marketplace> <name>(or/plugin install <path> [scope]) plus/plugin marketplace add|remove|updatedrive the host's install and marketplace writes. Change notices are diagnostics; clients re-read the extension instead of a cache. - Computer use — with the client's
_meta.muse.computerUseopt-in, the adapter requests both the per-sessioncomputerUseSetupand host-widecomputerUseHostSetupgrants./cua hostreads the host's setup state and its Screen Recording, Accessibility, and Input Monitoring grants;/cua host checkasks the host to inspect those grants without prompting or opening System Settings. Input Monitoring is reported separately because basic computer use can be ready without it, while some actions still need it. These are ordinary prompt commands, so clients do not need the_session/computerUseextension to use them. With the per-session grant,/cua [status],/cua on,/cua set computerControl|screenshots ask|alwaysAllow, and/cua block|unblock <bundleId>drive the session setup and settings patch. Muse 1.4.8 adds effective per-app rows with their last-asked and lock facts;/cua appsrequests the installed/running picker source, and/cua app <bundleId> ask|alwaysAllow|blocked [...]patches one or more apps atomically. The extension returns structured session setup plus settings, can requestinstalledApps, and includes built-in blocks on request. A withheld grant or disabled gate fails closed with a typed refusal; a busy host check keeps the host'scommandRejectedruntime_busydata. - Content — text, inline and local-file images,
resource_linktext expansion, and embedded context. Aresource_link(or embedded blob) whose media type isvideo/mp4orvideo/quicktimeis uploaded to the session's video store through Muse's chunkedmedia/uploadand submitted as avideoturn part; the receipt is threaded into the turn. Audio is rejected, because Muse's input type is closed totext,image,skill, andvideo. - Context compaction —
/compactdrives Muse's native compaction. ACP v2 receives standardcompaction_update; ACP v1 receives it only after advertisingclientCapabilities.session.compaction. Older v1 clients get a think-kind tool card fallback. Host token and outcome provenance rides_meta.muse.contextCompaction, and no retained summary is invented. - Cut-off replies — when the provider stream dies mid-reply and Muse
commits the partial text (
Item.cutOff, Muse 1.4.7+), that agent message is marked once with_meta.muse.cutOff: trueand the hostitemId, so an editor can show the reply as incomplete. A streamed reply gets the mark on an emptyagent_message_chunk(naming itsmessageIdon ACP v2); a resent or replayed reply carries it on its own text update. The mark is sent only when the host sets the flag, and never cleared. - Usage — context occupancy as
usage_update, cumulative session totals, subscription observations under_meta.muse.subscriptionUsage, and a session cost that on Muse 1.4.2+ is Muse's own figure (with its partial flag) and otherwise is a client-local list-price estimate explicitly labeled as not a billing figure. - Background checks — when the answer is done but Muse's reminder agents
still hold the turn open (
turn/foregroundCompleted), the prompt stays pending untilturn/completed, as the Muse SDK requires. Meanwhile the editor getssession_info_update._meta.muse.foregroundCompleted(turnId,blockingAgents) and an in-progressBackground checks: <agents>card. Both clear before the prompt result. - Model retries — when a provider backs off and Muse 1.4.8+ schedules a
retry of the model attempt (
turn/modelRetryScheduled), the prompt stays open and the editor getssession_info_update._meta.muse.modelRetry(turnId,attempt,nextAttempt,maxAttempts,retryDelayMs, plus the failingerrorKind,system, andhttpStatuswhen the host sends them), so it can show "retrying in Ss, attempt N/M". The hint clears withmodelRetry: nullbefore the prompt result. - Feedback —
/feedback [bug|bad|good|other] <note>sends feedback about Muse through the host'sfeedback/submitwhen Muse grants the capability. With form elicitation, one form collects the classification, the note, and explicit consent for each attachment (local tracing, the session record), both defaulting off. - Tasks and subagents — native subagents as the ACP subagents RFD's
subagent_update(ACP v1 clients that advertiseclientCapabilities.subagents, and every ACP v2 client) or as JetBrains AIR's updates, and backgrounded shell commands and workflows through negotiated ACP extensions, with synthesized tool cards otherwise. Actual tool calls carry Muse's programmaticItem.toolas standard ACPToolCall.name. On Muse 1.4.9+ a tool card's title uses the host'sItem.displayLabel: active wording while running, done wording after success, and neutral wording for failure or cancellation; older or unlabeled calls retain the synthesizedtool: detailtitle. On Muse 1.4.6+ a resume or re-attach asks the host for its authoritative background-task inventory (task/list), so work the replayed history did not carry still appears with a stoppable row. - Monitor feeds — Muse 1.4.8+
monitorFeedobservations render in one stable tool card per monitor. The card title uses the monitor's sanitized arm-time description, streamed text replaces the card content as events arrive, and completion settles the card._meta.muse.monitorIdlinks it to the monitor tool call, and host-truncated text is marked in_meta.muse.truncated. - File changes — per-turn workspace file-change reports from the host's
native file tools when the client negotiates the AIR extension. Tool cards
for Muse's file tools carry standard ACP
locations(absolute paths from their explicit path arguments), so editors can follow along; shell and other tools never get a guessed location. On ACP v2 a completed edit also carries standarddiffcontent (file changes plusgit_patchtext) built from Muse's stored patch; see the extension contract. - Streaming tool output — on ACP v2, tool output appears in the card as
Muse streams it (
tool_call_content_chunk, within the display budget), and the completed card carries the final bounded output. Shell commands (thebashtool and user shells) run in an ACP v2 agent-owned terminal instead: the card references the terminal, which gets the command, streamed output chunks, the bounded final output, and an exit status (a user shell's exit code; abashcall records none, so its terminal is only marked exited). ACP v1 keeps text cards. - Stored output — bounded tool output with head and tail retained, plus
opt-in
_session/readOutputaccess to the host's stored bytes. - Hooks —
hookRunitems render as hook cards with their event, durable outcome, and terminal reason; opt-in_session/hooksreads the session's installed handler rows (hook/list) and labels those cards with each handler's origin and enabled switch. - No JSON-RPC batches — ACP does not use batches. As in the ACP SDK, a
batch array in either version is refused with one
-32600(id: null) and none of its entries run.
Beyond core ACP, the adapter negotiates these extensions. Each activates only when the client opts in too, with the documented fallback otherwise:
_session/steering— exact-turn steering on ACP v1 and v2. Follows the ecosystem_session/steeringconvention (steering.supported, withsteering.idleBehaviors: ["promptRequired"]); the standards-tracksession/injectproposal is still unmerged — adopting it is future work._session/readOutput— opt-in reads of host-stored tool output._session/hooks— opt-in reads of the session's installed hook handlers._session/sideChat— opt-in creation of a side chat from a loaded session._session/plugins— opt-in reads of the session's installed plugin rows._session/computerUse— opt-in reads of the session's computer-use setup and settings, including effective app choices and the optional installed-app picker source. The host'scomputerUseSetupandcomputerUseHostSetupcapabilities are requested only for clients that opt in; the standard/cua hostand/cua host checkcommands do not require extension support._session/userShell— shell commands outside any turn; needs editor opt-in, AIRasyncTasks, and a host grant all together._session/async_task/stop— stop one background task;session/cancelmaps background work totask/stopAll.- JetBrains AIR v1 (
agentFileChangeReport,nativeSubagentSessions,asyncTasks,recommendedValue) — per-turn file-change reports, native subagent sessions, async-task observation and stops, model and reasoning recommendations.
An ACP client opts into these Muse capabilities by adding the listed names to
its initialize metadata. For computer use, ACP v2 clients send
"capabilities":{"_meta":{"muse":{"capabilities":["computerUse"]}}} and ACP
v1 clients put the same _meta object under clientCapabilities; the older
boolean form "computerUse":true is also accepted. The adapter requests both
Muse computer-use grants only after receiving that opt-in.
The ACP extension contract is the complete client-facing method and metadata reference. The MSP event compatibility matrix records the ACP mapping or intentional disposition of every notification in the pinned schema. ROADMAP.md tracks compatibility, reliability, and release priorities.
MCP servers that your editor attaches to a session become tools in that Muse
session. This includes Zed's context servers and the IDE server JetBrains
passes. It needs Muse 1.3.0 or newer: the adapter requests the host's
sessionMcp capability and loads the servers through session/start and
session/resume configuration.
- Transports — stdio servers and HTTP (streamable HTTP) servers. The
adapter advertises HTTP support (
mcpCapabilities.httpin ACP v1,session.mcpin ACP v2) only when the host grantedsessionMcp. SSE servers are not supported by Muse and are dropped with a log line. - Failures — every server is loaded as optional. A server that cannot start is skipped, and the session keeps working without its tools. An invalid entry (for example, one with no command) is dropped with a log line and does not fail the session.
- Approvals — the model sees each tool as
mcp__<server>__<tool>, and every call goes through Muse's normal approval flow. Muse's "Always allow this MCP tool" choice saves a persistent rule for that server and tool name. It applies to any later server with the same name, including one configured in Muse itself. - Reloading — Muse fixes a session's MCP servers when it loads the session, and it does not save them. Loading a session again re-sends the editor's current servers. If the adapter's Muse host already has the session loaded with a different set, for example after you close a thread, change context servers, and reopen it, the session keeps its current servers until muse-acp restarts. A restarted Muse host gets the servers again automatically.
- Forks — a forked session starts without the editor's MCP servers, because Muse's fork takes no configuration. It gets them the next time Muse loads it.
- Older Muse hosts — without the
sessionMcpgrant, the servers are dropped and the adapter logsignoring client-provided MCP servers: this Muse host did not grant sessionMcp. Configure MCP servers in Muse itself instead.
The adapter log names forwarded and dropped servers but never prints their commands, arguments, URLs, environment values, or headers.
An ACP v1 editor can offer the agent its own file system: with
clientCapabilities.fs.readTextFile the agent may ask it for a file's text
(fs/read_text_file), which includes changes the user has not saved, and
with fs.writeTextFile it may hand the editor new text to write
(fs/write_text_file), so open buffers update and the editor tracks the
change. ACP v2 removed both methods.
Muse runs its own read_file, write_file, and edit_file tools and has no
way to delegate them, so muse-acp gives each session one more MCP server,
named editor, whose tools go through the editor instead. It needs Muse 1.3.0
or newer (the sessionMcp grant).
- Tools —
mcp__editor__read_text_file(path, optionallineandlimit) withfs.readTextFile;mcp__editor__write_text_file(path,content) withfs.writeTextFile; andmcp__editor__edit_text_file(path,old_string,new_string, optionalreplace_all) with both. Their descriptions ask the model to prefer them to Muse's own file tools; Muse's own tools stay available. - Approvals — like any MCP tool, every call goes through Muse's approval flow first. "Always allow this MCP tool" keeps a tool from asking again.
- Scope — paths are relative to the session's
cwdor absolute, and must stay inside the session's roots (cwdplusadditionalDirectories), with symlinks resolved;MUSE_ALLOW_UNSCOPED_READSdoes not widen this. A path outside fails the call without contacting the editor. - Modes — Read-only and Plan sessions refuse the write and edit tools.
Muse's
--disable-writecovers only its own tools, so the adapter enforces this itself. - Tool cards — cards carry the file as an ACP
location, and a completed write or edit carries a standarddifffrom the editor's previous text. - Cancel —
session/cancelwithdraws a waiting editor request with$/cancel_requestand fails the call. - Transport — the server listens on
127.0.0.1on a random port, from the first session that needs it. Each session has its own 256-bit bearer token, sent to Muse in the server'sAuthorizationheader and never logged. A request with anOriginheader (any browser) is refused. - Turning it off — set
MUSE_EDITOR_TOOLS=0. An editor MCP server of its own namededitoralso takes precedence over the adapter's.
An ACP v1 editor that advertises clientCapabilities.terminal runs commands
for the agent in its own terminals (terminal/create,
terminal/wait_for_exit, terminal/output, terminal/kill,
terminal/release), where the user watches the output. ACP v2 removed these
methods. Muse's bash tool runs inside Muse, so the same editor MCP server
adds mcp__editor__run_in_terminal (command, optional cwd and
timeout_seconds).
- Running — the command line runs as
bash -c <command>(Windows:powershell.exe -NoProfile -NonInteractive -Command <command>), in the session'scwdor incwd, which must stay inside the session's roots. The editor keeps the last 256 KiB of output. The model gets the output and the exit code or signal; a command that fails still counts as having run. - Tool cards — the card shows the editor's terminal as soon as it starts
(ACP
terminalcontent) and keeps it after the command ends, instead of a copy of the output. Its kind isexecute. - Approvals — every call goes through Muse's approval flow, and the
prompt names the full command (and
cwd), from the approval'srawArgs. Muse 1.4.8 opens the prompt before it sends the arguments; the adapter then retitles the open prompt. The command runs outside Muse's sandbox, in the editor, so "Always allow this MCP tool" lets the model run any command without asking. - Stopping —
session/cancelstops a running command: the adapter withdraws its wait with$/cancel_request, kills the terminal, and still returns the output so far. A command that outlivestimeout_seconds(default 120, at most 1800) is killed the same way. Either way the call fails and the terminal is released. - Modes — Read-only and Plan sessions refuse the tool.
Several independent projects bridge Muse Code to ACP. Most fall into one of
two designs: adapters that speak Muse's native session protocol over a
long-lived muse serve, and bridges that wrap the one-shot muse exec --json
event stream. The table below reflects each project's public documentation and
package metadata as of mid-October 2026; check the projects themselves for
current behavior.
| Adapter | Language / runtime | Muse transport | Install | Editor targets | License |
|---|---|---|---|---|---|
| muse-acp (this project) | Rust; single native binary, no runtime | MSP over one long-lived muse serve |
npm @brokkai/muse-acp, release installers, cargo install |
Zed, JetBrains, any stdio ACP client | Apache-2.0 |
| bex-co/muse-code-acp | TypeScript; Node.js 22+ | Muse SDK over muse serve, with a legacy muse exec --json backend (MUSE_CODE_ACP_BACKEND=exec) |
npm @bex-co/muse-code-acp |
Zed, VS Code, other ACP clients | Apache-2.0 |
| 1broseidon/muse-acp | Rust | MSP over muse serve |
cargo install --git |
Zed, Hotline, any ACP client | Apache-2.0 |
| sanjay3290/muse-acp | TypeScript; Node.js 20+ | MSP over muse serve |
npm muse-acp |
ACP clients (Zed example) | Apache-2.0 |
| julianubico/muse-code-acp-bridge | JavaScript; Node.js 22.13+ | muse exec --json JSONL |
from source (documented npm name not currently published) | acpx custom agents | MIT |
| einklover/muse-acp-server | TypeScript; Node.js 22+ | muse exec --json, with model traffic proxied through OpenCode credentials |
from source | Paseo | MIT |
| jannotix/muse-acp-agent | TypeScript | Uses Muse Code models as the reasoning core | from source | ACP clients | Apache-2.0 |
Relative adoption, from the npm registry's last-30-day download counts
(2026-09-19 through 2026-10-18), was about 4.0k for @bex-co/muse-code-acp,
2.2k for @brokkai/muse-acp, and 160 for muse-acp. Download counts include CI and mirrors, so treat them as an
order-of-magnitude signal, not a usage measurement.
The headless muse exec --json design is a good fit for one-shot automation and
scripted workflows. This adapter chooses MSP so a single editor session keeps
native streaming, approvals, cancellation, configuration, resume, and usage,
instead of starting a new Muse CLI process for each prompt.
Adjacent projects worth knowing about, though not ACP adapters themselves: BrokkAi/mjolnir is a Rust control plane for several ACP coding agents including Muse, and agentic-control-plane/muse-code-acp-plugin is a Muse plugin that policy-checks tool calls.
One muse serve child serves all ACP sessions for the adapter's lifetime.
session/new starts a host session in the requested cwd; session/start
auto-subscribes the adapter to the session view so turns stream in as item/*
and turn/* notifications. The adapter folds those into ACP session/update
messages:
| Muse (MSP) | Editor (ACP) |
|---|---|
session/start, turns, and history |
session/new, session/prompt, session/load, session/resume, session/fork |
item/delta message text |
agent_message_chunk |
toolCall items |
tool_call and tool_call_update |
turn/completed |
v1 prompt response with stop reason and usage; v2 state_update |
approval/requested |
session/request_permission, answered with approval/decide; eligible requests can be answered directly by Auto-review |
userInput/requested |
elicitation/create form |
model/list, session/setModel, session/setApprovalMode, session/setReasoningEffort |
configOptions selectors and session/set_config_option |
skill/list, skill/changed |
available_commands_update |
session/contextUsage, session/tokenUsage |
usage_update |
turn/steer |
_session/steering (ACP v1 and v2) |
| Forks, subagents, async tasks, user shell, stored output | negotiated ACP extensions |
Muse's stable schema is the authority for MSP shapes (muse schema generate-json-schema). A schema fingerprint mismatch with the vendored bundle
is logged, not fatal.
ACP's cwd is the primary workspace root and the base for relative resource
paths; the adapter passes it to Muse as MSP's single workspaceRoot. If a
client sends additionalDirectories, each must be absolute and name an
existing directory, and the adapter treats
[cwd, ...additionalDirectories] as the ordered set of roots approved for
local image and textual resource_link expansion. On Muse 1.4.1+ the same
canonical set is sent to the host as MSP workspaceRoots, so Muse's own tools
work in the extra folders too, with duplicates collapsed and no root silently
dropped. Older hosts keep adapter-side confinement only, and the adapter logs
that Muse's own tools see just the primary root.
Local reads are confined to that root set. The adapter resolves each path and
root through the filesystem before checking containment, so ..,
percent-encoded separators, case differences, and symlinks cannot escape the
approved roots. Only valid UTF-8 text without binary control bytes is expanded,
up to 256 KiB per resource; malformed file:// escapes and remote hosts are
rejected. Setting MUSE_ALLOW_UNSCOPED_READS disables this boundary and should
not be used with untrusted sessions. The
editor file tools apply the same root check to
every path and ignore that setting.
Every workspace is trusted, as in claude-agent-acp: ACP has no trust prompt,
so the hosts that serve your sessions (the main and read-only hosts) run
muse serve --trust-workspace. Muse then loads each workspace's skills,
rules, and MCP servers. From Muse 1.4.15 that is also what lets the MCP
servers your editor attaches reach the session, including the
editor file tools and
terminals: an untrusted 1.4.15 host withholds
them without telling the client. The auto-review reviewer host is not
trusted, so a repository's own rules cannot steer the judge of an approval.
Open only folders whose skills and rules you are willing to run, as you would
with Claude Code.
When Muse has no credential, session/new and session/load fail with ACP's
-32000 authentication-required error, so editors such as Zed open their login
screen before the first prompt. The adapter learns this from Muse's
account/read, which reports only which kind of credential is in effect. That
method is experimental, so the adapter opts into Muse's experimental API. If
the check is unavailable, the first prompt reports the same error instead. A
credential that expires later also yields -32000 with login guidance.
Editors that support ACP terminal auth then offer the
adapter's muse-login method. It runs muse-acp login in a terminal, which
runs muse login with the executable selected by MUSE_CLI, so you can
approve the device code in your browser. You can also run either command
yourself, then restart the editor agent.
The typed type: "terminal" method is advertised only to clients that
declare terminal auth (clientCapabilities.auth.terminal: true on ACP v1,
capabilities.auth.terminal: {} on ACP v2). An ACP v1 client without it still
gets muse-login with the legacy _meta["terminal-auth"] command, which is
how Zed and the ACP Registry check find it. An ACP v2 client without it gets
no auth method, and its auth-required errors ask you to run muse login
instead of naming a button.
muse-acp login # or: npx --yes @brokkai/muse-acp loginMETA_API_KEY, when set in the agent's environment, takes priority over the
account login.
If muse serve cannot start at all, the adapter still completes the ACP
handshake. Every later request then returns the startup diagnostic, so the
editor shows what to fix.
If Muse is not installed, requests fail with the same -32000 error, and the
auth method is named Set up Muse Code. muse-acp login then
shows the official Muse installer command (curl -fsSL https://dev.meta.ai/install.sh | bash, or irm https://dev.meta.ai/install.ps1 | iex on Windows) and asks before running it. It installs only on Enter or
y, then continues with muse login. It never installs when MUSE_CLI is
set. Restart the editor agent afterwards if it still reports Muse as missing.
Muse's installer puts muse in ~/.local/bin (%LOCALAPPDATA%\Programs\muse
on Windows, or MUSE_INSTALL_DIR), and the adapter looks there when muse is
not on the editor's PATH.
Clients that can open a URL elicitation (elicitation.url) also get an
agent-handled method, muse-device-login: auth/login (v2) or authenticate
(v1) starts Muse's device-code login, the editor opens Muse's sign-in page
with the code to confirm, and the request succeeds once Muse grants the
login. Declining the page cancels the login. No credential passes through
ACP; Muse stores it as muse login would.
ACP auth/logout (v2) and logout (v1, advertised as
agentCapabilities.auth.logout) clear Muse's stored login through Muse's
account/logout and succeed only once account/read reports no credential;
new sessions then fail with -32000 until you log in again. A
META_API_KEY in the agent's environment cannot be removed from ACP, so
logout fails and says so instead of pretending. auth/login answers
-32602: muse-login is a terminal method, which clients run in a terminal
rather than through auth/login.
To clear the login without an ACP client, muse-acp logout runs muse logout
in your terminal and reports its exit status, for the same OS user the adapter
runs as; npx --yes @brokkai/muse-acp logout does the same through the npm
launcher. A META_API_KEY in the agent's environment still outranks the stored
login, so unset it to end that access.
Run the login where the adapter runs, as the same OS user. For SSH,
containers, remote IDE backends, or a different OS account, a login on your
desktop does not help; log in on the remote host. The adapter never opens a
browser, never prompts for credentials over ACP stdio, and never copies
credentials between machines. Its only ACP auth method runs Muse's own login
in a terminal, and neither account/read nor account/logout returns key
material, so credentials stay in the Muse environment.
Muse 1.0.2 may fail to start its sandbox on Linux arm64 when a required sandbox binary is missing. Prefer upgrading Muse or installing the sandbox support. Only if neither is possible, and only if you accept running host tools without the sandbox's isolation:
MUSE_SERVE_ARGS="--disable-sandbox"--disable-sandbox materially reduces isolation, and neither approval prompts
nor this adapter's read confinement replace it. Sandbox posture is fixed for
the life of muse serve; re-enable it as soon as the host supports your
platform.
muse-acp --selftest # static payload + schema compatibility + CLI probe
muse-acp --support # redacted support bundle, safe to paste into a report--selftest validates the adapter's built-in payloads, prints the MSP schema
compatibility table, and reports whether the configured Muse CLI can be invoked
(cli-ready or cli-unready). It exits 0 even when Muse is not installed, so
you can collect output on a machine that is still being set up.
The current vendored SDK surface is Muse 1.4.15
(sha256:ef311d2dbf051ed9114556e14ad3980eb515104baa3409306a6817082a61d8cd).
The adapter live suite passes against 1.4.15-R8339.1, and the 1.4.15 Linux,
macOS, and Windows artifacts are pinned in the nightly matrix. The nightly
live-host support floor is Muse 1.4.11:
1.4.11 through 1.4.15 run on Linux x86-64 and Windows x64, and the newest
build runs on macOS arm64. Older fingerprints remain recorded as historical
compatibility evidence but are no longer downloaded for every nightly run.
At startup the adapter logs whether the host can enforce its sandbox, as
reported by Muse 1.4.7+ on initialize:
host-sandbox server=<host> availability=available|unavailable|unknown reason=<reason>|none. unknown means the host did not say (Muse before 1.4.7)
or sent a value the adapter does not recognize. A reason appears only when the
host reports unavailable and names one (userNamespacesDenied or
bubblewrapUnavailable); the adapter never infers it. An unavailable host
also logs a warning, but nothing is blocked. --support runs one
initialize handshake with muse serve to report the same facts as
support-host-sandbox, and --selftest, which never starts muse serve,
reports them as unknown.
Set MUSE_LOG=debug for per-method protocol tracing. Tracing records method
names and outcomes, not payloads.
When the editor closes the ACP connection, the adapter starts a shutdown deadline, fails outstanding requests, and exits. If the deadline expires first, it records a diagnostic and exits nonzero.
| OS | Architecture | Notes |
|---|---|---|
| macOS | x86_64, arm64 | Supported |
| Linux | x86_64, arm64 | glibc; see the Linux arm64 sandbox advisory above |
| Windows | x86_64, arm64 | MSVC release |
Linux musl, 32-bit systems, and other operating systems have no published release target.
Build and run from a checkout:
cargo build
./target/debug/muse-acp --selftestBefore submitting a change:
cargo fmt --check
cargo clippy --locked --workspace --all-targets -- -D warnings
cargo test --locked --workspace
cargo run --locked -- --selftest
node --test npm/test/launcher.test.cjs
python3 -m unittest discover -s scripts -p 'test_*.py'The integration tests use the checked-in fake MSP host at
tests/fixtures/fake_serve.py, so they do not require a live Muse session.
See CONTRIBUTING.md for the full development workflow and
RELEASING.md for the release process.
Report vulnerabilities privately as described in SECURITY.md, never in a public issue.
Copyright 2026 Brokk.ai. Licensed under the Apache License, Version 2.0. See LICENSE and NOTICE.
MUSE_ACP_ALLOW_COMMAND_PREFIXES='[["gh"]]' lets the native adapter approve
an offered allow once choice before automatic review or an ACP permission
prompt. The value is a JSON array of nonempty argv prefixes, matched exactly
against the host's complete structured argv. Invalid values disable this policy
and emit a diagnostic. Unset or [] leaves normal approval handling in place.
Only Muse's bash tool with a single unresolved shell stage, matching requirement
IDs and consistent single-stage boundaries qualifies. Compound commands, wrappers,
incomplete argv, other tools and persistent grants continue through normal review. This grants no
standing Muse policy rule; every matching approval is decided separately.