0

BackfillGym

Fresh

Backfill testing for whole software projects

Type
RL Env
Runtime
ORS
License
unknown
Size
70 tasks
Published
Sep 2026
Updated
Nov 2026

Cite

Notes

Only stored in your browser.

BackfillGym

Long-horizon "re-implement a removed subsystem" tasks mined automatically from real Python repositories, graded by the repo's own test suite with the create-swe-env telescoping reward. The RepoLaunch idea (auto-build an executable environment per repo) applied to subsystem backfill instead of issue↔PR pairs.

A task

tasks/<instance_id>/ — one per (repo, commit, target test file):

filewho sees itpurpose
task.jsonserverrepo, commit, sandbox_image, test cmd, F2P visible / held-out ids, P2P ids, removed objects
instruction.mdagent (prompt)contract + FeatureBench task statement + per-object interface descriptions
stub.patch, tests.patchimage buildoriginal → stubbed sources; original → visible-only target test file
Dockerfile, image/image build (CI)FROM the FeatureBench instance image (recipe in image/): apply patches, purge /root/my_repo, pip cache, vendored copies (leak_scan.py + fingerprints.json), re-init .git to one commit
stubbed_src/, tests_visible/servertamper baseline + no-op ablation audit
tests_full/, gold.patchserverheld-out grading at finish; solvability / rot checks

The agent works in /testbed (editable install, conda env testbed). Stubs keep signatures, decorators, class attributes and (LLM-paraphrased) docstrings; bodies are raise NotImplementedError.

Reward

The target test file on disk is the visible half of the target tests (the agent's spec, runnable freely, unscored). The other half is hidden: it lives only in the task dir on the server and never enters the agent's sandbox. run_tests starts a fresh grader sandbox from the same image, copies in the agent's changed, added and deleted files under /testbed (never graded test files, conftest.py, pytest config, .pth or sitecustomize/usercustomize, and nothing outside /testbed), writes the full target test files there, runs hidden + visible + P2P with --junitxml (xunit1, exact node ids), discards the grader, and pays Δ = scored_passes_now − scored_passes_previous over the hidden ∪ P2P set (+1 newly passing, −1 regressed). The baseline is seeded with the P2P set (they pass on the stubbed tree by construction), so Σ rewards = hidden target tests passing at the end − net regressions; a first call on the untouched tree scores 0. Visible passes are reported but pay nothing, so building to the visible tests earns nothing unless it generalises (the reason for this design: with visible tests scored, a special-cased solution earned the same return as a correct one and a trainer truncating at max_turns never saw the finish gate). Failing hidden tests are reported by function name only (the prompt lists every hidden test name up front), never by id or body.

The agent's code runs next to the hidden tests in the grader, so nothing from the grader comes back but per-test status: the grader's pytest log is never shown. A collection error is explained from the same run against the visible tests in the agent's own sandbox; a timeout shows no log. Both score 0 and leave the baseline unchanged, as does a change set over 8 MB compressed (not graded). The next grader starts in the background as soon as a grade ends, so a grade does not wait for a sandbox.

Editing graded tests, conftest.py or pytest config → BLOCKED (sha256 baseline from setup()). Adding a conftest.py or pytest config anywhere in the repo, sitecustomize/usercustomize, a .pth file or a pytest11 plugin → BLOCKED (inventory baseline from setup()). The grader would not copy any of them, so these blocks are feedback that such a change cannot count. Grading runs without pytest's cache or PYTEST_ADDOPTS/PYTEST_PLUGINS. When every hidden + P2P test passes, the no-op audit re-stubs the subsystem files in the same grader and reruns the visible target tests; if they still pass the "solution" lives outside the subsystem → flagged, not finished. Otherwise finished.

Pipeline (in ../../backfill/)

  1. vendor/FeatureBench fb data with backfill/specs/spike.py (repo specs; glm-5.2 via vendor/FeatureBench/config.toml) → traced, LLM-selected, masked, validated level-1 cases.
  2. backfill/make_task.py <run> <SPECS_NAME> <case> <repo_dir> envs/BackfillGym/tasks → stubs (backfill/stubber.py), exact F2P/P2P sets re-derived in the instance image, 50% hidden/visible split at test-function level, leak fingerprints, task dir.
  3. Task sandbox images are built by CI: .github/workflows/build-task-images.yml runs build/build_task_image.py tasks/<id> --push per task (matrix), pushing ghcr.io/generalreasoning/backfillgym:<id>-<content sha> (linux/amd64, GitHub's GITHUB_TOKEN). The env server computes the identical tag from the task dir (tasklib.image_tag), so nothing is committed back. Each task dir is self-contained (image/ holds the recorded base/instance Dockerfiles + setup script; the repo is fetched from GitHub at base_commit). Build one locally: python3 build/build_task_image.py tasks/<id> (add --platform linux/arm64 on a Mac for speed).

Test

cd /Users/anthonyhartshorn/gr/envs && BACKFILL_TASKS_DIR=swe_lib_env_creator/envs/BackfillGym/tasks \
  uv run python swe_lib_env_creator/envs/BackfillGym/golden_tests.py     # fake sandbox, reward contract
# real env code against a LOCAL docker image (no platform): stub → gold → finished, tamper, syntax error
BACKFILL_TASKS_DIR=swe_lib_env_creator/envs/BackfillGym/tasks uv run python \
  swe_lib_env_creator/envs/BackfillGym/test_local.py <task_id> <local_image> <repo_dir>
uv run python swe_lib_env_creator/envs/BackfillGym/test_live.py <task_id> <repo_dir>  # same, through the platform sandbox

Known limitations

  • Code under test can still read the hidden tests inside the grader, and the per-test results that come back are a narrow channel: code that read them could choose which hidden tests fail, and the failing names are reported. Nothing it writes in the grader survives, and the telescoping reward pays nothing for passing a test it first failed on purpose; closing the channel fully means reporting counts only.
  • A forgery inside /testbed that detects the no-op audit would pass (e.g. the package patching pytest's reporting when imported). Startup hooks, installed packages and the interpreter are outside the copied tree, so only in-repo code can try this.
  • FeatureBench cannot remove a library's central core (anything a regression test touches is protected), so tasks skew to peripheral subsystems; a subsystem-first selector is future work.
  • Only Python / pytest for now.