Skip to content

Eval verdicts: harness, real zero, or wrong question

One row per eval. For each, whether its score is evidence about the agent, and what that judgement rests on.

docs/eval-health.md answers did it pass. This page answers does the pass mean anything, which is the question the 0.8.7 investigation (#31) showed is not the same one. It is written by hand and reviewed when an eval is added or rescoped; it is not generated, because a verdict is a judgement about evidence rather than a projection of it.

The premise, first

No eval in this repository is currently failing. docs/eval-health.md reports five evals, all passing, none without a passing run. So "why is this eval failing" has an empty subject, and answering it as asked would have invented one.

The question that does have a subject is the inverse: four of those five green rows were, at least in part, the harness reporting on itself. That is what the table records.

Verdicts

Eval Verdict Evidence Identifier
smoke Harness, both directions — never a real zero, and the 1.00 was half unearned 3 of 6 scorer results vacuous on every committed run. Recomputed from committed reports alone, no LLM and no stack eval_b79741a3b76f, eval_ded3c264045d, eval_f72a74e9b76f, eval_37f775526af0
regression Harness, both directions — same defect at full dataset size 40 of 80 scorer results vacuous on every committed run eval_27bd19443ba1, eval_63f4029ec890
asb Real pass Deterministic, seeded, offline. Never calls load_suite, so the discarded-scorer defect cannot reach it evals/reports/asb-20260810.json
injection_defense Real pass Same: separate @app.command, no suite loading, no run_context dependency evals/reports/injection_defense-20260810.json
governed-benchmark Real pass Same evals/reports/governed-benchmark-20260810.json

Vacuous means the scorer returned a full score for having nothing to check — SchemaScorer on a task declaring no output_schema, ExpectedOutputMatch on a task declaring no output expectations. CompositeScorer folded score * weight for those exactly as it did for earned passes, so a skip at 1.0 and a genuine pass were arithmetically indistinguishable.

Reproduce the ratio:

python3 - <<'PY'
import json, subprocess
V = ("no schema validation required", "no output expectations declared")
for p in subprocess.run(["git","ls-files","evals/reports/"],
                        capture_output=True, text=True).stdout.split():
    d = json.load(open(p)) if p.endswith(".json") else {}
    r = [s for t in d.get("results", []) for s in (t.get("scorer_results") or [])]
    if r:
        vac = sum(1 for s in r
                  if s.get("skipped") or any(v in (s.get("message") or "").lower() for v in V))
        print(f"{p.split('/')[-1]:44} {len(r):3} results, {vac:3} vacuous")
PY

Why the two LLM suites, and not the other three

The defect fixed in 0.8.7 had one entry point: fd_evals.cli.get_default_scorers() was used for every run, so the scorers: and filter: blocks in evals/suites/*.yaml were parsed and thrown away, and three of the four substituted scorers read run_context keys only _execute_mock_run() ever populates.

That path is fd_evals run --suite …, which calls load_suite. Checked against the tree at the time of writing, load_suite is called from exactly one place (cli.py, inside run_eval). asb, injection-defense, governed-benchmark and enforce-vs-observe are separate @app.command entry points that never call it and never build a run_context. The defect is structurally unreachable from them — not merely absent today.

So the split above is not a survey of which evals happened to be affected. It is the boundary of the one code path that could be.

Residual risk

get_default_scorers() still exists and is still reachable. It is now a guarded fallback rather than an unconditional substitution: a suite's own scorers win when it declares any, and the fallback prints a warning naming itself. But two ways in remain.

  • A bare dataset run (fd_evals run <path> with no --suite) still gets the four default scorers.
  • A suite that declares an empty scorers: block falls back to them.

On the safe-PR dataset both cases score a genuine 0, not a skip: FilesChangedScorer skips only when the task declares no files_changed, and every task in that dataset declares one. So the run reports 0.0 for a reason that has nothing to do with the agent.

The warning is printed. Nothing fails. If this recurs, that is where to look first.

What no eval here measures

None of these measure whether the agent writes good pull requests. evals/datasets/safe-pr-agent/tasks.jsonl expects files changed, a PR opened and tests passing against example/project — a repository that does not exist and that this control plane never clones. Those expectations were unsatisfiable on the day the dataset was written.

The suites were rescoped to assert what the control plane can actually observe (governance path, budget compliance, output non-degeneracy) rather than tuned to make the old assertions pass. LoadedSuite.unasserted_expectations() names the dataset keys no declared scorer reads, and the runner prints them before every run, so the next dataset that quietly stops being tested says so instead of averaging past it.

Maintaining this page

Add a row when an eval is added. Revisit a row when a suite is rescoped, when a scorer changes what it reads, or when load_suite gains a second caller — that last one widens the blast radius of the defect above, and this page is where that should be noticed.