Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Test and evaluation harness for AI agents — scenario suites, deterministic replay, regression diffing, cost and latency budgets. Use when agent quality is vibe-checked, before shipping a prompt or model change, or when evals drift.
.claude/skills/borghei-agent-harness/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-09 | ✗→✓ | ▲ Improved | 139% | 0% |
| case-13 | ✗→✓ | ▲ Improved | -2% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 81% | 0% |
| case-15 | ✗→✓ | ▲ Improved | 105% | 0% |
| case-16 | ✗→✓ | ▲ Improved | 60% | 0% |
Most agents ship on vibes: someone tries eight prompts, the output looks good, it goes to production, and the next prompt tweak silently breaks a refusal nobody re-tested. This skill builds the harness around an agent so its behaviour becomes measurable — scenario suites with structural assertions, deterministic replay of recorded tool calls, paired regression diffing across prompt and model changes, and per-scenario cost and latency budgets. The tools here score an agent; they never invoke one, so they run offline on every commit.
Before building the harness, confirm these inputs. If any is unknown or vague, ASK — do not assume:
tool_not_called assertions at critical severity, and what the release gate blocks onStop rule: ask only the 2-3 that most change the output. If the user says "just draft it," proceed and list your assumptions at the top of the artifact.
adversarial, failure-recovery, ambiguity) using assets/scenario_authoring_checklist.md. Structural assertions first — tool called / not called / order / arguments — text assertions only on domain tokens.
defaults for latency, cost, and turn ceilings so everyscenario is budgeted without repeating yourself.
the run with model and prompt_sha.
bashpython3 engineering/agent-harness/scripts/scenario_runner.py \ --suite engineering/agent-harness/assets/sample_suite.json \ --transcripts engineering/agent-harness/assets/sample_transcripts_baseline.json \ --strict-critical
as JSON reports.
(re-run the flipped scenario five times to tell the last two apart).
assets/eval_report_template.md and promote theaccepted candidate report to the new baseline.
bashpython3 engineering/agent-harness/scripts/scenario_runner.py \ --suite engineering/agent-harness/assets/sample_suite.json \ --transcripts engineering/agent-harness/assets/sample_transcripts_candidate.json \ --format json > /tmp/candidate.report.json python3 engineering/agent-harness/scripts/eval_diff.py \ --baseline engineering/agent-harness/assets/sample_baseline_report.json \ --candidate /tmp/candidate.report.json \ --fail-on-regression --drift-threshold 0.15
The shipped sample data demonstrates the core lesson: both runs score 83.3%, and the candidate contains a critical prompt-injection regression. A gate on pass rate ships it; the paired diff catches it.
ceiling at observed max + 2. Put them in the suite defaults, overriding only where a scenario is legitimately expensive.
minor assertions, sothey report without blocking.
bleed that stays inside budget.
bashpython3 engineering/agent-harness/scripts/eval_diff.py \ --baseline engineering/agent-harness/assets/sample_baseline_report.json \ --candidate engineering/agent-harness/assets/sample_candidate_report.json \ --drift-threshold 0.10 --format json
| Need | Use | Durability | |------|-----|------------| | The agent must take an action | tool_called, tool_call_order | PROVEN] Exact; survives rewording | | The agent must NOT take an action | tool_not_called | PROVEN] The single highest-value assertion in any agent suite | | The action must use the right data | tool_arg_equals | PROVEN] Catches the right tool with wrong arguments | | Structured output correctness | json_field_equals | PROVEN] Exact when the agent has a JSON mode | | A required domain fact appears | output_contains on an ID, number, or policy name | RECOMMENDED] Stable if you never quote sentences | | A forbidden phrase must not appear | output_not_contains | RECOMMENDED] Good for injection and leak checks | | Tone, helpfulness, faithfulness | Model-graded rubric (outside this harness) | EXPERIMENTAL] Noisy and drifts with the judge; calibrate against human labels first, and never gate on it alone |
| Severity | Covers | Gate | |----------|--------|------| | critical | Safety, money movement, data loss, refusals that must hold | Blocks on a single failure (--strict-critical) | | major | Task correctness — the user did not get what they asked for | Blocks below the pass-rate floor (--fail-under) | | minor | Budgets, verbosity, style | Reported; never blocks |
| Discordant scenarios (flipped either way) | Read it as | |-------------------------------------------|------------| | 0 | No behavioural change detected at this suite's resolution | | 1-5 | Read the individual scenarios; the p-value has no power here | | 6-24 | Exact McNemar p is meaningful; eval_diff.py reports it | | 25+ | Both the p-value and the aggregate rate movement are informative |
A single critical regression is actionable at n = 1. Significance testing is for aggregate movement, never for safety failures.
Mistake: The release check is "pass rate ≥ 90%," and everything else is advisory. Why it happens: One number is easy to put in a dashboard and easy to explain to leadership, and it genuinely looks like the summary statistic. Instead: Gate on critical-severity failures and on the paired per-scenario diff. The pass rate is the last number you read, always with its confidence interval — at 30 scenarios that interval is ±13 points, which cannot resolve the regressions you care about. The sample data here shows two runs at an identical 83.3% where one refunds money on an injected instruction.
Mistake: output_contains: "I've issued your refund of $49.00 and it should arrive in 3-5 business days". Why it happens: It is the fastest thing to do — copy the good output into the assertion and move on. Instead: Assert on the tool call (issue_refund with order_id=A-10041) and on a domain token in the text ("refund", the order ID). Structural assertions do not break when the model rewords, so the suite keeps signal across model upgrades instead of generating a wall of false failures that trains the team to ignore it.
Mistake: Every scenario is a happy path; the suite has no tool_not_called assertions. Why it happens: Suites get written from the product spec, and specs describe intended behaviour, not forbidden behaviour. Instead: For every irreversible action the agent can take, write a scenario where taking it is wrong. Refusal and adversarial scenarios are where prompt changes actually regress, because a change that makes an agent more capable usually makes it more eager. Target roughly 35% of the suite across refusal and adversarial buckets.
Mistake: Iterating on the prompt with the full suite visible until every scenario passes. Why it happens: It feels like the tight feedback loop that good engineering is supposed to have. Instead: Hold out 20% of scenarios and never look at them while iterating; run them only at the gate. Thirty scenarios is a small enough surface to overfit in an afternoon, producing an agent that passes the suite and fails users.
Mistake: Four scenarios flip after a prompt edit, so the team spends two days finding the cause. Why it happens: Nobody ever ran the identical configuration twice, so run-to-run variance is unmeasured and every flip looks causal. Instead: Before trusting any diff, score the same configuration twice and diff it against itself. That flip count is your noise floor. Then reduce it — temperature 0 where the product allows, replayed tool results rather than live backends, and re-runs of flipped scenarios to separate flaky from real.
| File | Purpose | |------|---------| | scripts/scenario_runner.py | Runs a JSON scenario suite against recorded transcripts; reports pass/fail per assertion with severity, budget checks, and CI exit codes | | scripts/eval_diff.py | Diffs two runs into regressed/fixed/stable, with Wilson intervals, exact McNemar on discordant pairs, and cost/latency drift | | references/scenario-and-fixture-design.md | The six scenario buckets, replay modes, fixture recording rules, assertion tiers, suite sizing | | references/eval-methodology-and-budgets.md | Scoring layers, small-sample statistics, budget setting, CI wiring, methodology anti-patterns | | assets/sample_suite.json | Six-scenario support-agent suite covering all assertion types | | assets/sample_transcripts_baseline.json | Recorded baseline run | | assets/sample_transcripts_candidate.json | Recorded candidate run containing a critical regression at an unchanged pass rate | | assets/sample_baseline_report.json | Scored baseline report — input for eval_diff.py | | assets/sample_candidate_report.json | Scored candidate report — input for eval_diff.py | | assets/eval_report_template.md | Release-decision report template | | assets/scenario_authoring_checklist.md | Pre-merge checklist for any scenario joining a gating suite |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-17 | pass→pass | 13,161 | 11,804 | -10% | 1 | 1 | 0% | 2,112 | 4,757 | +125% | 0 | 0 | — |
case-01 | fail→fail | 54,893 | 20,695 | -62% | 1 | 1 | 0% | 1,551 | 6,524 | +321% | 0 | 0 | — |
case-02 | fail→fail | 27,373 | 36,972 | +35% | 1 | 1 | 0% | 4,544 | 9,029 | +99% | 0 | 0 | — |
case-03 | fail→fail | 24,328 | 35,722 | +47% | 1 | 1 | 0% | 4,573 | 9,017 | +97% | 0 | 0 | — |
case-04 | fail→fail | 19,725 | 19,355 | -2% | 1 | 1 | 0% | 3,696 | 6,489 | +76% | 0 | 0 | — |
case-05 | fail→fail | 20,333 | 17,848 | -12% | 1 | 1 | 0% | 3,908 | 6,015 | +54% | 0 | 0 | — |
case-06 | pass→pass | 9,960 | 16,093 | +62% | 1 | 1 | 0% | 1,615 | 5,760 | +257% | 0 | 0 | — |
case-07 | pass→pass | 8,189 | 5,564 | -32% | 1 | 1 | 0% | 1,419 | 3,660 | +158% | 0 | 0 | — |
case-08 | pass→pass | 7,570 | 5,230 | -31% | 1 | 1 | 0% | 1,141 | 3,618 | +217% | 0 | 0 | — |
case-09 | fail→pass | 9,201 | 4,361 | -53% | 1 | 1 | 0% | 1,483 | 3,548 | +139% | 0 | 0 | — |
case-10 | pass→pass | 12,316 | 5,165 | -58% | 1 | 1 | 0% | 1,952 | 3,686 | +89% | 0 | 0 | — |
case-11 | pass→pass | 10,478 | 4,943 | -53% | 1 | 1 | 0% | 1,561 | 3,643 | +133% | 0 | 0 | — |
case-12 | pass→pass | 11,514 | 4,764 | -59% | 1 | 1 | 0% | 1,774 | 3,513 | +98% | 0 | 0 | — |
case-13 | fail→pass | 21,243 | 3,540 | -83% | 1 | 1 | 0% | 3,430 | 3,373 | -2% | 0 | 0 | — |
case-14 | fail→pass | 11,608 | 3,396 | -71% | 1 | 1 | 0% | 1,895 | 3,429 | +81% | 0 | 0 | — |
case-15 | fail→pass | 12,045 | 9,265 | -23% | 1 | 1 | 0% | 2,187 | 4,474 | +105% | 0 | 0 | — |
case-16 | fail→pass | 14,109 | 5,736 | -59% | 1 | 1 | 0% | 2,384 | 3,824 | +60% | 0 | 0 | — |
case-18 | pass→pass | 13,292 | 10,523 | -21% | 1 | 1 | 0% | 2,120 | 4,502 | +112% | 0 | 0 | — |
case-19 | pass→pass | 12,942 | 13,063 | +1% | 1 | 1 | 0% | 2,078 | 4,677 | +125% | 0 | 0 | — |
case-20 | pass→pass | 7,846 | 4,098 | -48% | 1 | 1 | 0% | 1,180 | 3,433 | +191% | 0 | 0 | — |
case-21 | fail→pass | 14,519 | 7,193 | -50% | 1 | 1 | 0% | 2,331 | 3,937 | +69% | 0 | 0 | — |
case-22 | fail→pass | 13,495 | 8,828 | -35% | 1 | 1 | 0% | 2,237 | 4,171 | +86% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 21 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +32 percentage points is the difference between those two pass rates over the 21 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.