ACP
Run zidane as an Agent Client Protocol agent in Zed and other editors, or embed the server.
Editors run zidane through the Agent Client Protocol (ACP) v1. They start zidane-acp and talk to it over stdio. Your own process can embed the same server from zidane/acp.
Install#
npm install -g zidane
zidane-acp --helpFrom a checkout: bun install, then bun run acp --help. stdout carries only JSON-RPC frames. Logs go to stderr.
Connect from Zed#
Add a custom agent to Zed's settings.json:
{
"agent_servers": {
"Zidane": {
"type": "custom",
"command": "zidane-acp"
}
}
}
If Zed cannot find zidane-acp on PATH, use an absolute path. With no provider or model flags, zidane-acp:
- offers a model for every provider with stored credentials
- exposes
BuildandPlanmodes - saves sessions as JSON files in
acp-sessions/in zidane's data folder (files and folders), so Zed reopens threads after a restart - gives sessions file read and write, edit, shell, directory listing, sub-agents,
web_searchandfetch_url
For a local or OpenAI-compatible provider:
{
"agent_servers": {
"Zidane Local": {
"type": "custom",
"command": "zidane-acp",
"args": ["--provider", "local", "--base-url", "http://localhost:8000/v1", "--model", "my-model"],
"env": { "LOCAL_LLM_API_KEY": "no-key" }
}
}
}Credentials#
ACP has no way for an editor to pass provider keys. zidane-acp looks in this order:
- Environment variables (
<PROVIDER>_API_KEY, or the client'senvblock). - zidane's credentials file, written when you sign in from the terminal app. OAuth tokens refresh from it. A variable already set wins.
- The legacy OAuth store,
~/.credentials.json.
Sign in once with the terminal app to keep keys out of settings.json. --no-credential-store uses environment variables only. ZIDANE_CREDENTIALS_PATH moves the credentials file (files and folders).
Models and modes#
Pick the models#
| Flag | Use |
|---|---|
--models claude-sonnet-4-5,claude-opus-4-7 | Several models on one --provider (shared credentials) |
--model-option anthropic:claude-sonnet-4-5 --model-option openrouter:google/gemini-2.5-pro:Gemini | Models from several providers, repeatable, with an optional label. Credentials resolve on first use. |
The first entry is the default (or --model, if listed). Switch models between turns. zidane refuses a switch during a prompt.
Modes#
| Mode | Tools |
|---|---|
Build (default) | All |
Plan | Read, search and fetch tools only. Mutating calls are also blocked. |
Both modes send the same tool list, so a switch keeps the prompt cache. A gate refuses the calls Plan does not allow, and the next prompt carries a note that tells the model about the mode.
Model options#
zidane turns the model catalog into ACP session config options, applied from the next prompt. Reasoning models get a Thinking selector with the effort levels. Model flags such as Anthropic's fast mode get On/Off toggles.
What zidane-acp supports#
| Area | Support |
|---|---|
| Sessions | session/new, session/load, session/resume, session/list, session/delete, session/close, session/cancel |
| Load or resume | session/load replays the history as message updates, without the notes zidane added for the model. session/resume reattaches without a replay. |
| Extra folders | additionalDirectories on new and resumed sessions, saved with the session and returned by session/list |
| Prompt content | Images and audio when the model accepts them; embedded resources always |
| MCP servers from the editor | stdio, HTTP and SSE, added to the ones from --mcp |
| Context meter | A usage_update after each turn: tokens in context, the model's window and the cost so far |
| Client tools | Tools the editor declares under _meta.zidane.clientTools in session/new, session/load or session/resume (details on GitHub) |
| The run | stopReason |
|---|---|
| You cancelled it | cancelled |
| It hit its turn limit | max_turn_requests |
| The last turn ran out of output tokens | max_tokens |
| A content filter or provider error ended it | refusal |
| Anything else | end_turn |
Files and terminals in the editor#
When the editor offers these at initialize, zidane runs the tool through the editor's buffers and terminals:
| Editor capability | Tool it backs |
|---|---|
fs.readTextFile | read_file |
fs.writeTextFile | write_file |
terminal | shell |
Through an editor terminal, shell cannot run in the background. The agent gets an error and reruns the command in the foreground.
Permissions#
shell, write_file, edit, multi_edit and mutating client tools ask through ACP's session/request_permission:
| Option | Does |
|---|---|
| Allow once | Runs this call |
| Always allow | Runs it, and runs the same call (same tool, same input) without asking for the rest of the session |
| Reject once | Refuses this call |
| Always reject | Refuses the same call for the rest of the session |
The preset is basic, plus web_search and fetch_url. zidane-acp advertises no slash commands.
Other ACP clients#
Any ACP v1 client can launch zidane-acp [options]. It takes the zidane run flags plus three of its own. The prompt and output flags (--prompt, --json, --stream-json, --format, --json-schema) do nothing.
| Flag | Default | Description |
|---|---|---|
-h, --help | DefaultNone | DescriptionPrint help and exit. |
-p, --prompt <text> | DefaultNone | DescriptionPrompt to run. |
-m, --model <id> | DefaultNone | DescriptionModel id. |
-t, --preset | Defaultbasic | DescriptionPreset name. |
-s, --system <text> | DefaultNone | DescriptionSystem prompt override. |
--thinking <level> | Defaultoff | DescriptionThinking (reasoning effort) level. |
--provider | Defaultanthropic | DescriptionModel provider. |
--base-url <url> | DefaultNone | DescriptionBase URL for local/openai-compat. |
--api-key-env <NAME> | DefaultNone | DescriptionEnv var with the local/openai-compat API key. |
--headers-env <NAME> | DefaultNone | DescriptionEnv var with JSON headers for local/openai-compat. |
--header 'Name: value' | DefaultNone | DescriptionExtra local/openai-compat header. Repeatable. |
--vision | DefaultNone | DescriptionThe local/openai-compat model reads images. |
--image-in-tool-result | DefaultNone | DescriptionThe local/openai-compat model reads images in tool results. |
--codemode | DefaultNone | DescriptionAdd the codemode tool to a --json or --stream-json run. |
--temperature <f> | DefaultNone | DescriptionSampling temperature for local/openai-compat. |
--seed <n> | DefaultNone | DescriptionSampling seed for local/openai-compat. |
-c, --context | Defaultprocess | DescriptionExecution context. |
--image <image> | DefaultNone | DescriptionDocker image for --context docker. |
--cwd <path> | DefaultNone | DescriptionTool execution working directory. |
--env KEY=VALUE | DefaultNone | DescriptionSet an env var in the context (repeatable). |
--pass-env VARNAME | DefaultNone | DescriptionForward a shell env var into the context (repeatable). |
--pregame <file> | DefaultNone | Descriptione2b/daytona: upload + run this setup script once the sandbox is ready, before prompting (shebang picks the interpreter; a non-zero exit aborts the run). |
--sandbox <id> | DefaultNone | Descriptione2b/daytona: connect to an existing sandbox by id instead of creating one (left running on exit). |
--sandbox-on-destroy | Defaultkill | Descriptione2b/daytona: what to do with a created sandbox at the end of the run (pause preserves state for reconnect via --sandbox; leave relies on provider TTL). |
--mcp <json> | DefaultNone | DescriptionMCP config as JSON, or a path to a JSON file. |
--session-db <path> | Default<data dir>/acp-sessions | DescriptionSession store directory. |
--session-id <id> | DefaultNone | DescriptionSession id to load or create. |
--durability | DefaultNone | DescriptionWith --session-db: checkpoint runs; on resumes an interrupted run first (resume-ask never does here). |
--json-schema <JSON> | DefaultNone | DescriptionSchema for result.output (implies --json). |
--schema <file.json> | DefaultNone | DescriptionRead the output schema from a file. |
--json | DefaultNone | DescriptionWait for the run, then print only the result JSON. |
--stream-json | DefaultNone | DescriptionPrint each turn as JSONL, then the result JSON. |
--format | Defaultzidane | DescriptionJSON shape. provider prints raw provider messages. |
--models <id,id,...> | DefaultNone | DescriptionAdvertise a model selector (all on --provider). |
--model-option provider:model[:label] | DefaultNone | DescriptionAdd a cross-provider selectable model (repeatable). |
--no-credential-store | DefaultNone | DescriptionIgnore ~/.zidane credentials; use only env. |
--cwd applies when the client sends no session folder. Ctrl+C closes the sessions and exits 130; SIGTERM exits 143.
Vim and Neovim#
editors/vim is a Vim 9 and Neovim 0.9+ plugin on zidane-acp. It opens a panel over any window. Its tools let the agent inspect your setup, read :help and run commands you approve.
Plug 'Tahul/zidane', { 'rtp': 'editors/vim' }Leaderz or :Zidane [prompt] opens it and sends your visual selection as context. See :help zidane.
Embed the server#
import { runAcpStdioServer } from 'zidane/acp'
import { anthropic } from 'zidane/providers'
const handle = runAcpStdioServer({
provider: anthropic(),
permission: { enabled: true },
})
process.once('SIGTERM', () => {
void handle.close().finally(() => process.exit(143))
})| Option | Does |
|---|---|
commands | Slash commands to advertise with available_commands_update when a session starts |
logout | A handler for the logout method. Setting it advertises the logout capability. |
useClientFileSystem, useClientTerminal | Set false to keep the local read_file, write_file and shell tools (both default to true) |
permission | Turns on permission requests. toolMatcher picks which calls ask |
freezeSystemPrompt | Set false to send the freshly built system prompt on every prompt (default true) |
ACP sessions use a 1-hour prompt cache (behavior.cacheTtl: '1h'), because editor sessions go at a human pace. Set behavior.cacheTtl on the server options, agentOptions or the preset to change it.
Each session's system prompt is frozen on its first prompt and sent unchanged afterwards. Changes to skills, searchable tools or MCP instructions reach the model as notes. When the base prompt changed (for example a restart with a new system), the next prompt records the new one, and the provider reads the whole conversation again once.
zidane/acp also exports createAcpServer, JSON-RPC and content-mapping helpers, and ACP types for a custom transport.
Troubleshooting#
- Run
zidane-acp --helpto confirm the binary starts. - Make sure that the editor uses the same command path and environment.
- In Zed, open the ACP logs from the Command Palette.
- Print nothing to stdout from a wrapper script. Clients expect only protocol frames.
- Set
ZIDANE_DEBUG=1to log prompt-cache breaks and prompt re-captures to stderr.