code_instruct¶
Magicoder OSS-Instruct, but grounded in a specific target repo and verified by execution. The LLM proposes a coding task seeded by a snippet from the repo's actual code; we run the synthesized test inside the repo's bootstrap container to confirm the test FAILS without the oracle and PASSES with it.
| Status | shipped (v0.6, hardened v0.8.6) — Python only |
| Sandbox required at gen | Yes |
| LLM required at gen | Yes (1 call per attempt for problem + test + solution; up to max_attempts_per_seed) |
| Reward kinds emitted | test_execution |
| Reference dataset | AdithyaSK/repo2rlenv-code-instruct — 100 tasks across click, flask, requests, attrs, starlette |
| Inspiration | Magicoder (ICML '24) |
What's different vs vanilla OSS-Instruct¶
Magicoder samples seeds from a global OSS corpus (~150K files) and emits text-only (problem, solution) pairs. Repo2RLEnv's variant differs in three ways:
- Seeds come from one target repo — the synthesized task is solvable in that repo's environment.
- Each task ships an executable verifier (a pytest test) the LLM also writes — not just prose.
- The oracle must actually pass the test in the repo's Docker env.
That third invariant is the load-bearing contribution. Magicoder doesn't do it; nobody currently does it for arbitrary repos.
Algorithm¶
flowchart TD
A[Repo URL] --> B[bootstrap: build env at HEAD]
B --> C[Sample random Python file +<br/>30–200-line window]
C --> D[Filter: skip mostly-boring blocks]
D --> E[ONE LLM call:<br/>Problem + Test + Solution]
E --> F[Decontaminate vs known benchmarks]
F --> G[Repo-anchoring gate:<br/>solution imports + uses the repo package]
G --> H[Symbol-collision gate:<br/>task_module names ≠ repo top-level names]
H --> I[Test-strength gate:<br/>≥3 non-trivial asserts, pytest.raises on error tasks]
I --> J[Dedup: problem-head + symbol-set fingerprints]
J --> K[Retry up to max_attempts_per_seed if any gate fails]
K --> L[Run test alone → must FAIL]
L --> M[Apply oracle → must PASS]
M --> N[Emit Harbor task<br/>(instruction bakes the task_module.py delivery contract)] Quality gates (added v0.8.6) — the same LLM ships very different tasks under different prompts. Baseline runs produced generic OSS-Instruct problems that ignored the target repo (mean repo-anchoring score 1.⅘). Four post-synthesis gates now enforce anchoring:
- Repo-anchoring — AST scan of
task_module.py: must havefrom <repo_pkg> import ...(orimport <repo_pkg>) that is actually used. - Symbol-collision — reject candidates whose top-level class/def names collide with an existing repo top-level symbol (blocks the
grep-and-re-export cheat). - Test-strength — reject weak tests (
assert True,<3non-trivial asserts, missingpytest.raiseson error-condition instructions). - Task dedup — two independent fingerprints (problem-head + sorted public symbol names); overlap on either counts as duplicate.
When any gate rejects a candidate, the pipeline retries the LLM up to max_attempts_per_seed times on the same seed before advancing.
Delivery contract in the instruction — the emitted instruction.md explicitly tells the solving agent to write its implementation to /workspace/task_module.py. Without this, agents (even Sonnet, Codex, and Qwen3.6-35B) write correct logic to naturally-chosen filenames (ranged_float.py, fetcher.py) and pytest collection fails with ModuleNotFoundError: No module named 'task_module'. Wiring this into the emitted instruction bumped Sonnet's solve rate from 40% to 80% at fixed dataset size.
Pipeline shape (emitted task)¶
<owner>__<repo>-cinst-<hash>/
├── task.toml # name = "<org>/<slug>"
├── instruction.md # LLM-authored problem statement
├── environment/Dockerfile # FROM bootstrap; HEAD state
├── tests/test.sh # `python -m pytest test_r2e_<hash>.py -v`
└── solution/
├── patch.diff # adds task_module.py + test file at repo root
└── solve.sh # `git apply patch.diff` shim
The gold patch.diff adds two new files: task_module.py (the oracle) and test_r2e_<hash>.py (the verifier). The agent's job is to make task_module.py satisfy the test.
Prompt + parsing¶
One call asks the LLM for three sections in fixed order:
[Problem Description]
<self-contained problem statement>
[Test]
<pytest test that imports from `task_module`>
[Solution]
<the `task_module.py` content>
parse_task_response extracts the three blocks via case-insensitive marker scanning; markdown code fences are stripped.
Verification invariants¶
We run two stages inside the bootstrap container:
| Stage | What runs | Required outcome |
|---|---|---|
| A — test only | write test file; pytest <test> | FAIL (else the test is trivial) |
| B — test + oracle | write both files; pytest <test> | PASS (else the oracle is wrong) |
If either invariant breaks, the task is skipped. Both stages clean up after themselves so the next candidate starts from a clean tree.
Options¶
See CodeInstructOptions in src/repo2rlenv/spec/options.py. Key fields:
| Field | Default | Notes |
|---|---|---|
limit | 50 | max emitted tasks |
seed_min_loc / seed_max_loc | 30 / 200 | snippet window size |
file_glob / exclude_glob | **/*.py / tests/etc. | seed source selection |
max_attempts_per_seed | 3 | retries the LLM when a gate rejects (was 1 in v0.6; bumped in v0.8.6 because the new gates reject more of the first draft) |
llm_temperature | 0.7 | issue + solution |
require_test_fails_without_oracle | True | stage A invariant |
require_test_passes_with_oracle | True | stage B invariant |
skip_decontamination | False | turn off benchmark substring check |
skip_validation | False | debug; emits without sandbox run |
Yield¶
Yield = emitted tasks ÷ seed snippets sampled. With the v0.8.6 gates + retries, expect ~60–90% on well-behaved Python libs (empirically, generating the 100-env reference dataset needed 132 candidates → 75.8% yield across 5 repos: click 27→20, flask 24→20, requests 24→20, attrs 23→20, starlette 34→20).
| Knob | Default | Effect on yield |
|---|---|---|
max_attempts_per_seed | 3 | ↑ retries a failed synthesis → higher yield, more LLM spend |
| LLM model quality | — | the biggest lever — a stronger model writes verifiable tests more often |
seed_min_loc / seed_max_loc | 30 / 200 | very small or very large seeds yield weaker tasks; mid-range is the sweet spot |
exclude_glob | tests/docs/… | keeps seeds anchored to real logic (avoids untestable boilerplate) |
skip_decontamination | False | True keeps would-be-decontaminated seeds (higher yield, contamination risk) |
Common skip reasons in the reference generation run: - oracle_does_not_satisfy_test — LLM's oracle failed its own test in the sandbox (majority of skips; noise inherent to LLM synthesis) - duplicate_task — dedup fingerprint match against an earlier candidate (fires ~2/repo on active seed distributions) - missing_pytest_raises — test-strength gate hit on an error-condition instruction with no pytest.raises block
Unlike the *_runtime pipelines, repo test health doesn't gate yield — the verifier is self-contained in the emitted task — but the repo still needs to bootstrap (the env runs the generated test). Cost scales with limit × max_attempts_per_seed LLM calls.
Solve rate on the reference dataset¶
Sample-validation of the 100-env reference dataset, one task per repo, three agents/backends:
| Agent (Harbor) | Model | Solved | Cost |
|---|---|---|---|
claude-code | Sonnet 4.6 (Anthropic direct) | ⅘ | $0.27 |
codex | GPT-5.3-Codex (OpenAI direct) | ⅘ (extrapolated — measured ⅖ before v0.8.6 delivery-contract fix) | $0.28 |
openhands-sdk | Qwen3.6-35B via HF Router | pending re-run on the fixed dataset | $0 (Router-hosted) |
Wall clock: ~5 min per trial for claude-code, ~4 min for codex, ~4 min for openhands-sdk.
End-to-end smoke¶
repo2rlenv generate \
--repo pallets/click \
--pipeline code_instruct \
--pipeline-opt limit=1 \
--pipeline-opt seed=42 \
--llm anthropic/claude-sonnet-4-6 \
--out ./workspace/datasets/click-cinst
# Oracle: should score 1.0
harbor run -p ./workspace/datasets/click-cinst -a oracle -l 1 -n 1 -k 1 -y
# Real agent: Sonnet via claude-code
harbor run -p ./workspace/datasets/click-cinst \
-a claude-code -m anthropic/claude-sonnet-4-6 \
-l 1 -n 1 -k 1 -y
# OpenHands + HF Router (any router-hosted model)
harbor run -p ./workspace/datasets/click-cinst \
-a openhands-sdk -m "openai/Qwen/Qwen3.6-35B-A3B:scaleway" \
--ae "LLM_BASE_URL=https://router.huggingface.co/v1" \
--ae "LLM_API_KEY=$HF_TOKEN" \
-l 1 -n 1 -k 1 -y
Or pull the published 100-env dataset directly:
repo2rlenv pull AdithyaSK/repo2rlenv-code-instruct ./workspace/datasets/code-instruct
harbor run -p ./workspace/datasets/code-instruct -a oracle -l 5 -n 4 -k 1 -y
What we adapted from references/magicoder/¶
- Seed-snippet → instruction recipe (
src/magicoder/generate_data.py:79-84) - Section-marker output format (
data/prompt.txt) - Section-by-section parsing (
src/magicoder/generate_data.py:87-102) - Substring-based decontamination (
decontamination/find_substrings.py)
No code is copied. The execution-verification layer is original.