STACX has three engines, but you set up two components: the sandbox engine (a ROCK-based service that owns Docker task containers and exposes the rock admin CLI) and the train/eval runtime (a pinned Docker image the train and eval drivers run in — there is no RL-engine install on the host). Every run — eval or training — follows the same path:

# the STACX loop, from dataset to gradient update
dataset config  rollout scheduler  agent + sandbox  verifier reward  trainer

The deep dives live under Architecture, Algorithm Recipes, and the Rollout / Environment sections — see Where to go next.

1 Prerequisites

STACX targets a Linux host with Docker and CUDA-capable GPUs. A single 8-GPU node (A100 / GH200 class) runs the full teacher + student + sandbox stack; smaller setups are fine for evaluation and CPU-only dev installs.

RequirementDetail
Linux + Docker Docker with the NVIDIA container runtime for GPU workloads — docker run --gpus all … nvidia-smi must work. The sandbox engine also supports Podman.
uv The uv package manager: curl -LsSf https://astral.sh/uv/install.sh | sh. Required for the sandbox-engine venv.
GPUs CUDA-capable GPUs for model serving and training. The DAgger recipes assume 8 GPUs on one node — teacher on 0–3, student on 4–7.
Disk ~60 GB under external/ for models + data, plus room for per-task Docker images, logs, and checkpoints.
Local disk, not NFS Clone onto local/scratch disk, not an NFS-mounted home — containers run as root and write under external/, and NFS root-squash turns those writes into PermissionError. The docker wrappers probe this and fail fast.
Docker Hub login Authenticated pulls (see below) — anonymous pull limits interrupt task-image builds and the on-demand sandbox pulls during training.
W&B key export WANDB_KEY=<your-key> when W&B logging is enabled. The training scripts require it; there is no default. This exact name — the scripts do not read WANDB_API_KEY.
Log in to Docker Hub before training — anonymous pull limits will interrupt a run (create a token at hub.docker.com/settings/security; ROCK inherits the daemon login).
echo <your-docker-hub-token> | docker login -u <your-docker-hub-user> --password-stdin

2 Install the sandbox engine

Clone the repo, then set up the sandbox engine (ROCK). It runs in its own small uv environment and must be started before any eval or training that executes verifiers. In a separate terminal:

# clone
git clone <this-repo> && cd stacx

# sandbox engine — its own env, its own terminal
cd env_engine
uv venv --python 3.11 --python-preference only-managed
uv sync --all-extras
source .venv/bin/activate

rock admin start                   # listens on http://127.0.0.1:8080

Verify that Docker and the admin server are both reachable:

docker --version
curl http://127.0.0.1:8080/        # admin server root; 200 = up

The admin server is the single entry point for sandbox lifecycle (/apis/envs/sandbox/v1/…): a Ray-based resource scheduler tracks GPUs/CPUs and starts, reuses, and tears down per-task Docker (or Podman) containers. Full detail lives in env_engine/README.md and env_engine/README_ROCK.md, plus the Sandbox Management page.

Smoke-test a sandbox

Before committing to a long run, confirm ROCK can start, exec into, and stop a real SWE-Bench container. Save this as test_rock.py and run it inside the ROCK venv:

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

async def main():
    t0 = time.time()
    cfg = SandboxConfig(
        image="swebench/sweb.eval.x86_64.django_1776_django-10097:latest",
        memory="4g", cpus=1.0)
    sb = Sandbox(cfg)
    await sb.start();                          print(f"[{time.time()-t0:.1f}s] started")
    await sb.create_session(CreateBashSessionRequest(session="bash-1"))
    r = await sb.arun(cmd="cat /etc/os-release | head -1", session="bash-1")
    print(f"[{time.time()-t0:.1f}s] exec OK:", r.output.strip())
    await sb.stop();                           print(f"[{time.time()-t0:.1f}s] stopped")

asyncio.run(main())
Cold image pulls take minutes; cached starts take seconds.

3 Pull the train/eval runtime

There is nothing to install for the RL engine itself — train and eval drivers run inside a pinned Docker image, entered through the thin wrappers shared/docker_run_train.sh / shared/docker_run_eval.sh (the repo is mounted into the container). Pull it once:

docker pull lichangh20/slime-rl:stable
Development setup (optional). For host-side development and CPU tests only — not needed to train or eval: bash scripts/setup_env.sh (add --cpu for a CPU-only install), then source activate_stacx.sh.

4 Run your first evaluation

Evaluating an installed-scaffold agent is four commands: download the Harbor task tree, convert it into a tasks.jsonl, build the per-task Docker images, then run the shared eval script. The eval driver connects to a running SGLang server rather than launching one — two one-time pieces of setup come first.

One-time: SGLang serving env

Installed-agent eval talks to an external SGLang server, run from a small pinned uv env at the repo root (an SGLang new enough for Qwen3.5 — the training image's is too old). Only installed-agent eval uses this env: training in both lanes and in-house agent eval serve SGLang in-process inside the runtime image.

uv venv .venv-sglang --python 3.11
source .venv-sglang/bin/activate
uv pip install --prerelease=allow "sglang[all]==0.5.10rc0"

One-time: site profile

Once per server, set the host IP and the sandbox GPU budget (gpus, minus the serving GPUs) in the site profile — it is the single source of truth for serving GPUs, TP size, and address, so the server and the eval agree by construction:

sed -i "s/REPLACE_WITH_SERVER_IP/$(hostname -I | awk '{print $1}')/" \
  rl_engine/examples/shared/profiles/cluster_layout_default.json

Detail: rl_engine/examples/shared/profiles/README.md.

Serve the model

# blocks in the foreground; run it in its own terminal
source .venv-sglang/bin/activate
bash rl_engine/examples/shared/serve_sglang.sh

curl http://<node-ip>:9001/v1/models    # 200 = ready

serve_sglang.sh reads the site profile, so the serving GPUs, TP size, and address match the eval automatically. Defaults: MODEL=Qwen/Qwen3.5-35B-A3B-FP8, PORT=9001; per-run knobs (MODEL, PORT, SGLANG_SERVE_GPUS, SGLANG_TP_SIZE) are in the script header. Wait for "The server is fired up and ready to roll!".

Download tasks, build images, run

With the sandbox engine running (§2) and the server up — SWE-bench Verified, Django-100 subset. Harbor task trees stage in the repo-local, gitignored external/harbor-datasets/, downloaded straight from the public Harbor registry — no Harbor install needed:

# 1. download the Harbor task tree (Django-100 subset; without
#    --task-names-file the FULL ~500-task verified set downloads and
#    step 3 then builds ~500 images)
python rl_engine/examples/shared/download_tasks.py swebench-verified@1.0 \
  -o external/harbor-datasets/swebench-verified_1.0 \
  --task-names-file rl_engine/examples/swebench/config/django100_task_names.yaml

# 2. Harbor task dirs → tasks.jsonl
python rl_engine/examples/shared/prepare_jsonl.py \
  --tasks-dir external/harbor-datasets/swebench-verified_1.0 \
  --task-names-yaml rl_engine/examples/swebench/config/django100_task_names.yaml \
  --output rl_engine/examples/swebench/config/tasks_django100.jsonl \
  --registry swebench

# 3. build the per-task images
bash rl_engine/examples/shared/build_images.sh \
  rl_engine/examples/swebench/config/tasks_django100.jsonl

# 4. run eval (OpenHands scaffold; SCAFFOLD=terminus2 for Terminus-2)
BENCH=swebench SCAFFOLD=openhands SGLANG_ROUTER_PORT=9001 \
  bash rl_engine/examples/shared/docker_run_eval.sh

Outputs land under external/results/eval/: the per-task results JSONL (reward, termination reason, verifier data), its .summary.json, the driver .log, and per-task sandbox logs under container_logs/<run>/. The wrapper detaches and prints a tail -f command to follow the run. Other benchmarks swap BENCH=<name> (terminal_bench, algotune, kernel_bench, seta, skyrl); each example's README is the self-contained walkthrough.

5 Launch your first training run

The launch scripts under scripts/train/swe/ encode the model, data, Docker, Ray, SGLang, sandbox-engine, checkpoint, and W&B settings for each recipe — start from one of these rather than calling rl_engine.train directly. We use the DAgger recipe (mixed-loss DAgger-OPD, 4B student) as the first run. It co-locates the whole stack on one node:

Single-node 8-GPU training layout teacher, student, and the sandbox orchestrator co-located on one Docker host (dagger / aggrevate) 1 NODE · 8 GPUs · DOCKER + NVIDIA RUNTIME Teacher Qwen3-Coder-30B · SGLang TP=4 · 127.0.0.1:30055 GPUs 0 · 1 · 2 · 3 Student Qwen3-4B / 8B · Megatron TP=4 · Ray head + rollout SGLang GPUs 4 · 5 · 6 · 7 action req logprobs rock admin 127.0.0.1:8080 sandbox lifecycle · Ray scheduler SWE-Bench sandboxes per-instance Docker containers sweb.eval…django-10097 sweb.eval…astropy-12907 + pulled once, reused after sandbox API spawn result host external/ bind-mounted into the student container models/ data/swe/ ckpts/ logs/ results/ bind-mount
The student calls the teacher's SGLang for K3 logprobs and β-mixture actions, drives per-instance SWE-Bench sandboxes through the ROCK admin, and reads/writes checkpoints and data over external/ bind-mounts.

Stage external/

external/ is .gitignored — you stage models, data, and output roots locally (~60 GB). For the 4B DAgger run you need the SFT-iter0 student (both the HF dir and the _torch_dist dir), the 30B teacher, and the four data JSONLs — all public, no HuggingFace login needed (uvx runs the HF CLI with no host env to install):

# pre-create the bind-mount roots as yourself (see the note below)
mkdir -p external/{models,data,results,ckpts,logs}

# 4B student (SFT-iter0): HF dir (tokenizer/chat template) + torch_dist (slime --load)
uvx --from 'huggingface_hub[cli]' hf download lichangh20/qwen3-4b-instruct-sft-swegym-iter0 \
    --local-dir external/models/qwen3-4b-instruct-sft-iter0
uvx --from 'huggingface_hub[cli]' hf download lichangh20/qwen3-4b-instruct-sft-swegym-iter0-distcp \
    --local-dir external/models/qwen3-4b-instruct-sft-iter0_torch_dist

# teacher (Qwen3-Coder-30B) + the 4 SWE data jsonls
uvx --from 'huggingface_hub[cli]' hf download Qwen/Qwen3-Coder-30B-A3B-Instruct \
    --local-dir external/models/qwen3-coder-30b-a3b-instruct
uvx --from 'huggingface_hub[cli]' hf download lichangh20/stacx-swe-online-dagger-data \
    --repo-type dataset --local-dir external/data/swe
Pre-create the output roots first. The student container bind-mounts host dirs for checkpoints, eval results, data, and models. If a mount's host dir is missing, Docker creates it as root:root — hence the mkdir -p above, run as yourself before launching.

Launch

With ROCK running (§2) and the runtime image pulled (§3), one command drives the whole run. This lane is self-contained — teacher and student SGLang are served in-process on the node's own GPUs, so the external server and site profile from §4 are not used. WANDB_KEY is the only required env var (this exact name — the scripts do not read WANDB_API_KEY):

export WANDB_KEY=<your-wandb-key>

bash scripts/train/swe/online_dagger/dagger_4b.sh        # dagger, 4B
# aggrevate_4b.sh · dagger_8b.sh · aggrevate_8b.sh — same pattern

End to end, with no manual steps in between, the script:

  1. Health-checks ROCK at 127.0.0.1:8080 (aborts if it is down).
  2. Launches the teacher SGLang (Qwen3-Coder-30B, TP=4) on GPUs 0–3 at 127.0.0.1:30055 and waits for ready (~3–5 min warmup; it reuses an already-healthy teacher).
  3. Launches the student training container on GPUs 4–7.
  4. Runs 5 rollouts × 512 tasks (≈1 epoch); per iter: rollout → finish-only filter → teacher force-decode (K3 logprobs) → 3 train passes → SGLang weight sync → eval on 3 datasets → save checkpoint.

Checkpoints land under external/ckpts/online_dagger_mixed_opd/<RUN_TAG>/, per-task eval JSONL + failure-class summaries under external/results/eval/swe_agent_online_dagger_mixed_opd/<RUN_TAG>/, and training logs under external/logs/. Re-running the same command auto-resumes from resume_state.json — it skips completed rollouts, reloads weights + optimizer state, and re-opens the same W&B run. For the full walk-through and knob catalog, read the DAgger-OPD quickstart and the Algorithm Recipes page.

6 Where to go next

The rest of the docs go deep on each engine. Start with the recipe you plan to run, then the component pages behind it.