Saphan StudioDocs
Infrastructure

The cursor backend

What a Cursor run executes, how its model and effort are named, how a Cursor seat is logged in and seated, where it is confined, and what to do when a spawn is refused.

For whoever seats the cursor backend on a machine and writes the orders that run on it.

After reading it you can name a cursor model correctly in an order, log a cursor seat in or give it a key, have it seated, and act on every refusal a cursor spawn can give you.

Before you start: an admitted machine (saphan machine admit) and an account with the vendor of cursor-agent.

What it runs

The cursor backend drives Cursor's command-line agent, cursor-agent. Cursor's installer puts the same program on disk under two names, cursor-agent and agent; the engine runs the absolute path you register for the machine and never looks the program up on PATH.

backend namecursor — - backend: cursor in an order, --backend cursor on a seat
how it is runheadless, one prompt per run, its output streamed as JSON lines that the run record and the console transcript read
the CLI versionone generation is pinned, exactly: 2026.10.14-941492e. A seat whose program reports any other version is refused cursor-version-skew before the spawn. ⚠ The pinned package exists for Linux x86-64 and macOS Apple silicon only
Cursor's own sandboxswitched off on every run, because nested inside the engine's confinement it breaks the run. The engine's confinement is the boundary — see below
self-updateswitched off on every run
the model endpointthe run is pointed at the guardrail proxy on Cursor's own command line, so its model traffic passes through that proxy
egressthe shipped baseline admits one host, api2.cursor.sh on port 443, reached through that proxy — Egress policies
grant filestwo, written into every run's worktree: .cursor/cli.json (the role's permission block) and .cursor/hooks.json (the hook that answers each tool call with the engine's verdict). A run that cannot be given the hook file is refused cursor-hooks-unavailable — Agent grants
money⚠ Cursor reports no cost per run, so --budget-usd is recorded and not enforced; the bound on a Cursor run is its time limit. A model with no row in the pricing registry is recorded as unpriced, never as zero, and the spawn warns
allowancesaphan fleet usage reads a Cursor seat's included allowance from the CLI's own usage panel without spending a model turn, and ranks the seat by headroom like any other — saphan fleet

Naming a model and its effort

An order names the model without an effort, and the effort on its own line — exactly as for every other backend:

- backend: cursor
- model: grok-4.7
- effort: high

The cursor-agent CLI has no effort switch: it spells the model and the effort as one id. The engine composes that id for you — model grok-4.7 with effort high runs as --model grok-4.7-high.

  • ⛔ Never type the effort into the model (- model: grok-4.7-high). The effort is a separate setting, and a price row matches the model without it, so one grok-4.7 row prices every effort.
  • ⛔ Never use the bracket form grok-4.7[effort=high]. Cursor's own help shows it for another model; for Grok, the vendor refused it.
  • ⛔ Never pick a -fast variant.
  • ⛔ auto is refused (cursor-leg-token-forbidden): Cursor resolves it on its side to another vendor's model, so the run would not run the model the order pinned.

The efforts an order may name for Cursor: low, medium, high, xhigh. Anything else is refused order-effort-not-accepted before any spawn.

Where the effort comes from, first to last: --effort, the order's - effort:, then the configured default dispatch.effort.<role>.cursor — for an executor, dispatch.effort.executor.cursor. A default model is dispatch.model.<role>.cursor; with one set, an order may name only - backend:.

Which models a cursor run may use is a list you keep, and a pair not on it is refused at dispatch:

saphan config show                                   # read the current list first
saphan config set probe.model.cursor grok-4.7        # the value REPLACES the whole list

⚠ The value replaces the list, so write every id you still want. The ids are the ones HOME=<seat home> cursor-agent --list-models prints, with the effort suffix removed.

⚠ There is no rehearsal of this check today. saphan dry-run dispatch place is refused (dry-run-no-rehearsal-path), and saphan dispatch check --model checks only the capability catalogue, so it passes a model the accepted list will refuse. Read saphan config show before you hand an order over.

Where it runs, and where it is confined

platformdoes the engine confine it?exercised
Linux x86-64, local or reached over sshyes — Landlock, with Cursor's own sandbox switched off, so the engine's jail is the only boundaryyes — a confined run completed on a remote Linux x86-64 machine, at CLI 2026.10.14-941492e
macOS Apple silicon, a local runyes — Seatbelt, with the same grant as on Linux⚠ the confinement was rehearsed on macOS up to Cursor's sign-in check with no sandbox denial; a completed confined model turn on macOS is not yet in the evidence
macOS reached over ssh⛔ no — the run record says no engine boundary was claimed, and a run that declares an egress policy is refused by namenot exercised: no such seat has been measured

What the jail grants inside the seat's home is only what cursor-agent was measured writing there: .cursor, .npm, and the data directory the engine points Cursor at.

⚠ Not covered, and not measured rather than known harmless: cursor-agent also writes session logs and run markers in the system temporary directory. Those writes are not granted, and whether denying them affects a turn has not been measured.

⚠ The prompt rides the cursor-agent command line. A brief that makes the composed prompt 131 072 bytes or longer is refused cursor-prompt-too-large-for-argv.

Attribution is off: the agent signs none of your commits

Before every cursor spawn — local or remote, behind the proxy or not — the engine sets two keys in the seat's own Cursor settings file, <seat home>/.cursor/cli-config.json:

attribution.attributeCommitsToAgent = false
attribution.attributePRsToAgent     = false

Every other key of that file is kept. So a commit or a pull request made by a cursor run carries no co-author trailer naming the agent — the cursor-agent CLI was found set to attribute on every seat, and this is why the engine writes the setting itself rather than trusting the seat.

If the engine cannot read that file as JSON, or cannot write it, the spawn is refused cursor-seat-http1-unavailable (the same refusal guards the HTTP/1.1 key a run behind the proxy needs), and the refusal names the keys. Repair or remove the file — a file holding valid JSON, or no file at all, is accepted — and dispatch again.

How a cursor seat authenticates

The platform of the machine that runs the leg decides it:

seat onauthenticates byexpiry
LinuxCursor's own stored login under the seat's home, <seat home>/.config/cursor/auth.json, made with saphan machine loginthe login ends; the engine reads when, before every spawn
macOSan API key, because Cursor's browser login cannot be stored under a seat's home on macOS (Cursor's write to the macOS keychain fails)none the engine can read; a revoked key surfaces at Cursor

⛔ There is no fallback. A macOS cursor seat with no key is refused, never run on whatever login the machine happens to hold. And the dispatcher's own CURSOR_API_KEY never reaches a run: the run's environment is built, not inherited, and --env CURSOR_API_KEY is refused by name.

The ceremony for a cursor seat, numbered

Every step here is a human act. Steps 2 and 3 are the owner's.

Install and seat (once per seat)

  1. Install Cursor's CLI at the pinned version (2026.10.14-941492e) on the machine that will run the seat. ⚠ The vendor's own installer installs the newest version, and a seat on any other version is refused cursor-version-skew.

  2. Register the program's absolute path for that machine — an owner act, confirmed with a one-time code:

    saphan machine set-binary --machine <machine-id> --binary cursor=<absolute path of the program on that machine>

    Skip this if saphan machine list already shows a cursor program for the machine, or if you passed the same --binary cursor=… to saphan machine admit.

  3. Seat it — the owner's act, signed with the root key. A cursor seat's profile directory is its home: Cursor keys its login on $HOME, so --config-dir is the home the engine binds every time it spawns or logs in that seat.

    saphan machine seat --machine <machine-id> --slot <n> --identity <seat> \
      --backend cursor --config-dir <the seat's home on that machine> \
      --billing-class subscription --added-by <owner>
  4. Accept the model if it is not already on the list: saphan config set probe.model.cursor <every id you want>.

Then follow one of the two acts below, by the seat's platform.

Log a Linux seat in (the first time, and whenever the engine says the login ended)

  1. From a terminal, run:

    saphan machine login --workspace <canonical root> --machine <machine-id> --identity <seat>

    The engine runs <registered program> login under the seat's own home — in place for a local seat, over its own ssh connection with a terminal for a remote one — so you never type a path or ssh anywhere yourself. It prints the HOME=<value> it bound, which is the same value every spawn of the seat binds. Without a terminal it is refused machine-login-no-operator.

  2. Complete the flow cursor-agent prompts for: open the URL it prints in a browser on your own machine, finish there, and let the CLI exit. A non-zero exit is refused machine-login-abandoned, and nothing is written.

  3. Read the verdict. The engine then sends one short real prompt under the same home — it costs one small model turn on the seat's allowance. ALIVE is the only pass; anything else is refused machine-login-unverified and names what to do. Cursor's own status command is not used: it reads a cached login and can report "logged in" over a login the vendor no longer accepts.

  4. Tell the record: run the saphan seat auth-performed --identity <seat> --machine <machine-id> --by <person> line the verb prints. It does not write that entry itself.

To measure a seat later without logging in again, add --verify-only to step 5's line; it sends the same one prompt.

Give a macOS seat its key

saphan machine login on a macOS Cursor seat is refused by name, and the refusal points here.

  1. Create a key in the vendor's dashboard and copy it once; the dashboard will not show it again. ⚠ Two seats may share one key, but then they are one identity to the vendor — give each seat its own entry name in step 6 either way.

  2. Add the key to credentials.json on the machine that runs the leg — the file the credentials.file row of saphan config show names, mode 0600, a flat object of strings (Secrets):

    { "CURSOR_SEAT_1_KEY": "<the key from step 5>" }

    ⛔ Never paste the key on a command line or into a configuration key: a shell history is a file.

  3. Name the entry on the seat — the entry's name, never the key:

    saphan config set seat.<seat>.credential CURSOR_SEAT_1_KEY
  4. Check without spending: saphan config show lists seat.<seat>.credential with its value redacted. Do not dispatch a test turn to check the key — it spends the seat's allowance; the first real dispatch is the proof.

Rotating or revoking: replace the value in credentials.json; the next dispatch reads it fresh. Revoke it in the vendor's dashboard.

⚠ A key reaches a run only on a local run. A key-based seat reached over ssh is refused cursor-seat-credential-transport, because the remote command line has no carrier that puts a key in the run's environment alone.

When a spawn is refused

what you seewhat happenedwhat you do
"the stored Cursor login on … ended … seconds ago … Refused: this Cursor spawn, before it started" — the pre-spawn seat check, rule R23a Linux seat's stored login has expired. The engine reads its expiry before every spawn and attempts no refreshlog the seat in again — steps 5–8 — then dispatch again
a note on a spawn that proceeded: "cursor login on … ends in N days"under 7 days of login leftlog the seat in again before then
a note on a spawn that proceeded: the Cursor login "was NOT measured"the expiry could not be read. This is an unread axis, not healthsaphan machine login … --verify-only for that seat
"cursor · <model> is not a pair the config accepts (probe.model.cursor)"the order's model is not on the accepted listsaphan config show, then saphan config set probe.model.cursor <every id you want> — step 4
order-effort-not-acceptedthe order's effort is not low, medium, high or xhighchange the order's - effort:, or remove it to take the configured default
cursor-leg-token-forbiddenthe run would carry something a governed Cursor run may not — for a model, autoname a concrete model
cursor-version-skewthe seat's program is not the pinned CLI versioninstall the pinned version on that machine — step 1
cursor-seat-credential-unseta key-based seat has no seat.<seat>.credentialsteps 5–7 of the macOS act
cursor-seat-credential-absentthe named entry left credentials.json between the configuration load and the spawnadd the entry, or name one that exists
cursor-seat-credential-emptythe entry's value is emptyput the issued key in the entry
cursor-seat-credential-transporta key-based seat is reached over sshrun the leg on the machine that holds the seat, or remove seat.<seat>.credential to keep an ssh seat on its home login
credentials-name-unknown — on any commandthe configuration names an entry credentials.json does not carryadd the entry, or correct the name
credentials-name-is-a-keya value shaped like a Cursor key was typed where the entry's name belongs; the refusal never prints itput the key in credentials.json and name its entry
cursor-api-key-env-inherited / seat-env-collision--env CURSOR_API_KEY, or a permission class minting itdrop it; the seat's key is delivered by the engine alone
cursor-seat-http1-unavailablethe engine could not read or write the seat's cli-config.json (attribution and, behind the proxy, HTTP/1.1)repair or remove that file, then dispatch again
cursor-prompt-too-large-for-argvthe composed prompt reached 131 072 bytesshorten the brief
machine-login-no-operator · machine-login-abandoned · machine-login-unverifiedthe login act itselfrun from a terminal · re-run (nothing was written) · check the home the verb printed; re-run or --verify-only

What this page does not prove

  • ⚠ A completed confined model turn on macOS is not yet in the evidence; the macOS confinement is rehearsed up to the cursor-agent sign-in check.
  • ⚠ A remote Mac is not claimed as confined, and no such seat has been run.
  • ⚠ Whether cursor-agent renews an expired login by itself is not measured. The engine refuses an expired login rather than find out; if it does renew, that refusal is stricter than it needs to be.
  • ⚠ A newer cursor-agent CLI is refused until the pinned version is measured again — the flags, the output shape and the refusals above are read from one generation.