Skip to main content

Command Palette

Search for a command to run...

Cloud Agents

Pools

A pool is a named routing target that connects requests with workers. Requests wait in the pool until an available worker claims them. Create separate pools for different execution environments, such as gpu for work that needs GPUs or ios for work that needs a Mac.

See Requirements for the plan, credential, dashboard settings, and machine dependencies pools need.

Register workers to a pool

Register a worker with the Cursor CLI:

export CURSOR_API_KEY="<team service-account API key>"cd /path/to/repo   # or any directory for an any-repo poolagent worker --pool my-pool start

If you supply a new name, the CLI creates the pool. If the pool name already exists, the worker joins the existing pool. If you omit the name, the worker joins the default pool.

Manage pools

Pools are durable. A pool stays registered and selectable after the last worker disconnects, so you can scale to zero and bring capacity back when requests arrive. Manage pools with the Cloud Agents API:

Route requests to a pool

You can select a pool in the Cursor app, on cursor.com/agents, or in the Cursor mobile app. Pool names also work in Cloud Agent integrations:

SurfaceExample
Slack@Cursor pool=gpu fix the failing test
GitHub@cursoragent pool=gpu fix the failing test
LinearInclude pool=gpu in the comment or issue text that triggers Cursor.

A team admin must enable Enable Self-Hosted Pool before users can request a pool explicitly. When Require Self-Hosted Pool for All Cloud Agents is enabled, Cursor routes Cloud Agent requests to eligible pool workers unless the request targets a specific personal machine.

Pool requests match the pool name and repository. A request for pool=gpu and repo=acme/payments only routes to a connected worker with both labels. GitHub only accepts self-hosted routing requests from trusted repository collaborators.

Pools and repos

Pools come in two repo configurations. A repo-backed pool ties the pool to one repository: Cursor routes repo-scoped requests to it, and workers serve a checkout of that repo. An any-repo pool leaves source control to you.

Repo-backed poolAny-repo pool
Source controlCursor selects the repo for each run; workers serve existing checkouts.You manage cloning and git state with your own image, hooks, or scripts, or pass --clone-git-repos so the worker clones on claim.
RoutingRequests carry a repo=<owner/repo> label and match workers serving that repo.Requests match on the pool name alone.
DashboardGrouped under the repository.Grouped under Any repo.
Good forPools whose workers serve one or more specific repositories.Sandboxes and custom SCM setups.

Repo-backed pools

Use a repo-backed pool when its workers serve specific repositories and you want requests for those repos routed automatically.

1

Register the pool with repo metadata

Starting a worker from a repo checkout registers the pool implicitly. Register up front when the pool should be selectable before any worker connects:

curl --request POST \  --url "https://api.cursor.com/v0/private-workers/pools" \  -u "$CURSOR_API_KEY:" \  -H "Content-Type: application/json" \  --data '{    "scope": "team",    "poolName": "payments-pool",    "repoOwner": "acme",    "repoName": "payments",    "repoUrl": "https://github.com/acme/payments"  }'
2

Clone the repository on each machine

The worker directory must be a checkout with a configured remote. The git remote provides the worker's repo=<owner/repo> label.

3

Start workers from the checkout

cd /path/to/paymentsagent worker --pool payments-pool start
4

Send a request

Pick the repo and pool on cursor.com/agents, or create agents through the API with repos plus env.type: "pool" (see Create An Agent). Requests route only to workers serving that repo.

A repo-backed worker can serve more than one repository. Pass --worker-dir once per repo root. The first root is the worker's assignment identity, and every root with a git remote registers a repo=<owner/repo> routing label:

agent worker --pool app-infra \  --worker-dir "$HOME/repos/app" \  --worker-dir "$HOME/repos/infra" \  start

Requests route on their selected repository and match any worker in the pool serving it.

Any-repo pools

If you prefer to manage source control yourself, create a pool without a repo attached. For these pools, you can add a .cursor/rules file in the directory where you run agent worker start. Use the rules file to teach Cursor which directories and tools are available on those machines.

1

Prepare a workspace directory

Point the worker at any existing directory. Your own image, hooks, or scripts handle cloning and git state:

mkdir -p "$HOME/cursor-sandboxes/default"

Add a .cursor/rules file in that directory describing the directories and tools available on the machine.

2

Start workers with --worker-dir

An any-repo worker doesn't need a git remote:

agent worker --pool sandbox --worker-dir "$HOME/cursor-sandboxes/default" start

The worker registers without repo= routing labels.

3

Optionally clone on claim

Default any-repo behavior does not clone. Pass --clone-git-repos so the worker clones the claimed agent's repos on claim. This implies --mint-github-token and is limited to any-repo named pools other than default.

agent worker --pool sandbox --clone-git-repos start
4

Send a request

Pick the pool under Any repo on cursor.com/agents, or create agents through the API with env.type: "pool" and env.name set to the pool name, and omit repos (see Create An Agent).

Add routing labels

Labels describe a worker's environment or capabilities. Add labels with repeatable CLI flags:

agent worker \  --pool gpu \  --label team=backend \  --label environment=production \  start

For managed worker images, store labels in a JSON or TOML file:

{  "team": "backend",  "environment": "production",  "capabilities": ["docker", "gpu"]}

Pass the file when the worker starts:

agent worker --pool gpu --labels-file /etc/cursor/labels.json start

Set CURSOR_WORKER_LABELS_FILE instead when your orchestrator injects the path. --label and --labels-file cannot be combined.

Set the pool with --pool, not --label. The --pool flag enables pool assignment and registers the pool=<name> label.

Cursor normally derives repo=<owner/repo> labels from the Git remotes of each --worker-dir. For multi-repo workers, prefer one --worker-dir per checkout. If a checkout does not have a usable Git remote, add its routing label manually by repeating --label repo=<owner/repo> or including repo in a labels file. A manual label controls routing only. The checkout must already exist under an exposed worker directory.

Use hooks and MCP servers

Pool workers run project hooks from .cursor/hooks.json. Enterprise workers also support team hooks and enterprise-managed hooks. See Hooks.

Cursor runs each MCP server based on its transport:

TransportRuns onUse it for
Command (stdio)WorkerPrivate APIs, databases, local tools, and services reachable from the worker's network.
HTTP or SSE (url)CursorHosted MCP servers where Cursor manages the connection, OAuth, and session.

Add and enable personal MCP servers through the MCP dropdown in cursor.com/agents. Team admins configure shared servers under Dashboard -> Integrations & MCP. Bake command-based MCP dependencies into the worker image so every machine in the pool has the same tools.

Scale a pool with a controller

A controller is code you run that adjusts worker capacity based on demand. The Cursor CLI ships with one: agent worker controller takes care of watching the request queue, and you provide a spawn script that starts new machines. We recommend using it. To build your own custom controller instead, see Build your own controller.

The request flow

  1. A user selects the pool in the Cursor app, on cursor.com/agents, or in the Cursor mobile app.
  2. They type a request and send it.
  3. The request enters the request queue for that pool.
  4. If the pool has an available worker, that worker claims the request. Cursor routes all follow-up activity in the chat to the claimed worker.

If the pool has no available workers, the request waits in the request queue until a worker becomes available.

Run the built-in controller

agent worker controller handles pool registration, the list-then-watch loop, and claims. You provide a spawn script that boots one worker machine per invocation:

agent worker controller \  --spawn ./spawn.sh \  --api-key "$CURSOR_API_KEY" \  --pool gpu --pool default

Warm pool (keeps five idle workers connected in gpu by pre-spawning unclaimed workers):

agent worker controller --spawn ./spawn.sh --api-key "$CURSOR_API_KEY" --pool gpu --warm-idle 5

agent worker controller --warm-idle keeps process-forked workers via --spawn. It does not patch a Kubernetes WorkerDeployment. On Kubernetes, keep warm size with WorkerDeployment.spec.readyReplicas. See the Kubernetes deployment guide.

The controller runs in one of two modes:

  • Claim-then-spawn (default). The controller lists and watches pending requests, claims each one, and runs the spawn script once per claim. The spawn script boots a machine that starts a worker with the claimed worker id, so the request is seated as soon as the machine connects.
  • Warm pool (--warm-idle <count>). For keeping a few always-idle workers in each pool. The controller pre-spawns unclaimed workers to hold count idle workers per --pool and never calls claim; Cursor assigns queued requests to the warm workers. Reconciles against GET /v0/private-workers/pools every 60 seconds; the pending-requests SSE stream only accelerates backfill. Run one warm controller per pool: without a claim there is no server-side lease, so concurrent controllers can transiently over-spawn.
FlagDescription
--spawn <path>Script to run once after each successful claim, or once per missing warm worker.
--api-key <key>Service account API key. Also readable from CURSOR_API_KEY. The same key is used for pool registration, the queue APIs, and the spawn script's environment.
--pool <name>Pool to watch (repeatable). Non-default names are registered at startup. In warm mode, the pools to keep warm. Mutually exclusive with --all-pools.
--all-poolsWatch every pool on the team. Does not register pools, and can't be combined with --warm-idle.
--warm-idle <count>Keep count idle workers per --pool and skip claiming. Reconciles against pool stats every 60 seconds.
--repository <url>Filter pending requests by repository. Required for repo-scoped API keys. In warm mode, also pins the pool idle-count to that repo's row.
--endpoint <url>Override the public API base (default https://api.cursor.com). Also readable from CURSOR_API_ENDPOINT.

The spawn script receives everything it needs as environment variables:

VariableSet inDescription
CURSOR_REQUEST_IDClaim modeAgent id of the claimed request.
CURSOR_USER_IDClaim modeCursor user id that created the request.
CURSOR_REPO_URL, CURSOR_REPO_OWNER, CURSOR_REPO_NAMEClaim modeRepository metadata when the request targets a repo. Unset for any-repo requests.
CURSOR_REPO_URLSClaim modeJSON array of repository URLs for multi-repo requests.
CURSOR_POOLBothPool the worker should join.
CURSOR_AGENT_WORKER_IDBothWorker id the machine must start with. The worker CLI reads this automatically.
CURSOR_WORKER_NAMEBothDisplay name for the worker.
CURSOR_API_KEYBothThe controller's API key, for the worker process.
CURSOR_API_URL, CURSOR_API_ENDPOINTBothAPI base the controller is using.

A minimal spawn script boots a machine or container that inherits those variables and starts a worker:

#!/usr/bin/env bashset -euo pipefaildocker run -d \  -e CURSOR_API_KEY \  -e CURSOR_AGENT_WORKER_ID \  -e CURSOR_WORKER_POOL_NAME="$CURSOR_POOL" \  your-worker-image \  agent worker --pool start

Session lifecycle

Once a worker is matched to a request, Cursor forwards all agent tool calls directly to the machine. The connection has an idle timeout that defaults to 1 hour. Configure it as needed:

agent worker --pool my-pool --idle-release-timeout 600 start

--idle-release-timeout (env var CURSOR_WORKER_IDLE_RELEASE_TIMEOUT) is the number of seconds the worker stays connected after a session ends, waiting for follow-up messages. If a follow-up arrives, the timer resets. When the timeout fires, the CLI exits with code 0 so a supervisor can recycle the machine. Pass 0 to disable idle-based release. Releasing a claim is a separate API: it stops preferring that machine for the agent, and does not exit the worker CLI.

Once a worker times out, Cursor marks it as freed. The machine can reset and re-enter the pool. If a user restarts a chat that has disconnected from its machine, the chat reconnects to a fresh machine from the pool. Workspace state from the original machine does not carry over.

Build your own controller

The built-in controller covers most setups. If you need custom logic, for example your own scheduling, quotas, or machine placement, you can build a controller on the same Cloud Agents API. A controller does three things: watch the request queue, claim a request, and start a worker for it.

Monitor the request queue

List the queue once to build your view of pending requests, then follow changes in real time over Server-Sent Events (SSE).

Start with GET /v0/private-workers/pending-requests. Add ?pool=<name> to watch a single pool. Paginate to completion and keep the streamCursor from the response:

curl --request GET \  --url "https://api.cursor.com/v0/private-workers/pending-requests?pool=my-pool&limit=50" \  -u "$CURSOR_API_KEY:"

Then open the event stream with GET /v0/private-workers/pending-requests/stream, passing that streamCursor and the same filters. Keep your view current as events arrive: add requests from created and claimed_offline events, and drop requests when you see claimed or expired:

curl --request GET --no-buffer \  --url "https://api.cursor.com/v0/private-workers/pending-requests/stream?pool=my-pool&cursor=$STREAM_CURSOR" \  --header 'Accept: text/event-stream' \  -u "$CURSOR_API_KEY:"

Cursors expire five minutes after the list that issued them. When the stream returns 410 Gone, list again and reopen the stream from the fresh streamCursor. Better yet, list again every five minutes with some jitter instead of waiting for the 410.

The number of requests in your view is the pool's queue depth. When it grows, add workers. GET /v0/private-workers/summary tells you how many workers are connected and how many are busy, so you can size the response. Treat events as hints and the list as the source of truth: event delivery is best-effort, and each new list corrects any drift. See Watch Pending Pool Requests for the full delivery guarantees and cursor rules.

Claim a request

Reserve a pending request for a specific worker before that worker starts, so two controller replicas never boot two machines for the same request. Call POST /v0/private-workers/claim with the request's id (from the list response or a created event) and the worker id you plan to start:

curl --request POST \  --url "https://api.cursor.com/v0/private-workers/claim" \  -u "$CURSOR_API_KEY:" \  --header 'Content-Type: application/json' \  --data '{    "id": "bc-00000000-0000-0000-0000-000000000002",    "workerId": "pw_123"  }'

Start a worker for the claim

When you connect the worker, supply the same worker id.

Environment variable (preferred, because older CLI builds ignore an unknown env var):

export CURSOR_AGENT_WORKER_ID="pw_123"agent worker --pool my-pool start

CLI flag:

agent worker --worker-id pw_123 --pool my-pool start

Release a claim

Use Release A Claim with the same id you passed to claim. That drops the routing preference for this machine so a replacement worker can claim the agent:

curl --request POST \  --url "https://api.cursor.com/v0/private-workers/claims/bc-00000000-0000-0000-0000-000000000002/release" \  -u "$CURSOR_API_KEY:"

A waiting follow-up returns to the pool queue at the next scheduling point. A connected worker finishes its current turn. A second claim while a live claim exists is rejected; release first, then claim a new workerId.

Hibernation

A pool machine does not have to stay online while its agent is idle. After a session ends, the worker waits for follow-ups until its idle timeout fires, and keeping every machine up between turns gets expensive.

The tradeoff is workspace locality. Without hibernation, a follow-up that arrives after the machine released reacquires from the pool: the agent lands on a fresh machine and may spend its first minutes reconstructing the workspace it already had. With hibernation, the machine comes back with its workspace intact and the follow-up resumes where the agent left off.

1

Give the pool a reconnect window

workerReadyTimeoutSeconds controls how long Cursor waits for a claimed machine to reconnect before assigning the request to another worker. The default is 0: follow-ups reacquire immediately.

curl --request POST \  --url "https://api.cursor.com/v0/private-workers/pools" \  -u "$CURSOR_API_KEY:" \  --header 'Content-Type: application/json' \  --data '{    "scope": "team",    "poolName": "gpu",    "workerReadyTimeoutSeconds": 900  }'
2

Snapshot machines when they go idle

Shorten the worker's --idle-release-timeout so machines release soon after the agent goes idle. When the worker exits (code 0 on idle release), or while Get An Agent reports a status of IDLE, snapshot the machine and stop it.

3

Recognize the wake-up call

When a follow-up arrives for an agent whose claimed machine is offline, Cursor waits up to the reconnect window and advertises the request as a claimed-but-offline queue entry. Your controller recognizes it two ways: List Pending Pool Requests returns the entry with claimedWorkerId and wakeTimeoutMs, and the event stream emits a claimed_offline event with the same fields.

4

Bring the machine back up

Restore the snapshot and start a worker with the same id before the window lapses:

export CURSOR_AGENT_WORKER_ID="<claimedWorkerId>"agent worker --pool gpu start

The follow-up resumes on the machine with its workspace intact.

5

Release the claim if you can't

If the machine is not coming back, for example the snapshot is gone, release the claim. The request returns to the queue immediately, and a replacement machine can claim it. If you do nothing, the window lapses on its own: the claim expires and the request is re-advertised as an unclaimed entry (a fresh created event) that any worker can serve.

Monitor workers

Start the worker management server:

agent worker --pool gpu --management-addr ":8080" start

It exposes:

EndpointPurpose
GET /healthzLiveness. Returns 200 while the worker process is running.
GET /readyzReadiness. Returns 200 when the worker is connected and idle, and 503 while it starts or serves an agent.
GET /metricsPrometheus metrics for connections, retries, activity, and session lifecycle.

The main metrics are:

  • cursor_self_hosted_worker_connected
  • cursor_self_hosted_worker_session_active
  • cursor_self_hosted_worker_last_activity_unix_seconds
  • cursor_self_hosted_worker_connect_attempts_total
  • cursor_self_hosted_worker_connect_retry_total
  • cursor_self_hosted_worker_connect_failures_total
  • cursor_self_hosted_worker_session_ends_total

Use GET /v0/private-workers/pools for connected and in-use counts per pool. Use the pending-request queue for demand and autoscaling. The team-wide worker summary is not a substitute for pool-specific demand.

CLI reference

Worker flags go before the subcommand:

agent worker [options] start
FlagEnv varDescription
--pool [name]CURSOR_WORKER_POOL_NAMEJoin a team pool instead of registering a personal My Machines worker. One agent claims the worker at a time. Optional pool name; defaults to default.
--api-key <key>CURSOR_API_KEYService account API key for pool workers.
--worker-dir <path>Workspace root to expose to agents. Repeatable; the first value is the assignment identity. Default: current directory.
--name <name>CURSOR_WORKER_NAMEDisplay name for the worker. Defaults to the machine hostname.
--label <key=value>Add a label. Repeatable. Mutually exclusive with --labels-file.
--labels-file <path>CURSOR_WORKER_LABELS_FILEPath to a JSON or TOML labels file. Mutually exclusive with --label.
--idle-release-timeout <sec>CURSOR_WORKER_IDLE_RELEASE_TIMEOUTSeconds the worker stays connected after going idle. When the timeout hits, the CLI exits with code 0 so a supervisor can recycle the machine. Default 3600; pass 0 to disable idle-based release.
--management-addr <addr>Listen address for /healthz, /readyz (Kubernetes probes), and /metrics (Prometheus), for example :8080.
--computer-useLet claimed agents drive this machine's desktop. See Computer use and desktop sharing.
--display <display>Existing X11 display to require for --computer-use, for example :0. When omitted, a reachable DISPLAY is reused or a managed desktop is started.
--share-desktop [mode]Let authorized viewers watch or control the agent desktop: view or view_and_control (default).
--on-session-start <command>, --on-session-end <command>Worker-host commands around a pool claim. The primary configuration path remains hooks.
--clone-git-reposOn claim, clone the agent's GitHub repos into the workspace. Any-repo named pools only (not default, and not a bound repo or named machine). Implies --mint-github-token. Requires git on PATH. Default: off.
--mint-github-tokenReceive short-lived GitHub tokens during claimed runs. Pool workers only; requires team-admin enablement, and at most one credential-enabled worker per OS user or container.
--sync-dashboard-secretsReceive eligible dashboard Cloud Agent secrets as environment variables during claimed runs. Pool workers only; same one-worker-per-user deployment rule.
--identity-socketExpose a per-claim OIDC identity socket to claimed agents (published as CURSOR_AGENT_SOCKET) for minting short-lived Cursor identity tokens. Off by default.
--auth-token <token>Pre-minted access token, for automation that exchanges an API key for a short-lived token externally.
--auth-token-file <path>File containing an access token. Re-read on reconnect, so a controller can rotate a mounted token without restarting the worker.
--worker-id <id>CURSOR_AGENT_WORKER_IDStable worker id used with claim. Prefer the env var so older CLI builds ignore it.
--data-dir <path>CURSOR_DATA_DIRBase directory for logs, artifacts, and recording data. Defaults to /opt/cursor if writable, otherwise ~/.local/share/cursor-agent.
--wait [seconds]Wait for another worker using this data directory to stop before starting. Without a value, waits indefinitely.
-e, --endpoint <url>API endpoint. Default: https://api2.cursor.sh.

Run agent worker --help for the full list, and agent worker debug for a preflight report.

FAQ

Size a worker like a CI runner or devbox for the repository it serves. It needs enough CPU, memory, disk, and network access to clone the repository and run the builds, tests, and tools your agents use.

Yes. Project skills in .cursor/skills/ or .agents/skills/ are available on the worker. Commit shared skills to the repository or include them in your worker image.

A pool worker serves one agent at a time. Add workers or scale the pool when requests wait for capacity.

Next steps