Skip to content

Add sandbox API and mini swe agent 2 resource agent - #1377

Merged
hemildesai merged 14 commits into
NVIDIA-NeMo:mainfrom
hemildesai:hemil/sandbox-api-part-1
Jun 23, 2026
Merged

Add sandbox API and mini swe agent 2 resource agent#1377
hemildesai merged 14 commits into
NVIDIA-NeMo:mainfrom
hemildesai:hemil/sandbox-api-part-1

Conversation

@hemildesai

@hemildesai hemildesai commented May 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Parent PR: #1368

Refs #1337

This is the first smaller PR split out from #1368. It keeps the scope to the provider-neutral sandbox API, the OpenSandbox provider, and the Mini SWE Agent 2 evaluation integration. Observability is intentionally left out for a follow-up PR.

Features

  • Adds the public nemo_gym.sandbox facade with async and sync sandbox clients, provider registration, image rewrite support, sandbox specs/handles, and batch create support.
  • Adds the OpenSandbox provider with create/connect/exec/file/close operations, SDK pool-backed batch creation, retry handling, create probes, direct exec endpoint support, and nested provider configuration sections.
  • Adds responses_api_agents/mini_swe_agent_2, a sandbox-backed mini-swe-agent v2 integration for SWE-bench style evals, including sandbox resource profiles, task metadata propagation, reward aggregation, and ng_collect_rollouts usage docs.
  • Adds focused unit coverage for the sandbox facade, provider registry, OpenSandbox provider behavior, Mini SWE Agent 2 run/aggregation behavior, and sandbox environment adapter.
  • Moves sandbox-related dependencies behind the nemo-gym[sandbox] optional extra.

Notes

Validation

Completed on the squashed commit:

uv run ruff check nemo_gym/sandbox responses_api_agents/mini_swe_agent_2 tests/unit_tests/test_sandbox.py tests/unit_tests/test_opensandbox_provider.py

Result: All checks passed!

uv run pytest tests/unit_tests/test_sandbox.py tests/unit_tests/test_opensandbox_provider.py responses_api_agents/mini_swe_agent_2/tests/test_app.py responses_api_agents/mini_swe_agent_2/tests/test_sandbox_environment.py -q

Result: 44 passed, 2 warnings

uv run coverage run --source=nemo_gym.sandbox,responses_api_agents.mini_swe_agent_2 -m pytest tests/unit_tests/test_sandbox.py tests/unit_tests/test_opensandbox_provider.py responses_api_agents/mini_swe_agent_2/tests/test_app.py responses_api_agents/mini_swe_agent_2/tests/test_sandbox_environment.py -q
uv run coverage combine results
uv run coverage report --include='nemo_gym/sandbox/*,responses_api_agents/mini_swe_agent_2/*' --fail-under=90

Result: focused coverage 92%.

Kubernetes smoke validation:

  • Model: Qwen/Qwen3.5-27B, served by SGLang with DFLASH draft model z-lab/Qwen3.5-27B-DFlash.
  • Launched the Mini SWE Agent 2 stack through the documented ng_collect_rollouts path.
  • Ran 8 SWE-bench Verified samples with 8 repeats and 64-way rollout concurrency against the OpenSandbox internal service path.
  • Result: 64/64 rollout rows, pass@8=0.875, 7/8 tasks resolved, mean reward 0.765625, eval error rate 0.0, reward profile completion 100%.
  • Cleanup completed with no leftover sandboxes for the successful internal-service run.

Full SWE-bench Verified validation:

  • Model: Qwen/Qwen3.5-27B, served by SGLang with DFLASH draft model z-lab/Qwen3.5-27B-DFlash.
  • Ran 500 SWE-bench Verified samples with pass@1, 500-way rollout concurrency, step_limit=250, and OpenSandbox cleanup metadata.
  • Result: 500/500 rollout rows, pass@1=0.698, 349/500 tasks resolved, mean reward 0.698, eval error rate 0.6, tests status rate 99.0, reward profile completion 100%.
  • Job duration: 4h6m; rollout collection duration: 4h04m.
  • Cleanup left no sandboxes with the run labels run_family=mini-swe2-firstpr-q35-cell-full-p1-r9 or cleanup_id=full-p1-r9-single-20260521-053410.

@hemildesai
hemildesai requested a review from a team as a code owner May 20, 2026 20:16
@copy-pr-bot

copy-pr-bot Bot commented May 20, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@hemildesai
hemildesai marked this pull request as draft May 20, 2026 20:17
@hemildesai hemildesai changed the title Add sandbox API and mini SWE agent 2 OpenSandbox eval Add sandbox API and mini SWE agent 2 resource agent May 22, 2026
@hemildesai hemildesai changed the title Add sandbox API and mini SWE agent 2 resource agent Add sandbox API and mini swe agent 2 resource agent May 22, 2026
@hemildesai
hemildesai marked this pull request as ready for review May 27, 2026 14:46

@ananthsub ananthsub left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks great! a few clarifying questions added inline

Comment thread nemo_gym/sandbox/api.py Outdated
Comment thread nemo_gym/sandbox/api.py Outdated
Comment thread nemo_gym/sandbox/api.py Outdated
Comment thread nemo_gym/sandbox/api.py Outdated
Comment thread nemo_gym/sandbox/api.py Outdated
Comment thread nemo_gym/sandbox/providers/base.py Outdated
Comment thread nemo_gym/sandbox/providers/registry.py Outdated
Comment thread nemo_gym/sandbox/providers/base.py Outdated
Comment thread nemo_gym/sandbox/providers/opensandbox/provider.py Outdated
Comment thread nemo_gym/sandbox/api.py Outdated
@cmunley1

cmunley1 commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

can we add sandbox api docs draft in a separate PR

cc @cwing-nvidia @lbliii

@cmunley1

cmunley1 commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

and if there is a simpler example, such as hello world (math + sandboxed python tool?), would be great

Comment thread nemo_gym/sandbox/providers/base.py
Comment thread nemo_gym/sandbox/providers/base.py Outdated
"""Provider-neutral sandbox creation request."""

image: str | None = None
snapshot_id: str | None = None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is snapshot_id?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. snapshot_id is not a provider-neutral sandbox concept. It is an OpenSandbox-specific create option for starting from a pre-existing snapshot, so I moved it out of SandboxSpec and into provider_options["snapshot_id"]. Generic callers no longer see it as part of the public API. The relevant change is in a93f703.

Comment thread nemo_gym/sandbox/providers/base.py Outdated

name: str

async def create(self, spec: SandboxSpec) -> SandboxHandle:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd appreciate to precise the contract here - will the sandbox handle be returned only when the sandbox is fully up & healthy, or do we expect it to just "create" the sandbox with some status param which can be eg. "STARTING". I mean sth like a pod definition. The later could be better for observability - if the sandbox fails to start what do we know about the failure type?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intended contract is that create returns only when the sandbox is ready enough to run commands and transfer files, otherwise it raises a create error. I kept STARTING as a status value for providers that expose lifecycle status after start, but not as a successful create result. Startup failures should be surfaced as typed create or verification errors with provider detail rather than returning a half-ready handle. The contract text and status support are in a93f703.

should pass it back to the provider through this handle rather than
inspecting or mutating it directly.
"""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we have some status marker? either as a property or a function, see https://github.com/NVIDIA-NeMo/Evaluator/blob/main/src/nemo_evaluator/sandbox/base.py#L178 for ref

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I added a provider-neutral SandboxStatus enum and a minimal public status() method on both AsyncSandbox and Sandbox. I also removed the extra is_running convenience wrapper so the public surface stays small while still allowing callers to inspect lifecycle state. The status API was added in a93f703 and then minimized in 97b3210.

Comment thread nemo_gym/sandbox/providers/base.py Outdated
"""
...

async def connect(self, sandbox_id: str) -> SandboxHandle:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does it mean? should it be more of an internal primitive perhaps, preceeding other operations like exec or upload?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Connect should not be part of the public sandbox API. It is only an OpenSandbox provider implementation detail for obtaining a loop-local SDK object before provider operations. I removed the public attach and connect methods entirely, so callers now start a sandbox and then use exec, upload, download, status, and stop on that object. The public API trim is in 97b3210.

Comment on lines +30 to +37
def register_provider(name: str, provider_class: ProviderClass, *, override: bool = False) -> None:
"""Register a sandbox provider class."""
if not name:
raise ValueError("Provider name must be non-empty")
if not override and (name in _PROVIDER_REGISTRY or name in _BUILTIN_PROVIDER_LOADERS):
raise ValueError(f"Sandbox provider {name!r} is already registered")
_PROVIDER_REGISTRY[name] = provider_class

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(not blocking) but for users to plug in their own providers, we should also support an entry_points configuration

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given the time pressure for 0.4. id recommend we create a separate issue to track this targeted for 0.5

Comment on lines +13 to +38
sandbox_provider:
opensandbox:
connection:
domain: opensandbox-server.opensandbox-system.svc.cluster.local
api_key: ${oc.env:OPENSANDBOX_API_KEY}
protocol: http
request_timeout_s: 300
use_server_proxy: true
create:
request_timeout_s: 1200
timeout_s: 1200
skip_health_check: true
retries: 10
retry_delay_s: 5.0
retry_max_delay_s: 90.0
probe:
timeout_s: 60
deadline_s: 180
stable_count: 2
stable_delay_s: 1.0
operations:
retries: 5
retry_delay_s: 1.0
retry_max_delay_s: 45.0
command_retries: 3
close_timeout_s: 30

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this config shape makes it hard to swap out for new providers. ideally we should provide an example of each of these provider configs as their own yamls and make it easy to swap in for different agent implementations.

e.g.

# nemo_gym/sandbox/providers/opensandbox/configs/opensandbox.yaml
sandbox_main:                       # the name the agent references
  opensandbox:                       # registry key -> provider class
    connection:
        domain: ${oc.env:OPENSANDBOX_DOMAIN}
        api_key: ${oc.env:OPENSANDBOX_API_KEY}
    create:     { timeout_s: 1200, skip_health_check: true, retries: 10 }
    probe:      { timeout_s: 60, deadline_s: 180, stable_count: 2 }
    operations: { retries: 5, command_retries: 3, close_timeout_s: 30 }
    options:    { platform: { os: linux, arch: amd64 } }   # run-level provider-specific defaults

then users could have other sandbox configs like

# nemo_gym/sandbox/providers/ecs/configs/ecs_fargate.yaml
sandbox_main:
  ecs_fargate:
    # define provider specific configs

the agent config can reference this in its config:

      env: sandbox
      sandbox_provider: sandbox_main  # inlined from the reference above, makes it easy to swap out for different providers
      sandbox_spec:
        timeout_s: 18000
        ready_timeout_s: 1200
        resources:
          cpu: "2"
          memory: 8Gi
          ephemeral-storage: 20Gi
        metadata:
          benchmark: swebench-verified
          harness: mini-swe-agent
          sandbox-api: opensandbox-sdk
      sandbox_environment_kwargs:
        cwd: /testbed
        conda_env: testbed
        activate_conda: true
        user: root
        delete: true
      run_golden: false
      step_timeout: 600
      eval_timeout: 1800
      skip_if_exists: false
      step_limit: 250
AGENT=responses_api_agents/mini_swe_agent_2/configs/mini_swe_agent_opensandbox.yaml
MODEL=responses_api_models/vllm_model/configs/vllm_model.yaml
# OpenSandbox
ng_run "+config_paths=[$AGENT, nemo_gym/sandbox/providers/opensandbox/configs/opensandbox.yaml, $MODEL]"
# ECS: swap one path
ng_run "+config_paths=[$AGENT, nemo_gym/sandbox/providers/runpod/configs/ecs_fargate.yaml, $MODEL]"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ananthsub agreed on the config direction. I am going to keep this PR scoped to the sandbox API/provider lifecycle cleanup, and follow up in a separate PR with the swappable provider config YAMLs and agent config wiring so providers can be exchanged by swapping a config path. Leaving this thread open for that follow-up rather than treating it as addressed here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strong agree on this, I would like to decouple sandbox and agent config as much as possible

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolving in #1708

if self._create.image_pull_policy is None:
return spec

provider_options = dict(spec.provider_options)

@ananthsub ananthsub Jun 8, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should the individual provider options configs be represented with frozen dataclasses here? otherwise it's hard for users to see the supported options + any validation logic for it

Comment thread responses_api_agents/mini_swe_agent_2/sandbox_environment.py Outdated
Comment thread responses_api_agents/mini_swe_agent_2/sandbox_environment.py Outdated
Comment thread nemo_gym/sandbox/api.py
Comment thread nemo_gym/sandbox/providers/base.py Outdated
Comment thread nemo_gym/sandbox/providers/base.py Outdated
ananthsub
ananthsub previously approved these changes Jun 10, 2026
config_output_dir = Path(output_file_dir) / "_configs"
config_output_dir.mkdir(parents=True, exist_ok=True)
config_path = config_output_dir / f"{instance_id}.sandbox.yaml"
config_path.write_text(yaml.safe_dump(config, sort_keys=False))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This dumps the fully-resolved agent config to disk per instance, and self.config.sandbox_provider includes connection.api_key. Server configs are built with OmegaConf.to_container(..., resolve=True) (see server_utils.py), so ${oc.env:OPENSANDBOX_API_KEY} is already the real key by the time we get here — yaml.safe_dump writes the secret in cleartext to results/<subset>/<model>/_configs/<instance_id>.sandbox.yaml, once per task. This also bypasses the framework's own _recursively_hide_secrets masking. Could we mask/strip connection.api_key before dumping and re-inject it in the Ray worker from the env var (or pass it via Ray runtime_env.env_vars)? It's gitignored, but it's still cleartext on disk and could leak via artifact upload.

Comment thread nemo_gym/sandbox/api.py
timeout_s: int | float | None = 180,
user: str | int | None = None,
) -> SandboxExecResult:
return await self._provider.exec(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exec() retries through the same policy as create/file ops, and _is_retryable_sdk_operation_error treats ConnectionError/OSError as retryable (the shipped config sets command_retries: 3). For arbitrary agent commands that isn't safe: if the connection drops after the command already ran server-side, the retry re-executes it and duplicates side effects (file writes, git apply, etc.). Suggest defaulting command_retries to 0 (so only create/connect/file ops retry) and/or documenting that command retries assume idempotency.

result = self._sandbox.exec(
self._command(command, exec_cwd),
timeout_s=timeout_s,
cwd="/",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cwd is hardcoded to /, and the configured working dir is only applied via the cd <cwd> that _command() injects — but _command() returns the bare command when activate_conda is off (lines 124–125). So with conda disabled, exec_cwd is silently dropped and every command runs in /. The default SWE-bench config uses conda so it works today, but it's a latent footgun. Suggest passing cwd=exec_cwd to exec() and letting the conda branch skip its own cd.

Comment thread nemo_gym/sandbox/api.py Outdated
self._closed = True
if not self._loop.is_closed():
self._loop.call_soon_threadsafe(self._loop.stop)
self._thread.join(timeout=5)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a provider call hangs longer than 5s, join(timeout=5) returns while the loop is still running and self._loop.close() then raises RuntimeError: Cannot close a running event loop from inside Sandbox.stop()'s finally. Also call()/run() block on future.result() with no timeout (lines 174/179), so a wedged loop thread hangs the caller indefinitely. Consider only calling loop.close() when the thread actually joined, and/or a bounded future.result(timeout=...).

@hemildesai
hemildesai force-pushed the hemil/sandbox-api-part-1 branch 2 times, most recently from 8127481 to 428238f Compare June 11, 2026 21:36
Glorf
Glorf previously approved these changes Jun 11, 2026
@Glorf

Glorf commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

@hemildesai can we merge? WDYT?

adil-a added a commit to adil-a/Gym that referenced this pull request Jun 19, 2026
…VIDIA-NeMo#1249)

First increment of NVIDIA-NeMo#1249 on top of the NVIDIA-NeMo#1377 Sandbox API: a provider-neutral
responses_api_agents/swe_env library (provisioning + exec + per-family harness
recipes + grading) and a required, stateless, fresh-only resources_servers/swe_env
verifier with a server-private verify_task orchestrator.

- swe_env library: SweTask/harness contract (provisioning vs server-private grading
  split), AsyncSweEnvironment over nemo_gym.sandbox, registry, pure grading helpers,
  swe-bench-ext reference harness.
- providers: DockerSandboxProvider (real/local, enables end-to-end testing without
  apptainer) + ApptainerSandboxProvider (ports the legacy .sif path; mocked-tested).
- verifier: SweEnvVerifier.verify() extracts the patch from the response, grades in
  its own fresh sandbox, masks infra failures as reward=0.0 (never None).
- 25 tests incl. an env-gated real docker-backed end-to-end (gold patch -> resolved).

See SWE_ENV_DECOUPLE_STATUS.md for scope, what's tested, and follow-ups.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: adil-a <adil.asif2000@hotmail.com>
@hemildesai
hemildesai merged commit e1e45b1 into NVIDIA-NeMo:main Jun 23, 2026
14 checks passed
Glorf added a commit that referenced this pull request Jun 23, 2026
Port NEL's ECS Fargate sandboxing into Gym as a third sandbox provider,
stacked on the provider framework from #1377.

- nemo_gym/sandbox/providers/ecs_fargate/{engine,provider}.py — NEL's engine
  lifted (task-def registration + SSM caching, RunTask with capacity retries,
  SSH sidecar + reverse-tunnel outside-endpoint routing, in-container exec
  server, CodeBuild->ECR image build) behind a thin adapter that keeps
  per-sandbox state in SandboxHandle.raw.
- registry loader + `sandbox-ecs` extra (boto3, lazy-imported).
- region-only config via SSM autodiscovery (/<ssm_project>/ecs-sandbox/config),
  matching NEL.
- mini_swe_agent_2 ecs_fargate config + 14 unit tests (AWS/SSH mocked).

Fast-follow P0 (separate PR): route the model endpoint over Teleport as an
internal option instead of the globally-exposed SSH reverse tunnel that
security flagged; SSH tunneling stays available for community use.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Michal Bien <mbien@nvidia.com>
ananthsub added a commit that referenced this pull request Jun 24, 2026
Two installed distributions publishing the same provider entry-point name
now raise a clear error naming both packages, instead of silently picking
one nondeterministically. An entry point shadowed by a higher-precedence
built-in or registered provider is logged as a warning and ignored.

Refs #1377

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
@anwithk anwithk linked an issue Jun 24, 2026 that may be closed by this pull request
ritaneves pushed a commit that referenced this pull request Jun 25, 2026
## Summary

Parent PR: #1368

Refs #1337

This is the first smaller PR split out from #1368. It keeps the scope to
the provider-neutral sandbox API, the OpenSandbox provider, and the Mini
SWE Agent 2 evaluation integration. Observability is intentionally left
out for a follow-up PR.

### Features

- Adds the public `nemo_gym.sandbox` facade with async and sync sandbox
clients, provider registration, image rewrite support, sandbox
specs/handles, and batch create support.
- Adds the OpenSandbox provider with create/connect/exec/file/close
operations, SDK pool-backed batch creation, retry handling, create
probes, direct exec endpoint support, and nested provider configuration
sections.
- Adds `responses_api_agents/mini_swe_agent_2`, a sandbox-backed
mini-swe-agent v2 integration for SWE-bench style evals, including
sandbox resource profiles, task metadata propagation, reward
aggregation, and `ng_collect_rollouts` usage docs.
- Adds focused unit coverage for the sandbox facade, provider registry,
OpenSandbox provider behavior, Mini SWE Agent 2 run/aggregation
behavior, and sandbox environment adapter.
- Moves sandbox-related dependencies behind the `nemo-gym[sandbox]`
optional extra.

### Notes

- This PR does not include the sandbox observability module from #1368.
- The Mini SWE Agent 2 README avoids internal deployment names and
user-specific paths; examples use placeholders and local `data/` /
`results/` paths.

## Validation

Completed on the squashed commit:

```bash
uv run ruff check nemo_gym/sandbox responses_api_agents/mini_swe_agent_2 tests/unit_tests/test_sandbox.py tests/unit_tests/test_opensandbox_provider.py
```

Result: `All checks passed!`

```bash
uv run pytest tests/unit_tests/test_sandbox.py tests/unit_tests/test_opensandbox_provider.py responses_api_agents/mini_swe_agent_2/tests/test_app.py responses_api_agents/mini_swe_agent_2/tests/test_sandbox_environment.py -q
```

Result: `44 passed, 2 warnings`

```bash
uv run coverage run --source=nemo_gym.sandbox,responses_api_agents.mini_swe_agent_2 -m pytest tests/unit_tests/test_sandbox.py tests/unit_tests/test_opensandbox_provider.py responses_api_agents/mini_swe_agent_2/tests/test_app.py responses_api_agents/mini_swe_agent_2/tests/test_sandbox_environment.py -q
uv run coverage combine results
uv run coverage report --include='nemo_gym/sandbox/*,responses_api_agents/mini_swe_agent_2/*' --fail-under=90
```

Result: focused coverage `92%`.

Kubernetes smoke validation:

- Model: `Qwen/Qwen3.5-27B`, served by SGLang with DFLASH draft model
`z-lab/Qwen3.5-27B-DFlash`.
- Launched the Mini SWE Agent 2 stack through the documented
`ng_collect_rollouts` path.
- Ran 8 SWE-bench Verified samples with 8 repeats and 64-way rollout
concurrency against the OpenSandbox internal service path.
- Result: 64/64 rollout rows, `pass@8=0.875`, 7/8 tasks resolved, mean
reward `0.765625`, eval error rate `0.0`, reward profile completion
`100%`.
- Cleanup completed with no leftover sandboxes for the successful
internal-service run.

Full SWE-bench Verified validation:

- Model: `Qwen/Qwen3.5-27B`, served by SGLang with DFLASH draft model
`z-lab/Qwen3.5-27B-DFlash`.
- Ran 500 SWE-bench Verified samples with pass@1, 500-way rollout
concurrency, `step_limit=250`, and OpenSandbox cleanup metadata.
- Result: 500/500 rollout rows, `pass@1=0.698`, 349/500 tasks resolved,
mean reward `0.698`, eval error rate `0.6`, tests status rate `99.0`,
reward profile completion `100%`.
- Job duration: `4h6m`; rollout collection duration: `4h04m`.
- Cleanup left no sandboxes with the run labels
`run_family=mini-swe2-firstpr-q35-cell-full-p1-r9` or
`cleanup_id=full-p1-r9-single-20260521-053410`.

---------

Signed-off-by: Hemil Desai <hemild@nvidia.com>
Signed-off-by: Rita Fernandes Neves <rfernandesne@nvidia.com>
Glorf added a commit that referenced this pull request Jun 26, 2026
Port NEL's ECS Fargate sandboxing into Gym as a third sandbox provider,
stacked on the provider framework from #1377.

- nemo_gym/sandbox/providers/ecs_fargate/{engine,provider}.py — NEL's engine
  lifted (task-def registration + SSM caching, RunTask with capacity retries,
  SSH sidecar + reverse-tunnel outside-endpoint routing, in-container exec
  server, CodeBuild->ECR image build) behind a thin adapter that keeps
  per-sandbox state in SandboxHandle.raw.
- registry loader + `sandbox-ecs` extra (boto3, lazy-imported).
- region-only config via SSM autodiscovery (/<ssm_project>/ecs-sandbox/config),
  matching NEL.
- mini_swe_agent_2 ecs_fargate config + 14 unit tests (AWS/SSH mocked).

Fast-follow P0 (separate PR): route the model endpoint over Teleport as an
internal option instead of the globally-exposed SSH reverse tunnel that
security flagged; SSH tunneling stays available for community use.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Michal Bien <mbien@nvidia.com>
hemildesai pushed a commit that referenced this pull request Jun 26, 2026
…aclass

Represent the recognized per-sandbox create options (spec.provider_options)
as a frozen OpenSandboxProviderOptions dataclass with a validating
from_mapping, so the supported options and their types are discoverable in
one place and unknown keys are rejected with a clear error. The create path
now reads typed attributes instead of scattered dict lookups.

SDK-owned nested structures (platform, volumes) stay pass-through mappings
so their inner fields remain validated by the OpenSandbox SDK rather than
over-constrained here.

Refs #1377

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
hemildesai pushed a commit that referenced this pull request Jun 27, 2026
Sandbox providers are now defined as named blocks in their own config
files (e.g. nemo_gym/sandbox/providers/opensandbox/configs/opensandbox.yaml)
that agents reference by name (sandbox_provider: sandbox). Swapping
providers becomes swapping one config path in +config_paths, with no edits
to the agent config.

- Add resolve_provider_config to resolve a sandbox name (or an inline
  single-key mapping) to a single provider config.
- Make mini_swe_agent_2's config provider-neutral and resolve the
  reference at runtime.
- Document single / swap / multiple-sandbox usage, including distinct
  instance names for mixing providers or running two configs of the same
  provider type.

Refs #1377

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
hemildesai pushed a commit that referenced this pull request Jun 27, 2026
…aclass

Represent the recognized per-sandbox create options (spec.provider_options)
as a frozen OpenSandboxProviderOptions dataclass with a validating
from_mapping, so the supported options and their types are discoverable in
one place and unknown keys are rejected with a clear error. The create path
now reads typed attributes instead of scattered dict lookups.

SDK-owned nested structures (platform, volumes) stay pass-through mappings
so their inner fields remain validated by the OpenSandbox SDK rather than
over-constrained here.

Refs #1377

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
hemildesai pushed a commit that referenced this pull request Jun 29, 2026
…aclass

Represent the recognized per-sandbox create options (spec.provider_options)
as a frozen OpenSandboxProviderOptions dataclass with a validating
from_mapping, so the supported options and their types are discoverable in
one place and unknown keys are rejected with a clear error. The create path
now reads typed attributes instead of scattered dict lookups.

SDK-owned nested structures (platform, volumes) stay pass-through mappings
so their inner fields remain validated by the OpenSandbox SDK rather than
over-constrained here.

Refs #1377

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
hemildesai added a commit that referenced this pull request Jun 29, 2026
)

## Summary

Follow-up to #1377 addressing the config-usability feedback
([thread](#1377 (comment))):
the inline `sandbox_provider:` block welded provider
connection/lifecycle config (and its secret) into the agent config,
making it hard to swap providers.

This decouples the two. A sandbox is now a **named block** — `<name>: {
<provider>: {config} }` — defined in its own provider config file, and
an agent points at it by name (`sandbox_provider: sandbox`). The
framework only ever resolves *a name → one provider config*.

- **Single (default):** ship a `sandbox` block; the agent defaults to
`sandbox_provider: sandbox`.
- **Swap providers (no agent edit):** every shipped provider config
binds the same name `sandbox`, so swapping providers is swapping one
`+config_paths` entry.
- **Multiple / mixed / same-type:** give blocks distinct instance names
and reference each explicitly — no framework change.

### Changes

- Add `nemo_gym.sandbox.resolve_provider_config` — resolves a sandbox
name (from the merged config) or an inline single-key mapping to a
single `{provider: config}` dict for `create_provider`.
- Add `nemo_gym/sandbox/providers/opensandbox/configs/opensandbox.yaml`
defining the named `sandbox` block.
- Make `responses_api_agents/mini_swe_agent_2` provider-neutral:
`sandbox_provider` accepts a name (default) or inline mapping; the
reference is resolved at runtime. Renamed
`mini_swe_agent_opensandbox.yaml` → `mini_swe_agent_2.yaml`.
- README documents the single concept plus single / swap / multi-sandbox
usage.
- Unit tests for the resolver and the named-reference agent path.

Bottom of a 2-PR stack; the follow-up adds provider-contributed default
sandbox metadata.

Refs #1377

## Test plan

- [x] `uv run pytest tests/unit_tests/test_sandbox.py
tests/unit_tests/test_opensandbox_provider.py
responses_api_agents/mini_swe_agent_2/tests/`
- [x] `uv run ruff check` + `ruff format --check` clean
- [x] End-to-end config composition verified through
`GlobalConfigDictParser`

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
Signed-off-by: Hemil Desai <hemild@nvidia.com>
Co-authored-by: Hemil Desai <hemild@nvidia.com>
kajalj22 pushed a commit that referenced this pull request Jun 30, 2026
…aclass

Represent the recognized per-sandbox create options (spec.provider_options)
as a frozen OpenSandboxProviderOptions dataclass with a validating
from_mapping, so the supported options and their types are discoverable in
one place and unknown keys are rejected with a clear error. The create path
now reads typed attributes instead of scattered dict lookups.

SDK-owned nested structures (platform, volumes) stay pass-through mappings
so their inner fields remain validated by the OpenSandbox SDK rather than
over-constrained here.

Refs #1377

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
kajalj22 added a commit that referenced this pull request Jun 30, 2026
…1709)

## Summary

Stacked on top of #1708.

Lets a sandbox provider config contribute default sandbox metadata* so
provider-identifying tags live with the provider rather than the
provider-neutral agent config. A sandbox block may carry an optional
`default_metadata` key:

```yaml
sandbox:
  default_metadata: { sandbox-api: opensandbox-sdk }
  opensandbox:
    connection: { ... }
```

Its entries are merged into each sandbox's `SandboxSpec.metadata`; the
agent's own `sandbox_spec.metadata` overrides them on conflict.

This restores the `sandbox-api: opensandbox-sdk` label that #1708
dropped from the agent config, now sourced from the provider config
instead.

### Changes

- Add `nemo_gym.sandbox.resolve_provider_metadata`; exclude reserved
keys (`default_metadata`) from `resolve_provider_config`.
- `mini_swe_agent_2` merges provider `default_metadata` into the sandbox
spec metadata at runtime.
- Add `default_metadata: { sandbox-api: opensandbox-sdk }` to the
opensandbox provider config; document it in the README.
- Unit tests for `resolve_provider_metadata` and the agent merge path.

Refs #1377

## Test plan

- [x] `uv run pytest tests/unit_tests/test_sandbox.py
responses_api_agents/mini_swe_agent_2/tests/test_app.py` (38 passed, 10
skipped)
- [x] `uv run ruff check` + `ruff format --check` clean
- [x] End-to-end resolution verified through `GlobalConfigDictParser`
(provider key + default_metadata)

---------

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
Signed-off-by: Hemil Desai <hemild@nvidia.com>
Signed-off-by: Kajal Jain <kajalj@nvidia.com>
Co-authored-by: Hemil Desai <hemild@nvidia.com>
Co-authored-by: Kajal Jain <kajalj@nvidia.com>
kajalj22 pushed a commit that referenced this pull request Jun 30, 2026
## Summary

Stacked on top of #1709. Addresses the open review thread on #1377
([here](#1377 (comment)))
asking to support an entry-points configuration so users can plug in
their own providers.

Adds a `nemo_gym.sandbox_providers` entry point group. A separate
package can publish a sandbox provider that becomes available on
install/import — no edits to the registry:

```toml
[project.entry-points."nemo_gym.sandbox_providers"]
my_provider = "my_pkg.provider:MyProvider"
```

Lookup precedence is **explicit `register_provider` > built-in loaders >
entry points**; discovery is cached. `list_providers()` now unions all
three sources.

### Changes

- `nemo_gym/sandbox/providers/registry.py`: add `ENTRY_POINT_GROUP`,
cached `_entry_point_loaders()`, and fold entry points into
`get_provider_class` / `list_providers`.
- Unit test for discovery + built-in precedence (mocked entry points).
- README: document registering a custom provider via entry points.

Refs #1377

## Test plan

- [x] `uv run pytest tests/unit_tests/test_sandbox.py
tests/unit_tests/test_opensandbox_provider.py` (23 passed, 11 skipped)
- [x] `uv run ruff check` clean

---------

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
kajalj22 pushed a commit that referenced this pull request Jun 30, 2026
…aclass (#1713)

## Summary

Stacked on top of #1712. Addresses the open review thread on #1377
([here](#1377 (comment)))
asking whether the individual provider options should be frozen
dataclasses so the supported options + validation are discoverable.

Represents the recognized per-sandbox create options
(`SandboxSpec.provider_options`) as a frozen
`OpenSandboxProviderOptions` dataclass with a validating `from_mapping`:

- Supported options and their types now live in one place (`platform`,
`snapshot_id`, `volumes`, `skip_health_check`, `extensions`).
- Unknown keys and wrong types are rejected with clear errors.
- The create path reads typed attributes instead of scattered
`provider_options.get(...)` lookups, replacing the ad-hoc
`_spec_extensions` / `_provider_option_bool` / `_spec_volumes` helpers.

### Design note

SDK-owned nested structures (`platform`, `volumes`) are kept as
pass-through mappings rather than strictly-typed sub-dataclasses, so
their inner fields stay validated by the OpenSandbox SDK and we don't
over-constrain options we don't own.

Refs #1377

## Test plan

- [x] `uv run pytest tests/unit_tests/test_opensandbox_provider.py
tests/unit_tests/test_sandbox.py
responses_api_agents/mini_swe_agent_2/tests/test_app.py` (40 passed, 11
skipped)
- [x] `uv run ruff check` + `ruff format --check` clean
- [x] Create-path behavior (platform passthrough, image-pull-policy
extensions, skip_health_check) preserved under the fake-SDK tests

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
Glorf added a commit that referenced this pull request Jul 6, 2026
Port NEL's ECS Fargate sandboxing into Gym as a third sandbox provider,
stacked on the provider framework from #1377.

- nemo_gym/sandbox/providers/ecs_fargate/{engine,provider}.py — NEL's engine
  lifted (task-def registration + SSM caching, RunTask with capacity retries,
  SSH sidecar + reverse-tunnel outside-endpoint routing, in-container exec
  server, CodeBuild->ECR image build) behind a thin adapter that keeps
  per-sandbox state in SandboxHandle.raw.
- registry loader + `sandbox-ecs` extra (boto3, lazy-imported).
- region-only config via SSM autodiscovery (/<ssm_project>/ecs-sandbox/config),
  matching NEL.
- mini_swe_agent_2 ecs_fargate config + 14 unit tests (AWS/SSH mocked).

Fast-follow P0 (separate PR): route the model endpoint over Teleport as an
internal option instead of the globally-exposed SSH reverse tunnel that
security flagged; SSH tunneling stays available for community use.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Michal Bien <mbien@nvidia.com>
Glorf added a commit that referenced this pull request Jul 8, 2026
ECS Fargate `SandboxProvider` on top of the sandbox API (#1377, now
merged to `main`). Auto-mirrors public images to ECR on demand; SSH
reverse-tunnel for exec / file-transfer / model egress.

Rebased onto `main` now that the sandbox base (#1377) has landed — this
PR stands on its own and is ready for review.

**Sandbox-bound agents line of work:**
**ECS Fargate (this PR)** → adapter middleware base (#1646) →
sandbox-bound CLI agents + capture (#1647). Interceptor follow-ups on
the adapter base: #1649 (caching), #1650 (observability), #1651
(rewrites).

3 commits, +5974: ECS provider + engine, on-demand ECR mirroring + conda
activation fix, and 233 unit tests (engine.py to 99%). `uv.lock`
reconciled with main's security upgrades (#1657).

---------

Signed-off-by: Michal Bien <mbien@nvidia.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
hemildesai added a commit that referenced this pull request Jul 10, 2026
Stacked on #1377.

Closes #1687 

Adds a Daytona-backed sandbox provider to the sandbox API provider
registry, including SDK dependency wiring and provider
lifecycle/exec/file helpers.

Smoke notes:
- Provider import and server startup passed against the rebased sandbox
API branch.
- The latest 16-sample Daytona SWE smoke completed at the Gym layer, but
12/16 rows hit Daytona org disk quota during sandbox creation (`Total
disk limit exceeded. Maximum allowed: 30GiB`), so the observed 1/16
reward is not a meaningful model-quality pass rate.
- Successful live sandbox labels were observed during the run with
`sandbox-api: daytona-sdk`.

### Daytona full SWE-bench Verified validation

- Run: Kubernetes job `hemild-daytona-full-500-c64-014139` completed
successfully (`1/1`, pod `0` restarts).
- Artifact:
`/mnt/rl-workspace/hemild/gym_eval/refactor/runs/mini_swe_agent_2_ng_collect_rollouts/20260624-014139-daytona-full-verified-c64-step1800/results/mini_swe_agent_2_sglang_qwen35_dflash_daytona_full500.jsonl`
- Scale/result: `500/500` rollout rows, `324/500` solved, pass@1 / avg
rollout reward `0.648` (`64.8%`).
- Key config: SWE-bench Verified, Daytona provider, Qwen 3.5 27B via
SGLang, max concurrency `64`, `enable_thinking=true`, `temperature=0.6`,
`top_p=0.95`, `max_output_tokens=32768`, step timeout `1800s`,
per-command timeout `1500s`, Daytona command retries `0`.
- Infra/model/Gym error summary: `31/500` runtime error rows (`6.2%`
eval error rate): `10` Daytona blank `/process/execute` HTTP/request
timeouts, `9` Daytona command-timeout `408`s, `10` Daytona container-IP
resolution failures, `1` Daytona `502 Bad Gateway`, and `1` model
`litellm`/API timeout. No separate Gym schema/config failure class was
observed; remaining failures were normal model/test failures.

---------

Signed-off-by: Hemil Desai <hemild@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Sandbox API Foundations

7 participants