Audience: the engineer who inherits this system when its author is gone.
Promise: everything you need to run it daily is on this page; everything
you need to debug it is one link away. Test: this exact command sequence
is executed by the clean-room release validator (python
scripts/validate_release.py) on every release — so the runbook cannot silently
rot. That is the field guide’s bar: “a runbook that someone other than you
has actually executed.”
If you are reading this fresh: start with make demo (90 seconds, shows the
gate catching an injected regression), then make status, then come back.
A CI gate for AI agents. Three teams ship agents; this platform runs each agent against a frozen set of golden cases (280 of them, hash-pinned), scores the outcome and the behavior, compares against committed baselines, and fails the merge when quality drops more than the team’s own threshold says. It is “pytest, but the unit under test is an entire agent.”
evals/runs/.evals/baselines/ (currently
main, react_basic).configs/gates.yaml thresholds; exit 1 = regression.| When | Who | Command | Expected |
|---|---|---|---|
| Every merge (CI) | GitHub Actions | make test-smoke && make gate |
exit 0 |
| Nightly (CI) | GitHub Actions | make eval-full && make gate + full suite |
exit 0 |
| Monday | ops | make status |
exit 0; anything else → §4 |
| Visual audit / PR triage | team lead | make serve (or agent-eval serve) |
browser UI at http://127.0.0.1:8000 |
| Containerized run | devops / CI | docker compose up dashboard (or make docker-run) |
containerized runner & UI |
| On incident | on-call | make demo (re-fires the path you’re about to debug) |
1.3 s |
| After dataset change | agent lead | make validate |
VALID: all benchmarks |
| Quarterly | platform | make experiments → review docs/final-report.md deltas |
all metrics within gates |
Benchmarks on the deterministic backend, seed 20260912, skill 0.85
(reproduce: agent-eval run --benchmark <name>):
| Benchmark | Pass rate | 95% CI |
|---|---|---|
| react_basic | 89.3% (50/56) | [78.5%, 95.0%] |
| supervisor_basic | 80.4% (45/56) | [68.2%, 88.7%] |
| swarm_basic | 82.1% (46/56) | [70.2%, 90.0%] |
| map_reduce_basic | 76.8% (43/56) | [64.2%, 85.9%] |
| plan_execute_basic | 73.2% (41/56) | [60.4%, 83.0%] — the weak one; known, not hidden |
Judge calibration: κ = 1.000 (rubric v1.1, n=56). Same-seed repro: 0.0pp. Drift beyond ±2pp on the same seed+skill is not noise — investigate.
make gate exit 1)agent-eval compare <baseline_run> <new_run> — the flipped-case list is
your bisect starting point. McNemar p < 0.05 means the drop is real,
not noise.adversarial/ambiguous categories first: suspect
prompt changes that traded robustness for happy-path quality.configs/gates.yaml in the same PR —
that file is the agreement; threshold changes go through the owning team’s
review, alone, with a justification comment.make status → find the benchmark with REGRESSION verdict.agent-eval run --benchmark <bm> and compare. Same
result → real. Different result → you have a determinism bug; file it as
INFRASTRUCTURE (see INCIDENTS.md #2 for how process-salted seeds did
exactly this once).agent-eval status exits 1The REGRESSION row names the benchmark; run
agent-eval regression <run_id> --baseline main for the full gate table.
If the delta is −1pp to −3pp (watch), do nothing dramatic: note it, watch
the next run. −3pp and beyond is the alarm.
make validate)The generator or the golden files drifted. Never hand-edit
datasets/*/golden.jsonl — regenerate with
python scripts/generate_datasets.py and review the diff; the files are
supposed to be reproducible byte-for-byte. If they are not, that is a bug
(see INCIDENTS.md #2).
make calibrate)κ < 0.6 means judge verdicts must stop gating. The rubric version and prompt
version are recorded per run; a κ drop after a prompt-version bump is a
prompt regression — revert the bump (prompt files are versioned in
prompts/). A κ drop with no version change means the cases drifted out of
distribution — that is the signal to refresh hand labels.
The deterministic backend runs in seconds; if the stage is slow, something
is calling the network (it shouldn’t — the tool env is sandboxed) or the
case count grew. Check network_guard_attempts in the run record first;
then agent-eval list datasets for case-count growth.
evals/baselines/main.json covers
react_basic only; other benchmarks gate on their own threshold tables
(configs/gates.yaml) but have no named baseline run yet. Creating one:
agent-eval run --benchmark <bm> --baseline main2.evals/runs/ or vary the seed. (This bit the demo script once:
INCIDENTS.md #8.)DECISIONS.md D-05), not a bug; it has
costs on tiny sample runs.cp1252; agent-eval
automatically reconfigures stdout to UTF-8 (INCIDENTS.md #9). If calling via custom
scripts or subprocesses, ensure PYTHONIOENCODING="utf-8" is set.INCIDENTS.md #10). The repo enforces LF via .gitattributes
and performs byte-level newline normalization during verification.| Thing | Path |
|---|---|
| Golden datasets (hash-pinned) | datasets/<pattern>/golden.jsonl |
| Benchmark registry (YAML) | benchmarks/*.yaml |
| Gate thresholds (the agreement) | configs/gates.yaml |
| Run records + event logs | evals/runs/<run_id>.{json,events.jsonl} |
| Baselines | evals/baselines/ |
| Web Dashboard (zero-dep HTTP server) | src/agent_eval_harness/web/ (agent-eval serve) |
| Container configuration | Dockerfile, docker-compose.yml, .dockerignore |
| Experiment results (all measured numbers) | evals/results/experiments.json |
| Hand labels (judge calibration) | evals/calibration/ |
| Judge prompts (versioned) | prompts/ |
| Decision records | DECISIONS.md |
| Incident log | INCIDENTS.md |
| All measured results + honesty annex | docs/final-report.md |
| Engagement record (brief→discovery→spec→bar) | docs/fde/ |
[runbook §4.x] ... so the runbook section gets fixed with the fix.INCIDENTS.md #2 for
the precedent.SECURITY.md escalation path, not a public issue.This runbook is itself versioned; if operating reality diverges from this page, the page loses — update it in the same commit as the fix.