Create the environment
Sandbox.create() boots a fresh microVM per rollout — or use it as a context manager so teardown is automatic.
Fan out one sandbox per rollout, run the episode, collect the reward, tear it all down. Each environment is an isolated microVM — real filesystem, real processes, zero cross-talk — and PillDrops is stress-tested to 1,000 concurrent.
Stress-tested to 1,000 concurrent sandboxes · per-second billing · E2B_API_KEY works too.
# pip install pilldrops from pilldrops import Sandbox from concurrent.futures import ThreadPoolExecutor def rollout(policy): with Sandbox.create(template="code-interpreter") as sb: sb.upload("./env", "/env") r = sb.run(f"python /env/episode.py --policy {policy}") return float(r.stdout) # reward for this episode with ThreadPoolExecutor(max_workers=64) as pool: rewards = list(pool.map(rollout, policies)) print(sum(rewards) / len(rewards)) # 0.8123
// npm i @pilldrops/sdk import { Sandbox } from '@pilldrops/sdk' async function rollout(policy: string) { const sb = await Sandbox.create({ template: 'code-interpreter' }) await sb.upload('./env', '/env') const r = await sb.run(`python /env/episode.py --policy ${policy}`) await sb.kill() return parseFloat(r.stdout) // reward } const rewards = await Promise.all(policies.map(rollout))
Each rollout gets its own microVM, so environments never leak state into one another and a crashed episode can't poison the batch. Spin up the whole harness at once — the scheduler places sandboxes across the fleet and reclaims them when the run ends.
Stress-tested to 1,000 live sandboxes at once — enough to run a full eval suite or an RL batch as a single fan-out.
A best-fit, multi-node scheduler packs environments onto the fleet by available vCPU and memory — no hot nodes, no wasted headroom.
If a host restarts mid-run, live sandboxes are re-adopted rather than orphaned — your reward collection keeps going.
Pre-warmed pools cut cold-start latency, so a thousand-way fan-out reaches the first step fast instead of waiting on boots.
vcpu, memory_mb and timeout per environment so heavy episodes get room and cheap ones stay cheap.Each sandbox is billed per-second on vCPU and RAM, and every run lands in your usage history keyed by sandbox id. Pull the history back to attribute cost per episode — and pause any environment that's waiting on a model turn so it bills storage-only until you resume.
# each sandbox id is one episode — pull its usage back $ pilldrops history --json [{"id":"sb_7a1","vcpu_s":42.1,"gb_s":88.5,"cost_usd":0.00099}, ...] # ...or over REST $ curl -H "Authorization: Bearer $PILLDROPS_API_KEY" \ https://pilldrops.com/v1/history # env is waiting on a model turn? park it — storage-only billing $ pilldrops sandbox pause sb_7a1 $ pilldrops sandbox resume sb_7a1 # pick up exactly where it left off
Billed at $0.0000140/vCPU-s and $0.0000045/GB-s. Because every episode is its own sandbox, its cost is its own line in GET /v1/history.
An environment blocked on a slow policy or a human step bills storage-only at $0.15/GB-month while paused — then resume restores its full state.
The whole environment lifecycle is four SDK calls — create, upload, run, kill — which drops cleanly into any RL loop or eval runner. The full evals guide walks through grading, scoring and collecting rewards at scale.
Sandbox.create() boots a fresh microVM per rollout — or use it as a context manager so teardown is automatic.
sb.upload() pushes your environment code and the policy under test; setup steps install anything the image lacks.
sb.run() executes the rollout and returns stdout, stderr and exit_code — parse the reward straight out of stdout.
sb.kill() (or the with block exit) reclaims the sandbox the instant the episode ends — you only paid for the seconds it ran.
Spin up disposable RL and eval environments in parallel, collect the reward, tear them down — with per-episode cost in your history. Starts on $5 of free credits, no card.