Zero-spend offline pipeline macOS seatbelt isolation Graded by execution, not by a model

Most AI coding benchmarks test the model on its own training data.

Canary does not. It scores coding agents on bugs published after the model's training cutoff, and verifies every answer by running the code — never by asking another model whether it looks right. The same agent that scores 90% on the usual kind of benchmark scores 0% here.

$git clone https://github.com/seyimusibau-cyber/Canary.git && cd Canary && make eval
The measurement, twice

Same model. Same harness. Same grader.

Textbook defects
90%
10 fixable synthetic bugs in a 13-task set. One direct prompt. The kind of defect an evaluation usually contains.
Real published defects
0%
12 real bugs from public security advisories. Every proposed fix was run, not just read.
Counted as a fix the usual way 67% 8/12 Actually fixed the bug 0% 0/12
Those two rows are the same run, graded two ways. Sixty-seven points of spread, produced by evaluation method alone.
Run: gemini-3.7-flash · 16 cases · training cutoff 2026-03-01

Two ways a benchmark score can mislead you

An engineering lead is about to let an AI agent open pull requests against a private codebase. All they have is a benchmark score, and that score carries two risks nobody prints beside it.

The answer may already be known

These models trained on essentially all of public GitHub — the bugs and the commits that fixed them. Evaluate an agent on well-known open-source defects and some part of what you measure is recall rather than reasoning. Their codebase is private; the model has never read it. The published number was measured under the one condition that does not hold for them.

The fix may never have been run

“Found and fixed” often means a plausible diff nobody executed. In this run, 8 of 12 patches applied without a merge conflict and none of them made the failing test pass. Score on application and you report 67%. Only running the code tells those apart.

Canary measures both, and only one of them showed up. The contamination effect this project was built to detect was not observed on this sample — see what the run found. The execution gap was, and it is large. Both results are reported as they stand.

Holding the model fixed while varying the rubric

Same model, same twelve real defects, same run — scored three ways:

How it is scored Dataset Success criterion Reported rate
Textbook bugs only 10 fixable synthetic bugs One direct prompt 90%
Patch-application check 12 real published defects Patch applies via git apply 67%
Canary, sandboxed execution 12 real published defects Failing test runs and passes 0%

The bottom two rows are one run graded two ways. The top row also changes which bugs are asked about, which is the other half of how a benchmark reaches ninety.

Three things the measurement found

1

Handing it the answer was not enough

One case is a canary: the security advisory naming the file and the defect is deliberately left in the source tree. The agent located it at 0.90 confidence, as it should — and still produced a patch that did not fix it. Across all twelve real defects it located three and repaired none. A benchmark scoring localisation alone would have called this run a partial success.

2

It was confident, and it was wrong

Of the findings stated at 90%+ confidence, 2 of 12 were correct. It raised 4 false alarms on clean control code, where the right answer was silence. An agent this sure and this wrong is more dangerous than one that admits doubt, because it gets believed.

3

Had it read the answers? Not provable here — and I am saying so

This project was built to detect training-data contamination. On this sample it did not find any.

Defects published Located Fixed n
before the cutoff20%0%5
after the cutoff17%0%6
gap3 points0

Three points, on five cases against six — indistinguishable from zero, since one case landing either way moves it by twenty. Twelve defects cannot resolve an effect of the size worth caring about; that claim needs a hundred. What the instrument demonstrably does catch is the canary, which fired on every run, on both models.

The headline stands on its own evidence anyway: 90% of textbook bugs, 0% of real ones. You do not need a story about training data to find that alarming.

Check the numbers yourself. Every figure on this page comes from two committed files: results/real_gemini37.json and results/live_synthetic_37.json. In the README the same figures are written in by make claims and re-checked by make verify, which fails on drift.

The part that did not work

Canary has two baselines, and it matters which one a gain is measured against.

The agent baseline — one direct prompt

Same model, same cases, same grader: the reasonable basic approach, and what a developer actually does today. The fenced agentic runner, with its toolbelt and its multi-step loop, was built to beat it.

It did not. On the synthetic set it lost — 80% against 90%. On the real set both scored zero. Across everything measured here, the scaffolding never won.

Reported as a negative result rather than quietly dropped. On the real set the agentic runner covered only 5 of 16 cases before an interrupted run, and a partial score is not comparable with a complete one, so it is excluded from the headline table and marked valid: false in the results JSON rather than averaged in.

The measurement baseline — how agents are evaluated today

This is where the gain is. Given the same twelve defects, the same model and the same run, the number you report depends entirely on how you score it: 90%, 67% or 0%.

The improvement is a reduction in measurement error, not an increase in agent capability. The number a buyer would act on stops being overstated by 67 to 90 points.

Only the last row survives the question an engineering lead is actually asking: will this fix my bug?

What is actually being measured. The agent is asked to find a latent defect by reading code, and is refused permission to run the regression test — because that test shipped with the fix and would hand over the diagnosis. That is harder than “here is a failing test, make it pass”. Canary measures unassisted defect discovery and repair, with execution feedback on each attempt. It does not measure test-driven bug fixing and does not claim to.

Six boundaries, and what each one is for

No model judges another model. The design replaces opinion with execution, audited redaction, and a control case that fails loudly when the harness itself is wrong.

01

The redaction fence

The agent sees the repository as it stood at the buggy commit. Anything naming the fix — the advisory, the CVE id, the commit message — is redacted line by line, and every redaction is logged to an auditable manifest.

02

A line drawn through the toolbelt

The agent gets list_files, read_file, search and run_tests. Each task declares whether its test predates the fix or shipped with it. The agent may run the first and is refused the second.

03

macOS seatbelt sandbox

The patch is applied to a throwaway copy and the test re-run under sandbox-exec, with the network denied, writes confined to the copy and secrets stripped from the environment.

04

Git ingestion with a proof gate

make ingest builds a task from two commits and writes nothing until it has proved it: the test must fail on the buggy tree and pass once the fix is applied.

05

Trajectory recording

Every step is logged as a structured trajectory. Past runs can be re-scored under a revised rubric, locally and deterministically, without spending anything on APIs.

06

The contamination canary

One case has the advisory deliberately left in the tree. If the harness reports that as a brilliant catch instead of flagging the leak, the harness cannot be trusted. It fires on every run.

The grader may know the future. The agent may not. Grading a fix against the regression test is sound science. Letting the agent read that test first is contamination wearing a lab coat.

Four runs, transcribed from the trajectories

Model output, patches and pytest transcripts below are quoted from the committed run artefacts in trajectories/real_37/. Long tracebacks are elided and marked as such; nothing is reconstructed. Each case links to the file it came from.

The task

What the agent said

Patch diff
Sandbox verbatim

Run the whole pipeline for nothing

Every command below runs on a clean clone. The default evaluation uses a deterministic offline stand-in model, so the full pipeline exercises end to end with no API key and no spend.

1. Clone and install

bash — clone & virtualenv
git clone https://github.com/seyimusibau-cyber/Canary.git
cd Canary
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

2. Run the integrity suite and the offline pipeline

bash — run pipeline
make test     # 47 integrity tests: fence, sandbox, task sets   (~16s)
make eval     # full pipeline, deterministic offline stand-in   (~25s)
make report   # renders results/ to report/index.html
Expected output. The runner prints comparison metrics across the linter, baseline and solution runners, and ends with [!] 1 contamination canary FIRED. If that line is missing, the run is invalid — that is what it is for.

Evaluating a live frontier model

The same pipeline runs against a live model through Google AI Studio, behind a budget gate that stops the run rather than trusting you to notice.

bash — live evaluation
# 1. Provide a Google AI Studio API key
cp env.template .env
set -a; . ./.env; set +a

# 2. Run with an enforced dollar ceiling
CANARY_AGENT_MODEL=gemini-3.7-flash \
  python3 -m canary.cli eval --tasks tasks/real --cutoff 2026-03-01 \
    --out results/real_gemini37.json --traj trajectories/real_37 \
    --budget-usd 1.75 --timeout 90

# 3. Render the HTML report
python3 tools/render_report.py results/real_gemini37.json report/index.html
Reasoning models bill their thoughts. A reasoning model charges internal chain-of-thought as output tokens. Measured on one real task: 22,097 thinking tokens against 243 tokens of answer — 98% of output volume, and invisible to a meter that counts only the answer. Setting CANARY_THINKING_BUDGET=512 cut one call from $0.084 to $0.013, a 6.6x reduction.

Point it at your own repository

The number that matters to you is the one measured on code the model has not read, and that code is yours. make ingest builds a task from a bug already in your git history: the buggy tree, the regression test lifted from the fixing commit, and ground truth taken from the lines that fix actually touched.

bash — ingest from git
# A real defect: buggy commit, fixing commit, and the test that separates them
make ingest REPO=~/src/your-project \
            BUGGY=a1b2c3d \
            FIX=e4f5g6h \
            ID=auth_token_bypass \
            TEST_CMD="python3 -m pytest -q tests/test_auth.py"

# A clean control, to measure false alarms on correct code
make ingest REPO=~/src/your-project \
            BUGGY=main \
            ID=clean_controller \
            TEST_CMD="python3 -m pytest -q tests/test_controller.py" \
            CLEAN=1
It writes nothing until it has proved the task. The test must fail on the buggy tree and pass once the fix is applied. A test that does not fail first proves nothing about a patch that makes it pass. And a task set without clean controls cannot report a false-alarm rate — which is most of what a code agent costs you in practice.

Re-grade without re-invoking the model

Model responses are persisted in trajectory files, so re-scoring under a revised rubric or a corrected test happens locally, at no cost, and produces the same answer every time.

bash — replay
# One case, end to end, with live sandboxed test execution
make demo CASE=scitokens_1_9_6

# Re-grade complete runs from recorded trajectories ($0.00)
make replay

Makefile targets

Target What it does API cost
make test Runs the 47 integrity guards — fence, sandbox, budget, task sets. $0.00
make eval Evaluates all three runners against the synthetic set with the offline stand-in. $0.00
make live-eval Runs the evaluation against a live model via Google AI Studio. live API
make ingest Builds a verified task from two commits in your own repository. $0.00
make report Renders a results JSON into a standalone HTML report. $0.00
make claims Rewrites every claim token in the README from committed run data. $0.00
make verify Fails if any README claim has drifted from the recorded results. $0.00
make demo CASE=<id> Runs a single task end to end with live sandbox execution. $0.00
make replay Re-grades recorded trajectories under the current rules. $0.00

The bugs that returned plausible numbers

Building a measurement instrument, the dangerous defects are never the ones that crash. They are the ones that hand you a believable figure.

A bare substring match

The fence's leak-marker list matched the bare substring "fix" — which also matches prefix and suffix. On real packages that excluded almost every source file. jinja2/lexer.py was fenced out entirely: the agent was graded on 868 lines it had never been shown, scored a miss, and the miss looked like an honest result.

Ground truth in a version string

The automated task builder produced a case whose ground truth was __version__ = (1, 2, 4) — asking the agent to find the bug in a version declaration. The fail-then-pass validation waved it through, because the old release genuinely does fail the new test, for reasons unrelated to the graded line.

A cost meter reading low

Spend was computed from answer tokens alone while the model billed its thinking as output. Reported $4.33 against an actual $11. Thinking is counted now and capped, but past runs cannot be corrected — thoughts_token_count was never recorded — so replayed results carry cost_is_understated and the report refuses to print those figures.

None of these were caught by ordinary correctness tests. All were caught by tests written to ask a different question: could this pass for the wrong reason? Those are standing guards in tests/test_realtasks.py now. The full account, including two ideas that turned out to be wrong, is in IMPROVEMENT_CHANGELOG.md.

What this does not establish

  • Twelve defects is a small sample. The pre/post-cutoff split is 5 against 6. No contamination claim survives that, and none is made.
  • One model family, one language. Everything here is Gemini through Google AI Studio, on Python packages from PyPI. Nothing is claimed about other models or other ecosystems.
  • Sandboxing is macOS-only. Isolation uses sandbox-exec. Linux and Windows have no equivalent path in this harness yet.
  • Localisation is graded by line overlap. A finding counts as located when it names a line the real fix touched. That is coarse: the scitokens case scored as located on a finding in an unrelated subsystem, and the fence had not shown the agent the file the defect lives in. Both are open issues, and both are visible in the case inspector rather than smoothed over.
  • Reported costs are a floor. Runs replayed from trajectories recorded before thinking tokens were metered are marked cost_is_understated.

Where the data comes from, and who built what

Data and licensing

Canary is MIT licensed. The real task set vendors roughly 20MB of third-party open-source code — the packages exactly as published to PyPI before each fix — so the evaluation reproduces from a clean checkout with no network. Every package's own licence is vendored alongside it, and tasks/real/PROVENANCE.md lists each case: package, version, licence, advisory, and the path to that licence file. That table is generated by the builder, not maintained by hand.

Advisory data comes from the public OSV database; sources come from PyPI's public JSON API. No credentials are involved and none are in the repository.

What existed before the hackathon

Disclosed per the rules. Canary was scaffolded before kickoff — the fence, the grader, the synthetic task generator, the CLI and a stubbed agent runner, about 900 lines, committed in 63a6132 and 5d68e2b, both dated 27 August 2026. Everything from f2558a4 onward was built during the event: the sandbox and its approval gate, trajectory recording, the toolbelt and the fenced agent loop, the budget guard, the real task pipeline and its scanner, the HTML report, and every test beyond the original four.

Two methods are reused from earlier personal projects and disclosed as such: point-in-time evaluation with a leak guard, and recompute-every-claim-from-data verification. Both were reimplemented here from scratch; no code was carried over.

Model under test: Gemini, via Google AI Studio. Harness author's assistant: Claude Code was used throughout to write this project, disclosed here rather than hidden.