cursor-acp
You are talking to Cursor's agent through a small daemon that keeps one
cursor-agent acp process alive per session. Every command below is a
short-lived client of that daemon, so a conversation survives across your own
tool calls, and Cursor keeps its warm context (file index, MCP servers,
server-side history) between turns.
Why this is worth having: Cursor runs a different model family with no access
to your conversation — it only sees the files. That independence is what makes
its review useful and what makes it wrong in different places than you. Treat
its findings as leads to verify, not verdicts (see Recipes).
Quick start
scripts/cursor-acp doctor # cursor-agent present, logged in, ACP handshake ok
scripts/cursor-acp start --policy readonly # one session per cwd; prints sessionId, model, transcript path
scripts/cursor-acp prompt "Read src/auth.py and list the three biggest risks. Do not praise it."
scripts/cursor-acp prompt "Go deeper on #2 — show me the exact call path." # same session, remembers turn 1
scripts/cursor-acp stop # or leave it running; it idles out after 8h
prompt prints Cursor's answer on stdout and nothing else; add --json for the
structured turn (text, segments, toolCalls, denied, allowed,
ranWithoutPermission, postText, stopReason, elapsed_ms). Long prompts:
--file path or - for stdin (any size — a whole design doc is fine). Attach
files with --attach path (repeatable). --stream echoes text to stderr as it
arrives. If a turn ends with no text, stderr says so; if Cursor explained
itself only after the turn (a team-blocked or unentitled model does exactly
that: "Model Blocked"), that explanation is in postText and on stderr —
treat it as a failed turn and pick another model (models).
Choose a policy — this is the safety decision
Cursor's ACP server does not route file writes or shell through the client
(verified — see references/acp-protocol.md). The levers that actually hold are
the session mode and our answer to shell permission requests, and the policy
sets both:
--policy |
Cursor mode |
permission requests |
what it means |
readonly (default) |
ask |
shell rejected if a model tries anyway; read-kind allowed |
Cursor cannot create, edit, or run anything. Verified on disk. Some models still attempt a shell command in ask mode — the request is denied and shows in denied[]. Use for every review/opinion task. |
edit |
agent |
edit-kind allowed, shell rejected |
Cursor may edit files in the workspace; shell is refused except commands on the user's own Cursor allowlist (~/.cursor/cli-config.json → permissions.allow, e.g. Shell(ls)) — those show up in ranWithoutPermission[] and on stderr. |
full |
agent |
allowed once each |
Everything. Only when the user has explicitly asked Cursor to do work that needs the shell. |
set-policy <p> changes a running session. Every denial is reported on stderr
(and in denied[] with --json) so you know Cursor tried something and was
refused rather than silently getting a weaker answer. Say so to the user when
it matters — "Cursor wanted to run the tests but the policy blocked it." Grants
are always one-shot: an allow_always is never selected, under any policy, so
no turn can leave a standing grant behind.
The daemon owns the mode: if Cursor ever reports a mode that disagrees with the
policy, the daemon re-asserts it and records the drift in denied[] (not
observed in practice; guarded anyway). edit's allowlist hole is the user's own
configuration; the tool makes it visible (ranWithoutPermission[], a stderr
note under edit) but cannot close it — worth a sentence if you rely on "no
shell".
Working with it well
- Name sessions when there is more than one thread. Default name is derived
from cwd (
cwd-<hash>), which is right for "the conversation about this
repo". Use --name review-auth etc. for parallel threads; sessions lists
them.
- Point, don't paste. Cursor has read/grep tools; name paths and let it read.
Pasting wastes tokens and denies it surrounding context.
- Shape review prompts. Bound the output ("up to 4 concrete problems"),
assign a stance ("skeptical senior engineer"), say "do not praise it", ask for
specifics with
file:line. Open-ended "thoughts?" produces padding.
- Spend a second turn. Continuing a session is much cheaper than starting
one, and the pushback turn — "I think #3 is wrong because X; concede or
defend" — is reliably the most valuable one. Stop after 2–3 turns; past that
it starts agreeing with you.
- Verify before relaying. Cursor's confidence and specificity do not
correlate with correctness. Check each finding (
references/recipes.md has a
typed, timeboxed procedure) and report Findings / Deferred / Hypotheses
separately. Never pass on unverified critique as fact.
- Model choice. Entitlements differ per account and team, so run
models
and pick from what this session offers; the session default is fine for
most reviews. start --model <name> / set-model <name> take the bare name
from that list (e.g. a fast, cheap model for breadth; a stronger one for the
pushback turn). A model that is listed but blocked for the account produces
an empty turn with the reason in postText — switch models.
- Exit codes mean something: 0 ok · 1 setup or agent-side error (bad
--resume id, unknown model, mode rejected — the message says which) · 3 no
daemon running for that session (status still prints the saved record —
start --resume if there is one, plain start if not) · 4 cursor-agent
missing, not speaking ACP, or unauthenticated (doctor; login is
interactive — ask the user to run cursor-agent login, in Claude Code via
! cursor-agent login) · 5 --timeout expired (the turn was cancelled for
you) or a command got no answer · 6 busy: a turn in progress, or the session
is already running/starting.
- If a turn hangs:
cancel (session survives). Default --timeout is 30 min.
A turn cancelled by someone else still exits 0 but is noted on stderr and
has stopReason: cancelled — its text may be truncated.
- Recovery: if the daemon died (machine slept, harness killed it),
start --resume reloads the same Cursor session with its history and its saved
policy and model (pass --policy/--model to override); status shows the
saved record even when nothing is running. A start --resume that fails
(auth blip, bad model) leaves the saved record intact — fix the cause and
resume again. Retrying start while the first is still setting up is safe:
it reports "already starting" (exit 6); there are never two live daemons for
one name (a lock guarantees it), and if the socket file was removed under a
live daemon, stop still stops it.
Recipes (details in references/recipes.md)
- Second opinion / independent review —
readonly; frame → run → verify
every finding → one pushback turn → report in three buckets.
- Delegate a bounded edit —
edit; give the exact goal and constraints, ask
it to summarise the diff; then you run the tests (its shell is blocked) and
review git diff.
- Cross-model comparison — start two sessions with different
--model,
same prompt, then feed each the other's answer.
Where things live
Session record and transcript: ~/.local/state/cursor-acp/<name>/{session.json,transcript.jsonl}
(Claude-Code-shaped: one JSON record, one append-only JSONL). Socket, pid and
lock: $XDG_RUNTIME_DIR/cursor-acp/<name>/ (fallback /tmp/cursor-acp-<uid>;
override CURSOR_ACP_RUNTIME_DIR), always a 0700 directory owned by the user
— the socket path is the authentication, so anything else there is refused.
The transcript is the only place Cursor's reasoning survives
(agent_thought_chunk — redacted in Cursor's own store); transcript renders
it — turns, text, [thought] blocks, tool calls with status and trimmed
output, permission decisions, fs events, mode drift — and --raw gives the
events. Reasoning is rendered precisely because it can be wrong ("I've created
the file" in a readonly turn that created nothing). Cursor's own copy of the
session is in ~/.cursor/acp-sessions/<sessionId>/.
Data note: whatever Cursor reads goes to Cursor's API. Fine for the user's own
code; worth remembering for anything sensitive.
Harness notes
- Claude Code: each
Bash call is a separate process — that is why the
daemon exists. Do not run_in_background the daemon yourself; start
detaches it. Interactive login must be run by the user (! cursor-agent login).
- Any other harness with a shell tool: identical. Nothing here is
Claude-specific; the CLI is the whole interface.
- Cursor driving Cursor is legal and occasionally useful (
--model makes it
a different model).
- Other ACP agents:
start --agent-cmd "<cmd>" (hidden option) points the
daemon at any ACP agent. Mode ids differ per agent — pass
--mode-map readonly=<id>,edit=<id>,full=<id> (or CURSOR_ACP_MODE_MAP);
agents that ask permission for every tool get per-kind answers (readonly:
read/search/think; edit: + edit/delete/move; full: all), and their
fs/read_text_file / fs/write_text_file requests are confined to the
workspace (paths fully resolved, symlinks included). doctor skips the auth
check for non-Cursor agents.
References
references/cli-reference.md — every command, flag, exit code, and the JSON shapes.
references/acp-protocol.md — what cursor-agent's ACP server actually does (probed), permission flow, load/list, what is not enforced.
references/cursor-agent-notes.md — modes, model values, allowlist, session stores, hidden-subcommand caveats, -p headless mode as the fallback.
references/recipes.md — the review-and-verify discipline, delegation, comparison.
1---2name: cursor-acp3description: Drive Cursor (the cursor-agent CLI) as a live, multi-turn peer from another agent harness such as Claude Code, over the Agent Client Protocol. A daemon holds one persistent Cursor session per project; each `cursor-acp prompt` is one turn in an ongoing conversation with a different model that sees only the files. Use this whenever the user wants to ask Cursor something, get a second opinion or independent cross-model review of code, a design, or a plan, have another model sanity-check or red-team work before landing it, delegate a bounded edit or investigation to Cursor, compare how a different model would approach a problem, or hold any back-and-forth with Cursor from here — even if they don't say "ACP" or name the tool. Also use it proactively before landing a substantial design doc or tricky refactor when a same-model self-review would share the author's blind spots. Requires the `cursor-agent` CLI and a logged-in Cursor account (`cursor-acp doctor` checks both).4---56# cursor-acp78You are talking to Cursor's agent through a small daemon that keeps one9`cursor-agent acp` process alive per session. Every command below is a10short-lived client of that daemon, so a conversation survives across your own11tool calls, and Cursor keeps its warm context (file index, MCP servers,12server-side history) between turns.1314Why this is worth having: Cursor runs a **different model family with no access15to your conversation** — it only sees the files. That independence is what makes16its review useful and what makes it wrong in different places than you. Treat17its findings as leads to verify, not verdicts (see *Recipes*).1819## Quick start2021```bash22scripts/cursor-acp doctor # cursor-agent present, logged in, ACP handshake ok23scripts/cursor-acp start --policy readonly # one session per cwd; prints sessionId, model, transcript path24scripts/cursor-acp prompt "Read src/auth.py and list the three biggest risks. Do not praise it."25scripts/cursor-acp prompt "Go deeper on #2 — show me the exact call path." # same session, remembers turn 126scripts/cursor-acp stop # or leave it running; it idles out after 8h27```2829`prompt` prints Cursor's answer on stdout and nothing else; add `--json` for the30structured turn (`text`, `segments`, `toolCalls`, `denied`, `allowed`,31`ranWithoutPermission`, `postText`, `stopReason`, `elapsed_ms`). Long prompts:32`--file path` or `-` for stdin (any size — a whole design doc is fine). Attach33files with `--attach path` (repeatable). `--stream` echoes text to stderr as it34arrives. If a turn ends with **no text**, stderr says so; if Cursor explained35itself only *after* the turn (a team-blocked or unentitled model does exactly36that: "Model Blocked"), that explanation is in `postText` and on stderr —37treat it as a failed turn and pick another model (`models`).3839## Choose a policy — this is the safety decision4041Cursor's ACP server does **not** route file writes or shell through the client42(verified — see `references/acp-protocol.md`). The levers that actually hold are43the session *mode* and our answer to shell permission requests, and the policy44sets both:4546| `--policy` | Cursor mode | permission requests | what it means |47|---|---|---|---|48| `readonly` (default) | `ask` | shell rejected if a model tries anyway; read-kind allowed | Cursor cannot create, edit, or run anything. Verified on disk. Some models still *attempt* a shell command in `ask` mode — the request is denied and shows in `denied[]`. Use for every review/opinion task. |49| `edit` | `agent` | edit-kind allowed, shell rejected | Cursor may edit files in the workspace; shell is refused **except commands on the user's own Cursor allowlist** (`~/.cursor/cli-config.json` → `permissions.allow`, e.g. `Shell(ls)`) — those show up in `ranWithoutPermission[]` and on stderr. |50| `full` | `agent` | allowed once each | Everything. Only when the user has explicitly asked Cursor to do work that needs the shell. |5152`set-policy <p>` changes a running session. Every denial is reported on stderr53(and in `denied[]` with `--json`) so you know Cursor *tried* something and was54refused rather than silently getting a weaker answer. Say so to the user when55it matters — "Cursor wanted to run the tests but the policy blocked it." Grants56are always one-shot: an `allow_always` is never selected, under any policy, so57no turn can leave a standing grant behind.5859The daemon owns the mode: if Cursor ever reports a mode that disagrees with the60policy, the daemon re-asserts it and records the drift in `denied[]` (not61observed in practice; guarded anyway). `edit`'s allowlist hole is the user's own62configuration; the tool makes it visible (`ranWithoutPermission[]`, a stderr63note under `edit`) but cannot close it — worth a sentence if you rely on "no64shell".6566## Working with it well6768- **Name sessions when there is more than one thread.** Default name is derived69 from cwd (`cwd-<hash>`), which is right for "the conversation about this70 repo". Use `--name review-auth` etc. for parallel threads; `sessions` lists71 them.72- **Point, don't paste.** Cursor has read/grep tools; name paths and let it read.73 Pasting wastes tokens and denies it surrounding context.74- **Shape review prompts.** Bound the output ("up to 4 concrete problems"),75 assign a stance ("skeptical senior engineer"), say "do not praise it", ask for76 specifics with `file:line`. Open-ended "thoughts?" produces padding.77- **Spend a second turn.** Continuing a session is much cheaper than starting78 one, and the pushback turn — "I think #3 is wrong because X; concede or79 defend" — is reliably the most valuable one. Stop after 2–3 turns; past that80 it starts agreeing with you.81- **Verify before relaying.** Cursor's confidence and specificity do not82 correlate with correctness. Check each finding (`references/recipes.md` has a83 typed, timeboxed procedure) and report *Findings / Deferred / Hypotheses*84 separately. Never pass on unverified critique as fact.85- **Model choice.** Entitlements differ per account and team, so run `models`86 and pick from what *this* session offers; the session default is fine for87 most reviews. `start --model <name>` / `set-model <name>` take the bare name88 from that list (e.g. a fast, cheap model for breadth; a stronger one for the89 pushback turn). A model that is listed but blocked for the account produces90 an empty turn with the reason in `postText` — switch models.91- **Exit codes mean something:** 0 ok · 1 setup or agent-side error (bad92 `--resume` id, unknown model, mode rejected — the message says which) · 3 no93 daemon running for that session (`status` still prints the saved record —94 `start --resume` if there is one, plain `start` if not) · 4 cursor-agent95 missing, not speaking ACP, or unauthenticated (`doctor`; login is96 interactive — ask the user to run `cursor-agent login`, in Claude Code via97 `! cursor-agent login`) · 5 `--timeout` expired (the turn was cancelled for98 you) or a command got no answer · 6 busy: a turn in progress, or the session99 is already running/starting.100- **If a turn hangs**: `cancel` (session survives). Default `--timeout` is 30 min.101 A turn cancelled by someone else still exits 0 but is noted on stderr and102 has `stopReason: cancelled` — its text may be truncated.103- **Recovery**: if the daemon died (machine slept, harness killed it), `start104 --resume` reloads the same Cursor session with its history **and its saved105 policy and model** (pass `--policy`/`--model` to override); `status` shows the106 saved record even when nothing is running. A `start --resume` that fails107 (auth blip, bad model) leaves the saved record intact — fix the cause and108 resume again. Retrying `start` while the first is still setting up is safe:109 it reports "already starting" (exit 6); there are never two live daemons for110 one name (a lock guarantees it), and if the socket file was removed under a111 live daemon, `stop` still stops it.112113## Recipes (details in `references/recipes.md`)114115- **Second opinion / independent review** — `readonly`; frame → run → verify116 every finding → one pushback turn → report in three buckets.117- **Delegate a bounded edit** — `edit`; give the exact goal and constraints, ask118 it to summarise the diff; then *you* run the tests (its shell is blocked) and119 review `git diff`.120- **Cross-model comparison** — start two sessions with different `--model`,121 same prompt, then feed each the other's answer.122123## Where things live124125Session record and transcript: `~/.local/state/cursor-acp/<name>/{session.json,transcript.jsonl}`126(Claude-Code-shaped: one JSON record, one append-only JSONL). Socket, pid and127lock: `$XDG_RUNTIME_DIR/cursor-acp/<name>/` (fallback `/tmp/cursor-acp-<uid>`;128override `CURSOR_ACP_RUNTIME_DIR`), always a `0700` directory owned by the user129— the socket path *is* the authentication, so anything else there is refused.130The transcript is the only place Cursor's *reasoning* survives131(`agent_thought_chunk` — redacted in Cursor's own store); `transcript` renders132it — turns, text, `[thought]` blocks, tool calls with status and trimmed133output, permission decisions, fs events, mode drift — and `--raw` gives the134events. Reasoning is rendered precisely because it can be wrong ("I've created135the file" in a readonly turn that created nothing). Cursor's own copy of the136session is in `~/.cursor/acp-sessions/<sessionId>/`.137138Data note: whatever Cursor reads goes to Cursor's API. Fine for the user's own139code; worth remembering for anything sensitive.140141## Harness notes142143- **Claude Code**: each `Bash` call is a separate process — that is why the144 daemon exists. Do not `run_in_background` the daemon yourself; `start`145 detaches it. Interactive login must be run by the user (`! cursor-agent login`).146- **Any other harness with a shell tool**: identical. Nothing here is147 Claude-specific; the CLI is the whole interface.148- **Cursor driving Cursor** is legal and occasionally useful (`--model` makes it149 a different model).150- **Other ACP agents**: `start --agent-cmd "<cmd>"` (hidden option) points the151 daemon at any ACP agent. Mode ids differ per agent — pass152 `--mode-map readonly=<id>,edit=<id>,full=<id>` (or `CURSOR_ACP_MODE_MAP`);153 agents that ask permission for every tool get per-kind answers (`readonly`:154 read/search/think; `edit`: + edit/delete/move; `full`: all), and their155 `fs/read_text_file` / `fs/write_text_file` requests are confined to the156 workspace (paths fully resolved, symlinks included). `doctor` skips the auth157 check for non-Cursor agents.158159## References160161- `references/cli-reference.md` — every command, flag, exit code, and the JSON shapes.162- `references/acp-protocol.md` — what cursor-agent's ACP server actually does (probed), permission flow, load/list, what is *not* enforced.163- `references/cursor-agent-notes.md` — modes, model values, allowlist, session stores, hidden-subcommand caveats, `-p` headless mode as the fallback.164- `references/recipes.md` — the review-and-verify discipline, delegation, comparison.