Evaluating the harness
A harness nobody measures is decoration. AgentRig ships three layers of evaluation, each making a bounded claim. Don't over-read what any one of them proves.
| Layer | Proves | Does NOT prove | Cost |
|---|---|---|---|
| A1 — install completeness | every canonical artifact present where the manifest declares | the artifacts work, or that agents respect them | ~1 second, no model |
| A2 — quality probes | content sanity (parseable YAML/JSON, no unfilled {{PLACEHOLDER}}, distinct model families, every skill has frontmatter, axes have issue codes) |
semantic quality of the content | ~1 second, no model |
| B — dynamic behavioral eval | how the harness changes agent behavior on fixed fixtures, with deterministic oracles for hard axes + an independent judge for soft axes, with paired sign-test lift vs a baseline | absolute "is this agent good" — only relative to baseline | minutes to hours, real model spend |
All three persist results under .agentrig/eval/results/ via score.mjs. Results are
validated on read AND on write (schemaVersion: 2) — never hand-edit JSON; invalid records
are quarantined into results/_legacy/.
New here? Generate scenarios for your repo first. Layer B needs scenarios, and the bundled ones are generic JS templates.
agentrig eval --scaffoldreads the investigationinitwrote (.agentrig/context.md) and writes fixture-based scenarios that use your repo's real test runner, package manager, and defect patterns — see Per-repo customization below.
Layer A1 + A2 — static audit#
Deterministic. Dependency-free. Safe in CI.
agentrig eval --static # human report, A1 and A2 in separate sections
agentrig eval --static --json # machine-readable
agentrig eval --static --min 80 # fail CI if Install Completeness < 80%Or the dep-free script directly (handy in fresh worktrees):
node .agentrig/eval/static-audit.mjsSample output:
AgentRig — install completeness audit
what this proves: every canonical artifact is present and minimally well-formed.
what this does NOT prove: that those artifacts work, or that agents respect them.
Layer A1 — structural completeness
[PASS] P1 Workflow is an explicit, connected state machine (DAG with queued→merged path)
[PASS] P2 Multiple specialized agent roles installed
…
Layer A2 — quality probes
[PASS] P2 Developer and reviewer use DIFFERENT model families (not just different ids)
[PASS] P4 Every skill declares description + allowed-tools (not just self-verify)
[PASS] P6 axes.json has at least one issue code per axis
[PASS] P12 AGENTS.md has no unfilled {{PLACEHOLDER}} tokens
…
Install Completeness: 100% (33/33 full credit)
Quality Probes: 100% (6/6 full credit)CI gates on Install Completeness with --min. Quality Probes are diagnostic — they
surface drift without failing the build.
Layer B — dynamic behavioral eval#
For each scenario in .agentrig/eval/scenarios/<id>/:
- Seed a throwaway worktree from
scenarios/<id>/fixture/. - Producer model runs
prompt.mdin the worktree. For--variant harnessthe AgentRig harness is staged into the worktree first; for--variant baselinethe agent runs bare. - Oracle (
scenarios/<id>/oracle.yml) deterministically scores hard axes (correctness, tests, scope, regression_risk, …) by running commands and inspecting the diff. No LLM. - Judge model — a different family from the producer — runs in a separate
provider.startConversation()call in its own cwd containing onlyprompt.md,diff.patch,transcript.md,oracle.json, andjudge_brief.md. It does NOT see the producer worktree or reasoning trace. It writes scores to a JSON file the orchestrator validates againstaxes.json.
Bare invocation#
agentrig eval --dynamic resolves models from .agentrig/agents/developer.yml (producer) and
.agentrig/agents/reviewer.yml (judge) by default — the roster already enforces these come from
different model families via the install-completeness audit. So the common path is just:
agentrig eval --dynamicOverride either model explicitly when running a one-off experiment:
agentrig eval --dynamic \
--producer-model claude-sonnet-4.6 \
--judge-model gpt-5.5Or via env vars (handy in CI scripts):
AGENTRIG_PRODUCER_MODEL=claude-sonnet-4.6 \
AGENTRIG_JUDGE_MODEL=gpt-5.5 \
agentrig eval --dynamicResolution chain (highest precedence first):
--producer-model/--judge-modelCLI flagsAGENTRIG_PRODUCER_MODEL/AGENTRIG_JUDGE_MODELenv vars--model(legacy back-compat — only sets the producer).agentrig/agents/developer.ymlmodel (producer) /.agentrig/agents/reviewer.ymlmodel (judge)- Provider default
The chosen model + source is logged at the start of the run and recorded in meta.json.
Useful flags#
| Flag | Purpose |
|---|---|
--scenario <id> |
Run one scenario (fix-failing-test / add-small-feature / review-catches-bug) |
--variant <name> |
Tag the run (harness vs baseline for A/B lift) |
--producer-model <id> |
Model that runs the task |
--judge-model <id> |
Model that scores soft axes — MUST be a different family |
--allow-same-family |
Override the family check (recorded in every result) |
--n <int> |
Trials per scenario (default 1 single; default 5 in baseline mode) |
--seed <int> |
Reproducibility seed (passed through where supported) |
--timeout <min> |
Absolute cap per agent turn (default 45) |
Rubric rules (enforced by score.mjs)#
- Strict 3-tier scores:
0/0.5/1.0. - Issue code required. Any axis < 1.0 with
confidence > 0must carry an issue code from that axis's bounded registry plus a one-line evidence string. - Confidence-gated. An axis you couldn't observe is
=na(confidence 0) and excluded from rollups. - Weighted aggregation. Axes carry an optional
weight(default 1) andveto: true. The aggregate is a weighted mean of observed axes. - Pass rule:
aggregate ≥ passThresholdAND no observed axis at 0 AND no veto axis < 1.0. Veto fails are surfaced infailReason.
Lifecycle types#
--type |
Categories | Veto axes |
|---|---|---|
spec |
spec_quality (5 axes) | acceptance_criteria |
run |
output_quality, agent_behavior, long_term_impact (10 axes) | correctness, gate_compliance |
review |
review_quality (7 axes) | finding_correctness, blocking_decision |
Inspect the live rubric in your repo:
agentrig eval --rubricStatistical lift — does the harness actually help?#
Single-trial deltas are coin flips. The eval requires n ≥ 3 paired trials for any verdict
other than INCONCLUSIVE. Pattern:
agentrig eval --dynamic --variant harness --n 5 \
--producer-model claude-sonnet-4.6 --judge-model gpt-5.5
agentrig eval --dynamic --variant baseline --n 5 \
--producer-model claude-sonnet-4.6 --judge-model gpt-5.5
node .agentrig/eval/score.mjs compare --scenario <id> --baseline baselinecompare --baseline pairs trial i of harness with trial i of baseline, computes the
median delta and a binomial sign-test p-value, and prints one of three verdicts:
- HELPS — p < 0.05 and median > 0.05
- HURTS — p < 0.05 and median < -0.05
- INCONCLUSIVE — n < 3, p ≥ 0.05, or |median| < 0.05
A HELPS verdict on a real fixture, in a different model family than the judge, is the only thing that justifies the line "AgentRig improved agent behavior here." Anything less is honest inconclusive.
Calibrating the judge#
A judge that returns 1.0 on every axis passes every save validation but tells you nothing.
.agentrig/eval/calibration/ ships hand-labeled rubric instances (scenario inputs + transcript
- diff + ground-truth axes). Two-step flow:
# 1. Have your judge model score one of the seed instances and write
# {"axes":[{"name":"...","score":1.0,"confidence":1}, …]} to /tmp/judge-out.json.
# 2. Score the judge against ground truth.
node .agentrig/eval/score.mjs calibrate \
--judge gpt-5.5 \
--instance .agentrig/eval/calibration/run/seed-correct.yml \
--judge-scores /tmp/judge-out.json
# Roll up across instances.
node .agentrig/eval/score.mjs calibrate --reportagentrig doctor reads the rollup and fails (exit 1) on any judge averaging below 80%
agreement within ±0.5 tier of the hand-labeled truth. See
.agentrig/eval/calibration/README.md
for the instance format and how to grow the calibration set.
Per-repo customization#
Everything under .agentrig/eval/ is editable:
- Scenarios —
.agentrig/eval/scenarios/<id>/(drop in your own fixture + oracle.yml) - Axes —
.agentrig/eval/axes.json(add domain-specific axes likeaccessibility_compliance) - Checks —
.agentrig/eval/checks.json(tighten or expand the structural audit) - Calibration —
.agentrig/eval/calibration/<type>/*.yml
Bundled vs repo-specific scenarios#
Scenarios that ship with agentrig init (currently add-small-feature, fix-failing-test,
review-catches-bug) are marked bundled: true in their scenario.yml. eval --dynamic
excludes them from the default run — you want signal about your repo's stack and conventions,
not generic JS micro-fixtures.
agentrig eval --dynamic # only repo-specific scenarios (default)
agentrig eval --dynamic --include-bundled # also run the bundled template scenarios
agentrig eval --dynamic --scenario fix-failing-test # explicit ids always run, bundled or notIf you've never run --scaffold, the default run will be empty and the CLI will tell you so:
No repo-specific scenarios found.
Generate some with: agentrig eval --scaffold
Or run the bundled template scenarios anyway: agentrig eval --dynamic --include-bundledGenerating repo-specific scenarios (eval --scaffold)#
The 3 bundled scenarios are language-agnostic JS micro-fixtures — useful as templates, but they don't exercise your repo's test runner, package manager, or common defect patterns. Run:
agentrig eval --scaffold # generate 2 repo-tailored scenarios (default)
agentrig eval --scaffold --scaffold-count 3
agentrig eval --scaffold --timeout 10 # raise the per-agent-turn cap (default 45min)--scaffold reads .agentrig/context.md (the investigation init wrote about your repo) +
the 3 generic scenarios as templates, then asks the agent to generate N new scenarios under
.agentrig/eval/scenarios/<id>/ that use your repo's actual stack. Each generated scenario is
validated against axes.json; invalid output is flagged but not silently deleted, so you can
inspect + fix it by hand.
The agent is constrained to:
- Use only the axis names already in
axes.json(no inventing axes) - Use your repo's actual test runner / package manager
- Keep the fixture ≤10 files
- Touch only
.agentrig/eval/scenarios/ - Omit
bundled: trueso the new scenarios run by default
Confirm what's actually measured with agentrig eval --rubric.
When to run what#
| When | What |
|---|---|
| Every PR | Layer A1 + A2 via eval --static (CI gate at --min 80 or higher) |
Nightly on main |
Layer B with --n 5 × harness and baseline, then compare --baseline baseline |
| Before publishing AgentRig itself | score.mjs calibrate --report ≥ 80% agreement for the default judge |
| When prompts/skills/rules change | Manual eval --dynamic --variant harness-v2 --n 5 + compare against the previous harness variant |
CI#
The dynamic eval is too expensive for every PR. Recommended split:
# ci.yml — every PR (cheap, deterministic, no model)
- run: npx -y @doidor/agentrig@latest eval --static --min 100
# agentrig-eval.yml — nightly on main + manual dispatch (real model spend)
on:
schedule: [{ cron: "17 6 * * *" }]
workflow_dispatch:The full nightly workflow ships at
.github/workflows/agentrig-eval.yml
— it runs both variants with n=5, computes lift, uploads JSON artifacts, and posts/updates a
tracking issue with the verdict table.