# Cursor Acp

> 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).

- Skill: `mandersogit-skills/cursor-acp` (Agent Skill, multi-file: 6 files)
- Install (CLI): `npx skillmds@latest add mandersogit-skills/cursor-acp`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mandersogit-skills/cursor-acp/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: mandersogit-skills (https://skillmd.com/u/mandersogit-skills)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/mandersogit-skills/cursor-acp

---


# 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

```bash
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.

