# Use ACP mode

> Run the Agent Client Protocol mode, map cold CLI launch, feature flags, stop reasons, and cwd constraints for editor integrations.

- Repository: PrimeIntellect-ai/prime-agent
- GitHub: https://github.com/PrimeIntellect-ai/prime-agent
- Human docs: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1
- Complete Markdown: https://grok-wiki.com/public/docs/primeintellect-ai-prime-agent-3367c32760b1/llms-full.txt

## Source Files

- `packages/coding-agent/docs/acp.md`
- `packages/coding-agent/src/modes/acp/acp-mode.ts`
- `packages/coding-agent/src/modes/acp/acp-events.ts`
- `packages/coding-agent/test/suite/acp-mode.test.ts`
- `packages/coding-agent/test/acp-cold-cli.test.ts`
- `packages/coding-agent/test/suite/regressions/623-acp-canonical-cwd.test.ts`

---

---
title: "Use ACP mode"
description: "Run the Agent Client Protocol mode, map cold CLI launch, feature flags, stop reasons, and cwd constraints for editor integrations."
---

ACP mode starts with `prime-agent --mode acp` and runs Prime Agent as an [Agent Client Protocol](https://agentclientprotocol.com) agent: JSON-RPC 2.0 over newline-delimited JSON on stdin/stdout. Implementation lives under `packages/coding-agent/src/modes/acp/`, driven in-process through `InProcessAgentConnection` and `@agentclientprotocol/sdk` rather than by translating RPC mode.

## When to use ACP

| Mode | Fit |
|---|---|
| **ACP** (`--mode acp`) | External client *drives* a session interactively: prompt, stream tool calls, cancel a turn (editors such as Zed/VS Code, evaluation harnesses). |
| JSON event stream | Batch runs that dump every event and exit with a status code. |
| RPC | Prime Agent’s richer, non-ACP command surface. |

Any standard ACP client can drive the agent without Prime Agent–specific knowledge. Prime-only capabilities travel in reverse-domain `_meta` and are ignored by vanilla clients.

## Launch

```bash
prime-agent --mode acp
```

Cold CLI coverage in tests also wires provider and isolation flags when spawning a real process:

```bash
prime-agent \
  --mode acp \
  --provider <provider-id> \
  --model <model-id> \
  --no-session \
  --offline \
  --daemon-socket /path/to/d.sock
```

| Flag / env | Role in cold ACP launch |
|---|---|
| `--mode acp` | Select ACP agent mode. |
| `--provider` / `--model` | Bind the session model (tests use a local intercept provider). |
| `--no-session` | Avoid durable session file coupling for the cold path. |
| `--offline` | Offline operation for isolated runs. |
| `--daemon-socket` | Socket path used by the cold-CLI harness. |
| `ENV_AGENT_DIR` | Agent data directory (e.g. `models.json` with provider `baseUrl`, `api`, `apiKey`, models). |

Provider credentials remain BYOK: point providers at your own endpoints and keys; ACP does not require a hosted Prime-specific model service.

### Transport rules

- One JSON-RPC message per line on **stdout**; requests are read from **stdin**.
- stdin stays open for the life of the connection; the agent exits when stdin closes (EOF).
- Diagnostics go to **stderr**. Never write non-protocol traffic to stdout.
- Startup non-interactive modes call `takeOverStdout()`, which redirects normal `process.stdout.write` to stderr. ACP frames must use the raw stdout escape hatch (`writeRawStdout`) so the protocol is not published on stderr.

```text
  Client (editor / harness)              prime-agent --mode acp
  ┌───────────────────────┐              ┌────────────────────────────┐
  │ JSON-RPC requests     │── stdin ──▶  │ ACP agent + InProcess      │
  │                       │              │ AgentConnection            │
  │ session/update +      │◀─ stdout ──│ NDJSON frames (raw stdout) │
  │ JSON-RPC responses    │              │ diagnostics → stderr       │
  └───────────────────────┘              └────────────────────────────┘
```

## Supported methods

| Method | Kind | Behavior |
|---|---|---|
| `initialize` | request | Returns protocol version, capabilities, and agent info (`agentInfo.name` is `"prime-agent"`). |
| `session/new` | request | Creates the session. **One session per connection.** |
| `session/prompt` | request | Runs one turn; resolves with a stop reason. |
| `session/cancel` | notification | Aborts the addressed session’s turn. |
| `session/close` | request | Releases the session and frees the connection for a new one. |

### Connection and turn constraints

- **One session per connection.** The underlying session is fixed at process startup. A second concurrent session would silently share conversation, working directory, and model, so a second `session/new` is **refused**. Start another process for another session.
- **`session/prompt` refuses a concurrent turn** while one is already running.
- **Working directory cannot change after startup.** A client-supplied `cwd` that does not match the agent’s real cwd is reported in `_meta` (not applied, not silently ignored).

### Minimal handshake

```json
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":1,"clientCapabilities":{}}}
```

```json
{"jsonrpc":"2.0","id":2,"method":"session/new","params":{"cwd":"/path/to/project","mcpServers":[]}}
```

```json
{"jsonrpc":"2.0","id":3,"method":"session/prompt","params":{"sessionId":"<id>","prompt":[{"type":"text","text":"Say hello"}]}}
```

Expected success path for a clean turn:

- `initialize` → `protocolVersion` matches `acp.PROTOCOL_VERSION`, `agentInfo.name === "prime-agent"`, and `_meta` includes the Prime Agent namespace.
- `session/new` → `sessionId` string.
- `session/prompt` → `{ "stopReason": "end_turn" }` with streamed `session/update` notifications containing `agent_message_chunk` text.

### Prompt content blocks

`session/prompt` blocks are split into text and images the agent accepts:

| Block `type` | Handling |
|---|---|
| `text` | Concatenated into the turn text. |
| `image` | Passed as image content when `data` and `mimeType` are strings (advertised in `initialize`; not dropped silently). |
| `resource` | Embedded `resource.text` becomes model context; optional `resource.uri` is prefixed. |
| `resource_link` | `uri` is appended as text. |

## Streamed updates

Session activity arrives as `session/update` notifications. Mapping is pure (`acpUpdatesForSessionEvent`) so one Prime Agent event can fan out to zero or more ACP updates.

| Prime Agent activity | ACP `sessionUpdate` |
|---|---|
| Assistant text (`text_delta`) | `agent_message_chunk` |
| Reasoning (`thinking_delta`) | `agent_thought_chunk` |
| Tool starts | `tool_call` with `status: "in_progress"` |
| Tool finishes | `tool_call_update` with `completed` or `failed` |
| Shell output | Synthetic `tool_call` plus incremental `tool_call_update` |

### Tool kinds and titles

`acpToolKind` maps tool names to ACP kinds:

| Tool name | ACP `kind` |
|---|---|
| `ipython`, `bash` | `execute` |
| `read` | `read` |
| `edit`, `write` | `edit` |
| other | `other` |

Supported kind union: `read` \| `edit` \| `delete` \| `move` \| `search` \| `execute` \| `think` \| `fetch` \| `other`.

Statuses: `pending` \| `in_progress` \| `completed` \| `failed`.

IPython is the model-facing tool (`IPYTHON_TOOL_NAME = "ipython"`):

- Start: `tool_call` titled `"IPython cell"`, `kind: "execute"`, `rawInput: { code: <cell source> }` when `args.code` is a string.
- End: `tool_call_update` with optional text content and optional `_meta` rich IPython fields.

Bash runs outside the normal tool-call lifecycle. Mapping tracks `activeBashRunId` from `bash_start` so incremental `bash_output` attaches to a synthetic tool call id derived from the run id (not an orphan fallback id).

### Rich IPython `_meta` (tool end)

When the IPython tool returns `details.attachments` / `details.diffs`, the update may include:

```json
{
  "sessionUpdate": "tool_call_update",
  "toolCallId": "...",
  "status": "completed",
  "_meta": {
    "ai.primeintellect.prime-agent": {
      "ipython": {
        "attachments": [{ "mimeType": "...", "path": "...", "bytes": 1234 }],
        "diffCount": 1
      }
    }
  }
}
```

Attachment payloads are **not** inlined in `_meta` (decoded base64 length only as `bytes`); images already ride as ACP image content blocks.

## Prime Agent `_meta` extensions

Namespace: reverse-domain key **`ai.primeintellect.prime-agent`** (`PRIME_AGENT_META_NAMESPACE`). Nothing non-standard is placed on ACP object roots (reserved for future protocol fields).

Capabilities without native ACP fields—subagents, autonomous quality gates, goals, heartbeats, continual-harness refinement, compaction, rich IPython—use this envelope. Example subagent tree update:

```json
{
  "sessionUpdate": "session_info_update",
  "_meta": {
    "ai.primeintellect.prime-agent": {
      "subagents": [
        { "id": "sub-1", "sessionName": "reviewer", "status": "running" }
      ]
    }
  }
}
```

When autonomous mode is enabled, status may also be mirrored under `_meta` with fields such as `enabled`, `continuationsUsed`, `turnsUsed`, `tokensUsed`, `gateAttempt`, and `gateFailure`.

## Stop reasons

`session/prompt` resolves with one of:

| `stopReason` | Meaning |
|---|---|
| `end_turn` | Turn finished normally. |
| `cancelled` | `session/cancel` aborted the turn. |
| `max_tokens` | Autonomous token budget exhausted. |
| `max_turn_requests` | Autonomous turn, continuation, or wall-clock limit stopped the run. |

Autonomous quality gates run **inside** a single prompt turn. A failing gate is a **continuation**, not a stop reason; the turn resolves only when the gate loop settles. Gate attempts remain visible in `_meta` during that loop.

### Provider failure must not look like success

A real cold CLI process talking to a provider that returns **401** must **not** answer `session/prompt` with `{ "stopReason": "end_turn" }` and zero `session/update` notifications (that pattern reads as a successful empty turn). The response must either carry a JSON-RPC `error` or a non-`end_turn` stop reason.

## Working directory (`cwd`) constraints

Client `session/new` supplies `cwd`. The agent does not chdir for a mismatched path; comparison is canonical:

1. `resolve` the path.
2. `realpathSync` when possible; on missing/inaccessible paths, fall back to the lexical resolved path.
3. On Windows, normalize the drive letter to lowercase.
4. If canonical strings differ, compare filesystem identity (`statSync` `dev` + `ino` as bigints). If either side has `dev === 0n` or `ino === 0n`, identity is untrusted and comparison fails (Windows path-based stat can report `dev` 0 with a real `ino`; inode alone is volume-local).

| Client `cwd` situation | `session/new` result | `_meta` under Prime namespace |
|---|---|---|
| Symlink or alias that resolves to the agent process cwd | Session created | No `cwd` mismatch field |
| Case-only spelling of the same directory (same `dev`/`ino`) | Session created | No mismatch field |
| Path that cannot be canonicalized to the real cwd (e.g. missing path) | Session still created | `cwd: { requested, actual }` where `actual` is `process.cwd()` |

```text
session/new { cwd }
        │
        ▼
  sameCwd(requested, process.cwd()) ?
     │ yes                    │ no
     ▼                        ▼
  sessionId only      sessionId + _meta
                      ai.primeintellect.prime-agent.cwd
                      = { requested, actual }
```

Editors should pass a path that resolves to the process working directory, or tolerate `cwd` mismatch metadata without assuming the agent switched directories.

## Turn boundary and compaction

Turn failure detection uses a **TurnBoundary** (message object identities + content keys), not a pre-turn message count. Auto-compaction can rebuild `state.messages` mid-turn so this turn’s messages may sit at lower indices than a pre-prompt count. Content keys also cover transports that re-parse JSON (fresh object identity) and compaction paths that re-materialize kept messages.

## Clean shutdown

Prefer **closing stdin** so the agent sees EOF and unwinds children (daemon supervisor, Python kernel) over hard-killing the process. Cold-CLI tests end stdin, wait for exit, then escalate `SIGTERM` / `SIGKILL` only if the process does not exit.

## Integration checklist

<Steps>
  <Step title="Start the agent">
    Run `prime-agent --mode acp` with cwd set to the project root the editor intends. Wire provider/model via settings or CLI as needed.
  </Step>
  <Step title="Handshake">
    Call `initialize`, then a single `session/new` with `{ cwd, mcpServers: [] }`. Assert `agentInfo.name === "prime-agent"` and inspect optional `_meta` / `cwd` mismatch.
  </Step>
  <Step title="Drive turns">
    Call `session/prompt` with text and optional image/resource blocks. Subscribe to `session/update` for chunks and tool calls. Cancel with `session/cancel` if needed; close with `session/close` before a new session on the same connection.
  </Step>
  <Step title="Verify">
    Successful text turn: `stopReason: "end_turn"` and non-empty `agent_message_chunk` stream. Failed provider: not silent `end_turn` with zero updates. Protocol frames only on stdout; logs on stderr.
  </Step>
</Steps>

## Troubleshooting

| Symptom | Likely cause | What to check |
|---|---|---|
| No frames on stdout / protocol on stderr | Stdout taken over without raw write path | Frames must go through the raw stdout sink; diagnostics only on stderr. |
| Second `session/new` fails | One-session-per-connection limit | Start a new process; or `session/close` then open a new session on the same connection if free. |
| Concurrent prompt rejected | Turn already in progress | Wait for the prior `session/prompt` result or send `session/cancel`. |
| `_meta.cwd` present after `session/new` | Client path ≠ agent process cwd after canonical/`dev`+`ino` compare | Align editor cwd with process cwd; treat mismatch as advisory, not a chdir. |
| Empty successful-looking turn | Historical provider-failure bug path | Require non-`end_turn` or JSON-RPC `error` when the provider rejects (e.g. 401). |
| Orphan shell chunks | Bash mapping without `runId` correlation | Mapping must retain last `bash_start` run id for subsequent output. |
| Process hangs after client exit | stdin left open | Close stdin for clean EOF unwind. |

## Related pages

<CardGroup cols={2}>
  <Card title="ACP protocol reference" href="/acp-reference">
    Entrypoints, event types, meta fields, stop reasons, and protocol-facing constraints.
  </Card>
  <Card title="Agent connection modes" href="/agent-connection">
    In-process, daemon, and snapshot connection paths used under ACP.
  </Card>
  <Card title="Sessions and runtime" href="/sessions-runtime">
    Session lifecycle, runtime events, and session-scoped state.
  </Card>
  <Card title="Long-running tasks" href="/long-running-tasks">
    Autonomous mode, gates, heartbeats, and multi-turn progression.
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    Auth failures, provider 401s, and connection-mode probes.
  </Card>
  <Card title="Overview" href="/overview">
    CLI, SDK, and mode entry points across Prime Agent.
  </Card>
</CardGroup>
