# 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](https://agentclientprotocol.com) (ACP) v1. They start `zidane-acp` and talk to it over stdio. Your own process can embed the same server from `zidane/acp`.

## Install

```bash
npm install -g zidane
zidane-acp --help
```

From 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`:

```json title="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 `Build` and `Plan` modes
- saves sessions as JSON files in `acp-sessions/` in zidane's data folder ([files and folders](https://zidane.si/docs/files.md)), so Zed reopens threads after a restart
- gives sessions file read and write, edit, shell, directory listing, sub-agents, `web_search` and `fetch_url`

For a local or OpenAI-compatible provider:

```json title="settings.json"
{
  "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:

1. Environment variables (`<PROVIDER>_API_KEY`, or the client's `env` block).
2. zidane's credentials file, written when you sign in from the [terminal app](https://zidane.si/docs/tui.md#sign-in). OAuth tokens refresh from it. A variable already set wins.
3. 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](https://zidane.si/docs/files.md)).

## 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](https://zidane.si/docs/agents.md#thinking). 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](https://github.com/Tahul/zidane/blob/main/docs/ACP.md#client-declared-tools)) |

| 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`](https://zidane.si/docs/cli.md#flags) 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` | None | Print help and exit. |
| `-p, --prompt <text>` | None | Prompt to run. |
| `-m, --model <id>` | None | Model id. |
| `-t, --preset` | `basic` | Preset name. One of `basic`. |
| `-s, --system <text>` | None | System prompt override. |
| `--thinking <level>` | `off` | Thinking (reasoning effort) level. One of `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`, `adaptive`. |
| `--provider` | `anthropic` | Model provider. One of `anthropic`, `openai`, `openrouter`, `cerebras`, `xai`, `google`, `google-vertex`, `arcee`, `baseten`, `neuralwatt`, `local`, `openai-compat`. |
| `--base-url <url>` | None | Base URL for local/openai-compat. |
| `--api-key-env <NAME>` | None | Env var with the local/openai-compat API key. |
| `--headers-env <NAME>` | None | Env var with JSON headers for local/openai-compat. |
| `--header 'Name: value'` | None | Extra local/openai-compat header. Repeatable. |
| `--vision` | None | The local/openai-compat model reads images. |
| `--image-in-tool-result` | None | The local/openai-compat model reads images in tool results. |
| `--codemode` | None | Add the codemode tool to a --json or --stream-json run. |
| `--temperature <f>` | None | Sampling temperature for local/openai-compat. |
| `--seed <n>` | None | Sampling seed for local/openai-compat. |
| `-c, --context` | `process` | Execution context. One of `process`, `docker`, `e2b`, `daytona`. |
| `--image <image>` | None | Docker image for --context docker. |
| `--cwd <path>` | None | Tool execution working directory. |
| `--env KEY=VALUE` | None | Set an env var in the context (repeatable). |
| `--pass-env VARNAME` | None | Forward a shell env var into the context (repeatable). |
| `--pregame <file>` | None | e2b/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>` | None | e2b/daytona: connect to an existing sandbox by id instead of creating one (left running on exit). |
| `--sandbox-on-destroy` | `kill` | e2b/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). One of `kill`, `pause`, `leave`. |
| `--mcp <json>` | None | MCP config as JSON, or a path to a JSON file. |
| `--session-db <path>` | `<data dir>/acp-sessions` | Session store directory. |
| `--session-id <id>` | None | Session id to load or create. |
| `--durability` | None | With --session-db: checkpoint runs; on resumes an interrupted run first (resume-ask never does here). One of `on`, `resume-ask`, `off`. |
| `--json-schema <JSON>` | None | Schema for result.output (implies --json). |
| `--schema <file.json>` | None | Read the output schema from a file. |
| `--json` | None | Wait for the run, then print only the result JSON. |
| `--stream-json` | None | Print each turn as JSONL, then the result JSON. |
| `--format` | `zidane` | JSON shape. provider prints raw provider messages. One of `zidane`, `provider`. |
| `--models <id,id,...>` | None | Advertise a model selector (all on --provider). |
| `--model-option provider:model[:label]` | None | Add a cross-provider selectable model (repeatable). |
| `--no-credential-store` | None | Ignore \~/.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.

```vim
Plug 'Tahul/zidane', { 'rtp': 'editors/vim' }
```

`Leader`+`z` or `:Zidane [prompt]` opens it and sends your visual selection as context. See `:help zidane`.

## Embed the server

```ts
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

1. Run `zidane-acp --help` to confirm the binary starts.
2. Make sure that the editor uses the same command path and environment.
3. In Zed, open the ACP logs from the Command Palette.
4. Print nothing to stdout from a wrapper script. Clients expect only protocol frames.
5. Set `ZIDANE_DEBUG=1` to log prompt-cache breaks and prompt re-captures to stderr.

## Reference

- [`zidane/acp`](https://zidane.si/docs/reference/acp.md)
