Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 184 additions & 0 deletions fern/versions/latest/pages/infrastructure/sandbox/e2b.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
---
title: "E2B Provider"
description: "Configure NeMo Gym sandboxes backed by E2B or an E2B-compatible gateway."
position: 2
---

The `e2b` provider creates isolated cloud sandboxes through the E2B Python SDK. It supports
the hosted E2B service and E2B-compatible gateways with custom API and sandbox URLs.

## Setup

Install the sandbox extra in the environment that creates sandboxes:

```bash
uv sync --extra sandbox
```

For a package install, use:

```bash
pip install "nemo-gym[sandbox]"
```

The provider requires `e2b>=2.36.0,<3.0.0`. Set `E2B_API_KEY` for the hosted service. For a
compatible gateway, also set `E2B_API_URL` and `E2B_SANDBOX_URL` to the endpoints supplied by
the gateway operator.

## Provider Config

NeMo Gym ships an E2B config at `nemo_gym/sandbox/providers/e2b/configs/e2b.yaml`. It defines
a top-level `sandbox` block that agents reference with `sandbox_provider: sandbox`:

```yaml
sandbox:
default_metadata:
sandbox-api: e2b
e2b:
connection:
api_key: ${oc.env:E2B_API_KEY,null}
api_url: ${oc.env:E2B_API_URL,null}
sandbox_url: ${oc.env:E2B_SANDBOX_URL,null}
request_timeout_s: 120.0
create:
template: null
template_map: {}
timeout_s: 3600.0
secure: true
allow_internet_access: true
strict_resources: false
exec:
default_timeout_s: 180.0
user: null
request_timeout_s: null
background: true
reconnect_attempts: 2
operations:
retries: 2
retry_delay_s: 0.5
retry_max_delay_s: 8.0
```

Pass the provider config beside the agent and model configs:

```bash
gym env start \
--config responses_api_agents/mini_swe_agent_2/configs/mini_swe_agent_2.yaml \
--config nemo_gym/sandbox/providers/e2b/configs/e2b.yaml \
--config responses_api_models/vllm_model/configs/vllm_model.yaml
```

## Prepare Templates

E2B starts a sandbox from a template name or ID rather than an OCI image reference. Because
tagged template names and OCI references can both contain `:`, the provider resolves a template
conservatively:

1. `SandboxSpec.provider_options.template`
2. `create.template_map[SandboxSpec.image]`
3. `SandboxSpec.image`, when it is an untagged name matching `[A-Za-z0-9_-]+`
4. The `create.template` fallback, only when `SandboxSpec.image` is omitted

Use `provider_options.template` for a tagged template name or template ID. A non-empty,
unmapped image raises instead of silently selecting an unrelated fallback template.

Build templates from OCI images as a separate provisioning step:

```bash
python -m nemo_gym.sandbox.providers.e2b.build \
--image ghcr.io/acme/task:1.0 \
--cpu-count 8 \
--memory-mb 16384 \
--output template_map.yaml
```

The generated YAML contains an image-to-template mapping ready to place under `create`:

```yaml
template_map:
"ghcr.io/acme/task:1.0": task-1-0__f00ee9d593d9
```

Template building can create E2B resources and is never performed implicitly by sandbox
creation. Generated names are deterministic for the image, CPU count, and memory size, so a
matching existing template can be reused. The helper's local timeout stops waiting but cannot
cancel a remote build that E2B has already accepted; check E2B before retrying after a timeout.
By default a batch failure prevents queued siblings from starting and waits for already-started
builds to finish; pass `--continue-on-error` to keep building the remaining images.

## Relevant `SandboxSpec` Fields

| Field | E2B behavior |
| --- | --- |
| `image` | Untagged names matching `[A-Za-z0-9_-]+` are direct; tagged names, IDs, and OCI references require `provider_options.template` or `create.template_map`. |
| `ttl_s` | Sandbox lifetime; overrides `create.timeout_s` and must be positive. |
| `ready_timeout_s` | Positive per-sandbox override for the E2B create request timeout. Omitted values use the connection timeout. |
| `workdir` | Used by the NeMo Gym facade as the default command working directory. |
| `env` | Passed to E2B when the sandbox is created. |
| `files` | Uploaded after creation and before `start()` returns. |
| `metadata` | Passed to E2B as string metadata. |
| `resources` | Fixed by the template rather than applied per sandbox. Requests warn, or raise when `create.strict_resources` is true. |
| `entrypoint` | Unsupported and rejected before allocation; define it in the E2B template. |
| `provider_options` | Supports only `template`; unknown or invalid values are rejected before allocation. |

The bundled builder controls template CPU count and memory. Disk and GPU requirements need a
suitable template prepared through E2B tooling.

## Timeouts and Reconnection

`SandboxSpec.ttl_s` controls the remote sandbox lifetime. Treat it as a cleanup backstop and
still call `stop()` or use a context manager so the sandbox is killed as soon as work finishes.

`SandboxSpec.ready_timeout_s` overrides the E2B create request timeout. The SDK retains an
explicit create timeout on the returned sandbox connection; the shipped
`connection.request_timeout_s` is reapplied to later calls, while a programmatic config that
leaves it unset inherits `ready_timeout_s` for that sandbox object.

With E2B 2.36+, the connection or exec `request_timeout_s` only bounds opening command and
reconnect streams. Command `timeout_s` is NeMo Gym's total wait/stream budget across the initial
connection and any reconnects. The public facades normally supply 180 seconds, so pass a larger
value explicitly for long builds or test suites. With background execution, a timed-out stream
does not guarantee that the remote process was killed; close the sandbox before reuse when a
lingering process would be unsafe.

Background execution lets the provider reconnect to a running process by PID after an output
stream interruption. Output emitted before reconnection is not replayed. If the process exits
during the gap, its result cannot be recovered. Reconnects consume the original wait budget
rather than starting a new one.

Create is not automatically retried because an ambiguous failure could allocate two billable
sandboxes. Rate-limit responses are returned without automatic retries so callers can choose
their backoff window. Close retries transient kill failures, treats an expired sandbox as
already closed, and clears the local handle only after confirmed cleanup. Serialized handles
carry the E2B sandbox ID for reconnection through another configured provider instance. E2B's
public connect operation may extend a near-expiry sandbox to its SDK-default connection lifetime
(300 seconds in E2B 2.36).

## Security and Isolation

E2B supplies the sandbox isolation boundary; when using a compatible gateway, its operator is
responsible for the deployment's security posture. The shipped config uses `secure: true`, so
the sandbox envd service requires an access token.

Outbound internet access is enabled by default for workloads that install dependencies. Set
`create.allow_internet_access: false` for offline or untrusted workloads. Values in
`SandboxSpec.env` are injected into the remote sandbox, so pass only secrets required by the
workload and keep E2B credentials in the provider connection config or host environment.

`mini_swe_agent_2` removes `e2b.connection.api_key`, `headers`, and `api_headers` from generated
per-instance worker YAML. It passes them through the worker environment, then reconstructs the
header mappings only in worker memory. The API key stays out of the serializable provider config
and is read directly from `E2B_API_KEY` by the E2B SDK.

## Integration Attribution

NeMo Gym appends `nemo-gym/<version>` to the E2B SDK `User-Agent` for both runtime sandbox
requests and template provisioning. This lets E2B distinguish NeMo Gym traffic without adding
metadata to the sandbox itself. An explicit custom `User-Agent` in connection headers takes
precedence in the E2B SDK and can hide this attribution.

Sandbox and template operations use the E2B SDK's public high-level APIs; attribution uses
E2B's set-once integration hook. Because E2B 2.x does not expose transport injection on those
APIs, NeMo Gym replaces its module-level HTTP transport factories with an aiohttp adapter backed
by Gym's shared client session. The adapter accepts HTTP and HTTPS proxies; SOCKS proxies are
rejected explicitly. E2B's ConnectRPC command streams keep their SDK-owned pyqwest transport.
6 changes: 6 additions & 0 deletions fern/versions/latest/pages/infrastructure/sandbox/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ Run sandboxes through an OpenSandbox server and SDK.
<Badge minimal outlined>provider</Badge> <Badge minimal outlined>opensandbox</Badge>
</Card>

<Card title="E2B Provider" href="/infrastructure/sandbox/e2b">
Run isolated cloud sandboxes through E2B or an E2B-compatible gateway.

<Badge minimal outlined>provider</Badge> <Badge minimal outlined>e2b</Badge>
</Card>

<Card title="Apptainer Provider" href="/infrastructure/sandbox/apptainer">
Run sandboxes as local Apptainer instances on a host or HPC node.

Expand Down
156 changes: 156 additions & 0 deletions nemo_gym/sandbox/providers/e2b/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# E2B Sandbox Provider

The `e2b` provider runs NeMo Gym sandboxes through the E2B Python SDK. It works with the
hosted E2B service and with E2B-compatible gateways that expose custom API and sandbox URLs.

## Setup

Install NeMo Gym's sandbox dependencies and set an API key:

```bash
uv sync --extra sandbox
export E2B_API_KEY=<your-api-key>
```

For a package install, use `pip install "nemo-gym[sandbox]"`. The provider requires
`e2b>=2.36.0,<3.0.0`.

The shipped config is `nemo_gym/sandbox/providers/e2b/configs/e2b.yaml`. It reads
`E2B_API_KEY`, `E2B_API_URL`, and `E2B_SANDBOX_URL`; leave the URL variables unset for the
hosted service. For an E2B-compatible gateway, set both URLs to the endpoints provided by
the gateway operator.

Add the provider config beside the agent and model configs:

```bash
gym env start \
--config responses_api_agents/mini_swe_agent_2/configs/mini_swe_agent_2.yaml \
--config nemo_gym/sandbox/providers/e2b/configs/e2b.yaml \
--config responses_api_models/vllm_model/configs/vllm_model.yaml
```

## Templates

E2B creates sandboxes from a template name or ID, not directly from an OCI image reference.
For convenience, the provider treats an untagged `SandboxSpec.image` matching
`[A-Za-z0-9_-]+` as a direct template name. Tagged template names such as `task:v1`, template
IDs, and OCI image references must be explicit through `provider_options.template` or
`create.template_map`, because their syntax is ambiguous in the provider-neutral `image`
field. `create.template` is used only when `SandboxSpec.image` is omitted; a non-empty,
unmapped image fails instead of silently selecting an unrelated fallback.

The bundled provisioning helper builds E2B templates from OCI images before a run:

```bash
python -m nemo_gym.sandbox.providers.e2b.build \
--image ghcr.io/acme/task:1.0 \
--cpu-count 8 \
--memory-mb 16384 \
--output template_map.yaml
```

Paste the generated mapping under the provider's `create` block:

```yaml
sandbox:
e2b:
create:
template_map:
"ghcr.io/acme/task:1.0": task-1-0__f00ee9d593d9
```

Template names generated by the helper are deterministic for the image, CPU count, and
memory size. Template builds are provisioning operations and can create E2B resources; they
are never performed implicitly by `E2BProvider.create()`. The helper's local build timeout
stops waiting but cannot cancel a remote build that E2B has already accepted, so check E2B
before retrying a timed-out build. In a batch, the first failure prevents queued sibling builds
from starting and waits for already-started builds to finish; pass `--continue-on-error` to keep
provisioning the remaining images.

## Configuration

| Section | Important fields |
| --- | --- |
| `connection` | SDK credentials, hosted or gateway URLs, headers, proxy, and control-plane `request_timeout_s`. |
| `create` | Default template/mapping, sandbox `timeout_s`, `secure`, `allow_internet_access`, and `strict_resources`. |
| `exec` | Fallback command timeout, default user, stream-open request timeout, background execution, and reconnect attempts. |
| `operations` | Retry count and exponential-backoff delays for safe, retryable operations. |

`SandboxSpec.ttl_s` overrides `create.timeout_s`. `SandboxSpec.ready_timeout_s` is a positive
per-sandbox override for the E2B create request timeout; when omitted, the connection request
timeout applies. The E2B SDK retains an explicit create request timeout on the returned
sandbox connection. The shipped `connection.request_timeout_s` is reapplied to later provider
calls; if a programmatic config leaves it unset, `ready_timeout_s` also becomes that sandbox
object's default request timeout.

E2B 2.36+ applies command `request_timeout` only while opening an exec or reconnect stream.
Command `timeout_s` is the provider's total wait/stream budget, including reconnects. With
background execution, a timed-out stream does not guarantee that the remote process was
killed; close the sandbox before reuse when a lingering process would be unsafe.

The public `AsyncSandbox.exec()` and `Sandbox.exec()` facades normally supply a 180-second
command timeout. `exec.default_timeout_s` is only the fallback when the provider receives
`timeout_s=None`; pass a larger timeout explicitly for long builds or test suites.

## `SandboxSpec` Mapping

| Field | E2B behavior |
| --- | --- |
| `image` | Untagged names matching `[A-Za-z0-9_-]+` are direct; tagged names, IDs, and OCI references require `provider_options.template` or `create.template_map`. |
| `ttl_s` | Sandbox lifetime; overrides `create.timeout_s` and must be positive. |
| `ready_timeout_s` | Create-request timeout override; must be positive. |
| `workdir` | Used by the NeMo Gym facade as the default working directory for commands. |
| `env` | Passed to E2B when the sandbox is created. |
| `files` | Uploaded by the NeMo Gym facade after creation and before `start()` returns. |
| `metadata` | Passed to E2B as string metadata. |
| `resources` | Fixed by the E2B template, not applied per sandbox. Requests warn, or raise when `create.strict_resources` is true. |
| `entrypoint` | Unsupported and rejected before creating a billable sandbox; define it in the E2B template. |
| `provider_options` | Supports only `template`; unknown or invalid values are rejected before allocation. |

The bundled template builder can set CPU count and memory. Disk and GPU requirements need a
suitable E2B template prepared through E2B tooling.

## Lifecycle and Reliability

- Create is not automatically retried because an ambiguous transport failure could leave the
first, billable sandbox running while a retry creates another one.
- Rate-limit responses are returned without automatic retries so callers can choose an
appropriate backoff window.
- Close retries transient kill failures and treats an already-expired sandbox as closed. The
local handle is cleared only after E2B confirms the sandbox is killed or already absent.
- The configured TTL is a remote cleanup backstop, not a replacement for `stop()` or a context
manager.
- Background commands can reconnect by process ID after a stream interruption. Output emitted
before reconnection is not replayed, and a process that exits during the gap cannot be
recovered. Reconnects share the original command wait budget rather than restarting it.
- Serialized handles contain the E2B sandbox ID so another process can reconnect through a
separately configured `E2BProvider`. E2B's public connect operation may extend a
near-expiry sandbox to its SDK-default connection lifetime (300 seconds in E2B 2.36).

## Security and Isolation

E2B supplies the remote sandbox isolation boundary; a custom gateway's operator controls its
deployment and security posture. The shipped config keeps `secure: true`, which protects the
sandbox envd service with an access token.

Outbound internet access is enabled by default for compatibility with workloads that install
dependencies. Set `create.allow_internet_access: false` for offline or untrusted workloads.
Values in `SandboxSpec.env` are injected into the remote sandbox, so pass only secrets that the
workload needs. Keep E2B API credentials in the provider connection config or host environment,
not in `SandboxSpec.env`.

When `mini_swe_agent_2` writes its per-instance worker config, it removes
`e2b.connection.api_key`, `headers`, and `api_headers`. It passes them through the worker
environment, then reconstructs the header mappings only in worker memory. The API key stays out
of the serializable provider config and is read directly from `E2B_API_KEY` by the E2B SDK.

NeMo Gym identifies provider and template-builder SDK traffic by adding
`nemo-gym/<version>` to the E2B SDK `User-Agent`. An explicit custom `User-Agent` in
`connection.headers` or `connection.api_headers` takes precedence in the E2B SDK and can hide
that attribution.

Sandbox and template operations use the E2B SDK's public high-level APIs; attribution uses
E2B's set-once integration hook. Because E2B 2.x does not expose transport injection on those
APIs, NeMo Gym replaces its module-level HTTP transport factories with an aiohttp adapter backed
by Gym's shared client session. The adapter accepts HTTP and HTTPS proxies; SOCKS proxies are
rejected explicitly. E2B's ConnectRPC command streams keep their SDK-owned pyqwest transport.
35 changes: 35 additions & 0 deletions nemo_gym/sandbox/providers/e2b/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""E2B provider package."""

from nemo_gym.sandbox.providers.e2b.provider import (
E2BConnectionConfig,
E2BCreateConfig,
E2BCreateError,
E2BExecConfig,
E2BOperationConfig,
E2BProvider,
)


__all__ = [
"E2BConnectionConfig",
"E2BCreateConfig",
"E2BCreateError",
"E2BExecConfig",
"E2BOperationConfig",
"E2BProvider",
]
Loading
Loading