8–12 min read · 2026-08-18
Lego-RL: Harness-Native Reinforcement Learning for Coding Agents
TL;DR
Coding agents rely on a harness to manage context, execute tools, and decide when to stop. Lego-RL lets us train inside that existing loop. We focus on three requirements: preserve the information needed for faithful policy updates, keep execution and rewards reliable, and make failures observable at the trajectory level.
Starting from Qwen3.5-35B-A3B, we train a separate policy in each of OpenHands SDK, Claude Code, and OpenCode without changing their control flow. Their best evaluated checkpoints improve SWE-bench Verified from 64.0 / 62.4 / 57.2 to 70.4 / 68.2 / 66.6. Each result comes from one training run.
- SWE-bench Verified (%)
- OpenHands SDK64.0 → 70.4 (+6.4)Claude Code62.4 → 68.2 (+5.8)OpenCode57.2 → 66.6 (+9.4)
- Train/Inference Correlation
- ≥ 0.998
- Sampled-token probability Pearson r; median over steps
- Training Speed-up
- 2.5×
- Measured async vs sync; ≈1.9× after compute-throughput correction
- Training task pool
- 2,699
- Tasks retained after difficulty screening
Why train inside the native harness?
A coding agent does more than ask a model for the next token. Its harness decides which files and tool outputs enter the context, when to compact the conversation, and how to continue after a failed command. Adapting that loop to a trainer can change the environment in which the policy learns. We ask what it takes to train directly inside the harness that will run the agent.
The same Qwen3.5-35B-A3B checkpoint scores 64.0, 62.4, and 57.2 on SWE-bench Verified under OpenHands SDK, Claude Code, and OpenCode. This 6.8-point spread shows that the surrounding harness matters to measured performance. It motivates preserving the native interaction loop during training; it does not, by itself, establish that native training outperforms a rewritten harness.
| Model | OpenHands SDK | Claude Code | OpenCode |
|---|---|---|---|
| Qwen3.5-35B-A3B (starting point) | 64.0 | 62.4 | 57.2 |
| Qwen3.6-35B-A3B (next-generation base) | 67.4 | 63.4 | 60.6 |
| KAT-Coder-V2.5-Dev (post-trained from Qwen3.6) | 67.0 | 66.8 | 64.8 |
| Lego-RL-Qwen3.5-35B-A3B (one policy per harness) | 70.4 | 68.2 | 66.6 |
The trained policies score above the starting checkpoint in all three harnesses. They also exceed Qwen3.6-35B-A3B by 3.0, 4.8, and 6.0 points under this evaluation protocol. These comparisons measure the outcome of the full training recipe; they do not isolate the contribution of any one component.
All three runs begin with the same checkpoint and use the same 2,699 tasks and 200k context budget for three epochs (126 steps). Training reward increases in each run without an observed collapse in policy entropy. The validation curves below show how the separately trained policies develop over time.
The policies use that budget differently. Mean response length grows from 43.5k to 90.9k tokens on OpenHands SDK and from 41k to 51k on Claude Code. Differences in context management may help explain this pattern, but we have not isolated their effect in an ablation. A shared context limit does not imply equal realized inference cost.
The trajectories also reveal a change in how the agent checks its work. In the sampled OpenHands SDK trajectories, the share that reread an edited file rises from 73.6% to 98.1%, while recovery after a failed command changes much less. We examine these behaviors below, alongside the additional interaction cost.
Framework capability comparison
This comparison records the capabilities described in the paper’s cited sources, rather than a live audit of current releases. The first four capability columns concern faithful optimization, the next three reliable execution, and the last observability.
| Framework | Black-box harness | Token-in / Token-out | History align | R3 | Fully async | Sandbox | Anti-cheat | Observable |
|---|---|---|---|---|---|---|---|---|
| verl | – | ✓ | – | ✓ | ✓ | △ | – | △ |
| slime | ✓ | ✓ | ✓ | ✓ | ✓ | △ | △ | – |
| MOLT | △ | ✓ | – | ✓ | ✓ | – | – | – |
| SkyRL-Agent | △ | △ | △ | ✓ | ✓ | ✓ | – | – |
| AReaL | ✓ | △ | – | – | ✓ | – | – | – |
| Agent Lightning | △ | ✓ | – | – | △ | – | – | △ |
| Polar | ✓ | ✓ | ✓ | – | ✓ | ✓ | – | – |
| rLLM | ✓ | ✓ | – | ✓ | ✓ | ✓ | – | ✓ |
| OpenForgeRL | ✓ | △ | – | – | ✓ | ✓ | – | – |
| ALE (ROLL/ROCK) | – | – | – | – | ✓ | ✓ | △ | – |
| Lego-RL | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
View exact values
| Training step | OpenHands SDK | Claude Code | OpenCode |
|---|---|---|---|
| 0 | 0.640 | 0.624 | 0.572 |
| 5 | 0.634 | — | — |
| 10 | 0.622 | 0.590 | 0.616 |
| 15 | 0.636 | — | — |
| 20 | 0.646 | 0.630 | 0.622 |
| 25 | 0.660 | — | — |
| 30 | 0.670 | 0.630 | 0.644 |
| 35 | 0.662 | — | — |
| 40 | 0.656 | 0.656 | 0.638 |
| 45 | 0.672 | — | — |
| 50 | 0.688 | 0.668 | 0.666 |
| 55 | 0.644 | — | — |
| 60 | 0.684 | 0.646 | 0.636 |
| 65 | 0.680 | — | — |
| 70 | 0.686 | 0.670 | 0.608 |
| 75 | 0.672 | — | — |
| 80 | 0.696 | 0.650 | 0.624 |
| 85 | 0.656 | — | — |
| 90 | 0.666 | 0.678 | 0.654 |
| 95 | 0.704 | — | — |
| 100 | 0.678 | 0.644 | 0.646 |
| 105 | 0.684 | — | — |
| 110 | 0.692 | 0.682 | 0.638 |
| 115 | 0.678 | — | — |
| 120 | 0.686 | 0.664 | 0.648 |
| 125 | 0.686 | — | — |
Data notes
Matched runs through step 126. Reward and entropy use a centered 5-point mean; validation and response length are unsmoothed.
Connecting the agent to the trainer
Lego-RL connects verl for optimization with Harbor for task execution. The harness runs in a fresh sandbox and sends model requests through a proxy. The proxy captures generation-time tokens and metadata; the task’s executable verifier supplies the reward. The harness keeps control of the interaction loop.
This separation lets the same training and execution infrastructure serve all three harnesses. Adding a harness requires an adapter to launch it, configure its model endpoint, and collect its result, followed by checks that its request and history handling are captured correctly.
Implementation: workers, proxy, and sandbox
- verl handles actor updates and weight sync (FSDP / Megatron-LM / VeOmni), PPO / GRPO / GSPO, a vLLM rollout pool, and sync / fully async / partial-rollout scheduling.
- Harbor wraps a problem statement, a repository snapshot, and an executable verifier into one task. It starts a sandbox, runs the agent, runs the task's tests for a 0/1 reward, then tears the environment down. Production uses Kubernetes (one pod per trajectory). Smaller runs use local or remote Docker.
Lego-RL fills the gap between them.
- AgentLoopWorker. Each trainer step hands a batch of tasks to concurrent workers. A worker owns one trial: launch the unmodified harness in a fresh sandbox, point its base URL at the proxy, let it run, then collect the verifier reward and the token-level trace. Agent execution accounts for 91.3% of trial wall time in the measured OpenHands SDK runs, so workers overlap trials to reduce waits between them.
- An in-process proxy that speaks OpenAI and Anthropic. It is the only path between harness and policy. Token IDs, response masks, log-probabilities, and MoE expert routing get recorded at generation time (see Faithful).
- Defenses live in the task environment, not in the harness. Anti-cheat and staged network policy sit in the sandbox because the harness cannot be edited (see Reliable).


The training objective uses the task’s pass/fail result to update policy-generated tokens. Each model response is conditioned on the context the harness actually supplied. The challenge is preserving that relationship while the harness edits history and trials run asynchronously.
Technical detail: the optimization objective
A task instance consists of a problem statement, an initialized repository environment, and a task-specific executable verifier. The harness belongs to the environment, not to the policy. At turn , the harness maps the current interaction and repository state to a context , the policy generates an action , and the harness executes whatever tool actions were requested, producing . A rollout is the sequence of prompt/response pairs actually exchanged at the model API, and the verifier ultimately outputs a single bit:
Only policy-generated tokens participate in training. Writing for those positions:
Every turn conditions on the harness-supplied context , not on the raw history. We maximize the expected verifier reward with group-relative advantages. Each task samples trajectories; the mean and standard deviation are computed only over admitted trajectories. For groups with at least two admitted trajectories, , with . Filtered trajectories receive zero advantage. A surviving singleton uses the implementation’s zero-mean, unit-scale fallback; an empty group contributes no reward-driven update. All three production runs use GSPO's sequence-level surrogate:
where is the policy version that generated the group, and zeroes out trajectories terminated by infrastructure failures. The asymmetric bounds give the sequence-level ratio more room to move up than down. The trainer also supports token-level objectives such as PPO and GRPO, with their corresponding advantage estimators.
For a group with at least two admitted trajectories, identical rewards give : the group contributes no reward-driven policy gradient. Task difficulty relative to the current policy therefore affects how much learning signal a batch provides. And because comes from executing code, the sandbox and verifier determine the quality of that signal.
Pillar 1: Faithful optimization
The problem: the archived transcript ≠ the token sequence at sampling time
A readable conversation transcript is useful for inspecting a run, but re-tokenizing it may not recover the sequence seen during generation. Policy-gradient training needs probabilities for the sampled tokens under the contexts that produced them. If either is reconstructed incorrectly, the resulting policy ratio is unreliable.
Real harnesses rewrite their own history:
- Claude Code injects
<system-reminder>blocks mid-conversation - OpenHands compacts history once the context window fills
- tool-call arguments get re-serialized, sometimes with keys in a different order
- sub-agents share a session with their parent agent
These changes can introduce mismatches between the recorded interaction and the sequence used for optimization. The run may continue without an error, so we check alignment explicitly.
Solution 1: an in-process proxy at the serving boundary
The proxy accepts Anthropic- and OpenAI-compatible requests and records token IDs, response masks, log-probabilities, and MoE routing metadata at generation time. Tool-call echoes are matched by ID, and sub-agent conversations are isolated. Exact token reuse applies to spans that preserve the captured prefix; history rewrites require reconstruction and remain a limitation of capture fidelity.
Solution 2: replay MoE expert routing
Matching token IDs is still not enough for MoE. If vLLM routes token to experts {3, 17} at sampling time and the training forward pass picks {3, 41}, the two sides are computing different probabilities.
Replaying rollout-time routing (R3) raises sampled-token probability Pearson correlation from 0.9946 to 0.9993 and reduces mean absolute token-probability error from 0.0062 to 0.0025. Two silent bugs showed why this has to be checked. A one-position routing misalignment scored worse than no replay (0.750 vs 0.995). An undersized capture buffer for hybrid-attention models wrote out-of-bounds entries as 0, and coverage fell to 24% before the guard started raising. After the fix, coverage stays above 99.8%.
Write for the log-probability recorded at generation time and for the value the trainer recomputes. Faithful optimization requires:
This comparison uses the same weights on both sides. During asynchronous training, the trainer may have moved to ; policy ratios account for that version difference, provided the captured tokens and probabilities are correct. They cannot repair a corrupted record. Across the three production runs, the median probability Pearson correlation over training steps is at least 0.998 for each harness. We also inspect probability errors and KL divergence, since correlation alone does not establish numerical agreement.
Pillar 2: Reliable execution
Layer 1: Reward integrity. Close the paths that score without solving
Each task’s executable tests supply a binary reward: 1.0 if resolved, 0.0 otherwise. That makes the feedback directly testable, but it also makes the verifier and sandbox part of the learning signal. We found several ways to obtain a passing result without producing the intended repair.
| Cheat path | Incidence | Notes |
|---|---|---|
| Reading the fix from local git history | 4.6%–20.5% | log -p, show, checkout; no command blacklist covers them all |
| Modifying test files | 2.4%–19.4% | tests pass by construction |
| Downloading the reference patch from GitHub | ~1.9% | one request away if the network is open |
| Grader applying the reference patch itself | ~2.5% | score is 1.0 regardless of the agent |
Defenses sit in the task environment, on by default, and flip with Harbor's phases: denied while the agent runs, restored for grading.
Sandbox defenses by execution phase
| Resource | Agent phase | Grading phase |
|---|---|---|
| Network | Egress firewall in a privileged sidecar; public traffic dropped. The main container has no NET_ADMIN. | Relaxed so graders can still install PyPI deps. |
| Git history | Rebased to a single commit; fix objects no longer exist. | .git.orig restored for git apply. |
| Test files | Not provided; edits to test paths are rolled back. | Restored, then graded. |
Layer 2: Difficulty is relative to the current policy
Each task samples 8 rollouts. All-success or all-fail groups contribute no gradient, so a fixed pool gets less informative as the policy improves: on OpenHands SDK, zero-variance groups climb from 44.7% to 51.4%; OpenCode holds around 43.3%.
Share of task groups · shared 0–50% scale.
Screening starts from 36,884 OpenSWE candidates. A task has to be valid, executable, and solved 1 to 3 times out of 4 by Qwen3.6-27B on OpenHands SDK. That leaves 2,699 tasks, which also work on Claude Code and OpenCode. We ablated the cutoff on four 951-task pools: the selected band and its upper half improve; the lower half and an unscreened sample do not. 72.7% of the unscreened pool was never solved.
Keep tasks solved 1–3 times out of four screening attempts by Qwen3.6-27B on OpenHands SDK.
View exact values
| Training step | random | 1–2/4 | 1–3/4 | 2–3/4 |
|---|---|---|---|---|
| 0 | 0.636 | 0.631 | 0.634 | 0.628 |
| 10 | 0.650 | 0.616 | 0.653 | 0.678 |
| 20 | 0.644 | 0.641 | 0.692 | 0.649 |
| 30 | 0.634 | 0.638 | 0.687 | 0.688 |
| 40 | 0.634 | 0.655 | 0.690 | 0.682 |
Bands: successes out of four screening attempts.
Data notes
Four pools of 951 tasks, grouped by screening successes out of four attempts. Scores use the paper’s correction for harness failures. Passes are shown every 10 steps; the degraded 2–3/4 pass at step 35 is excluded.
Infrastructure failures also affect which trajectories enter the update. We exclude timeouts and environment-setup failures from the group’s advantage statistics and assign them zero loss weight: 7.1% of Claude Code trajectories, 2.4% of OpenHands SDK, and 6.4% of OpenCode. Valid trajectories that reach a turn or token limit retain their verifier outcome.

Layer 3: 91% of a single trial is the agent executing
An OpenHands SDK trial takes 920 seconds on average, with agent execution accounting for 91.3% of wall time. This stage includes model inference and tool interaction. Long and uneven trial lengths make synchronous batches wait for their slowest members; asynchronous execution lets new trials start as slots become available.
The measured comparison shows a 2.5× reduction in time per step. The runs used GPU groups with different compute throughput, however. Correcting the synchronous run’s optimizer and log-probability recomputation time gives an estimated 1.9 hours per step, versus 1.0 hour asynchronously, at a maximum staleness of one update. This correction narrows the estimated gain attributable to scheduling.

Partial rollout keeps a ten-minute trial when the weights need to sync. It interrupts vLLM, loads the new weights, and resumes on the same replica using the prefix KV cache. The harness receives the completed generation through the original request.
Infrastructure optimizations and timings
| Optimization | Stage | On | Off | Median speedup |
|---|---|---|---|---|
| Prebuilt task images | sandbox startup | 1.04s | 36.2s | 33.2× |
| Mounted agent runtime | agent startup | 0.51s | 7.82s | 15.4× |
| Lazy image pull | sandbox startup | 1.57s | 2.66s | 1.7× |
| Packaged grading toolchain | grading | 3.81s | 2.72s | 0.71× |
Pillar 3: Observable training
Failure attribution: quickly telling policy problems from infrastructure problems
Failures can look the same on the reward curve and have different causes. One run died at step 3 because the tool-call parser was set to hermes instead of qwen3_coder. In another, validation score fell from 0.556 to 0.150. It looked like policy collapse, but only 60 of 172 trajectories had reached grading; the rest never finished environment setup.
Another parser mismatch terminated all 1,024 trajectories on the first turn. A separate run did show policy collapse. Inspecting the trajectories helped distinguish these cases; retrospective analysis of the collapse yielded an early-stop condition that would have triggered eight steps earlier. That retrospective result still needs testing on new runs.

To explore a run, start with the Claude Code dashboard’s termination breakdown, then compare the train/inference consistency diagnostics with the reward curve. These views help check whether a change in score coincides with execution failures or probability mismatches.
Loading Claude Code dashboard…
Live data may extend beyond the archived step-103 snapshot.
What did training actually change? Evidence at the behavioral level
The reward curve tells us whether performance improved. To examine what changed, we scanned 420 trajectories from each end of the OpenHands SDK production run for tool use and verification behavior. These transcript-derived measures describe actions we can observe; they do not establish the model’s intent.
Self-verification moved the most:
| Behavioral metric | Before training | After training | Change |
|---|---|---|---|
| Rereading an edited file | 73.6% | 98.1% | +24.5pp |
| Files examined before the first edit | 3.45 | 6.92 | 2× |
| Running the test suite | 85.0% | 93.6% | +8.6pp |
The later trajectories show more inspection before editing and more rereading afterward. Among trajectories with a failed command, the share that ultimately resolves the task changes from 63.9% to 66.8%, a smaller shift than the self-checking measures. These data do not establish why the behaviors changed at different rates, or whether the recovery difference is robust across repeated runs.
Improvement appears in both coverage and repeated success. The share of task groups with at least one success in eight attempts (pass@8) rises from 83.2% to 87.9%; the share with all eight attempts successful (pass8) rises from 28.3% to 39.4%. These group statistics compare the first and last thirds of training. They suggest a larger observed change in consistency, without implying that training only helps tasks the model could already solve.
There is also a cost: average turns rise from 46.6 to 83.1, while tokens per turn increase by 17%. Longer interaction histories can put pressure on turn limits even when the context budget is large. In the tool-allocation comparison below, percentages are shares of all tool calls. This differs from Table 6, where the test-running percentage counts trajectories that run tests at least once.
| Tool category | Before | After | Change (pp) |
|---|---|---|---|
| 35.65% | 39.31% | +3.66 | |
| 14.5% | 24.63% | +10.13 | |
| 7.22% | 12.58% | +5.36 | |
| 9.39% | 9.79% | +0.4 | |
| 15.79% | 8.51% | -7.28 | |
| 7.24% | 2.49% | -4.75 | |
| 1.63% | 1.3% | -0.33 | |
| 4.26% | 1.23% | -3.03 | |
| 1.07% | 0.15% | -0.93 | |
| 3.25% | 0.02% | -3.23 | |
| 0% | 0% | 0 |
Data notes
First and last 420 production trajectories per harness. Percentages are shares of all tool calls. 23,203 and 29,888 total calls, respectively. Bars share a 0–45% scale. Categories follow the paper’s tool-name mapping.
What these experiments establish
These runs show that an existing coding-agent harness can remain in control of interaction while its policy is trained with RL. Making that work requires checking the path from sampled tokens to policy updates, from sandbox execution to reward, and from an aggregate score to the trajectories behind it. Those checks make the training result easier to interpret and failures easier to investigate.
The evidence here covers one initial model and one production run per harness, with the reported checkpoints selected by periodic validation. Repeated runs are needed to quantify uncertainty. We have not isolated the contribution of every component, or directly compared native-harness training against a matched rewritten harness. The reward-integrity defenses address observed failure modes; they are not a guarantee against every possible shortcut.
What's next
- More tasks. The next pool will go past Python SWE-bench-style repair: more languages, NL2Repo (stand up a repository from a natural-language spec), and agent-user interaction where the agent has to ask and iterate instead of closing a ticket alone.
- Joint scaffold training. The three production runs each trained one policy on one harness. Next we will train a single policy across scaffolds, so it has to stay useful under more than one control flow. That is the same harness dependence that opened a nearly 7-point gap on the starting checkpoint.
- PPO critic. A terminal reward can reinforce earlier actions, but offers no direct label for which intermediate step helped. We plan to test whether a learned value function improves credit assignment and recovery after failed actions. The current behavior measurements motivate that experiment; they do not predict its outcome.
Framework updates, harness adapters, checkpoints, and task indices will stay open.
BibTeX
@article{du2026legorlharnessnativereinforcementlearning,
title={LEGO-RL: Harness-Native Reinforcement Learning for Coding Agents},
author={Yiming Du and Yuxin Jiang and Tao Yuan and Jianbo Dai and Shaowei Wang and Jierun Chen and Chaofan Tao and Xianzhi Yu and Lifeng Shang and Kam-Fai Wong and Xiaohui Li and Haoli Bai},
year={2026},
eprint={2608.17393},
archivePrefix={arXiv},
primaryClass={cs.AI},
url={https://arxiv.org/abs/2608.17393},
}