1 Overview

The Environment Engine (env_engine/) is the execution backend for agentic rollouts — a self-contained service the training pipeline reaches only over a small HTTP API, sending a request and reading back a result rather than touching container logic or the scheduler directly.

It is built on ROCK (Reinforcement Open Construction Kit), a client–server framework that owns the container lifecycle. On the reference 8-GPU node (see Getting Started), the ROCK admin server runs co-located on 127.0.0.1:8080 and orchestrates one SWE-Bench container per task.

Decoupled service

Runs as an independent admin server, isolated from the trainer. Swap Docker for Podman, add nodes, or change task logic without editing a line of training code.

Ray-scheduled concurrency

Each sandbox is a Ray actor reserving CPU/GPU. The scheduler tracks capacity, queues when exhausted, and reclaims on completion — hundreds of concurrent tasks, jobs lasting days.

Docker and Podman

Containers are managed through a pluggable deployment backend, so the same task graph runs on cloud Docker hosts or rootless Podman without changes.

2 Layered architecture

A request travels down four layers; the result returns up:

Environment Engine layered architecture: client, admin REST server, Ray resource scheduler, sandbox manager, and container runtimes with in-container rocklet agents. request (down) result (up) Rollout · SWE Agent RockSandbox client · rock.sdk Sandbox REST call observation Admin Server — REST API /apis/envs/sandbox/v1 · 127.0.0.1:8080 acquire slot release Resource Scheduler (Ray) GPU / CPU actors · queue · auto-clear lease start · exec · stop stdout · exit Sandbox Manager lifecycle · sessions · Docker / Podman deployment spawn / run Sandbox container rocklet Docker / Podman Sandbox container rocklet Docker / Podman Sandbox container N rocklet Docker / Podman

Every layer maps to a concrete module under env_engine/rock/:

LayerModuleRole
Client rl_engine/rollout/sandbox/rock.py
rock/sdk/sandbox/client.py
The rollout's RockSandbox wrapper and the ROCK Sandbox SDK — the only surfaces training code touches.
Admin rock/admin/entrypoints/sandbox_api.py
rock/admin/main.py
FastAPI router mounted at /apis/envs/sandbox/v1; validates requests and delegates to the SandboxManager.
Scheduler rock/admin/core/ray_service.py
rock/deployments/ray.py
RayService calls ray.init(resources=…); each sandbox is a Ray actor reserving actor_resource units, so Ray queues when capacity is full.
Sandbox mgr rock/sandbox/sandbox_manager.py
rock/deployments/manager.py
Owns start / session / exec / stop and container placement; DeploymentManager selects the Docker/Podman/Ray deployment.
Runtime rock/deployments/docker.py
rock/rocklet/
Launches the container and starts the in-container rocklet agent (rocklet/server.py), which runs commands and streams back stdout / exit code.

The rocklet

The rocklet is a lightweight execution agent inside each container (based on SWE-ReX, per env_engine/README_ROCK.md) that owns persistent bash sessions, file I/O, and process management.

3 The sandbox REST API

All sandbox verbs are registered on sandbox_router (rock/admin/entrypoints/sandbox_api.py) and mounted with a version prefix in rock/admin/main.py:

# rock/admin/main.py
app.include_router(sandbox_router, prefix="/apis/envs/sandbox/v1", tags=["sandbox"])
app.include_router(warmup_router,  prefix="/apis/envs/sandbox/v1", tags=["warmup"])
app.include_router(gem_router,     prefix="/apis/v1/envs/gem",   tags=["gem"])

Every handler is wrapped by @handle_exceptions(…) and returns a RockResponse[T] envelope whose status field is "Success" or "Failed". The core lifecycle verbs:

Method & pathHandlerPurpose
POST /startstartLaunch a container from an image; returns a sandbox_id.
POST /start_asyncstart_asyncNon-blocking start for warmup / pre-provisioning.
POST /create_sessioncreate_sessionOpen a persistent bash session inside the container.
POST /run_in_sessionrunRun a command in a session (shell state persists); returns a BashObservation.
POST /executeexecuteOne-shot stateless command; returns a CommandResponse.
POST /close_sessionclose_sessionTear down a single session.
POST /read_file · /write_file · /uploadread_fileFile transfer in and out of the container.
GET /is_alive · /get_statusis_aliveHealth / liveness probe (see §8).
GET /get_sandbox_statisticsget_sandbox_statisticsPer-sandbox resource / usage stats.
POST /stopcloseTerminate the container and release its Ray slot.

Two sibling routers share the same prefix: warmup_api.py pre-pulls task images so the first task avoids full cold-start, and sandbox_proxy_api.py routes each request to the node owning that sandbox. A separate GEM router (rock/admin/gem/api.py, /apis/v1/envs/gem) exposes the Gym-style make / reset / step protocol for turn-based environments like the Sokoban demo.

4 Python SDK

Callers use the async SDK in rock/sdk/sandbox/: Sandbox (in client.py) wraps every REST verb as a coroutine, and SandboxConfig (in config.py) declares the container spec. The canonical smoke test from env_engine/README_ROCK.md:

import asyncio
from rock.actions import CreateBashSessionRequest
from rock.sdk.sandbox.client import Sandbox
from rock.sdk.sandbox.config import SandboxConfig

async def run_sandbox():
    cfg = SandboxConfig(image="python:3.11", memory="8g", cpus=2.0)
    sb  = Sandbox(cfg)
    await sb.start()                                            # POST /start
    await sb.create_session(CreateBashSessionRequest(session="bash-1"))
    r = await sb.arun(cmd="echo Hello Rock", session="bash-1")  # /run_in_session
    await sb.stop()                                             # POST /stop

asyncio.run(run_sandbox())

Sandbox also exposes execute, run_nohup_and_wait, write_file/read_file, upload, commit, and close; SandboxGroup starts a pool of identical sandboxes with bounded start concurrency and retry. The config surface:

FieldDefaultMeaning
image"python:3.11"Container image; per-instance override in SWE (see §5).
memory"8g"Container memory limit.
cpus2CPU limit.
startup_timeoutROCK_SANDBOX_STARTUP_TIMEOUT_SECONDS (180)Max wait for the container to become ready.
auto_clear_seconds14400 (4 h)Idle lease; ROCK reclaims the container if untouched this long.
route_keyNoneSticky key to pin related sandboxes to one node.
cluster"zb"Target cluster label for placement.
base_urlROCK_BASE_URL (http://localhost:8080)Admin server the client talks to.

5 How training consumes it

The in-house SWE agent skips the high-level SDK: rl_engine/rollout/sandbox/rock.py defines a thin RockSandbox wrapper that POSTs to the same /apis/envs/sandbox/v1 endpoints with aiohttp — deliberately dependency-free ("ROCK API is NOT mandatory"), so the rollout never imports ROCK:

RockSandbox methodREST callNotes
start(image, cpus, memory)/startUp to 10 retries with backoff; returns sandbox_id.
execute_in_session(cmd, session)/run_in_sessionPersistent shell; scrubs prompt / broken-pipe artifacts from output.
execute(cmd)/executeStateless one-shot command.
upload_dir(local, container)/uploadTars a host dir and extracts it inside the container.
stop() · remove_image()/stop · /remove_imageTeardown; remove_image reclaims host disk (logs a LEAKED IMAGE warning on failure).

The SWE-Bench per-step flow

Task configuration lives in rl_engine/examples/swe_agent/environment/swe_sandbox.py (SWE_TOOL_CONFIGS). For each task the agent:

  1. resolves the per-instance image with get_swe_bench_image(instance_id) — e.g. swebench/sweb.eval.x86_64.django_1776_django-16911:latest;
  2. starts a container (cpus=4, memory="16g") with the editor entrypoint bind-mounted read-only (SWE_ENTRYPOINT_LOCAL; mount details in Sandbox Management §5);
  3. dispatches each tool call into a bash session (through run_in_session) under conda activate testbed — file-edit actions run python3 /home/swe_execute_sandbox.py --action … --params …; bash actions run the command directly.
Sequence of one environment step: SWE agent to RockSandbox to admin REST server to the sandbox container running the entrypoint, and results returning back up. SWE Agent evaluator loop RockSandbox rollout SDK client Admin Server REST /run_in_session Sandbox container rocklet + entrypoint tool call execute_in_session(cmd) POST /run_in_session dispatch to bash session run swe_execute_sandbox.py COMMAND_TIMEOUT = 120 s stdout · exit_code BashObservation {output, exit_code} observation → manage_context() call return

One env.step round trip against an already-started sandbox; container start is a separate call (timing below).

Startup timing & image supply

Warm start

With the SWE-Bench image already in the local Docker cache, start() returns in ~15–25 s; later env.step RPCs are dominated by command runtime.

Cold start

The first time an instance is hit, ROCK pulls a 1–8 GB image from Docker Hub (several minutes); startup_timeout defaults to 300 s, overridable via STACX_SANDBOX_STARTUP_TIMEOUT.

Docker Hub auth

Anonymous pulls are rate-limited (100 / 6 h / IP) and a run may touch 200+ unique images, so docker login first (AGENTS.md); ROCK inherits the daemon login.

Offline tar-cache fallback

Before pulling from a registry, DockerDeployment checks DOCKER_IMAGE_CACHE_DIR (rock/deployments/docker.py); a matching tar is loaded with docker load instead, letting air-gapped or tmpfs-backed hosts re-hydrate images without network access.

6 Deployments & configuration

The bottom layer is a family of deployment backends under rock/deployments/, selected by DeploymentManager (manager.py) and sharing the AbstractDeployment contract (abstract.py):

ModuleBackend
deployments/docker.pyDockerDeployment — local Docker (or Podman) container; hosts the tar-cache fallback.
deployments/ray.pyRayDeployment(DockerDeployment) — wraps the Docker deployment as a Ray actor with resources={actor_resource: actor_resource_num} reservations.
deployments/local.pyIn-process / local runtime for tests and demos.
deployments/remote.pyAttach to a container on a remote node.

Podman is not a separate module — DockerDeployment shells out through a container provider (rock/utils/providers/) that abstracts over both runtimes, so the same task graph runs unchanged on either.

Configuration files

Runtime config lives in env_engine/rock-conf/: rock-local.yml drives single-node development, rock-test.yml targets the test suite:

# env_engine/rock-conf/rock-local.yml (excerpt)
ray:
    runtime_env:
        working_dir: ./
        pip: ["apscheduler", "opentelemetry-api", "gem-llm", ...]
    namespace: "rock-sandbox-local"
warmup:
    images: ["python:3.11"]
runtime:
    enable_auto_clear: true

The rock CLI

The rock command (rock/cli/main.py, subcommands under rock/cli/command/) manages the admin process via AdminCommand, with two actions:

# start the admin server (default 127.0.0.1:8080)
rock admin start

# graceful SIGTERM to running admin processes
rock admin stop

Install independently of the trainer: build the ROCK venv per env_engine/README_ROCK.md (uv-managed Python recommended), then rock admin start; a source/pip install also needs ROCK_WORKER_ENV_TYPE=pip. Launch scripts health-check this server before every run and abort if it is down (AGENTS.md).

7 Directory tour

PathContents
rock/actions/Action schemas — sandbox/ (bash, file, config requests/responses) and envs/ (GEM); CreateBashSessionRequest is re-exported from rock.actions.
rock/admin/Admin server: main.py, entrypoints/ (sandbox / proxy / warmup APIs), core/ (ray_service.py, tables), gem/, metrics/, proto/.
rock/sandbox/Sandbox abstraction: sandbox_manager.py, sandbox_actor.py, gem_manager.py, remote_sandbox.py, job/, service/.
rock/rocklet/In-container agent: server.py, local_sandbox.py, local_api.py, __main__.py.
rock/sdk/Python client: sandbox/ (Sandbox, SandboxConfig), envs/ (Gym registration), envhub/, builder/ (image builders).
rock/deployments/Backends: docker.py, ray.py, local.py, remote.py, manager.py, abstract.py.
rock/envhub/Environment registry service: server.py, api/, core/, database/.
rock/cli/CLI: main.py, command/ (admin.py), loader.py, config.py.
rock/utils/Shared: container providers/, docker.py, http.py, retry, DB, concurrency helpers.
rock/config.py · rock/env_vars.pyTyped config model and the ROCK_* environment-variable registry.
rock-conf/rock-local.yml, rock-test.yml.
examples/sandbox_demo.py, sandbox_files_demo.py, sokoban_demo.py.
tests/unit/ (admin, sandbox, rocklet, cli, utils) and integration/.
pyproject.toml · LICENSEPackage metadata / dependencies; ROCK ships under its own Apache-2.0 license.

8 Operations reference

Ports & health

EndpointDefaultPurpose
Admin REST127.0.0.1:8080Sandbox API (ROCK_BASE_URL); the trainer's ROCK_PORT.
EnvHub127.0.0.1:8081Environment registry (ROCK_ENVHUB_BASE_URL), SQLite-backed (ROCK_ENVHUB_DB_URL, default ~/.rock/rock_envs.db).
Health probeGET /apis/envs/sandbox/v1/get_status?sandbox_id=…Returns SandboxStatusResponse{is_alive, host_name}; is_alive() wraps it.

Key environment variables

The full registry is rock/env_vars.py; the knobs that matter most in training:

VariableDefaultEffect
ROCK_BASE_URLhttp://localhost:8080Admin server the SDK / client targets.
ROCK_SANDBOX_STARTUP_TIMEOUT_SECONDS180SDK-level SandboxConfig.startup_timeout default.
STACX_SANDBOX_STARTUP_TIMEOUT300SWE-agent start timeout (swe_sandbox.py); raise for slow networks.
DOCKER_IMAGE_CACHE_DIRunsetDirectory of image tars loaded before any registry pull (§5).
ROCK_DEFAULT_AUTO_CLEAR_TIME_MINUTES360Idle-container reclaim window (6 h).
ROCK_RAY_NAMESPACExrl-sandboxRay namespace for sandbox actors (overridden by rock-local.yml).
ROCK_WORKER_ENV_TYPElocalRuntime env type; set to pip for PyPI installs.
ROCK_MONITOR_ENABLEfalseToggle metrics collection (rock/admin/metrics/).

EnvHub

EnvHub (rock/envhub/server.py) is a standalone, SQLite-backed registry for reproducible environment definitions. The SWE lane bypasses it (images come from Docker Hub by instance id); Gym-style environments resolve their images through it.

Where to look next

Sandbox Management

Container lifecycle, sessions, auto-clear leases, and multi-node failure recovery.

Rollout Architecture

How evaluators drive env.step() and fold observations back into samples.

env_engine/README_ROCK.md

ROCK install, admin startup, GEM protocol, and the SDK smoke test.