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

# Set up an environment

> Create, verify, restrict, update, and delete the sandbox configuration that sessions run in.

An environment is the saved definition of the sandbox a session runs in: its compute size, workspace disk, setup script, and network access. You create it once, prove its setup script works with a setup run, and then name its `environment_id` when you start sessions.

Creating an environment starts no compute. Compute starts only when you run a setup test or start a session. Each top-level session gets its own fresh sandbox, so two sessions on the same environment never share files. Subagents and teammates that a session starts share that session's sandbox.

The TypeScript samples assume the `rl` client from [Client setup](/managed-agents/api#client-setup). For every field, default, and limit, see [Environment reference](/managed-agents/environments-reference).

## Before you begin

* You need the organization developer or admin role to create, change, test, or delete environments. The organization user role can view environments and their setup runs. See [Organizations and roles](/managed-agents/organizations-and-roles) and [API keys](/managed-agents/api-keys).
* Setup runs start real compute, the same kind a session uses.
* Decide which hosts the sandbox must reach, such as package registries or `github.com`. New environments start with no internet access.

## How an environment becomes usable

An environment without a setup script is usable as soon as you create it. An environment with a setup script is usable only after a setup run passes for its current configuration.

```mermaid theme={"theme":"css-variables"}
flowchart LR
  create["Create environment"] --> hasScript{"Setup script?"}
  hasScript -->|"No"| usable["Usable: sessions can start"]
  hasScript -->|"Yes"| run["Start a setup run"]
  run --> result{"Run passed?"}
  result -->|"Yes"| usable
  result -->|"No"| fix["Read the hint and log, fix, run again"]
  fix --> run
  usable -->|"Change setup, compute, disk, variables, mounts, or network"| stale["Stale: run setup again"]
  stale --> run
```

**What success means for an environment.** A session can start on the environment when either of these is true:

* `setup_verification.status` is `not_applicable`, because there is no setup script.
* `setup_verification.status` is `verified`, `stale` is `false`, and the service kept a reusable image of the prepared sandbox. In the console, the **Setup** column reads **Verified**.

Otherwise `startSession` returns `422 environment_not_verified`, and `details.next_action` names the call that fixes it.

When a setup run passes, the service saves the prepared sandbox as a reusable image. New sessions start from that image, so they don't repeat the setup work. In rare cases the service can't boot the saved image and runs the setup script once on the new session's own compute instead.

## Choose a runtime

A runtime (the `provider` field) is the kind of sandbox the environment runs on. List the runtimes you can select. Today the list contains `runs`, shown in the console as **Agent runner**: managed Linux compute with a persistent workspace, network policy, and setup verification. Always choose a runtime from this list rather than hard-coding one.

<Tabs>
  <Tab title="Console">
    1. In the sidebar, click **Environments**.
    2. Click **Create environment**.
    3. Open the **Runtime** menu. It lists the same runtimes, and you can't change the choice after you create the environment.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    const { items } = await rl.managedAgents.listSandboxProviders();
    console.log(items.map((provider) => provider.provider));
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl 'https://api.recursion.labelbox.com/managed-agents/v1/sandbox-providers' \
      -H "Authorization: Bearer $RECURSION_API_KEY"
    ```
  </Tab>
</Tabs>

```json theme={"theme":"css-variables"}
{
  "items": [
    {
      "provider": "runs",
      "displayName": "Agent runner",
      "default": true,
      "requiresCredential": null
    }
  ]
}
```

The response also carries a `description` for each runtime. The list is the same for every organization and supports `If-None-Match` for caching.

## Create an environment

This example creates an Agent runner environment with 2 vCPU, 4 GiB of memory, a 20 GiB workspace, and a setup script that installs two pinned Python packages. Its network policy allows only the two Python package hosts the script needs. `verify=true` starts a setup run as soon as the environment is saved.

<Tabs>
  <Tab title="Console">
    1. In the sidebar, click **Environments**, then click **Create environment**.
    2. Enter a **Name** and choose **Agent runner** for **Runtime**.
    3. For **Internet access**, choose **Enabled**. The console offers only **Enabled** or **No access**. To allow only specific hosts, use the API as in the other tabs, or edit the policy later as shown in [Control network access](#control-network-access).
    4. Under **Compute**, choose **CPU** and click **Standard** (2 vCPU, 4 GiB).
    5. Under **Storage and lifecycle**, set **Workspace disk (GiB)** to `20`.
    6. Paste the script into **Setup script**, or pick one from **Start from a template**.
    7. Click **Save and test setup**. The **Setup test** panel streams the run's log.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    const environment = await rl.managedAgents.createEnvironment({
      verify: true,
      body: {
        name: 'data-analysis',
        provider: 'runs',
        resources: { cpu_milli: 2000, memory_mib: 4096 },
        pvc_size_gi: 20,
        network_policy: {
          version: 'v1',
          rules: [
            { match: { host_glob: 'pypi.org' }, action: { type: 'allow' } },
            { match: { host_glob: 'files.pythonhosted.org' }, action: { type: 'allow' } },
          ],
        },
        setup: {
          script:
            'uv venv --seed /workspace/.venv\n' +
            'uv pip install --python /workspace/.venv/bin/python "requests==2.32.3" "pandas==2.2.3"',
          timeout_seconds: 900,
        },
      },
    });
    console.log(environment.environment_id, environment.setup_verification?.active_setup_run_id);
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/environments?verify=true' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'Content-Type: application/json' \
      -d '{
        "name": "data-analysis",
        "provider": "runs",
        "resources": {"cpu_milli": 2000, "memory_mib": 4096},
        "pvc_size_gi": 20,
        "network_policy": {"version": "v1", "rules": [
          {"match": {"host_glob": "pypi.org"}, "action": {"type": "allow"}},
          {"match": {"host_glob": "files.pythonhosted.org"}, "action": {"type": "allow"}}
        ]},
        "setup": {
          "script": "uv venv --seed /workspace/.venv\nuv pip install --python /workspace/.venv/bin/python \"requests==2.32.3\" \"pandas==2.2.3\"",
          "timeout_seconds": 900
        }
      }'
    ```
  </Tab>
</Tabs>

The response is the saved environment. Because `verify=true` started a run, `setup_verification.status` is `running` and `active_setup_run_id` names the run to follow.

```json theme={"theme":"css-variables"}
{
  "organization_id": "cl9x2k4f1000008l5h3g7a2bq",
  "environment_id": "9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13",
  "name": "data-analysis",
  "scope": "organization",
  "provider": "runs",
  "setup": {
    "script": "uv venv --seed /workspace/.venv\nuv pip install --python /workspace/.venv/bin/python \"requests==2.32.3\" \"pandas==2.2.3\"\n",
    "timeout_seconds": 900
  },
  "resources": {"cpu_milli": 2000, "memory_mib": 4096},
  "network_policy": {
    "version": "v1",
    "rules": [
      {"match": {"host_glob": "pypi.org"}, "action": {"type": "allow"}},
      {"match": {"host_glob": "files.pythonhosted.org"}, "action": {"type": "allow"}}
    ]
  },
  "privileged": false,
  "pvc_size_gi": 20,
  "computer_use": false,
  "setup_verification": {
    "status": "running",
    "stale": false,
    "setup_run_id": "3a8e5c10-7d42-4f96-b1a3-9c6e2d5f0b74",
    "active_setup_run_id": "3a8e5c10-7d42-4f96-b1a3-9c6e2d5f0b74",
    "verified_by_user_id": "cl9x2k4f1000108l5b7d1e3kq"
  },
  "setup_updated_by_user_id": "cl9x2k4f1000108l5b7d1e3kq",
  "setup_updated_at": "2026-09-17T12:04:11Z",
  "created_at": "2026-09-17T12:04:11Z",
  "updated_at": "2026-09-17T12:04:11Z"
}
```

Keep `environment_id` to start sessions, and `active_setup_run_id` to [follow the setup run](#wait-for-the-result).

A few behaviors to know:

* **`verify` never loses your save.** If the environment has no setup script, `verify=true` is ignored. If the run can't start, the environment is still saved and `setup_warnings` contains a `verify_not_started` entry; start the run yourself with `createEnvironmentSetupRun`. Starting the run needs the same permission as `createEnvironmentSetupRun`.
* **Creates are not idempotent.** Retrying a create that timed out can make a second environment with the same name. List environments before you retry.
* **Network access is closed by default.** Omitting `network_policy` stores `{"version":"v1","rules":[]}`, which blocks every host. Send `{}` only when you deliberately want unrestricted outbound access.
* **Privileged Docker is off by default.** `privileged` defaults to `false` whatever network policy you choose.

## List and get environments

`listEnvironments` returns every environment in your organization, newest first, with the full configuration. It is not paginated, and deleted environments are left out. `getEnvironment` returns one environment. An unknown, deleted, or other-organization id returns `404 not_found`.

<Tabs>
  <Tab title="Console">
    1. In the sidebar, click **Environments**. The table shows each environment's **Name**, **ID**, **Provider**, **Resources**, and **Setup** status.
    2. Search by name, ID, or provider.
    3. Click a name, or choose **Edit** from its action menu, to open the full configuration.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    const { environments } = await rl.managedAgents.listEnvironments();
    const environment = await rl.managedAgents.getEnvironment({
      environment_id: '9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13',
    });
    console.log(environments?.length, environment.setup_verification?.status);
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl 'https://api.recursion.labelbox.com/managed-agents/v1/environments' \
      -H "Authorization: Bearer $RECURSION_API_KEY"

    curl 'https://api.recursion.labelbox.com/managed-agents/v1/environments/9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13' \
      -H "Authorization: Bearer $RECURSION_API_KEY"
    ```
  </Tab>
</Tabs>

```json theme={"theme":"css-variables"}
{
  "environments": [
    {
      "environment_id": "9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13",
      "name": "data-analysis",
      "provider": "runs",
      "resources": {"cpu_milli": 2000, "memory_mib": 4096},
      "setup_verification": {"status": "verified", "stale": false},
      "created_at": "2026-09-17T12:04:11Z",
      "updated_at": "2026-09-17T12:04:11Z"
    }
  ]
}
```

Some fields are omitted here. `environments` is `null`, not an empty array, when the organization has no environments.

## Start from a setup template

Templates are starting-point scripts for common stacks: `python-uv` (Python with uv), `node-npm` (Node with npm), `system-tools` (Debian packages with apt), and `gpu-pytorch` (PyTorch with CUDA). Copy one into `setup.script` and edit it. A template is a copy, not a link: later template changes never change your environment.

Every template downloads packages, so it needs network access. Choosing a template doesn't change the network policy. Allow the hosts the script uses, or choose **Enabled** for **Internet access**.

<Tabs>
  <Tab title="Console">
    1. Open the environment form.
    2. Next to **Setup script**, open **Start from a template** and pick one.
    3. Edit the script for your project. The form shows **From:** and the template title while you work.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    const { items } = await rl.managedAgents.listEnvironmentSetupTemplates();
    const python = items.find((template) => template.id === 'python-uv');
    console.log(python?.script);
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl 'https://api.recursion.labelbox.com/managed-agents/v1/environment-setup-templates' \
      -H "Authorization: Bearer $RECURSION_API_KEY"
    ```
  </Tab>
</Tabs>

```json theme={"theme":"css-variables"}
{
  "items": [
    {
      "id": "node-npm",
      "title": "Node project with npm",
      "summary": "Install global Node tooling and a project's dependencies from its lock file.",
      "script": "# Node tooling. npm ci installs exactly what the lock file says.\nnode --version\nnpm --version\n\nnpm install -g \"typescript@5.6.3\"\n"
    }
  ]
}
```

The response is shortened to one template. `accelerator` is `true` on templates that assume a GPU. The response also repeats the list under `templates`, an older name for `items`.

## Write the setup script

The setup script runs on fresh compute after the sandbox starts and before the agent's first turn. It runs as the sandbox user in a bash login shell with `set -eo pipefail`. In a setup run, it gets the environment's variables and network policy, but no session credentials such as vault grants.

* **One command per line.** The first failing line ends the run, and the result names that line.
* **Don't wrap lines in `bash -lc`.** The script already runs under bash, and the wrapper's quoting is a common failure.
* **Pin versions.** The run proves the exact script, so unpinned installs can drift from what was proven.
* **Put Python packages in `/workspace/.venv`.** The sandbox selects that virtual environment for the agent's commands automatically. Install through its interpreter, for example `uv pip install --python /workspace/.venv/bin/python`.
* **Don't rely on `export`, `source`, aliases, or shell functions.** The agent's commands run in a new shell, so none of these carry over.
* **Use `sudo` for apt.** The script doesn't run as root.
* **Put files the agent needs under `/workspace`.** The workspace persists for the life of the session's sandbox.

The script can be at most 64 KiB. `setup.timeout_seconds` bounds the whole script; it defaults to 600 and accepts 10 through 3600. A script that runs past the timeout fails with exit code `124`.

The service saves the script with Windows line endings converted and trailing whitespace removed. `setup_warnings` flags risky lines such as `curl | sh`, unpinned installs, and `bash -lc` wrappers. Warnings are advice; they never block a save or a run.

## Verify the setup script

A setup run starts fresh compute from the environment, runs the setup script exactly as a session would, records every line of output, checks the compute, saves a reusable image, and releases the compute. The result becomes the environment's `setup_verification`.

Only one setup run can be in flight per environment. Your organization can have 4 manual setup runs in flight at once.

### Start a setup run

Skip this step if you created or updated the environment with `verify=true`.

<Tabs>
  <Tab title="Console">
    1. In **Environments**, open the environment.
    2. In the **Setup test** panel, click **Test setup**.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    const run = await rl.managedAgents.createEnvironmentSetupRun({
      environment_id: '9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13',
    });
    console.log(run.setup_run_id, run.status);
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/environments/9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13/setup-runs' \
      -H "Authorization: Bearer $RECURSION_API_KEY"
    ```
  </Tab>
</Tabs>

The call returns `202 Accepted` with the queued run.

```json theme={"theme":"css-variables"}
{
  "organization_id": "cl9x2k4f1000008l5h3g7a2bq",
  "environment_id": "9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13",
  "setup_run_id": "3a8e5c10-7d42-4f96-b1a3-9c6e2d5f0b74",
  "kind": "manual",
  "status": "queued",
  "requested_by_user_id": "cl9x2k4f1000108l5b7d1e3kq",
  "message": "Queued; compute will be provisioned shortly.",
  "created_at": "2026-09-17T12:05:02Z"
}
```

Some fields are omitted here. If a run is already in flight, the call returns `409 setup_run_in_progress` with that run's id in `details.setup_run_id`. An environment with no setup script returns `400 invalid_request`, because there is nothing to verify.

### Wait for the result

Poll `getEnvironmentSetupRun` until `status` is `succeeded`, `failed`, or `cancelled`. Pass `wait_seconds` to have the server hold the request until the run finishes or the wait ends. The server caps each wait at 5 seconds, so loop until the status is terminal.

<Tabs>
  <Tab title="Console">
    1. Watch the **Setup test** panel. It shows each phase and streams the log.
    2. The status reads **Setup verified.** on success, or shows the hint and failing line on failure.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    let run = await rl.managedAgents.getEnvironmentSetupRun({
      environment_id: '9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13',
      setup_run_id: '3a8e5c10-7d42-4f96-b1a3-9c6e2d5f0b74',
      wait_seconds: 5,
    });
    while (!['succeeded', 'failed', 'cancelled'].includes(run.status)) {
      run = await rl.managedAgents.getEnvironmentSetupRun({
        environment_id: run.environment_id,
        setup_run_id: run.setup_run_id,
        wait_seconds: 5,
      });
    }
    console.log(run.status, run.hint);
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl 'https://api.recursion.labelbox.com/managed-agents/v1/environments/9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13/setup-runs/3a8e5c10-7d42-4f96-b1a3-9c6e2d5f0b74?wait_seconds=5' \
      -H "Authorization: Bearer $RECURSION_API_KEY"
    ```
  </Tab>
</Tabs>

```json theme={"theme":"css-variables"}
{
  "environment_id": "9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13",
  "setup_run_id": "3a8e5c10-7d42-4f96-b1a3-9c6e2d5f0b74",
  "kind": "manual",
  "status": "succeeded",
  "exit_code": 0,
  "duration_ms": 74210,
  "log_lines": 42,
  "log_bytes": 3180,
  "created_at": "2026-09-17T12:05:02Z",
  "started_at": "2026-09-17T12:05:03Z",
  "finished_at": "2026-09-17T12:06:17Z"
}
```

Some fields are omitted here. `next_action` names the call to make next, such as polling again or reading the log.

A setup run moves through these statuses:

```mermaid theme={"theme":"css-variables"}
stateDiagram-v2
  [*] --> queued: run requested
  queued --> provisioning: compute starts
  provisioning --> running: script and checks run
  running --> succeeded: script and checks passed
  queued --> failed: run could not start
  provisioning --> failed: compute failed
  running --> failed: script or checks failed
  queued --> cancelled: cancel
  provisioning --> cancelled: cancel
  running --> cancelled: cancel
  succeeded --> [*]
  failed --> [*]
  cancelled --> [*]
```

`phase` tells you where the run is or where it ended: `provision`, `gpu_check`, `setup`, `profile`, `commit` (saving the reusable image), or `cleanup`. Only a failure in `setup` points at your script.

**What success means for a setup run.** `status` is `succeeded`, and the environment now reads `setup_verification.status: "verified"` with `stale: false` for the configuration the run tested. Sessions can start. If the script passed but the image step failed, the console shows **Setup passed, but its reusable image failed.** and sessions still return `environment_not_verified`; run setup again.

Other outcomes:

* **`failed`** records a failed verdict. Sessions can't start until a later run passes.
* **`cancelled`** records nothing. If the environment was `verified` and unchanged when the run started, it stays `verified`. If it read `running`, it goes back to `never`.
* **Re-verifying doesn't interrupt sessions.** While a new run is in flight on a verified, unchanged environment, it stays `verified` and sessions keep starting. The console shows **Re-testing**. In every other case, starting a run sets the status to `running`, replacing an earlier `failed` or stale verdict.

### Read the log

The log interleaves four streams: `stdout`, `stderr`, `marker` (the script line about to run), and `system` (notes about phases outside your script). Secrets are redacted before the log is stored.

Read from the start with `after=0`. Each page returns up to `limit` lines (default 500, maximum 2000) and a `next_after` cursor. To tail a live run, pass `next_after` as `after` on the next call with `wait_seconds=0`, and wait about two seconds between calls. Stop when `status` is terminal and a page comes back empty.

<Tabs>
  <Tab title="Console">
    1. Open the environment. The **Setup test** panel streams the log of the selected run.
    2. Under **Recent runs**, pick an earlier run to read its log.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    const page = await rl.managedAgents.getEnvironmentSetupRunLog({
      environment_id: '9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13',
      setup_run_id: '3a8e5c10-7d42-4f96-b1a3-9c6e2d5f0b74',
      after: 0,
      limit: 500,
      wait_seconds: 0,
    });
    console.log(page.lines, page.next_after, page.status);
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl 'https://api.recursion.labelbox.com/managed-agents/v1/environments/9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13/setup-runs/3a8e5c10-7d42-4f96-b1a3-9c6e2d5f0b74/log?after=0&limit=500&wait_seconds=0' \
      -H "Authorization: Bearer $RECURSION_API_KEY"
    ```
  </Tab>
</Tabs>

```json theme={"theme":"css-variables"}
{
  "lines": [
    {"seq": 8, "stream": "marker", "line": 2, "text": "uv pip install --python /workspace/.venv/bin/python \"requests==2.32.3\" \"pandas==2.2.3\"", "at": "2026-09-17T12:06:01Z"},
    {"seq": 9, "stream": "stderr", "line": 2, "text": "Resolved 7 packages in 412ms", "at": "2026-09-17T12:06:02Z"}
  ],
  "next_after": 9,
  "status": "running"
}
```

The service keeps up to 4 MiB of log per run. Past that, `truncated` is `true`; the beginning of the log is kept, and the last 2 KiB of stderr stays in the run's `stderr_tail`.

### List setup runs

`listEnvironmentSetupRuns` returns the environment's recent runs, newest first. It includes manual runs (`kind: "manual"`, which you start) and session runs (`kind: "session"`, recorded when a session ran setup on its own compute, with its `session_id`). The newest manual run is the one `setup_verification` describes. `limit` accepts 1 through 50 and defaults to 50.

<Tabs>
  <Tab title="Console">
    1. Open the environment.
    2. Read the **Recent runs** list in the **Setup test** panel.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    const { setup_runs } = await rl.managedAgents.listEnvironmentSetupRuns({
      environment_id: '9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13',
      limit: 10,
    });
    console.log(setup_runs.map((run) => `${run.kind} ${run.status}`));
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl 'https://api.recursion.labelbox.com/managed-agents/v1/environments/9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13/setup-runs?limit=10' \
      -H "Authorization: Bearer $RECURSION_API_KEY"
    ```
  </Tab>
</Tabs>

```json theme={"theme":"css-variables"}
{
  "setup_runs": [
    {
      "environment_id": "9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13",
      "setup_run_id": "3a8e5c10-7d42-4f96-b1a3-9c6e2d5f0b74",
      "kind": "manual",
      "status": "succeeded",
      "created_at": "2026-09-17T12:05:02Z",
      "finished_at": "2026-09-17T12:06:17Z"
    },
    {
      "environment_id": "9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13",
      "setup_run_id": "1f6b2d90-4e8a-4c37-9a15-7d0c3e8b2a61",
      "kind": "manual",
      "status": "failed",
      "hint_code": "egress_blocked",
      "created_at": "2026-09-17T11:52:40Z",
      "finished_at": "2026-09-17T11:53:31Z"
    }
  ]
}
```

`setup_runs` is an empty array when no run has been requested.

### Cancel a setup run

Cancel a manual run that is `queued`, `provisioning`, or `running`, for example after you spot a mistake in the script. The run stops, its compute is released, and `active_setup_run_id` is cleared. A cancelled run records no verdict.

<Tabs>
  <Tab title="Console">
    1. Open the environment while a run is in flight.
    2. In the **Setup test** panel, click **Cancel run**. The status reads **Cancelled.**
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    const run = await rl.managedAgents.cancelEnvironmentSetupRun({
      environment_id: '9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13',
      setup_run_id: '3a8e5c10-7d42-4f96-b1a3-9c6e2d5f0b74',
    });
    console.log(run.status);
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/environments/9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13/setup-runs/3a8e5c10-7d42-4f96-b1a3-9c6e2d5f0b74/cancel' \
      -H "Authorization: Bearer $RECURSION_API_KEY"
    ```
  </Tab>
</Tabs>

```json theme={"theme":"css-variables"}
{
  "environment_id": "9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13",
  "setup_run_id": "3a8e5c10-7d42-4f96-b1a3-9c6e2d5f0b74",
  "kind": "manual",
  "status": "cancelled",
  "message": "Setup run was cancelled.",
  "created_at": "2026-09-17T12:05:02Z"
}
```

A run that already finished returns `409 setup_run_finished`, and nothing changes. A session run returns `400 invalid_request`; stop the session instead.

## Fix a failed setup run

On a failed run, read `hint` first. Then read `failed_line`, `failed_command`, `exit_code`, and `stderr_tail`. Fetch the full log only if those don't explain the failure. The same summary appears in the environment's `setup_verification.last_run`.

```json theme={"theme":"css-variables"}
{
  "environment_id": "9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13",
  "setup_run_id": "1f6b2d90-4e8a-4c37-9a15-7d0c3e8b2a61",
  "kind": "manual",
  "status": "failed",
  "phase": "setup",
  "exit_code": 2,
  "failed_line": 2,
  "failed_command": "uv pip install --python /workspace/.venv/bin/python \"requests==2.32.3\" \"pandas==2.2.3\"",
  "hint_code": "egress_blocked",
  "hint": "A host the script reaches for could not be reached. If this environment uses a restricted network_policy, add an allow rule for the host or set network_policy to {} for unrestricted internet access. Otherwise, check the host or registry.",
  "stderr_tail": "error: Failed to fetch: `https://pypi.org/simple/requests/`\n  Caused by: Could not resolve host: pypi.org",
  "failure_code": "environment_setup_failed",
  "created_at": "2026-09-17T11:52:40Z",
  "finished_at": "2026-09-17T11:53:31Z"
}
```

| `hint_code`         | Likely cause                                                            | Fix                                                                            |
| ------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| `egress_blocked`    | The network policy blocks a host the script needs.                      | Add an allow rule for the host, or choose **Enabled** for **Internet access**. |
| `timeout`           | The script ran longer than `setup.timeout_seconds`. Exit code is `124`. | Raise the timeout (up to 3600), or make the slow step faster.                  |
| `bash_lc_wrapper`   | A line wraps its command in `bash -lc` and the quoting broke.           | Write the command directly.                                                    |
| `command_not_found` | A command isn't installed. Exit code is often `127`.                    | Install it earlier in the script, or call it by full path.                     |
| `pip_not_installed` | `pip` isn't on the path.                                                | Create `/workspace/.venv` with uv and install through its interpreter.         |
| `apt_lock`          | Another process holds apt's lock on fresh compute.                      | Wait for the lock before installing, as the `system-tools` template does.      |
| `apt_permission`    | apt ran without root.                                                   | Prefix apt commands with `sudo`.                                               |
| `no_space`          | The workspace disk filled up.                                           | Raise `pvc_size_gi`, or download less during setup.                            |
| `cuda_unavailable`  | The script expects a GPU the sandbox doesn't have.                      | Add an accelerator to the environment's compute, or drop the CUDA step.        |

When `phase` is anything other than `setup`, the failure happened outside your script; retry once before you change anything. `failure_code` classifies the failure with the same codes session failures use, such as `environment_setup_failed`, `sandbox_capacity_unavailable`, or `sandbox_provision_timeout`.

## Control network access

`network_policy` controls which hosts the sandbox can reach. It applies to the setup script, the agent's commands, package downloads, and websites opened by the sandbox browser. Outbound traffic that no rule allows is blocked.

Some traffic doesn't leave from the sandbox, so the policy doesn't govern it: model calls, calls to the agent's MCP servers, and the `web_search` and `web_fetch` tools. Control those in the agent's configuration. See [Tools](/managed-agents/tools).

| Setting               | `network_policy`                                    | Console **Internet access**                                      |
| --------------------- | --------------------------------------------------- | ---------------------------------------------------------------- |
| No internet (default) | `{"version": "v1", "rules": []}`                    | **No access**                                                    |
| Only listed hosts     | `{"version": "v1", "rules": [ ...allow rules... ]}` | Set through the API; the console shows **Custom policy (saved)** |
| Unrestricted          | `{}`                                                | **Enabled**                                                      |

Rules are checked in order, and the first match wins. For the rule grammar, see [Network policy](/managed-agents/environments-reference#network-policy).

<Warning>
  Access is granted per host, not per action. Once a host is allowed, sandbox code can send anything to it, including uploads such as `git push`. Allow only the hosts the work needs, especially when agents process untrusted content.
</Warning>

### Allow specific hosts

A policy change must prove you saw the current access settings. Read the environment, then send its current `network_policy` and `privileged` values in `expected_access` along with the new policy. This example allows the hosts that `git` and the `gh` CLI use for GitHub. Attaching a GitHub credential in a vault doesn't open these hosts; the environment's policy must allow them.

<Tabs>
  <Tab title="Console">
    The console can't edit individual host rules. Under **Internet access**, choose **Enabled** for unrestricted access or **No access** to block everything, then click **Save environment**. Choosing either replaces a saved custom policy. Use the API for host rules.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    const environmentId = '9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13';
    const current = await rl.managedAgents.getEnvironment({ environment_id: environmentId });
    const allow = (host: string) => ({ match: { host_glob: host }, action: { type: 'allow' } });
    const environment = await rl.managedAgents.updateEnvironment({
      environment_id: environmentId,
      verify: true,
      body: {
        network_policy: {
          version: 'v1',
          rules: [
            ...(current.network_policy?.rules ?? []),
            ...['github.com', 'api.github.com', 'codeload.github.com',
              'objects.githubusercontent.com', 'github-cloud.s3.amazonaws.com'].map(allow),
          ],
        },
        expected_access: {
          network_policy: current.network_policy!,
          privileged: current.privileged!,
        },
      },
    });
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl -X PATCH 'https://api.recursion.labelbox.com/managed-agents/v1/environments/9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13?verify=true' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'Content-Type: application/json' \
      -d '{
        "network_policy": {"version": "v1", "rules": [
          {"match": {"host_glob": "pypi.org"}, "action": {"type": "allow"}},
          {"match": {"host_glob": "files.pythonhosted.org"}, "action": {"type": "allow"}},
          {"match": {"host_glob": "github.com"}, "action": {"type": "allow"}},
          {"match": {"host_glob": "api.github.com"}, "action": {"type": "allow"}},
          {"match": {"host_glob": "codeload.github.com"}, "action": {"type": "allow"}},
          {"match": {"host_glob": "objects.githubusercontent.com"}, "action": {"type": "allow"}},
          {"match": {"host_glob": "github-cloud.s3.amazonaws.com"}, "action": {"type": "allow"}}
        ]},
        "expected_access": {
          "network_policy": {"version": "v1", "rules": [
            {"match": {"host_glob": "pypi.org"}, "action": {"type": "allow"}},
            {"match": {"host_glob": "files.pythonhosted.org"}, "action": {"type": "allow"}}
          ]},
          "privileged": false
        }
      }'
    ```
  </Tab>
</Tabs>

The response is the updated environment. Because the policy changed, the earlier verdict no longer applies. `verify=true` started a new run, so the environment reads `running` until that run finishes. Some fields are omitted here.

```json theme={"theme":"css-variables"}
{
  "environment_id": "9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13",
  "network_policy": {
    "version": "v1",
    "rules": [
      {"match": {"host_glob": "pypi.org"}, "action": {"type": "allow"}},
      {"match": {"host_glob": "files.pythonhosted.org"}, "action": {"type": "allow"}},
      {"match": {"host_glob": "github.com"}, "action": {"type": "allow"}},
      {"match": {"host_glob": "api.github.com"}, "action": {"type": "allow"}},
      {"match": {"host_glob": "codeload.github.com"}, "action": {"type": "allow"}},
      {"match": {"host_glob": "objects.githubusercontent.com"}, "action": {"type": "allow"}},
      {"match": {"host_glob": "github-cloud.s3.amazonaws.com"}, "action": {"type": "allow"}}
    ]
  },
  "privileged": false,
  "setup_verification": {
    "status": "running",
    "stale": false,
    "setup_run_id": "cd7a01a8-6f35-43d0-bef9-54c91612de48",
    "active_setup_run_id": "cd7a01a8-6f35-43d0-bef9-54c91612de48"
  }
}
```

How the access precondition works:

* `network_policy` in a PATCH replaces the whole policy. Include every rule you want to keep.
* `expected_access` is required when a PATCH changes a saved policy other than `{}` (including the default no-internet policy), or turns `privileged` on. Without it the API returns `428 precondition_required`. If either value changed since your read, it returns `412 precondition_failed`; read the environment again and retry.
* Closing access (changing `{}` to a restricted policy, or turning `privileged` off) doesn't need `expected_access`.
* Omitting `network_policy` or `privileged` in a PATCH keeps the saved value.

## Update an environment

An update is partial: omitted fields keep their saved values. A few fields replace the saved value as a whole:

* `setup` replaces the whole setup object. Send both `script` and `timeout_seconds` to keep both. `{"script": ""}` removes the script.
* `resources` replaces the whole compute size.
* `env_vars` and `mounts` replace the whole map or list. Send `{}` or `[]` to clear them.

Changes apply only to sandboxes started afterward. A session already running keeps the configuration it started with. Pass `verify=true` to start a setup run in the same call.

This example raises compute to 4 vCPU and 8 GiB and raises the idle stop to one hour.

<Tabs>
  <Tab title="Console">
    1. In **Environments**, click the environment name, or choose **Edit** from its action menu.
    2. Under **Compute**, click **Heavy** (4 vCPU, 8 GiB).
    3. Under **Storage and lifecycle**, set **Idle stop after (seconds)** to `3600`.
    4. Click **Save and test setup**, or **Save environment** to save without testing. **Runtime** is read-only after creation.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    const environment = await rl.managedAgents.updateEnvironment({
      environment_id: '9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13',
      verify: true,
      body: {
        resources: { cpu_milli: 4000, memory_mib: 8192 },
        idle_stop_after_seconds: 3600,
      },
    });
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl -X PATCH 'https://api.recursion.labelbox.com/managed-agents/v1/environments/9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13?verify=true' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'Content-Type: application/json' \
      -d '{"resources": {"cpu_milli": 4000, "memory_mib": 8192}, "idle_stop_after_seconds": 3600}'
    ```
  </Tab>
</Tabs>

The response is the updated environment. Some fields are omitted here.

```json theme={"theme":"css-variables"}
{
  "environment_id": "9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13",
  "resources": {"cpu_milli": 4000, "memory_mib": 8192},
  "idle_stop_after_seconds": 3600,
  "setup_verification": {
    "status": "running",
    "stale": false,
    "setup_run_id": "5e2f9a47-0c1d-4b83-a6e2-91d7f3b0c85a",
    "active_setup_run_id": "5e2f9a47-0c1d-4b83-a6e2-91d7f3b0c85a"
  }
}
```

Changing compute makes the earlier verdict stale, so sessions wait for the new run to pass. Changing only the name, description, metadata, idle stop, retention, or computer use keeps the verdict. The full list is in [Setup verification](/managed-agents/environments-reference#setup-verification).

A concurrent edit to the same environment's access or compute can return `409 revision_conflict`. Read the environment again and retry.

## Delete an environment

Deleting removes the environment from lists and reads, and new sessions can't use it. Sessions that are running keep their sandbox, and past session history stays readable. There is no restore, so create a new environment if you need it back.

<Tabs>
  <Tab title="Console">
    1. In **Environments**, open the environment's action menu.
    2. Click **Delete**.
    3. In the **Delete environment** dialog, click **Delete**.
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={"theme":"css-variables"}
    await rl.managedAgents.deleteEnvironment({
      environment_id: '9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13',
    });
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl -X DELETE 'https://api.recursion.labelbox.com/managed-agents/v1/environments/9d3e7b52-1a4c-4f80-b6e9-2c8a5d0f7e13' \
      -H "Authorization: Bearer $RECURSION_API_KEY"
    ```
  </Tab>
</Tabs>

```json theme={"theme":"css-variables"}
{ "deleted": true }
```

## What can go wrong

| Symptom or code                                       | Cause                                                                                                 | Fix                                                                                                                             |
| ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `422 environment_not_verified` on `startSession`      | The script has never passed, failed last time, is still running, is stale, or has no reusable image.  | Follow `details.next_action`: start a setup run, or wait for the one in flight.                                                 |
| `409 setup_run_in_progress`                           | A setup run is already in flight for this environment.                                                | Poll the run in `details.setup_run_id`, or cancel it.                                                                           |
| `429 setup_run_limit`                                 | Your organization already has 4 manual setup runs in flight.                                          | Wait `Retry-After` seconds, or cancel a run listed in `details.in_flight`.                                                      |
| `409 setup_run_finished`                              | You cancelled a run that had already finished.                                                        | Nothing to do; read the run's result.                                                                                           |
| `400 invalid_request` on `setup.script`               | The script is over 64 KiB or contains a NUL byte, or you requested a run with no script.              | Shorten or fix the script, or skip verification for a script-free environment.                                                  |
| `400 invalid_request` on `network_policy`             | The policy isn't `v1`, has over 200 rules, or has an unknown field.                                   | Fix the policy; see [Network policy](/managed-agents/environments-reference#network-policy).                                    |
| `400 invalid_request` on `resources`                  | CPU or memory is out of range, or no machine offers that CPU and memory together.                     | Pick a size from the console shortcuts, or follow the size named in the error message.                                          |
| `400 invalid_request` on `resources.accelerator.name` | The GPU model is missing or isn't offered.                                                            | Use a model listed in the error message. See [Compute](/managed-agents/environments-reference#compute).                         |
| `400 invalid_request` on `idle_stop_after_seconds`    | Computer use is on and the idle stop is under 600 seconds.                                            | Use `0` or at least 600.                                                                                                        |
| `400 invalid_request` on `env_vars.PATH`              | `PATH` can't be set through `env_vars`.                                                               | Call tools by full path, or prepend to `$PATH` inside the command that needs it.                                                |
| `428 precondition_required`                           | A PATCH changed a saved policy other than `{}`, or turned on `privileged`, without `expected_access`. | Read the environment and resend with its `network_policy` and `privileged`.                                                     |
| `412 precondition_failed`                             | Access settings changed since your read.                                                              | Read the environment again and retry.                                                                                           |
| `409 revision_conflict`                               | Another edit to access or compute landed first.                                                       | Read the environment again and retry.                                                                                           |
| `setup_warnings` contains `verify_not_started`        | The save succeeded but the run requested with `verify=true` didn't start.                             | Call `createEnvironmentSetupRun`.                                                                                               |
| Setup run failed with `egress_blocked`                | The network policy blocks a host the script needs.                                                    | Allow the host, then run setup again.                                                                                           |
| Agent's `git clone` or `gh` fails in a session        | The policy doesn't allow the GitHub hosts. A vault credential doesn't open the network.               | Allow the GitHub hosts shown in [Allow specific hosts](#allow-specific-hosts).                                                  |
| Duplicate environments after a retry                  | Creates are not idempotent.                                                                           | List environments before retrying a create, and delete the extra one.                                                           |
| `404 not_found`                                       | The id is wrong, deleted, or belongs to another organization.                                         | Check the id with `listEnvironments`.                                                                                           |
| `403 forbidden`                                       | Your role can't make this change. `verify=true` also needs permission to start setup runs.            | See [Organizations and roles](/managed-agents/organizations-and-roles). Use an account or key with the developer or admin role. |

For the full error catalog and retry guidance, see [Errors](/managed-agents/errors).

## Limits

| Limit                             | Value                                 |
| --------------------------------- | ------------------------------------- |
| Setup script size                 | 64 KiB                                |
| Setup timeout                     | 10 to 3600 seconds (default 600)      |
| Setup runs in flight              | 1 per environment, 4 per organization |
| `wait_seconds` on setup-run reads | Capped at 5 seconds per request       |
| Log page size                     | Up to 2000 lines (default 500)        |
| Log kept per run                  | 4 MiB, plus the last 2 KiB of stderr  |
| Setup runs listed                 | Up to 50, newest first                |
| Network policy rules              | 200                                   |
| Environment name                  | 256 characters                        |

For compute, disk, and lifecycle limits, see [Environment reference](/managed-agents/environments-reference). For every product limit, see [Limits](/managed-agents/limits).

## Next steps

<CardGroup cols={2}>
  <Card title="Environment reference" href="/managed-agents/environments-reference">
    Every field, default, compute size, sandbox path, and network rule.
  </Card>

  <Card title="Start a session" href="/managed-agents/sessions">
    Combine a usable environment with an agent and start work.
  </Card>

  <Card title="Authenticate with vaults" href="/managed-agents/vaults">
    Give agents credentials without putting secret values in the environment.
  </Card>

  <Card title="Deliverables and artifacts" href="/managed-agents/artifacts">
    Where agents save the files you asked for.
  </Card>
</CardGroup>
