π€ feat: Add mux acp (stdio ACP bridge) subcommand
#1304
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This PR adds an Agent Client Protocol (ACP) bridge via
mux acpso editors like Zed can create a Mux workspace and interact with it over stdio.Implementation
mux acpsubcommand with flags for server discovery, runtime, project, etc.mux acpandmux api)workspace.onChatβ ACPsession/updatenotificationssession/promptsends message and awaits completionsession/cancelcallsworkspace.interruptStreamdocs/acp.mdxwith Zed setup exampleTesting
bun test src/cli/acpUtils.test.tsfor unit testsmux server, then runmux acpand send ACP messages over stdinagent_serversin Zed settings (see docs)π Implementation Plan
Plan: Add
mux acp(stdio ACP bridge) subcommandGoal
Expose a stdio-compliant Agent Client Protocol (ACP) endpoint via
mux acpso editors (Zed, etc.) can create a Mux workspace/chat and interact with it.Non-goals (initial scope)
Recommended approach (summary)
Implement
mux acpas an ACP Agent (stdio JSON-RPC/NDJSON) that acts as a thin bridge to Mux's existing oRPC HTTP/WebSocket API server.mux acpand speaks ACP over stdin/stdout.mux acpdiscovers a running Mux API server (desktop already starts one on localhost unless disabled) via~/.mux/server.lockorMUX_SERVER_URL, and calls:workspace.create/workspace.listworkspace.sendMessageworkspace.onChat(stream)workspace.interruptStream(cancel)Net LoC estimate (product code only): ~700β1000 LoC (mostly the ACPβMux translation + streaming/cancel plumbing).
Implementation plan
1) Add
mux acpCLI plumbingsrc/cli/acp.tsfollowing the same commander + lazy-loading pattern used bysrc/cli/run.tsandsrc/cli/server.ts.src/cli/index.ts(help stub + lazy-load branch).CLI flags (MVP):
--server-url <url>(override discovery)--server-token <token>(override discovery)--project <path>(default project root; used when ACP request omits workingDirectory)--workspace <id|name>(optional: attach instead of creating)--runtime <local|worktree|ssh>(default:localto operate in the editor's folder)--trunk-branch <name>(required if--runtimeisworktreeorssh)--log-level <level>(ensure logs go to stderr, never stdout)Why flags matter: editors usually need a deterministic command line to spawn.
2) Implement ACP stdio server using the ACP TypeScript SDK
ndJsonStream) +AgentSideConnection.Agentclass (e.g.,MuxAcpAgent) with required ACP methods:initializesession/newsession/promptsession/cancelauthenticate(no-op for MVP unless we choose to expose auth at the ACP layer)Hard requirement: keep
stdoutreserved for ACP messages only. Route all logs/diagnostics tostderr.3) Connect to a running mux instance (server discovery)
Reuse the existing server discovery model already used by
mux api:MUX_SERVER_URLenv var override~/.mux/server.lock(baseUrl + token)mux serveror enable API server in desktop settings")Implementation detail: centralize this in a helper (so both ACP and future subcommands can reuse it).
4) Implement a typed Mux oRPC client (HTTP + WebSocket)
workspace.create,workspace.sendMessage, β¦)workspace.onChat,workspace.onMetadata)MVP streaming: only
workspace.onChatis required.5) Map ACP sessions β Mux workspaces
Use Mux workspace IDs as ACP
sessionIdto avoid translation tables.session/newbehaviorworkingDirectory(must be absolute per spec)--projectCLI flag /process.cwd()runtimeConfig: { type: "local" }) so any file edits happen in the editor's working directory.branchName) likeacp/<short-uuid>so each ACP session gets an isolated chat history without touching git branches in LocalRuntime.--workspacegiven: attach (validate exists viaworkspace.getInfo)workspace.create({ projectPath, branchName, trunkBranch?, runtimeConfig }).workspace.onChatsubscription immediately for that workspace and keep it open.session/load/ listing (optional)If ACP clients need this, implement ACP "unstable" methods by mapping:
unstable_list_sessionsβworkspace.listsession/loadβ attach to an existing workspacePut this behind a small compatibility layer so we can adjust to ACP spec churn.
6) Translate Mux chat streaming to ACP
session/updateMux's stream is richer than ACP; for MVP, flatten:
session/updatenotifications of typeagent_message_chunk.tool_call_update(nice-to-have).Key edge case: Mux's stream subscription may replay history and then emit a "caught-up" marker; ignore replayed history until caught up.
Event translation (MVP mapping)
StreamStartEventβ ACPagent_message_chunk(empty or "β¦")StreamDeltaEventβ ACPagent_message_chunkwithcontent: { type: "text", text: delta }StreamEndEventβ signal internal completion sosession/promptcan return{ stopReason: "end_turn" }tool_call_update(optional)7) Implement
session/prompt(send message + await completion)messages[]into a single prompt string (or keep last user message for MVP).workspace.sendMessage(workspaceId=sessionId, message=text, options=β¦).workspace.onChatsubscription for the nextStreamEndEventthat occurs after sending.{ stopReason: "end_turn" }.Concurrency rule (MVP): reject concurrent
session/promptcalls for the samesessionIdwith a JSON-RPC error.8) Implement
session/cancelsession/cancel, callworkspace.interruptStream(sessionId)and stop waiting.session/promptresolves with{ stopReason: "cancelled" }.9) Tests & validation
mux acpas a child process.initializeβsession/newβsession/prompt.session/updatenotifications and the finalPromptResponse.10) Documentation + editor setup examples (Zed + others)
Add a user-facing doc page under
docs/(and include it indocs.jsonnav) covering:mux acpis: "ACP Agent over stdio that bridges to a running mux instance via the local oRPC server."MUX_NO_API_SERVER=1), ormux serverrunning.mux api workspace list).mux acp(no flags) when the editor supplies ACPworkingDirectory.mux acp --project <path> --runtime localas a fallback if Zed doesn't passworkingDirectory.mux server/ setMUX_SERVER_URL.--trunk-branchwhen using--runtime worktree|ssh.command: mux,args: ["acp", β¦].MUX_LOG_LEVEL=debug(or--log-level debug) for verbose output.Alternatives
A) Run Mux backend in-process inside
mux acp(no HTTP server)mux acpwould instantiateServiceContainerdirectly and callWorkspaceServicemethods.Net LoC estimate (product code only): ~500β900 LoC.
B) Implement ACP transport inside mux's existing server (no CLI bridge)
Add an ACP stdio (or socket) transport directly to the mux server runtime.
Net LoC estimate (product code only): ~900β1500 LoC.
Risks / gotchas
1) stdout contamination (protocol breakage)
ACP over stdio is fragile: any stray
console.logbreaks the editor connection.stderrlogging only; consider a guard that throws if anything writes to stdout outside the protocol writer.2) Server availability / multi-instance ambiguity
The lockfile model assumes one "active" server.
--server-urlwhen ambiguous; detect stale lockfile (PID no longer running).3) Streaming correlation
Mux chat events include history replay and rich event types; ACP wants a clean turn-based
prompt.StreamEndEventas the prompt completion signal.4) ACP permission model mismatch (future)
ACP is designed so the editor mediates FS/terminal operations via
request_permission,fs/*,terminal/*.For an MVP "chat + run mux workspace" bridge, we can accept that Mux will operate directly on disk (the editor will observe file changes normally).
If we want full ACP semantics (editor-driven permission gating / terminal proxy), that likely requires deeper changes:
Generated with
muxβ’ Model:anthropic:claude-sonnet-4-5β’ Thinking:high