feat(evals): three-tier skill eval framework (trigger, routing, behavioral)#342
Conversation
There was no way to measure whether skills trigger correctly, stay distinct, or change agent behavior. This adds evals, aligned with what the community has converged on, with a deterministic CI tier on top: - evals/cases/<skill>.json for all 24 skills. The evals[] block uses Anthropic skill-creator's evals.json schema verbatim (id, prompt, expected_output, expectations[]) so its runner, benchmarks, and eval viewer work against our files unmodified. A trigger block (this repo's extension) adds positive/negative routing prompts per skill. - scripts/run-evals.js, zero-dependency runner: Tier 2 (CI): trigger evals via stemmed TF-IDF ranking over skill descriptions (positive prompts must rank top-k, negative prompts must not rank first), catalog collision detection between skill descriptions, schema and coverage checks. Tier 3 (opt-in): --behavioral <skill> executes each eval through headless claude -p and grades the transcript against expectations[] (superpowers-style); --dry-run previews without spending tokens. - CI: run the deterministic tier in the validate-skills job. - Docs: evals/README.md defines the framework and prior art; CONTRIBUTING requires an eval file for new skills (warning-level in the runner until in-flight skill PRs clear); CLAUDE.md pointers. Current baseline: 120 checks pass, 85% trigger rank-1 rate across 72 positive prompts, zero catalog collisions.
|
Hi @addyosmani! Really strong PR. I pulled the branch locally and verified:
A few thoughts, mostly around making Tier 3 trustworthy before we lean on it heavily:
None of this blocks the deterministic tier, which is the part running in CI and looks solid. This gives the repo a real measurement spine; I’d just be careful to make Tier 3 measure actual behavior rather than polished self-reporting before using it as evidence for pressure tests. |
|
Agree with @federicobartoli’s feedback; the deterministic tier is solid and mergeable as-is; Tier 3 needs trace + real fixtures before it’s used as evidence for anything beyond “sanity check.” A few additions: Blocking before relying on Tier 3 results:
Tracking, not blocking:
None of this blocks the deterministic tier — happy to see that part land now with Tier 3 hardening tracked as explicit follow-ups rather than open-ended future work. |
|
Verified locally (on top of Federico's checks): added a throwaway 25th skill (
No hardcoded skill count anywhere in the TF-IDF corpus build, coverage check, or collision scan — confirmed by reading Side note (not a bug, just worth flagging): Removed the dummy skill/case file after testing — not part of this diff. |
Per review from @federicobartoli and @nucliweb on #342: Tier 3 (behavioral): - Grade the execution trace, not the final output: executor runs with --output-format stream-json --verbose so the grader judges tool calls and file edits rather than the model's self-reporting. - Run each eval in a throwaway workspace; files[] fixtures materialize from evals/fixtures/ so evals can operate on real code. - Node-level timeouts on executor and grader calls; grader output parsed and shape-validated before writing (raw saved on failure); the trace is fenced as untrusted data in the grader prompt. - All 24 behavioral evals flagged trust_level: "provisional" until they gain fixtures; the runner surfaces this and exits nonzero on failed expectations. Tier 2 (deterministic): - Negative triggers accept an "owner" skill that must outrank this one, turning them into pairwise routing tests that cannot pass vacuously; 37 of 48 negatives now declare owners (the rest are tracked in #351). - Warn when a case file is below the documented minimums (3 positive / 2 negative / 1 behavioral); promotion to error tracked in #352. - Stemmer: cluster trailing y/i ("simplify"/"simplifies"). Baseline holds: 120 checks, 0 errors, 85% trigger rank-1 rate.
|
This is exactly the review this PR needed, thank you both. Federico, the "grades whether the model narrated the behavior rather than whether it happened" line was the sharpest observation in the thread, and you're right that it's the difference between a measurement spine and a vibes generator. And nucliweb, spinning up a throwaway 25th skill to probe for fixed-count assumptions is above-and-beyond reviewing; glad the catalog logic held up (and yes, the validator yelling at your intentionally incomplete dummy skill was it working as intended). Pushed two follow-up commits addressing the blocking-adjacent stuff. Tier 3 now captures the full stream-json execution trace with --verbose, so the grader judges tool calls and file edits instead of prose, with the trace fenced as untrusted data, timeouts on both calls, and grader JSON validated before it's written. Each eval runs in a throwaway workspace and files[] fixtures materialize from evals/fixtures/, so the mechanism Federico asked for is wired even though fixture content is still to be authored. Every behavioral eval now carries trust_level: "provisional" per nucliweb's suggestion, and the runner says so out loud, so nobody cites current Tier 3 numbers as evidence. On the deterministic side, negatives can declare an owner that must outrank the skill, which killed the vacuous-pass problem: 37 of 48 negatives are now real pairwise routing tests, and wiring them up immediately exposed five prompts where the would-be owner had zero lexical overlap. Federico called that failure mode before I'd seen a single instance of it. Minimums are now warning-level checks in the runner too. The rest is tracked rather than left as vibes, exactly as nucliweb suggested: #351 covers the frontend and performance description gaps (including the one owner I had to defer), and #352 bundles the whole graduation-and-ratchet path in one place: fixture authoring per skill, dropping trust_level as they land, the --min-rank1 gate, and promoting the minimums and coverage warnings to errors once the in-flight skill PRs clear. Both are linked from the README and CONTRIBUTING so "warning for now" has an explicit expiry instead of becoming permanent. Deterministic tier still holds at 120 checks, 0 errors, 85% rank-1. Thanks again to my two most reliable reviewers; this framework is meaningfully more trustworthy than what I opened with. |
|
Two Tier-3 findings from re-reading 1. The grader passes the full trace as a CLI argument; it will hit 2. The executor call sets no tool-permission mode. The executor Neither blocks the deterministic tier, which is solid and mergeable as-is. Both fit naturally under the #352 graduation work, before any Tier-3 numbers get cited as evidence. |
…ions Two latent Tier-3 bugs caught in review before the path was ever exercised: - The grader prompt embeds the full stream-json trace (up to megabytes) and was passed as an argv entry, which would fail with E2BIG on any real run. It now goes to `claude -p` over stdin; the executor prompt moves to stdin for the same reason. - The executor ran headless with no permission mode, so file edits and command runs could be denied, forcing the narrate-instead-of-perform failure mode that trace grading exists to catch. It now runs with --permission-mode acceptEdits and a pre-approved tool list (Read,Glob,Grep,Edit,Write,Bash), documented in the README.
|
Great catches @nucliweb, and slightly humbling ones: both bugs live on the exact path we hardened last round, and both would have survived every CI run precisely because CI never exercises Tier 3. The ARG_MAX one especially would have been a confusing first-contact failure for whoever authored fixtures first and ran a behavioral eval for real. You suggested parking these under #352, but they were small enough to fix now, so they're in. The grader prompt (and the executor prompt, for symmetry) now go over stdin instead of argv, and the executor runs with --permission-mode acceptEdits plus a pre-approved tool list (Read, Glob, Grep, Edit, Write, Bash) so the agent can perform the skill in its throwaway workspace instead of being denied into narration. Both are documented in the evals README, the dry-run output reflects the real invocation now, and the deterministic tier still holds at 120 checks, 0 errors, 85% rank-1. Thanks for reading the code twice; that's twice it's paid off. |
|
LGTM! |
|
Thanks for all the help on reviews, folks! |
Why
We have 24 skills and no way to measure whether they work - well, beyond manual testing :) The validator checks structure, but nothing checks the things that matter in practice: does a skill trigger when a user asks for it in their own words, do two skills' descriptions blur into each other, and does an agent following a skill behave the way the skill promises. Every new skill PR makes this gap more expensive.
What the community has settled on (I went looking first)
Short version: there isn't one settled standard for skill evans, but there are two clear leaders.
claude -p, prompt fixtures, and grader scripts. Our stale feat: introduce evaluation framework for skill testing #51 was already modeled on this, credit where due.Neither covers what a multi-skill catalog needs deterministically in CI: routing. So the approach here is to adopt the community schema where it exists and define the missing piece ourselves.
What this adds
Three tiers, defined in evals/README.md:
node scripts/run-evals.js --behavioral <skill>runs them through headlessclaude -pand grades the transcript against the expectations. There's a--dry-runso you can preview without spending anything.The schema compatibility is deliberate: because our evals[] block matches skill-creator's format field for field, you can point Anthropic's own runner, benchmarks, and eval viewer at this repo without changing a thing.
What's in the box
Baseline on main today: 120 checks pass, 85% of positive prompts rank their skill first, zero catalog collisions.
It already caught two real things
Writing fair prompts surfaced two description gaps I did not paper over: frontend-ui-engineering's description never says "responsive", "accessible", or "WCAG", and performance-optimization's never mentions queries or N+1. A user saying those words may not route to the right skill. I kept the eval prompts honest rather than quietly editing the descriptions here; that's a small follow-up PR, and these evals will verify it.
Coordination notes
Test plan
node scripts/run-evals.jspasses clean (120 checks, 0 errors, 0 warnings)node scripts/validate-skills.jsandvalidate-commands.jsstill pass--behavioral test-driven-development --dry-runprints the execution plan without spending tokens