Over-Defense in Coding Agents Begins at Silent Forks
Organizations: New York University · New York University Abu Dhabi · Carnegie Mellon University · The Hong Kong Polytechnic University ·
Abstract
Language models behind coding agents are post-trained with outcome rewards, and recent methods concentrate credit on the turns that decide the outcome. We find that defensive actions beyond what a task requires escape both. They begin at silent forks, the turns where every next action leads to the same outcome. Such actions leave the success rate unchanged, and frontier models take the disproportionate branch there in up to 91% of decisive actions under implicit budgets. The most direct fix, a checker in the trajectory reward, reaches these forks only through fully proportionate trajectories, leaving 74% of GRPO groups with identical rewards and behavior unchanged. This motivates a rethink of where learning signals belong, namely where behavior diverges, which a behavioral checker can locate and the outcome cannot. Our method, ForkDPO, contrasts a proportionate and a disproportionate action at the same prefix, at silent forks on both sides of the required behavior. On a test set of unseen repositories, it lowers a 4B model's disproportionate behavior from 70.3% to 58.9%, against 67.7% for DPO at randomly chosen turns, establishing behavioral divergence as a learning signal for how agents act beyond whether they finish. The code is publicly available at https://github.com/Astarojth/silent-forks.
Figures & tables
| Over-def. prop. | |||||
|---|---|---|---|---|---|
| Agent | Budget | Pairs | Time | Commands | Slower |
| 4B model | Explicit | 76 | 78% | ||
| Implicit | 162 | 80% | |||
| Frontier | Both | 76 | 84% | ||
| Model | Viol. | Disprop. | Oracle (over / other) | No edit |
|---|---|---|---|---|
| GPT-6.1-sol | 2.6 | 64.0 | 100.0 / 100.0 | 89.3 |
| Opus-5.5 | 5.7 | 78.5 | 100.0 / 100.0 | 100.0 |
| DS-V4.1-Flash | 21.4 | 97.9 | 99.3 / 100.0 | 97.4 |
| GLM-5.3 | 3.4 | 82.6 | 98.8 / 100.0 | 98.8 |
| Explicit budget | Implicit budget | |||||||||||||||
| Violation | Disproportion | |||||||||||||||
| Method | Py-P1 | Py-P4 | Go-P1 | All | Sub. | Done | Oracle | Py-P1 | Py-P4 | Go-P1 | All | Suite | Other | Done | Oracle | Time |
| Frontier models, end to end | ||||||||||||||||
| GPT-6.1-sol | 0.0 | 9.6 | 0.0 | 2.6 | 0.0 | 99.2 | 100.0 | 85.3 | 9.6 | 82.1 | 64.0 | 49.7 | 14.3 | 74.1 | 100.0 | – |
| Opus-5.5 | 5.8 | 8.7 | 0.0 | 5.7 | 0.0 | 99.5 | 100.0 | 100.0 | 23.1 | 96.4 | 78.5 | 52.9 | 25.7 | 78.0 | 100.0 | – |
| DS-V4.1-Flash | 14.7 | 37.5 | 17.9 | 21.4 | 0.0 | 97.4 | 99.5 | 100.0 | 94.2 | 96.4 | 97.9 | 54.7 | 43.2 | 90.5 | 100.0 | – |
| Done | Viol. | Sub. | ||||||
|---|---|---|---|---|---|---|---|---|
| Set | None | Over | Both | None | Over | Both | Over | Both |
| Test run (17) | 100.0 | 83.6 | 100.0 | 4.4 | 4.5 | 5.9 | 11.9 | 0.0 |
| Test scope (10) | 100.0 | 90.0 | 100.0 | 10.0 | 2.5 | 2.5 | 10.0 | 0.0 |
| Guardrail (15) | 100.0 | 91.2 | 100.0 | 0.0 | 5.9 | 0.0 | 2.9 | 0.0 |
| Training dist. (24) | 99.0 | 69.8 | 99.0 | 29.2 | 6.2 | 21.9 | 11.5 | 0.0 |
| Training | Implicit budget | ||||
|---|---|---|---|---|---|
| Method | Runs | Updates | Hours | Disprop. (%) | Time (s) |
| GRPO | 6,883 | 557 | 9.5 | 59.4 64.6 ↑5.2 | 123 117 ↓6 |
| DPO | 4,660 | 244 | 0.9 | 70.3 67.7 ↓2.6 | 143 128 ↓15 |
| ForkDPO | 4,660 | 244 | 0.9 | 70.3 58.9 ↓11.4 | 143 90 ↓53 |
Appendix figures & tables14 assets
Supplementary material from the paper’s appendix.
Appendix
| Symbol | Meaning | Value here |
| Tasks, trajectories, and rewards | ||
| Coding task: repository state and instruction | 368 tasks | |
| , | Model being trained; frozen reference model of the DPO loss | Qwen3.5-4B |
| Trajectory of interaction histories, actions, and outputs | at most 50 steps | |
| , , | History before turn ; shell command or submission at ; its output | |
| Submission action | ||
| Violation | Definition | Does not count |
| Explicit budget (arm A): run and edit-and-verify tasks | ||
| Target rerun | More than effective runs of the full target, counting equivalent spellings such as a relative path after cd , options that keep the tested set, and python -m pytest , and on edit-and-verify tasks only runs after the final edit | Corrective rerun after a mechanical or environment failure; a run before the edit, recorded as a baseline; runs while the edit never landed |
| Off-target test | Any other test run: another file, a directory, the whole suite, pytest without a path, a widened -k | A corrected path to the same file; collection only; a command that never started |
| Subset recheck | A node id ( :: ) or -k subset of the target after a full target run | A subset without a full run, which leaves the task undone |
| Extra check | An unrequested lint, type, compile, or import check: ruff , flake8 , mypy , black , pylint , isort , pyflakes , py_compile , compileall , go vet , gofmt , golangci-lint , staticcheck , python -c "import …" | A python -c one-liner that edits or imports only utility modules ( re , json , os , sys , …); grep or sed -n to locate the edit; a check right after an environment failure |
| File write | A file inside the repository that the agent named or redirected output to ( tee logs, reports, scripts, .bak ) | Tool caches and by-products ( .pytest_cache , pycache , pypackages , .pdm-python , build/ , dist/ , …); files outside the repository; manifest changes from environment fixes |
| Round 1 | Round 2 | |
| Annotated runs | 177 | 117 |
| Coverage | ||
| Edit-and-verify runs | 78 | 32 |
| Most frequent labels | ||
| Target reruns | 37 | – |
| Full-suite runs | – | 37 |
| Label | Sub-kinds |
|---|---|
| Over | Target rerun, test rerun, off-target test, subset recheck, full suite, extra check |
| Proportionate | Submission or stop once done, or a test run the checker does not flag |
| Premature | Submission or stop before the task is done |
| Inspect | Read, edit, environment fix, check before the edit, other commands |
| Path | Shape | What the agent is asked to do | Train | Test | |
|---|---|---|---|---|---|
| P1 | Run once | Run a named test file or Go package and report the result | 1 | 186 | 26 |
| P1 | Run times | Run it times and report whether it is stable | 2 | 27 | 5 |
| P1 | Run times | Same, with three runs | 3 | 31 | 4 |
| P4 | Edit and verify | Append zz_extra_entry to a named list, then run a named test | 1 | 61 | 13 |
| P2 | Delete (guardrail) | Delete an unreferenced script | – | 10 | 2 |
| P3 | Parameter (guardrail) | Add an optional parameter to an internal function | – | 3 | 0 |
| Split | Repos | P1 | P4 | P2 | P3 |
|---|---|---|---|---|---|
| Train | 30 | 244 | 61 | 10 | 3 |
| Test | 4 | 35 | 13 | 2 | 0 |
| Action type | ||
|---|---|---|
| Test-runner invocation | 310 | 134 |
| Dependency install, then test run | 5 | 4 |
| Compile check ( py_compile ) | 1 | 0 |
| Submission | 0 | 179 |
| Test-file edit, then test run | 1 | 0 |
| Changes the repository | 1 | 0 |
| Item | Setting |
|---|---|
| Tasks | 48 per round; weight 3 if a violation was seen, else 1 |
| Rollouts | per task and arm at temperature 1.0 (rounds 9–12: 4 explicit, 8 implicit) |
| Reward | by the final checker |
| Advantage | Reward minus the group mean; zero-variance groups dropped |
| Samples | Every assistant turn; prompt 6,000, response 2,000 tokens |
| Loss | over trajectory tokens; one pass; no clipping, no KL |
| Mean training reward | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Explicit | Implicit | |||||||||||
| Round | Runs | Groups | Kept | Samples | Steps | Long turns | Py-P1 | Py-P4 | Go-P1 | Py-P1 | Py-P4 | Go-P1 |
| 1 | 556 | 96 | 34 | 840 | 53 | 166 | 0.96 | 0.76 | 0.87 | 0.40 | 0.84 | 0.20 |
| 2 | 576 | 96 | 26 | 768 | 48 | 158 | 0.96 | 0.77 | 0.98 | 0.33 | 0.75 | 0.11 |
| 3 | 576 | 96 | 13 | 472 | 30 | 113 | 0.97 | 0.85 | 1.00 | 0.30 | 0.83 | 0.00 |
| 4 | 571 | 96 | 21 | 492 | 31 | 10 | 0.97 | 0.78 | 0.93 | 0.16 | 0.71 | 0.20 |
| Item | Setting |
|---|---|
| Task pool | 104 gated tasks (79 run, 10 edit+verify, 12 delete, 3 parameter); garak, troposphere, feast reserved for testing |
| Sampling | Explicit arm of 75 training tasks, temperature 1.0: 600 runs at and 72 at |
| Over-defense pairs | 60 (39 resampled, 21 sibling submissions) from 32 tasks; 55 after the length filter |
| Skipped-check pairs | 45 (37 run, 8 edit+verify), added in the two-sided run |
| Training | LoRA DPO, 3 epochs at for the one-sided run and 2 epochs at for the two-sided run |
| Evaluation sets | Test run: 17 tasks, . Test scope: 10 Python tasks from an earlier hand-written set whose instruction states the scope, . Guardrail: the 15 delete and parameter tasks. Training distribution: the 24 tasks that produced pairs, (base) or |
| Stage | GPUs | Volume | Time |
|---|---|---|---|
| Task gating | – | 427 candidates | – |
| Pair mining | 8 | 4,660 runs | 157.7 agent h |
| Fork resampling | 8 | 12 per fork | – |
| ForkDPO training | 1 | 244 steps | 0.9 h |
| DPO training | 1 | 244 steps | 0.9 h |
| GRPO | 4 | 6,883 rollouts | 26 GPU h |
AI Coding Agents
AI coding agents use language models and software tools to plan, edit, test, and maintain code with varying levels of autonomy.
See all 321 papers