v0.4.0
Released October 13, 2026.
-
Console:
deepgym console(aliasdeepgym web) serves a management UI and a JSON API under/api: searchable environments with task/verifier/reference views, background runs and small evals on any provider with per-case results, episode replay with screenshots, live Docker/Apple sandboxes with kill, and QA reports from~/.deepgym/qa/. Runs and episodes persist in~/.deepgym/console.db;deepgym.console.record_episode(trajectory, result)stores an episode from any agent loop. The API needs a per-launch token written into the served pages, refuses cross-origin writes and non-JSON bodies, and runs pasted solutions locally only with--allow-local-exec. Wheels ship the built UI. Replaces the single-pagedeepgym webdebug UI (deepgym.webis removed). -
verl masking:
deepgym.integrations.verl_mask.DeepGymSingleTurnAgentLoopscores single-turn rows itself and zeroes the response mask of samples whose sandbox or verifier failed (error: 1.0), so they get no gradient instead of training as a 0. A verl reward function or reward manager can’t drop a sample. -
Per-environment images: set
imageon anEnvironment, aMultiTurnEnvironmentor inmetadata.jsonto boot its sandbox from that image. Docker, Apple container, Modal and Daytona pull it from a registry (a Daytonasnapshotwins); E2B uses it as a template id. The image must run as root and have python3, bash, useradd and util-linux. The HTTP API refuses caller-chosen images. -
Quality gate:
deepgym qa [ENV ...] [--all]checks single-turn environments before training: the reference solution scores 1.0 on every rerun, empty and stub solutions score below 1.0, and no heuristic reward-hack attack reaches the pass threshold.--modeladds pass@1/pass@k from an OpenAI-compatible endpoint and flags tasks that are too easy or too hard. Writes one JSON report per environment to~/.deepgym/qa/and exits 1 if any environment fails.--modelwith--mode localorautoneeds--allow-local-exec. All 26 built-in single-turn environments pass; the environment smoke test now runs the gate. -
Reward-hack attacks (
AdversarialTester,RewardAuditor, the audit API) run through theDeepGymclient they’re given instead of always running locally, so a Daytona or container client runs them in its sandboxes. -
Sandbox providers:
mode='modal','e2b'and'docker'join'daytona'and'apple'for single runs,AsyncDeepGymand shell sessions, all with the same isolation. Each provider is a folder indeepgym/sandboxes/implementing one smallSandboxprotocol; Modal and E2B are optional extras. Judge runs fail closed when a sandbox can’t cut the solution off the network. In sandbox sessions, verifiers run as their own user from a file the agent can’t read, so leftover agent processes can’t read or forge the score; agent and solver drops add no-new-privs; container sandboxes are capped at 2 CPUs, 2 GB and (Docker) 512 processes.deepgym.apple_containermoved todeepgym.sandboxes.apple,AppleContainerBackendis now the provider-agnosticSandboxBackend, andcreate_sandbox/cleanup_sandboxmoved todeepgym.sandboxes.daytona. -
Apple container backend:
DeepGym(mode='apple')/AsyncDeepGym(mode='apple')run judges and multi-turn sessions in throwaway Linux VMs from Apple’scontainerCLI on macOS. Same isolation as Daytona: the solution and session commands run as non-root users with all capabilities dropped and no network; a judge run fails rather than run online if the image lacks util-linux. Image set byDEEPGYM_CONTAINER_IMAGE. Desktop envs still need Daytona. On Daytona the solution now also runs without network when the sandbox allows it. -
Rewards: NaN, inf and out-of-range scores are verifier errors, never reward. A single run raises
InvalidScoreError; batches and trainer reward functions score that sample 0 witherror_type='invalid_score'. Non-finite reward components are zeroed. Before, NaN scored 1.0. -
Benchmark-backed envs: SWE-bench Pro, Terminal-Bench and
MixedEnvironmentnow score correctly inAsyncDeepGymand in single sync runs. Before, both silently scored a 0.0 placeholder. -
Benchmark names: a fully qualified name such as
humaneval_plus/HumanEval_0beats loose matches, and an ambiguous name raises instead of loading the wrong benchmark. Before, HumanEval+ requests could load HumanEval. -
Isolation hardening:
- Local runs hide the home directory and every other run; only the Python install and the run’s own directories are visible.
- On Daytona the judge’s temp files are private.
- On macOS solutions can’t fork unless
run_solution(..., allow_subprocess=True). - Scripts’ leftover processes are killed when the script exits.
cli_taskreads.gitwithout running git.read_outputrefuses symlinked paths.
-
Security: the solution no longer runs inside the judge.
- Before, single-turn verifiers imported the model’s code into their own process, and in both local and Daytona mode it shared a folder with the verifier and test cases. It could print a fake score line, patch the verifier, read the expected answers, import the reference solution from the installed package, or return an object that equals everything.
- Now verifiers call
load_solution()/run_solution()fromdeepgym_solution, which run the solution in a separate isolated process:- Daytona: a dedicated Linux user.
- Local:
sandbox-exec(macOS) orbwrap(Linux), with the judge, the deepgym package and~/.deepgymhidden.
- Only plain data crosses the boundary. All built-in verifiers and the HumanEval, MBPP and EvalPlus importers are migrated; re-run the import scripts for cached benchmarks.
- BigCodeBench tests run with the solution inside the sandbox, because they need rich objects. That result is still forgeable by a determined model.
-
Per-call
case_timeout(default 10s), capped by the run’stimeout: a slow case fails alone and the run returns partial credit instead of timing out. -
Verifier shape is detected by parsing the source (
script,verify_function,body) or set withverifier_mode, replacing a text search that misclassified verifiers.from __future__imports are hoisted when wrapping. -
support_filesonEnvironment(andmetadata.json) for multi-file verifiers and shared helpers across environments. -
cli_tasknow works in its own directory (my_repoin the working dir) instead of a shared/tmp/my_repo. -
Breaking:
LocalExecutor.run()/AsyncLocalExecutor.run()take aJudgeJob.is_standalone_verifier,setup_sandboxandexecute_verifierare replaced byprepare_verifierandrun_judge_in_sandbox. -
Added live multi-turn sessions (
dg.session(),dg.run_episode(),Session): one sandbox per episode, actions applied step by step, final verifier run against the resulting state. Local and Daytona backends. -
Added real computer-use support on Daytona’s desktop: typed GUI actions (click, type, key, scroll, drag, …), full-resolution screenshots in observations, GUI-only action sets by default
-
Added 5 computer-use environments (
signup_form,todo_list,settings_tabs,expense_sheet,file_manager) with progress/guard verifiers and scripted reference runs checked live on Daytona -
Added 8 seeded variants per computer-use env (
load_environment(name, variant=i)), each with a scripted reference run verified live -
Desktop lockdown during setup: Chromium devtools and
javascript:/chrome:/file:URLs blocked, terminals and the run dialog disabled, file manager removed from browser tasks -
Added
/v1/sessionsHTTP endpoints (create, step, score, delete) with a race-safe session cap and 30-minute idle eviction -
Added verl multi-turn support:
DeepGymAgentLoop(agent_name: deepgym_agent) keeps one sandbox per rollout, since verl’s stock loop creates and releases tools per call, and setsreward_scorefrom the final verifier;DeepGymSessionToolis its tool -
Verifiers in sessions run with
python -Iand are passed inline, never written to the sandbox, so the agent can’t swap them or hijack them with planted modules -
Setup scripts that exit non-zero raise
SandboxError; shell steps reportexit_codein observation metadata; both backends run commands under bash -
Step verifiers that print garbage now raise
VerifierErrorinstead of scoring 0 -
CI: ruff skips markdown task prompts and JSON payloads stored as
.py, which formatting would change -
ActionErrorfor actions the environment rejects (not allowed, unknown key name); the verl tool returns these to the model as text and raises everything else -
Sessions serialize concurrent step/score/close calls; a failed step verifier ends the episode with a consistent trajectory; step-verifier stderr is ignored
-
setup_timeout(default 120s) bounds setup separately from per-step actions -
Daytona sessions create their work dir up front and tell command timeouts apart from connection timeouts via the SDK’s typed errors
-
dg.eval()andAsyncDeepGym.run()reject multi-turn envs with a clear error instead of crashing -
One shared
parse_verifier_output()replaces five copies of verifier JSON parsing -
Daytona sessions wait for sandbox deletion before freeing an API slot, bound every desktop call with a request timeout, and delete sandboxes left behind by a failed create (found via a per-session label)
-
Session API store is locked so idle eviction can’t race a session that was just used
-
Computer-use app state saves are atomic and applied in order (todo app chains its saves)
-
Local backend runs each command in its own process group and kills leftovers on timeout and close
-
Breaking: removed
MultiTurnRunner(usedg.run_episode()),ScreenshotVerifier, andToolUseEnvironment;ComputerUseEnvironmentis now a desktopMultiTurnEnvironment -
Breaking:
file_organizerandcli_taskare now typecoding(they were single-shot scripts, not computer use); computer-use envs only appear in thecomputer-usesuite -
Switched the Daytona dependency from the deprecated
daytona-sdktodaytona; Daytona session sandboxes block outbound network