ACP agents
Run a session on any agent that speaks the Agent Client Protocol, such as Gemini CLI or Zed's adapters.
A session can run on any external agent that speaks the Agent Client Protocol (Gemini CLI, Zed's adapters for Claude Code and Codex, and others). Configure the agent, pick its model in /model, and that session's turns go to it.
Configure
<dataDir>/acp-agents.json:
{
"gemini": { "command": "gemini", "args": ["--acp"] },
"codex": { "command": "codex-acp", "args": [], "env": { "CODEX_HOME": "/path" } }
}
| field | meaning |
|---|---|
| name | Letters, digits, ., -, _; becomes the model acp:<name>. |
command | Executable, looked up in PATH. |
args | Arguments. |
env | Extra environment for the agent process (added to the daemon's). |
Open /acp (also linked from /settings) to list agents: a adds, e edits, d removes after y confirmation, and r refreshes. The form takes a name, command, args as a JSON string array, and comma-separated environment keys. Names stay fixed when editing; recreate an agent to rename it. Environment values are never shown or returned to clients. Existing values are preserved for retained keys; new keys are stored as ${KEY} references and resolved from the daemon environment (missing keys resolve to an empty string). Literal values configured by hand still work.
The UI uses settings.get and per-agent settings.set { patch: { acpAgents: { name: { command, args, envKeys } } } }; name: null removes an entry. This writes the existing acp-agents.json atomically, with mode 0600, without putting env values in the settings table. Unrelated agents are preserved. Invalid JSON refuses edits rather than replacing the file.
The file is re-read when it changes, so a new agent appears in /model. New sessions use edits; already-running agent processes keep their configuration until they exit or the daemon restarts (restart to apply on the page). A bad entry is skipped. The models appear only on the native engine (engine: native). Set MENDCODE_ACP_TRACE=<file> to log the JSON-RPC lines.
The agent's own models
An agent that offers a model select in its configOptions (OpenCode does) gets one /model
entry per value: acp:<name>/<value>, named <its name> · <Agent> harness (· OpenCode harness, the registry's
name, else the id), after acp:<name> (OpenCode harness). The name tells them from the same models on a native
provider, which run on MendCode's own harness (· OpenCode Zen). The list is the one the agent
gave in its last session/new, session/load, session/set_config_option answer or config_option_update, kept in
<dataDir>/acp-models.json, so it appears after the agent's first turn and survives restarts. Choosing one sends
session/set_config_option before the prompt when the agent's value differs; a refused value fails the turn
(acp_model) with the agent's reason, and so does an acp:<name>/<value> for an agent with no model option.
acp:<name> leaves whatever model the agent is on. Every acp:<name>/… is the same engine as acp:<name>: switching
between them keeps the agent's session and process.
Effort
An agent that offers an effort select (ACP category thought_level; OpenCode's effort) lists the levels of the model
in force: for OpenCode's deepseek-v4.1-flash-free low, high, max and default; a model with no levels has no
select. Each time it lists them, those levels (without default) become that model's efforts in
<dataDir>/acp-models.json, so /model, its ←→, F3 and --effort offer them for acp:<name>/<value> after the first
turn on that model. Before that, a model whose id is models.dev's provider/model (OpenCode's are) shows the levels
models.dev names for it (providers.md); a level the agent does not list
then is not sent, and the agent's default applies. With neither, the model has no levels and --effort is refused. Before each prompt, after any model
switch, the session's effort is sent with session/set_config_option when it differs from the agent's; no effort asked
sends default, the agent's own level, and a level the model does not list sends default too. A refused level fails
the turn (acp_effort). Checked live on 2026-10-17 with OpenCode 1.19.3: the same question took 20 s and 1,719
characters of reasoning at max, 4.5 s and 873 at low.
Offline registry (full tier)
Enable with /tier full or /tier acp_registry on, then open /acp and press c.
The bundled catalog lists Gemini CLI, Devin, Cline, Kimi CLI, Factory Droid, codex-acp, OpenCode
and Claude Agent (the maintained successor of claude-code-acp), with installation
instructions, upstream links and exact command/argument arrays. Catalog provenance:
official ACP registry, curated 2026-10-04.
No network is required to browse, detect or add. Install manually outside MendCode,
using pnpm 11+ with release-age protection for npm packages, or the vendor's binary
instructions. Sign in using the agent's own CLI. The catalog never installs, downloads,
authenticates, launches or probes session/new.
installed means its executable is on the daemon's PATH, not that auth or ACP
readiness has been verified. r rechecks PATH. Enter adds only the selected preset to
acp-agents.json, even if missing; existing names refuse replacement. Choose the
resulting acp:<name> in /model to run through the existing daemon-owned ACP engine.
Catalog presets use no npx/uvx runner, so they do not implicitly download packages.
The acp.registry command (operation: list|add, agentId for add) is lazy-validated,
local-only, safe-mode disabled and excluded from read-only remote commands. Adding
is an explicit user configuration action; actual turns retain normal ACP approvals.
Remote/peer clients cannot edit acpAgents or submit ACP turns.
Agent transport
MendCode starts the agent with its stdio as the transport (one JSON-RPC 2.0 message per line) and acts as the ACP client:
initializewith protocol version 1. No file-system or terminal capability is offered, so the agent works with its own tools and sandbox, and a request forfs/*orterminal/*is answered method-not-found.session/newwith the session's directory ascwd. An agent that advertisesmcpCapabilities.httpgets MendCode's own tools (featureharness_tools, balanced and full: code graph, memory, computer use when on, settings…) as the loopbackmendcodeserver insession/newandsession/load, with the same bearer, approvals and turn ownership as Claude Code; an agent without it gets an empty list and its own tools only. Full-tier per-agent thread bridge opt-in supplies a loopback HTTP entry to bothsession/newandsession/load, requiring advertisedmcpCapabilities.http. The agent's session id is stored per MendCode session and agent. After a process restart the conversation is resumed withsession/loadwhen the agent advertisesloadSession; otherwise a new agent session starts.session/promptwith the text and, when the agent accepts images, image blocks. Other attachments become a text marker. ACP has no system prompt, so the first prompt of a new agent session (not one resumed withsession/load) starts with one more text block: what MendCode draws and where its docs are (mendcode docs), as Claude Code gets after its prompt. The agent's own prompt is untouched; theminimalprompt mode sends no such block.session/updatenotifications become items:agent_message_chunka streamed agent message,agent_thought_chunkreasoning,tool_callandtool_call_updateone tool card that completes oncompletedorfailed, drawn like MendCode's own: once its input is known, a call of kindexecute,read,edit(or awrite),searchorfetchtakes MendCode's name and detail (shell: <command>,edit: <path>…), an edit shows its unified diff with line numbers (OpenCode's ownrawOutput.metadata.diff, else a hunk built from thediffblock, numbered from the call's location line or 1) and a writeCreated <path> (N bytes); anything else keeps the agent's title and its text or raw output,plana checklist card that keeps the first plan while it streams and is completed with the last one when the turn ends. Everything else (commands, modes, history replay) is ignored.session/request_permissiongoes through the normal approval flow. The tool kind decides the class (read,search,think,fetchread;edit,delete,movewrite; the rest execute). Modefullapproves,editsapproves writes,planrefuses anything that is not reading, otherwise the user is asked. Approve once selectsallow_once, approve for the sessionallow_always, denyreject_once, and an abandoned question answerscancelled; when the agent did not offer the preferred kind the other one of the pair is used.- Stopping the turn sends
session/cancel; the agent'scancelledanswer ends the turn as stopped. An agent that does not answer within 5 seconds is cut off anyway.
Turn outcomes: end_turn is ok; refusal and max_tokens / max_turn_requests are errors (acp_refusal, acp_limit); a JSON-RPC error is acp_turn_failed; a quiet agent is never cut (it runs as long as it would on its own; Esc stops it); a process that exits mid-turn is acp_exited with the end of its stderr; an unreadable command is acp_missing; an agent that asks for sign-in (error -32000) is acp_auth (sign in with the agent's own CLI; MendCode does not run authenticate).
One process per MendCode session stays up between turns and stops after 10 idle minutes, 1 minute once no client shows the session (switched away or closed: each holds several hundred MB), or when the session switches to another agent. The next turn starts it again and resumes with session/load. The footer's provider for an agent's model is its harness (OpenCode harness), not a native provider. Opted-in thread bridge turns use isolated processes that stop at turn completion, with fresh project-scoped leases each turn and raw tracing disabled.
Limits
- Context, compaction and
/contextbelong to the agent. Non-main roles (aside, dream, permission reviewer) cannot use an ACP model; a session on one falls back to the default model for those calls. - No token usage or cost is shown: the stable protocol does not report it.
- The plan card updates at the end of the turn, not on every plan change.
- Agents without HTTP MCP support cannot use MendCode's tools or the optional thread bridge; regular turns still work with an empty client server list.
- pi has no MCP client, and Codex runs one app-server for every session, so neither gets MendCode's tools; they keep their own.
- Sessions of an agent without
loadSessionlose their history when the process restarts. - Per-turn
--model acp:<name>and--agentselections work like those of Claude Code (see claude-code.md): the model routes that turn to the agent (other agent, other process), the MendCode agent is a permission ceiling plus instructions in front of the message, and--variantis refused because ACP agents have no variants. A fork of an ACP session hands the copied conversation to the agent on its first turn as a budgeted text handoff, and so does a switch from another engine (see sessions.md).
Catalog identity
ACP models use acp:<name> or acp:<name>/<model> without a ModelInfo.engine value. Prefix routing preserves the specific agent identity
in engine_bindings (acp:<agent>). Model validation refuses ACP choices for non-main roles, and auxiliary resolution
skips them even when saved older role settings reference one. The native permission reviewer also rejects them.
This works without changing protocol engine enums or introducing another external transport.