Sankur Kundu

crucible · sandboxes for RL on code

tests it cannot fool.

Training a model to write code with reinforcement learning means running its code thousands of times per step, safely. crucible builds a disposable sandbox for every answer from Linux primitives, with no virtual machines, and grades it with tests that never share a process with the model's code.

user namespace · own user
mount · network · PID namespaces
resource limits · no capabilities
seccomp-BPF: no network
the model's answer
34.9→38.6%MBPP pass@1 for Qwen2.5-Coder-0.5B after 100 GRPO steps on two T4s, mean of three seeds; paired test p = 0.013
0 of 9Reward hacks that pass crucible's grader; the usual grader passes 4
4.5 msTo fork a live sandbox in a state that takes 138 ms to rebuild (30×)
5.3 msTo start a fully isolated sandbox: 526 sandboxes a second
01 · Reward hacking

The grader a model cannot game

Unit-test rewards invite reward hacking, so the asserts run outside the sandbox and only plain values come back from it. tests/test_grader.py tries nine hacks against both graders:

HackcrucibleUsual grader
An always-equal objectfailpass
Exiting before the tests runfailpass
Forged test outputfailpass
sys.exit at importfailpass
Hacks that earn the reward0 of 94 of 9

During GRPO training every answer was also graded the usual way, to count the rewards a hack would have won.

02 · Isolation at speed

Faster than a bare Python

A warm process with the task's modules already imported forks each sandbox; a live sandbox can itself be forked mid-episode, its memory shared copy-on-write, so an agent can branch several attempts from one state. Four CPUs:

Time to start a sandbox. 8 branches of a 256 MB sandbox use 508 MB instead of 2,304 MB copied (bench/spawn.py, bench/fork.py).

03 · Training with it

RL on code, graded safely

Served through OpenEnv's interface, crucible graded every answer in GRPO training on ratchet. MBPP, 499 test tasks, two Tesla T4s:

Greedy pass@1Seed 0Seed 1Seed 2Mean
Before training34.9%34.9%
After 100 GRPO steps38.9%38.1%38.9%38.6%
Tasks fixed / broken44 / 2448 / 3248 / 28

Paired test over the 499 tasks: p = 0.013. 115 of the 140 fixes were answers that already ran and gave wrong results: the gain is mostly correctness, not formatting (scripts/pool_m3.py).

04 · Branching rollouts

Fork the episode, not the replay

A branching rollout reaches each leaf through a shared prefix (a workspace of 200 files and an index over a 300,000-entry table, 0.33 s to build) and one more step per level. Every tree was built two ways: fork the snapshot at each node, or start a new sandbox per leaf and replay its path. Four CPUs, one leaf at a time:

TreeLeavesForkReplayGain
4 × 140.42 s1.56 s3.7×
4 × 2160.97 s5.81 s6.0×
8 × 2642.32 s23.1 s10.0×
4 × 3642.21 s24.2 s10.9×

Tree is branches × depth. Steps run, fork against replay: 5 / 8, 21 / 48, 73 / 192 and 85 / 256. Every leaf ends in the same state either way, but only because these steps are deterministic. When the prefix records a time or a random id, as real environments do, the 16 forked leaves of a 4 × 2 tree share the one prefix the episode had, while 16 replays rebuild 16 different ones (bench/branch.py). A branch also outlives the sandbox it was forked from, so a tree can close a node whose children still run.

What crucible adds

Safe rewards for code-writing models

The infrastructure that RL on code depends on, built from Linux primitives and measured against the attacks a model actually finds.

  1. Sandboxes from Linux primitives

    Namespaces, limits, no capabilities and seccomp: 5.3 ms per isolated sandbox, no VMs.

  2. A grader that resists hacking

    Tests outside the sandbox: 0 of 9 hacks pass, against 4 of 9.

  3. Branching episodes

    Fork a live sandbox in 4.5 ms, copy-on-write: a rollout tree grown from forks needs up to 10.9× less environment time than replaying each path, and every leaf keeps the state the episode had.

  4. A measured training gain

    34.9% → 38.6% MBPP pass@1, three seeds, p = 0.013.

Where it falls short

A 0.5B model and 100 steps, so the gain is a few points, and each run also broke 24 to 32 tasks it had solved before.

@software{kundu2026crucible,
  author  = {Kundu, Sankur},
  title   = {crucible: sandboxed, hack-resistant rewards for reinforcement learning on code},
  year    = {2026},
  url     = {https://github.com/Shakhtar-Sankur/crucible},
  license = {Apache-2.0}
}
Sankur Kundu

Sankur Kundu

ML systems engineer & independent researcher · Bhubaneswar, India · sankur.kundu.tw@gmail.com

Builds the systems that train and serve language models. Also the author of lipi, a tokenizer that needs fewer tokens than GPT-4o's and Gemini's in 23 of 24 Indian languages; Babelguard, an open early-warning system for scams across languages; ratchet, RL post-training from scratch; kessler, an independent space-traffic audit that runs every day. Fixes merged into Apple's MLX, Meta's ExecuTorch, Hugging Face's OpenEnv and Oxford's PyZX, and pull requests in review at Google's JAX, NVIDIA, MIT, Carnegie Mellon and ETH Zurich.