Layered evaluation for the Reaper skills, following Demystifying Evals
for AI Agents.
The model-based judge is the local claude CLI — no API key required.
| Layer | Grader | Cost | Cadence | What it covers |
|---|---|---|---|---|
| L1 Structural | Code (evals/graders/) |
Free | Every PR (CI) | Required sections present, min lengths, no broken refs, keep-or-discard cycle invariant |
| L2 Skill rubric | claude -p (evals/judge/) |
Subscription tokens | Locally / nightly | Per-skill quality dimensions (groundedness, specificity, completeness) |
| L3 End-to-end | Both | Subscription tokens | Pre-release | Full /reaper pipeline against the 3 cases in evals/evals.json |
evals/
evals.json # case registry (kept for human reference)
fixtures/<skill>/<case>/ # one directory per fixture
spec.yaml # variant declarations + expected layer outcomes
inputs/ # what the skill consumes (paper text, etc.)
reference/ # gold-standard output (must pass every layer)
negative-structural/ # planted L1 violation (drops a section, etc.)
negative-quality/ # planted L2 violation (fabricated claim, etc.)
rubrics/<skill>.yaml # which dimensions apply, and their pass thresholds
judge/
judge.py # claude CLI wrapper, JSON-schema enforced
schemas/rubric.json # per-dimension structured-output shape
prompts/<dimension>.md # one judge persona per rubric dimension
graders/
structural.py # L1 assertion helpers (pure Python)
consistency.py # cycle invariant verifier
run_evals.py # orchestrator
runs/ # per-trial workspaces (gitignored)
reports/ # md + json reports (gitignored)
# L1 only — same thing CI runs (no claude CLI required)
python3 -m evals.run_evals --layer structural
# L2 only — judges every variant of every fixture (uses claude CLI)
python3 -m evals.run_evals --layer judge --skill analyze-paper
# Full run (L1 + L2)
python3 -m evals.run_evals --layer all --skill analyze-paper
# One variant of one case
python3 -m evals.run_evals --layer all --skill analyze-paper --variant referenceThe orchestrator stages each variant into a fresh evals/runs/<run-id>/
directory before grading — per the eval guide's "isolated environments"
recommendation. Reports land in evals/reports/<run-id>.{md,json}.
The pytest entry point that CI uses lives at tests/test_skill_outputs.py
and exercises the same L1 graders.
- Create
evals/fixtures/<skill>/<case>/. - Put what the skill consumes under
inputs/(paper text, prior notes, etc.). - Hand-write a gold-standard output under
reference/. - Write at least one structural negative (drops a required section, etc.)
under
negative-structural/and one quality negative (fabricated claim, generic content) undernegative-quality/. One-sided evals create one-sided optimization; both directions matter. - Declare the variants in
spec.yaml(see the existingcryptography-samplefixture). Each negative carries atarget_layerso the orchestrator knows which grader is supposed to fail. - If this is a new skill, add an entry to
SKILL_STRUCTURAL_RULESinevals/run_evals.pyand a rubric file underevals/rubrics/.
tests/test_skill_outputs.py::test_every_fixture_skill_has_rules will fail
if you add a fixture without graders — coverage without graders is invisible.
- Drop a per-dimension prompt at
evals/judge/prompts/<dim>.md. Lead with the score scale, require a verbatimevidencequote, and include the"unknown"escape hatch (the schema enforces these fields, but the prompt has to ask for them clearly). - Add the dimension to the skill's rubric YAML, with a
passing_score. - Calibrate before relying on it: hand-grade ~10 transcripts, compare to
judge verdicts, iterate the prompt until ≥80% agreement. Keep the
calibration corpus under
evals/golden/.
To check whether a judge prompt agrees with expert opinion, run it against the gold reference and the planted negative for a fixture:
python3 -m evals.run_evals --layer judge --skill analyze-paper --variant reference
python3 -m evals.run_evals --layer judge --skill analyze-paper --variant qualityExpected: reference passes every dimension; the quality negative fails the
dimensions it's planted to violate (see expected_failures.judge in
spec.yaml). When they don't, fix the prompt, not the fixture.
- No API key in CI or in maintainer envs — uses each maintainer's local
claudeCLI auth (subscription orclaude setup-token). --allowedTools ""makes the judge a pure grader (no tool calls).--json-schemapins the output shape — prompt drift can't reshape the result.--no-session-persistence+ per-trial--add-dirkeep trials isolated.--model claude-opus-4-7is pinned, so judge drift is detectable when the model is bumped.