> ## Documentation Index
> Fetch the complete documentation index at: https://docs.devin.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Outposts

> Run Devin sessions on your own infrastructure

<Note>
  Outposts is in early access. APIs and CLI commands described here may change.
  Contact your account team to enable Outposts for your organization.
</Note>

Outposts lets you run Devin sessions inside infrastructure you control — your own VMs, containers, Kubernetes clusters, or even a Mac Mini under your desk. Devin's agent loop (inference and planning) continues to run in Devin's cloud, while all command execution, file edits, and repository access happen on machines you operate.

Use Outposts when you need:

* Sessions to run inside your network, next to internal services, registries, and secrets
* Custom hardware profiles (e.g. GPUs, large memory machines, specific OS images)
* Existing dev box, VM, or Kubernetes infrastructure to host Devin workloads
* Enterprise controls over network access, build outputs, and monitoring

## How it works

Outposts has two layers:

1. **The worker (e.g. `devin worker start`)** — a binary that you run on a machine to serve a single queued session. It opens an outbound connection to Devin's cloud and executes the session's tool calls locally. Cognition provides this binary; you never need to implement it. The [Devin CLI](/work-with-devin/devin-cli) contains the logic for fetching and executing this binary.

2. **The orchestrator** — software that watches the fleet API for sessions waiting on workers, provisions a VM or container for each one, and starts the worker inside it. We provide some reference-implementations for common platforms (like Kubernetes), but you should feel free to adapt these (they're open source!) or write your own.

Workers only need **outbound** HTTPS access. No inbound ports, public IPs, or VPN tunnels are required.

When a user starts a session in Devin Cloud and selects one of your registered pools, the session is placed in that pool's queue. Your orchestrator claims it, spawns a machine, and runs the worker. When the session ends, the worker exits and your orchestrator tears the machine down.

## Prerequisites

* An organization with Outposts enabled
* A [v3 API token](/api-reference/v3/overview) with the appropriate Outposts scopes:
  * `account.outposts.orchestrator` for orchestrators managing pools (implies the machine scope)
  * `account.outposts.machine` for workers reading the queue and claiming/releasing sessions
* A machine image (VM or container) with:
  * The Devin CLI installed
  * The [machine dependencies](#machine-dependencies) below
  * Your repositories cloned, with configured remotes
  * Access to the build tools, package registries, secrets, and internal services your sessions need

### Machine dependencies

Sessions execute directly on your machine, so the worker relies on tools you install there.

**Required**

| Dependency        | Used for                              |
| ----------------- | ------------------------------------- |
| `git` (on `PATH`) | Cloning and all repository operations |

**Optional** — install these to unlock specific features:

| Dependency           | Feature                                                                                                                                                                                                                                                                                                   |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ffmpeg` (on `PATH`) | Devin's screen-recording features. Without it, sessions cannot record the screen.                                                                                                                                                                                                                         |
| Chrome or Chromium   | Browser and computer-use features. The worker looks for Chrome in standard install locations by default; set `DEVIN_CHROME_PATH` in the worker's environment to the absolute path of the binary to override (e.g. `DEVIN_CHROME_PATH=/usr/bin/google-chrome`). Without it, browser tools are unavailable. |
| Passwordless `sudo`  | Lets Devin install software it needs during a session (e.g. missing build tools or system packages). Only grant this when the machine is dedicated to Devin and recycled after each session — never on shared or long-lived machines.                                                                     |

## The core flow

### 1. Register a pool

A pool is a named queue of sessions served by your infrastructure (for example, `rhel`, `gpu-h200`, or `my-pool`). Create one with `devin worker pool create`:

```bash theme={null}
devin worker pool create
```

Provide the pool name, platform, and description.

Once registered, the pool appears as a machine option in Devin Cloud (alongside Ubuntu, Windows, etc.) when starting a session. Sessions targeting it wait in its queue until a worker claims them.

<Note>
  In the fleet API, pools are represented as `pools` resources, scoped to
  your account (shared across all of its organizations).
</Note>

### 2. Poll the fleet API for waiting sessions

Your orchestrator lists pending sessions for the pools it serves:

```bash theme={null}
curl -H "Authorization: Bearer $DEVIN_API_TOKEN" \
  "https://api.devin.ai/opbeta/outposts/devins?pool=<pool_id>&phase=pending"
```

The list response wraps queued sessions in `items`:

```json theme={null}
{
  "items": [
    {
      "metadata": {
        "session_id": "devin-...",
        "pool_id": "outpost_env-...",
        "created_at": 1781050000,
        "updated_at": 1781050000
      },
      "spec": {
        "kind": "new",
        "platform": "linux",
        "remote_binary_sha": null
      },
      "status": {
        "phase": "pending",
        "acceptor_id": null,
        "claim_deadline": null,
        "session_status": "pending"
      }
    }
  ],
  "cursor": "djE6MTc4MTA1MDAwMC4w",
  "has_next_page": false,
  "total": 1
}
```

Use the response cursor to paginate without repeatedly reconciling the full
queue. Set `first` to the page size (up to 200), then pass each response's
`cursor` into the next request while `has_next_page` is `true`:

```bash theme={null}
curl -H "Authorization: Bearer $DEVIN_API_TOKEN" \
  "https://api.devin.ai/opbeta/outposts/devins?pool=<pool_id>&first=200&cursor=<cursor>"
```

The API provides at-least-once delivery. A session at a page boundary can
appear in both pages, so upsert entries by `metadata.session_id` rather than
treating every item as new. When `has_next_page` becomes `false`, save the
returned cursor as the starting position for the watch API.

#### Watch for changes

After the initial list, start a Server-Sent Events (SSE) watch with the final
cursor:

```bash theme={null}
curl -N -H "Authorization: Bearer $DEVIN_API_TOKEN" \
  "https://api.devin.ai/opbeta/outposts/devins?pool=<pool_id>&watch=true&cursor=<cursor>"
```

The stream sends `MODIFIED` events when a session's queue entry changes and
`DELETED` events when it is removed. Newly queued sessions also arrive as
`MODIFIED` events. Each SSE `data` field contains JSON with this shape:

```json theme={null}
{
  "type": "MODIFIED",
  "object": {
    "metadata": {
      "session_id": "devin-...",
      "pool_id": "outpost_env-...",
      "created_at": 1781050000,
      "updated_at": 1781050100
    },
    "spec": {
      "kind": "new",
      "platform": "linux",
      "remote_binary_sha": null
    },
    "status": {
      "phase": "pending",
      "acceptor_id": null,
      "claim_deadline": null,
      "session_status": "pending"
    }
  },
  "cursor": "djE6MTc4MTA1MDEwMC4w"
}
```

Persist each event's top-level `cursor` after processing it. If the connection
closes, reconnect with the last persisted cursor to replay any changes that
occurred while disconnected. Watch delivery is also at least once, so clients
must tolerate duplicate events. Streams end after at most five minutes; a
reconnecting watch loop is expected.

The `pool` filter applies to both list and watch requests. The `phase` and
`acceptor_id` filters apply only to list requests and are ignored when
`watch=true`; filter watched events using the fields in each event's `object`.
Omitting the cursor starts from the beginning, so use list-then-watch for
normal reconciliation.

Before starting a machine for a session, atomically claim it so no other worker picks it up. Pass an `acceptor_id` — a self-reported identity for your worker:

```bash theme={null}
curl -X POST -H "Authorization: Bearer $DEVIN_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"acceptor_id": "worker-1"}' \
  "https://api.devin.ai/opbeta/outposts/devins/{session_id}/claim"
```

Claims are atomic: if another worker claimed the session first, you get a `409`. Claiming promises that a worker will be ready within the server-assigned claim deadline (`status.claim_deadline`); expired claims return to the queue automatically. If provisioning fails, release the claim so the session returns to the queue immediately:

```bash theme={null}
curl -X POST -H "Authorization: Bearer $DEVIN_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"acceptor_id": "worker-1"}' \
  "https://api.devin.ai/opbeta/outposts/devins/{session_id}/release"
```

### 3. Spawn a machine and run the worker

For each claimed session, provision a VM or container from your image. Inside it, run the worker from the directory where the session's repositories are already checked out:

```bash theme={null}
cd /path/to/repos
devin worker start --session=<session_id> --pool=<pool_id> --acceptor-id=<worker_id>
```

All of the session's repositories must be checked out relative to the working directory where `devin worker start` is invoked:

<Tree>
  <Tree.Folder name="repos" defaultOpen>
    <Tree.Folder name="app" defaultOpen>
      <Tree.File name=".git" />
    </Tree.Folder>

    <Tree.Folder name="infra" defaultOpen>
      <Tree.File name=".git" />
    </Tree.Folder>
  </Tree.Folder>
</Tree>

In this example, you would run `devin worker start` from `repos/`, and the session sees `app/` and `infra/` relative to its working directory.

Useful flags:

| Flag            | Description                                                                                                                    |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `--session`     | Identifies the session to serve; the worker exits when it ends.                                                                |
| `--pool`        | Identifies the pool for the session to serve.                                                                                  |
| `--acceptor-id` | Uses the same acceptor ID as the API claim for this worker.                                                                    |
| `--token`       | Optional auth token for the worker. If omitted, the worker uses `DEVIN_OUTPOSTS_TOKEN`; if neither is set, the command errors. |

Examples:

```bash theme={null}
devin worker start --session=<session_id> --pool=<pool_id> --acceptor-id=<worker_id> --token="<token>"
DEVIN_OUTPOSTS_TOKEN="<token>" devin worker start --session=<session_id> --pool=<pool_id> --acceptor-id=<worker_id>
```

The worker connects out to Devin's cloud, marks the session ready, and begins executing tool calls.

### 4. Fetching the remote binary directly

The `devin worker start` command automatically downloads the correct `devin-remote` binary. If you are building a custom orchestrator that does not use the Devin CLI, you can fetch the binary directly from:

```
https://static.devin.ai/devin-rs/remote/
```

**Determine the latest version:**

```bash theme={null}
# Returns the git SHA of the latest published binary for your platform
curl -fsSL "https://static.devin.ai/devin-rs/remote/latest_linux_x64"
```

**Download and verify:**

```bash theme={null}
SHA=$(curl -fsSL "https://static.devin.ai/devin-rs/remote/latest_linux_x64")

# Download the binary
curl -fL "https://static.devin.ai/devin-rs/remote/devin-remote_${SHA}_linux_x64" \
  -o devin-remote

# Download and verify the checksum
curl -fsSL "https://static.devin.ai/devin-rs/remote/devin-remote_${SHA}_linux_x64.sha256" \
  -o devin-remote.sha256
echo "$(cat devin-remote.sha256)  devin-remote" | sha256sum -c

chmod +x devin-remote
```

**Available platforms:**

| Suffix            | OS / Architecture   |
| ----------------- | ------------------- |
| `linux_x64`       | Linux x86\_64       |
| `macos_arm64`     | macOS Apple Silicon |
| `windows_x64.exe` | Windows x86\_64     |

If the session's queue entry includes a `spec.remote_binary_sha`, use that SHA instead of `latest` — it pins the session to a specific tested version.

#### Spawn contract

If your orchestrator launches `devin-remote` itself, spawn it as:

```bash theme={null}
devin-remote serve
```

with the following environment variables:

| Variable                      | Required             | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| ----------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `DEVIN_OUTPOST_GATEWAY_URL`   | Yes                  | Outpost gateway base URL, e.g. `wss://outpost-gateway.devin.ai`.                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `DEVIN_OUTPOST_CONNECT_TOKEN` | Yes                  | Bearer connect token for the gateway, from the claim response.                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `DEVIN_OUTPOST_SESSION_ID`    | Yes                  | The session ID being served. All three `DEVIN_OUTPOST_*` variables must be set together.                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `DEVIN_REMOTE_STATE_DIR`      | Strongly recommended | Per-session state directory where the remote stores its credentials, tokens, and shell-integration files. Use a unique directory per session (e.g. `~/.devin/worker/sessions/<session_id>`, which is what `devin worker` uses). If unset, the remote falls back to a shared system-wide default (`/opt/.devin` on Linux, `~/.devin` on macOS, `C:\ProgramData\devin` on Windows), which must then exist and be writable — and which leaks per-session state across concurrent sessions. Always set this. |
| `DEVIN_CHROME_PATH`           | Optional             | Path to a Chrome/Chromium binary on the box for the browser tool (there is no Devin-managed Chrome on Outposts).                                                                                                                                                                                                                                                                                                                                                                                         |
| `DEVIN_OUTPOST_DESKTOP`       | Optional             | Set to `true` to enable the desktop (VNC) stream. It is lazy on the remote side — nothing is captured until a viewer connects — so it is safe to enable unconditionally.                                                                                                                                                                                                                                                                                                                                 |

Give the remote a clean environment containing only the variables above plus basic system variables (`PATH`, `HOME`, `USER`, `LOGNAME`, `TMPDIR`, `LANG`, `TZ`, and — for the desktop stream's screen capture on Linux/X11 — `DISPLAY`, `WAYLAND_DISPLAY`, `XAUTHORITY`). Do not leak anything the agent should not be able to see into the remote: it is inherited by the agent's shell.

Additional lifecycle expectations:

* **Working directory**: launch the remote from the directory containing the session's repositories (the same rule as `devin worker start`).
* **Session end**: when the session ends (sleeps or terminates), Devin notifies the remote and it exits with status 0 on its own. Treat a clean exit as the end of the session: confirm the queue entry's `status.session_status` is `suspended` or `terminated` (the status update can lag the exit by a few seconds, so re-read a few times), then release the claim. As a fallback, also poll `status.session_status` while the remote runs and kill the process yourself once it reaches `terminated` (or the queue entry disappears).

### 5. Terminate the machine when the worker exits

When `devin worker start` exits, the session is over (or has been suspended). Terminate the VM or container. If your pool is resumable, snapshot the machine before terminating so you can restore it if the session resumes.

Your orchestrator can track its claimed sessions and their states:

```bash theme={null}
curl -H "Authorization: Bearer $DEVIN_API_TOKEN" \
  "https://api.devin.ai/opbeta/outposts/devins?phase=claimed&acceptor_id=worker-1"
```

Each entry reports a `status.session_status` of `pending`, `running`, `suspended`, or `terminated`.

## Centralization-free scheduling

<Note>
  Planning to run more than \~16 coordinators (workers or orchestrators
  watching and claiming from a pool)? Contact your account team first — larger
  fleets amplify claim contention and queue read load, and we want to make
  sure the pool is provisioned for it.
</Note>

You don't need a central scheduler to run a fleet. The queue API is designed so that many independent workers can serve the same pool without talking to each other:

* **Claims are the only coordination primitive.** Every worker independently watches the queue and races to claim pending sessions. The claim is an atomic compare-and-swap on the server: exactly one worker wins, and every loser gets a `409` and simply moves on to the next pending session. Losing a claim race is normal operation, not an error.
* **Each worker has its own identity.** The `acceptor_id` scopes a worker's claims, renewals, and restart recovery to that worker alone. `devin worker start` generates and persists one automatically per machine, so a fleet needs no identity configuration. Never share an acceptor ID (or a copied worker data directory) across machines — colliding workers will steal each other's claims.
* **Failures self-heal.** If a worker dies after claiming, its claim expires at the claim deadline and the session returns to the queue for another worker to pick up. No fleet-level health tracking is required.

This means scaling out is just running the worker on more machines pointed at the same pool: N machines serve N concurrent sessions, and the rest wait as pending.

Two operational notes:

* **Use the watch endpoint, not repeated full lists.** Do one paginated list to build initial state, then hold a [watch stream](#watch-for-changes) from the returned cursor. Re-polling the entire queue from every worker scales poorly and adds claim latency; the watch stream delivers changes as they happen.
* **Talk to us before going past \~16 machines on a pool.** Coordination-free claiming works well at small fleet sizes, but larger fleets amplify claim contention and queue read load. If you plan to run more than about 16 workers against a single pool, contact your account team first so we can make sure the pool is provisioned for it.

## API reference

All Outposts endpoints live under `https://api.devin.ai/opbeta` and share a common resource shape (`metadata` / `spec` / `status`). List responses return `items`, `cursor`, `has_next_page`, and `total`.

### Devins (`/outposts/devins`)

| Endpoint                                              | Description                                                                            |
| ----------------------------------------------------- | -------------------------------------------------------------------------------------- |
| `GET /outposts/devins?pool=...&phase=pending`         | List sessions waiting for a worker.                                                    |
| `GET /outposts/devins?pool=...&first=...&cursor=...`  | Continue a paginated list from a previous response cursor.                             |
| `GET /outposts/devins?pool=...&watch=true&cursor=...` | Stream `MODIFIED` and `DELETED` events after a list or watch cursor.                   |
| `GET /outposts/devins?phase=claimed&acceptor_id=...`  | List sessions claimed by a given acceptor.                                             |
| `GET /outposts/devins/{session_id}`                   | Get a single queue entry.                                                              |
| `POST /outposts/devins/{session_id}/claim`            | Atomically claim a session (`409` if already claimed). Body: `{"acceptor_id": "..."}`. |
| `POST /outposts/devins/{session_id}/release`          | Release a claim, returning the session to the queue. Body: `{"acceptor_id": "..."}`.   |

### Pools (`/outposts/pools`)

| Endpoint                           | Description                                                                            |
| ---------------------------------- | -------------------------------------------------------------------------------------- |
| `GET /outposts/pools`              | List your account's pools.                                                             |
| `POST /outposts/pools`             | Create a pool. Body: `{"name": "my-pool", "platform": "linux", "description": "..."}`. |
| `GET /outposts/pools/{pool_id}`    | Get a single pool.                                                                     |
| `DELETE /outposts/pools/{pool_id}` | Delete a pool (`409` while it has active claims).                                      |

```json theme={null}
{
  "metadata": {
    "pool_id": "outpost_env-...",
    "account_id": "...",
    "created_at": 1781050000
  },
  "spec": {
    "name": "my-pool",
    "platform": "linux",
    "description": "..."
  },
  "status": {
    "queue_depth": 3,
    "active_claims": 2
  }
}
```

`status.queue_depth` and `status.active_claims` are useful autoscaling signals: if the queue is backing up, your orchestrator can provision more warm machines.

## What workers can do

Sessions running on Outposts workers are full-featured Devin sessions: skills, knowledge, MCP servers, and secrets all work the same way as in Devin Cloud, delivered through the worker's connection. Your repositories, build caches, and tool execution stay in your environment; session artifacts like screenshots are uploaded to Devin Cloud so you can view them in the session and in PRs.

<Warning>
  Outpost sessions have strict readiness timeouts. After your orchestrator
  claims a session, the worker must connect before the claim deadline —
  otherwise the claim expires and you are still billed for the fixed and hourly
  costs incurred during the timeout window.
</Warning>
