RL on AgentENV sandboxes
AgentENV is a self-hosted platform
for running agent environments at scale on Firecracker microVMs, built by the
Kimi K3 team: snapshot-backed sandboxes boot or resume in well under a second,
per-task OCI images load on demand, and its native API is the E2B API — so
Miles drives it through the standard e2b SDK with no AgentENV-specific code.
AgentENV plugs in wherever E2B does. This page covers what is specific to AgentENV — deploying a server and pointing the SDK at it — then trains Terminal-Bench-2 GRPO through the OpenEnv recipe on AgentENV sandboxes.
1. Deploy an AgentENV server
Requirements: a Linux host with /dev/kvm (bare metal, or a VM with nested
virtualization) and kernel 6.8+. We validated on an AWS m7i.metal-24xl
running Ubuntu 22.04 with the 6.8 HWE kernel.
# ublk is in linux-modules-extra on stock cloud kernels
sudo apt-get install -y linux-modules-extra-$(uname -r) && sudo modprobe ublk_drv
# host prerequisites + server container (Ubuntu 24.04 hosts can use the
# native install script instead; see the AgentENV README)
curl -fsSL https://raw.githubusercontent.com/kvcache-ai/AgentENV/main/scripts/docker-setup.sh | sudo bash
docker run -d --name aenv --privileged -v /dev:/dev -p 8000:8000 -p 80:8000 \
-e AENV_SANDBOX_PROXY_DOMAINS=<ip-with-dashes>.sslip.io \
ghcr.io/kvcache-ai/aenv-server:latest
curl -sf http://127.0.0.1:8000/health
Three parts of that command are not defaults. They exist because a training run reaches the env server inside each episode's microVM over one path only: the per-sandbox URL AgentENV's gateway routes. Get these wrong and no episode can connect.
AENV_SANDBOX_PROXY_DOMAINSturns those URLs on. It is what makes the SDK'sget_host(port)return a routable host, shaped{port}-{sandboxID}.{domain}.- The domain has to wildcard-resolve to the server, because every sandbox
id is new. sslip.io gives you that with no DNS setup of
your own —
203-0-113-10.sslip.ioresolves to 203.0.113.10 — and a wildcard record on a domain you own does the same. - Publish port 80 too (
-p 80:8000): those URLs carry no port, so they arrive on 80. Fronting the server with a proxy on 80/443 works instead, as long as it forwards WebSocket — episode I/O runs over/ws, and only the health check is plain HTTP.
AgentENV does not enforce API keys yet — run it only on a trusted network. Multi-node deployment (gateway + scheduler) is documented in the AgentENV docs.
2. Point the E2B SDK at it
pip install -e '<miles>[e2b]' # e2b>=2.12: older releases send the template name in a field AgentENV does not read
export E2B_API_URL=http://<server>:8000 # control plane
export E2B_SANDBOX_URL=http://<server>:8000 # data plane (envd proxy)
# plain-HTTP deployments only; the default is https
export OPENENV_E2B_URL_SCHEME=http
# required, but any well-formed key passes: AgentENV does not check it, while
# recent SDKs do validate the format client-side
export E2B_API_KEY=e2b_0000000000000000000000000000000000000000
3. Train
One AgentENV microVM per episode, on the OpenEnv tbench2 recipe's training pipeline unchanged. Follow that recipe's prompt-data preparation and tbench2_env install, then select the backend at launch:
export OPENENV_TB2_TASKS_DIR=/workspace/terminal-bench-2
OPENENV_SANDBOX_BACKEND=agentenv python ../openenv/run-openenv-tbench2.py
From here the e2b backend behaves as that recipe describes it, with each episode
running in a microVM instead of a cloud sandbox: the same per-task templates,
the same pre-baking step, the same GPU-free sanity checks. One caveat is
specific to baking against a self-hosted server: the CLI builds tasks one
after another, so --all over the full suite is a long single run — give it
its own session, or bake in chunks with --tasks.
We validated this path with a sustained GRPO run on 8×H200 (GLM-4.7-Flash,
TP=4/EP=2) over the full task set: 55 rollouts, ~3,400 episodes, each in a
fresh microVM warm-started from its pre-baked template, scored by the task's
own tests, with every rollout's weights serving the next. One
m7i.metal-24xl server sustained 64 concurrent microVMs throughout; episode
failures (WebSocket drops with the host at saturation) stayed near one
percent, each bounded to its own episode by the adapter's abort handling.
Size from there: ~64 concurrent episodes saturate one such host, and
OPENENV_E2B_CREATE_CONCURRENCY (default 4) paces creation bursts — we ran
it at 16.