Skip to main content
skillevaluation is an open specification and reference runner for benchmarking an agent skill via declarative A/B test cases. The format lives next to SKILL.md as eval.yaml and answers a single question:
Does this skill actually help an agent? By how much?
The spec and runner are independent of DecimalAI — pip install "skillevaluation[runner]" executes a full A/B benchmark on your machine, on your own API key, with no account. DecimalAI is a conforming hosted runner of the same spec (it imports the same judge and validator code) and adds what a local run can’t: history, verified results, rankings, and distribution.

Package

skillevaluation on PyPI — Apache 2.0

Schema

JSON Schema for eval.yaml — ships in the wheel: load_schema("eval-yaml") (see Bring your own runner)

Why a separate spec

SKILL.md tells an agent how to do something. eval.yaml tells DecimalAI (or any conforming runner) how to measure whether it’s working. Keeping the two side-by-side on disk means:
  • Skill authors version eval cases with the skill itself
  • A skill pulled via decimalai skills pull brings its eval suite with it
  • A regression is detectable against the same cases that proved the skill worked

The 60-second format

Each case runs twice — once with the skill loaded into the agent’s manifest, once without. The runner classifies each case into one of five outcomes: The aggregate — the skill’s lift (the with-vs-without improvement) — is what becomes the registry headline, e.g. “+34 pts pass rate, −46% agent turns” (illustrative).

Assertion kinds

Each case can mix two assertion kinds:
  • expectations — natural-language claims, graded by an LLM judge
  • validators — shell commands, graded by exit code
A case MUST have at least one of either. Use validators when a precise structural check is possible (cheaper, deterministic); use expectations for genuinely semantic claims. (From spec 0.3.0 there is one exception: a trigger-only case — should_trigger with no graders — see below.)

Run it locally (free)

The open-source package ships a complete reference runner. Your own API key, your machine — nothing is sent to DecimalAI:
Each case executes twice (with the skill / without), both arms are graded with the same expectations + validators, and you get the delta table plus a results.json conforming to the open wire schema. The without-skill baseline is cached locally, so re-runs while you iterate on SKILL.md cost half. Gate it in CI with --fail-on-verdict fail --min-delta-pts 10, or dry-run the plumbing for free with --adapter mock. Local runs are unlimited and unmetered — iterate as much as you like.

--runs: average out the luck

Model behavior is probabilistic — one run per case measures luck. skillevaluation run … --runs 3 re-runs the whole suite three times, uniformly, and averages the per-case results by mean: each (case, run) execution enters the aggregate at equal weight, so the headline pass-rate is a mean whose expected value does not depend on the run count — more runs only narrow the error bars. Use it on any suite whose verdict you intend to act on. Repetition is a runner flag, not a per-case field: there is no way for one case to be weighted more heavily than another (that was the flaw in the old per-case trials/pass^k knob, retired in 0.6.0 — see ADR-0007). Note the collapse is real: results.json carries one representative record per case, not a record per (case, run) — the aggregate rates are means over every run (the document’s runs field discloses the count), but per-run records with a run index are not emitted. If you need run-by-run transcripts to inspect flakiness case by case, execute the suite once per run instead.

Spec 0.3.0 additions

Available since skillevaluation 0.3.0 on PyPI — every current release has them (PyPI is on the 0.7.x line). These are additive: existing suites parse unchanged. The pin only matters if your environment somehow resolves a strict 0.2.x parser, which rejects suites using the new fields.
Four version axes, one project — don’t conflate them. (1) The package version on PyPI (currently 0.7.x) — what pip install resolves. (2) The spec feature line (“spec 0.3.0”) — which eval.yaml fields exist, per this section. (3) The schema revision (“schema rev 2”, shipped in 0.6.0) — the shape of the results document. (4) The wire format (test-run-result@v1) — the versioned name a results document declares when pushed. A sentence like “requires 0.3.0” is about the spec line, not the package.

Trigger cases: should_trigger

Graded cases prove a skill helps when it’s loaded. Trigger cases prove it loads at the right times — the failure mode graded cases can’t see. Mark a case with should_trigger:
A case with should_trigger and no expectations/validators is a trigger-only case (exempt from the at-least-one-grader rule); a case can also carry both and be graded for lift and trigger. What the open-source runner does with them: it records each should_trigger case and discloses cases_skipped_trigger_only in the results document — trigger-only cases carry no lift evidence, so they’re kept out of the A/B divisor. The runner does not score trigger accuracy locally. Measuring trigger accuracy is a hosted feature. The menu-selection simulation — build a skill menu (your name + description alongside distractor rows), ask the model per prompt which skill it would use, and roll up menu_selection_rate (should-fire recall) + false_fire_rate (should-NOT-fire) — runs on the DecimalAI hosted runner, together with router_recall (the retrieval stage against the live skill index, which can only be measured server-side). This moved out of the open-source runner in 0.6.0 (ADR-0007): the OSS spec keeps the should_trigger boolean, the trigger-only grader exemption, and the cases_skipped_trigger_only disclosure; the simulation contract is platform policy. When trigger cases fail on the hosted runner, fix the description, not the body — the description is what both readers (retrieval and menu) see. See Authoring Skills.

Error-dominated runs: no headline from an outage

When more than 25% of a run’s cases errored (a provider outage, a rate-limit storm), the result is stamped error_dominated: true and the headline pass-rate delta is nulled. A lift number computed from the few surviving cases isn’t a measurement — re-run instead of shipping it. The hosted runner applies the same floor, so a local run and a verified run can never disagree about what counts as valid.

setup.files: declarative workspace files

Cases that need files in the workspace can declare them directly, instead of echo-ing them via shell commands:
Files are written before any setup command runs. The legacy list-of-commands setup: form still parses.

Push results to DecimalAI

Attach a local run to your skill’s Benchmark tab (free — it’s a JSON upload, no quota consumed). This is the one step in this guide that crosses the account boundary: everything above runs with no account, but pushing needs the decimalai package, an API key, and — order matters — a skills sync first, so the platform knows the skill and its eval cases. Pushing results for a skill the platform has never seen fails with an error telling you to sync it first:
Pushed runs are tagged unverified: they show on your skill’s own page but never feed registry rankings — self-reported numbers can’t poison the leaderboard.

Verified runs (hosted)

A verified run is one the DecimalAI runner executed — same open-spec judge and validators (the platform literally imports them from the skillevaluation package), but in a trusted environment, stamped with model + date. Only verified runs feed registry cards, rankings, and SkillScore. Two ways to get one:
…or publish the skill — publishing automatically triggers a verification run, free and quota-exempt. Hosted runs are metered in cases (one metered case = one eval case executed, both arms + judge included; errored cases refunded). See Pricing → Quota enforcement.

Bring your own runner

The full runner contract (spec/runner-contract.md) and the golden compatibility-tests/ fixtures ship inside the skillevaluation package. The JSON Schemas load by short name — the four valid names are eval-yaml, test-run-result, test-case-result, and judge-result (enumerate them with list_schemas()):
Any implementation that reproduces the golden fixtures is conforming; the reference runner itself passes the suite — compare against it.

Composing with agentversion

A skillevaluation run produces a numeric score. That score can be recorded on an AgentVersion manifest’s evaluation.gates[] via the skillevaluation:// URI scheme:
Each gate object carries the fields below:
string
required
Human-readable identifier for the gate, e.g. skillevaluation:gdpr-pii-classifier.
number
required
The score the run produced, 0.01.0 (pass rate of the with-skill arm).
number
required
The minimum actual_score required for the gate to pass.
boolean
required
Whether actual_score met threshold.
string
required
A skillevaluation:// URI pinning the exact eval suite + version that produced the score, e.g. skillevaluation://abc123def456@v0.1.0.
string
ISO 8601 timestamp of when the run completed.
This lets a lifecycle transition cite a specific eval suite’s verdict as evidence — “this manifest reached production because the gdpr-pii-classifier benchmark passed at 92%.”

Status

  • The package is pre-stable (breaking changes possible before v1.0). By spec/feature line: the 0.2 releases added the reference runner + CLI; 0.3.0 shipped spec 0.3.0 (trigger cases, the error-dominated floor, setup.files — see above); 0.4.0 added skillevaluation.safety — the same deterministic static scanner behind the registry’s Tier-1 safety gate — and the skillevaluation scan CLI (text / JSON / SARIF output); 0.6.0 is schema rev 2 (ADR-0007): one execution contract, per-case mode/trials/simulator/policy_check removed, and runner-level --runs N (mean-averaged) replacing pass^k. All of these are on PyPI; check the package changelog for the current release. The wheel ships spec/ + schemas/ (from skillevaluation.resources import load_schema).
  • DecimalAI’s hosted runner consumes the same package — judge and validator behavior is shared by construction, not by copy
  • Conformance suite + JSON Schemas ship inside the skillevaluation package (from skillevaluation.resources import load_schema)
  • Want a different language implementation? The package’s CONFORMANCE.md + golden in/out fixtures define conformance — anything that reproduces them conforms