--- title: Where py-harness stands description: A status board for 6 September 2026 — the seventeen fixes that took the benchmark from 51 of 75 to 64 of 75, the two findings left open, and what to do next, ranked by harm. permalink: /status/ date: 2026-09-06 type: article --- # Where py-harness stands **6 September 2026.** A day spent fixing the tool rather than asking anything of a model. The benchmark moved from **51 of 75 to 64 of 75** on the same fifteen cases — and the two faults that started it had made two days of model comparison say nothing about any model.
64 / 75benchmark, all tiers, five passes — was 51 / 75
17changes merged: 7 harness, 6 instrument, 4 write-ups
2findings filed and deliberately left alone
9 → 19tier 3, twenty runs, after one guard
## What was done Grouped by what each change touches rather than by order. Almost none of it was the model. ### Harness behaviour — what the agent does with a draft | | | | | --- | --- | --- | | #283 | Take the code fence off before it reaches the file | a hosted 32B scored 1 of 10; with four backticks stripped, **9 of 10** | | #303 | Read the draft a chat model writes | bold labels, list markers, a reason after the verb, a whole-reply fence — five shapes that parsed to nothing | | #308 | Take the name from the brackets, and stop guessing a signature it gave | tier 3 from **9 of 20 to 19 of 20** | | #320 | A suite that ran nothing is not a suite that passed | `unittest discover` exits 0 on zero tests, and 5 from Python 3.12 — so the check reads the output, not the code | | #326 | Write the class the test method needs | suites that actually ran a test: **2 of 8 → 8 of 8** | | #336 | Keep the function you were sent to fix | deleted the subject in 4 of 12 runs, now 0 of 12 — the score did not move | | #338 | Make `--scope` fence the writes, not only the reads | it fenced every read and no write; found by running the tool on a real repository | ### The instrument — what the tool reports about itself | | | | | --- | --- | --- | | #295 | Keep the turns the benchmark produces | every run recorded into a temporary directory and deleted it | | #306 | Say how big a gap this sample can resolve | the runner knew the number and did not print it | | #307 | Stop the contributors workflow cancelling itself | a red check on every branch that meant nothing | | #328 | Blame the file the action was actually about | on an exception the step kept the previous file — and the repair prompt reads the same field | | #333 | Say the module did not import, instead of calling the function missing | a broken import was reported as an absent function | | #335 | Print the loader's path with forward slashes | `str(path)` passed locally and broke all three Windows jobs | ### Written up [The fence was the whole story]({{ '/investigations/the-fence/' | relative_url }}) · [The wall two local models share]({{ '/investigations/the-wall/' | relative_url }}) · [What the totals were hiding]({{ '/investigations/totals-hide-things/' | relative_url }}) · [A day of repairs]({{ '/investigations/a-day-of-repairs/' | relative_url }}) ## Where it stands All fifteen cases, five passes, `llama3.1:8b`. What is left is concentrated in three cases. | Tier | Score | Weakest case | | --- | --- | --- | | 1 · one small component | 15 / 15 | — | | 2 · a component and a test | 10 / 10 | — | | 3 · a new module | 8 / 10 | `wordcount` 3/5 | | 4 · a test for existing code | 10 / 10 | — | | 5 · fix a bug already there | 7 / 10 | `fix-offbyone` 2/5 | | 6 · platform and operations | 14 / 20 | `env-flag` 2/5 | The benchmark now states its own resolution, so a difference inside the noise cannot be written up as a result. Twice in one day it would have stopped a wrong conclusion: ``` This sample resolves a gap of 2 case(s) or more. Identical code scored 1-2 across 10 passes, so a difference of 1 or fewer is noise and must not be reported as a result. ``` Tier six is the tier two local models were said to stop at together. Measured on the repaired instrument, the two local models are level with each other by this project's own noise rule, and the larger model is clear of both — which reversed the standing advice to keep that work local. | Tier six, twenty runs each | worked | `env-flag` | | --- | --- | --- | | `Qwen2.5-Coder-32B` (hosted) | **18 / 20** | 4/5 | | `qwen2.5-coder:7b` (local) | 11 / 20 | 3/5 | | `llama3.1:8b` (local, the default) | 8 / 20 | **0/5** | ## Open Both found by running the harness on a real 259-file repository rather than the two-file fixture. - **#339 — a run that has broken the suite keeps going, and never says so.** It ran the suite, saw `exit 1`, and appended for thirteen more steps. It ended with 996 tests passing before and an `ImportError` after, reporting only "stopped after 20 steps". - **#340 — the same patch is accepted over and over.** An identical `atexit` block written four times to three files. The harness refuses a repeated read; there is no equivalent for a write. The wider backlog holds 53 open issues, most predating this work and unaudited. ## What should be done Ranked by expected harm, not by size. The first two protect a working repository. 1. **Stop a run that has broken the suite** (#339). Higher real harm than anything else open: the benchmark scores it as one failure among many, and a person loses a working test suite without being told. The harness already runs the suite and already knows the exit code. 2. **Refuse a patch identical to one already applied** (#340). Cheap to detect — the loop already holds every step and its draft — and it turns a wasted twenty-step budget into an early, honest stop. 3. **Take on `env-flag`, the weakest case left** (2 of 5). Failure messages are trustworthy now, so start by reading them rather than inferring. The default 8B never passes it; the 7B coder passes it three times in five. 4. **Narrow the fine-tune target.** Not "be a better agent" but what a 32B knows about flags, paths and environment files that neither local model does — small enough for an adapter to carry. 5. **Get variety into the training data, not volume.** Traces are kept now, but the builder de-duplicates and fifteen fixed cases repeat themselves: seventeen turns yielded four unique pairs. Dogfooding on real repositories is worth more per run. ## What the day actually taught Every fault had one shape: **the tool reported something other than what happened**, and each was invisible until something outside its assumptions was plugged in. The harness had quietly specialised to the single model it runs itself — local weights rarely ask a question, and they do not fence their code, so neither fault could appear until a hosted model was measured. Two habits came out of it, both cheap, and both from numbers the tool was already printing: - **Read the variance, not only the mean.** A flat line across passes of a benchmark that changes verdict two thirds of the time means something stopped being decided by the model. - **Count the runs that used no model steps.** They went from none to ten when a mechanical path started answering wrongly. On the current suite fifteen of seventy-five runs use no model, and all fifteen are correct. And the counterweight: a fix that moves no score is the normal case here. Two were kept for that reason. Guarding against a draft that deletes the function it was sent to fix took that outcome from four runs in twelve to none, while the pass rate stayed inside the noise. It converts *deleted the function* into *did not fix the bug* — the same score, and a file left intact rather than damaged.