Harness published — no results yetThe apparatus is built and reproducible. It has not been run here, so there are no measurements on this page and none are implied.
Original research
A Reproducible Benchmark Harness for Coding Agents
Three tasks that fail before an agent runs and pass after, a runner that measures the diff as well as the result, and a schema that refuses a record without a version. No numbers yet.
The question
On identical tasks in identical starting repositories, how do coding agents compare — not on whether they produce something plausible, but on whether a test suite that failed before passes after, and at what cost in unrelated changes?
Environment
- Python 3.11+ with pytest
- Harness: projects/copilot-agent-benchmark/ in this repository
- Three tasks: decimal-rounding, sql-injection, pagination-off-by-one
- Agent profiles: GitHub Copilot CLI, Copilot CLI with a selected model, Claude Code
Why most agent comparisons are not evidence
Almost every published comparison of coding agents shares three properties. The tasks are described but not shipped, so nobody can run them. The scoring is a judgement of output quality, so nobody can check it. And the versions are not recorded, so by the time you read it the thing being described no longer exists.
The result is a genre of article that reads like measurement and functions as opinion. This is an attempt at the other thing.
What makes a task a measurement
Each task is a small repository with a failing test suite. The agent is given a prompt and the repository. Afterwards the same suite runs again.
That gives one number that is not a proxy for anything: the tests failed, and now they pass, or they do not. No rubric, no similarity to a reference solution, no scoring of how idiomatic the code looks. There is more than one correct answer to every one of these problems, and a rubric that rewards resemblance to ours would measure conformity.
Two properties are enforced before a task counts, by harness/verify_tasks.py:
- The tests must fail on the starting repository. A task whose tests already pass scores every agent a point for doing nothing. This is a very easy mistake to make and completely invisible afterwards.
- The tests must pass with the reference solution. A task nothing can satisfy scores zero for everybody and looks like a hard problem rather than a broken one.
Both checks are run on the three tasks below, and both pass.
The three tasks
| Task | Category | The defect | Why this one |
|---|---|---|---|
decimal-rounding | Correctness | Invoice totals accumulate in float and use round(), so some totals are a cent out and 0.125 rounds down | The wrong code looks exactly like the right code, and the failure is a customer query rather than a crash |
sql-injection | Security | Two queries built with % formatting | Tests assert the input arrived as a bound parameter, not that the SQL string looks a particular way — so patching the symptom does not pass |
pagination-off-by-one | Correctness | Pages are 0-indexed against a 1-indexed contract and the page count truncates | Has a property test: every record must appear exactly once across all pages |
Each ships as repo/ (the starting state), tests/ (the verifier) and
solution/ (a reference fix used only by the task verifier, never shown to an
agent).
What is measured besides pass and fail
Passing is not the whole story, and the columns that are usually missing are the interesting ones.
| Metric | Why it earns a column |
|---|---|
unnecessary_files | Files changed outside the task’s allowed_paths. tests/ is never allowed — an agent that edits the test suite to make it pass has not solved the task, and this is how that is caught rather than hoped against |
lines_added / lines_removed | A four-hundred-line answer to a six-line problem is a finding, even when it passes |
lint_ok, build_ok | Passing tests while breaking the build is a specific and common failure |
wall_seconds | Median rather than mean, because one timeout should not become the headline |
timed_out | A recorded outcome, not a missing row |
interventions | Entered by the operator; see below |
What the harness refuses to do
The failure mode of a benchmark is not a wrong number. It is a plausible number nobody checked. So the runner stops rather than warns:
- No agent version, no result. The version command runs first and its output is stored verbatim. A record without one is not reproducible.
--no-containermarks the run unpublishable. It exists for development and the flag is recorded in the output file, so a convenient run cannot quietly become a published one.- A task that passes before the agent runs aborts the run, rather than scoring a point.
- A missing agent is a recorded skip with a reason, not an omitted row. A comparison table missing a row has quietly answered a different question.
This is also the model benchmark
Running one agent across several selectable models uses the same tasks, the same
runner and the same scorer — the model is a field on the agent profile. That is
why there is one repository rather than two, and why harness/agents.yaml
already carries a profile that pins a model.
Getting a number out of it
git clone <this repository>
cd projects/copilot-agent-benchmark
python3 harness/verify_tasks.py # the tasks are still valid measurements
python3 harness/runner.py --agent copilot-cli --task all --out results/run-$(date +%F)
python3 harness/score.py results/run-$(date +%F) --format markdownIf you run this and get numbers, they are yours to publish. If you publish them and they disagree with what we eventually publish, that disagreement is the most useful thing either of us could produce — open an issue with both result files.
The rules this follows
Every study here follows the same set, written down once at research methodology: what the three status labels mean, what a page must state before it publishes, and how to reproduce or dispute a result.
What would change this page
When a run happens, this page gains a results table, a date, the versions tested
and an environment description, and its status changes from harness to
measured. Until then the absence of numbers here is not an omission. It is the
finding: nobody has run this comparison in a way you can check, including us.
Limitations
What this cannot tell you. Stated in the frontmatter so it cannot be dropped in an edit.
- No results are published on this page. Running the benchmark needs paid accounts for several products under test, and none of them is available in the environment where this site is built. Nothing here implies a measurement.
- Three tasks is a harness, not a benchmark. Three small, well-specified bugs can distinguish an agent that reads a failing test from one that does not; they cannot rank two agents that both pass all three.
- Every task is Python and every task is a bug fix. Nothing here measures writing a feature, working across services, or any language where the agent's training data is thinner.
- Tasks are seeded, which means they are solvable by construction and the tests state exactly what correct means. Real work rarely arrives with a failing test attached.
- The intervention count is entered by the operator, not measured. It is the only field on the record that depends on somebody being honest, and it is marked as unrecorded rather than zero when nobody answered.
- Agents change weekly. Any result is a measurement of specific versions on a specific date, and the runner refuses to write a record that does not name them.
Reproduce this
Clone projects/copilot-agent-benchmark/, install the agent you want to test and authenticate it, then run `python3 harness/verify_tasks.py` to confirm the tasks still fail before and pass after, and `python3 harness/runner.py --agent <profile> --task all --out results/run-$(date +%F)`. Aggregate with `python3 harness/score.py results/run-<date> --format markdown`. Adding an agent means adding a command template to harness/agents.yaml, never editing the runner.
Sources
Every version-sensitive claim on this page was checked against first-party documentation. Only sources actually used are listed.
Primary sources
Cite this research
Free to use, republish and build on. A link back is appreciated and is not a condition. The one thing we ask is that theverification date travels with the numbers — several of these figures change monthly, and a table with no date on it becomes misinformation within a quarter however accurate it was when copied.
Plain citation
The Copilot Stack. "A Reproducible Benchmark Harness for Coding Agents." Version 2026-09-07. https://thecopilotstack.com/research/agent-benchmark/ (verified 2026-09-07).Markdown
[The Copilot Stack — A Reproducible Benchmark Harness for Coding Agents](https://thecopilotstack.com/research/agent-benchmark/) (verified 2026-09-07)BibTeX
@misc{copilotstack_research-agent-benchmark_2026,
title = {A Reproducible Benchmark Harness for Coding Agents},
author = {{The Copilot Stack}},
year = {2026},
note = {Version 2026-09-07. Maintained research, not peer reviewed},
howpublished = {\url{https://thecopilotstack.com/research/agent-benchmark/}},
urldate = {2026-09-07}
}@misc rather than @article or@dataset, and the note records that this is a maintained research that has not been peer reviewed. Formatting it as a journal reference would imply a review process that did not happen.
Licence. Free to reuse for any purpose, including commercially. This is original work by The Copilot Stack — the method, the harness and the results — and it has not been peer reviewed.
Share this
https://thecopilotstack.com/research/agent-benchmark/
Was this lesson helpful?
We record which lesson you rated and whether it helped. Nothing identifies you — no account, no cookie, no session.