Skip to main content

skillproof

pytest for agent skills: prove your skill actually works.

ci license: MIT

skillproof demo

pip install skillproof

Works with: Claude Code · Codex · any OpenAI-compatible endpoint


Agent skills (SKILL.md folders for Claude Code, Codex, Cursor, Gemini CLI, OpenCode...) are everywhere, but almost nobody can show that a skill helps. Often it isn't even loaded: the agent never decides to use it. skillproof runs your tasks N times with the skill installed and N times without it, then tells you:

  • Trigger rate: did the agent actually load the skill?
  • Lift: pass rate with the skill minus pass rate without it, with a 95% confidence interval
  • Cost: extra (or saved) tokens, dollars and seconds per run
  • A verdict: helps, hurts, no clear effect, or too few runs to say

60-second quickstart

pip install skillproof
export ANTHROPIC_API_KEY=sk-ant-...      # or use your `claude` login

skillproof init ./my-skill                # writes my-skill/evals.yaml
# edit evals.yaml: 2-3 real tasks, how to check them, one task the skill should ignore
skillproof run ./my-skill                 # prints the plan and cost estimate, then runs

uvx skillproof run ./my-skill works too, without installing anything.

Example output

This is a real run of the release-notes example (Claude Code, 5 trials per variant):

acme-release-notes · runner claude-code · model claude-opus-5-5

 case                             ┃        triggered ┃ pass with ┃ pass without ┃  lift ┃ tokens Δ ┃ latency Δ
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━
 release notes from merged PRs    │              5/5 │       5/5 │          0/5 │ +100% │      +7% │       −3%
 release with no breaking changes │              5/5 │       5/5 │          0/5 │ +100% │      +7% │       +3%
 unrelated question               │ 0/5 (should not) │       4/5 │          5/5 │  −20% │      ±0% │       −2%

skill lift: +100% (95% CI +61% to +100%)  |  triggers 10/10  |  false triggers 0/5  |  tokens +7%  |  cost +10%
verdict: the skill helps (95% CI for lift: +61% to +100%)
• Small sample: as few as 5 completed run(s) per variant. Treat these numbers as rough; use --trials 10 or more
  before quoting them.

Every run also saves a self-contained HTML report (with each run's checks and answer), report.md, summary.json and the full transcripts under .skillproof/runs/<timestamp>/.

examples/ has skills that help, do nothing and hurt, so you can see all three outcomes.

How it works

for each case in evals.yaml, N trials, interleaved:
    with skill:     fresh temp dir + fixtures + skill installed  ->  agent  ->  checks
    without skill:  fresh temp dir + fixtures, no skill          ->  agent  ->  checks
  • Every run gets a fresh temp directory with your fixtures copied in. Your project is never touched.
  • Your personal skills, settings and instructions (~/.claude, ~/.codex, ~/.agents) are not loaded for either variant, so the only difference is the skill.
  • The agent can't see your evals.yaml or its checks.
  • Transcripts, token usage and tool calls are saved for every run, with API keys redacted.

evals.yaml

skill: ./                 # folder containing SKILL.md
runner: claude-code       # claude-code | codex | openai-compatible
# model: claude-sonnet-5-5  # optional; the agent's default otherwise
trials: 5                 # runs per case, per variant
timeout: 300              # seconds per run

cases:
  - name: "summarizes stack trace"
    prompt: "Here is an error log in error.log. What's wrong?"  # don't name the skill
    fixtures: ./fixtures/case1/     # copied into each run's temp dir
    assert:
      - type: contains
        value: "NullPointerException"
      - type: file_exists
        path: fix.patch
      - type: command               # runs in the temp dir; passes on exit code 0
        run: "python -m pytest -q"
      - type: max_tokens
        value: 20000
      - type: llm_judge             # optional; graded by a model from another vendor
        rubric: "States the root cause in the first sentence."

  - name: "unrelated question"      # catches skills that load when they shouldn't
    prompt: "What's the capital of France?"
    should_trigger: false
    assert:
      - {type: contains, value: "Paris"}
assertion passes when
contains / not_contains the final answer contains / doesn't contain value (case-insensitive by default)
regex the final answer matches pattern
file_exists / file_contains a file in the temp dir exists / contains value
command run exits 0 in the temp dir (API keys are removed from its environment)
max_tokens the run used at most value tokens (input + output + cache)
llm_judge a judge model says the answer meets rubric

Use deterministic checks first. llm_judge defaults to a judge from a different vendor than the agent (Claude agents are graded by an OpenAI model and vice versa), so no model grades its own family. Change it with judge: {provider: anthropic, model: ...}.

Runners

runner how it runs where the skill goes how a trigger is detected
claude-code Claude Agent SDK <temp>/.claude/skills/<name>/ a Skill tool call naming the skill
codex codex exec --json <temp>/.agents/skills/<name>/ a shell command that reads the skill's folder
openai-compatible a small tool-using loop over Chat Completions SKILL.md pasted into the system prompt not measured (approximate)

Keys come from environment variables only: ANTHROPIC_API_KEY (or your claude login), CODEX_API_KEY / OPENAI_API_KEY (or codex login). Runner settings go under runner_options:; see each runner's module docstring.

Reports, CI and badges

skillproof run ./my-skill --format md -o report.md    # also: html, json (to stdout)
skillproof report                                      # re-render the latest run
skillproof run ./my-skill --min-lift 0 --min-trigger-rate 80   # exit 1 if not met
skillproof run ./my-skill --dry-run                    # plan + cost estimate only

Cost guard: before every run skillproof prints the number of runs and an estimate based on your previous runs. Above $5 it asks first; in CI it refuses unless you pass --yes. If the first runs all fail (bad key, wrong model), it stops and tells you what to fix.

GitHub Action: comments the results on pull requests and keeps the comment up to date.

# .github/workflows/skillproof.yml (full example: action/example-workflow.yml)
on: pull_request
permissions: {contents: read, pull-requests: write}
jobs:
  skillproof:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: akhilgarg07/skillproof@v0
        with: {path: my-skill, trials: "10", min-lift: "0"}
        env: {ANTHROPIC_API_KEY: "${{ secrets.ANTHROPIC_API_KEY }}"}

Badge for your skill's README:

skillproof badge ./my-skill
# [![skillproof: +100% lift · 10/10 triggered](https://img.shields.io/badge/skillproof-...-brightgreen)](...)

It is green when the skill helps, red when it hurts and grey when the result is not significant or comes from fewer than 5 runs per variant. --json prints a shields.io endpoint file instead, so CI can keep a badge up to date.

Honest numbers: what this can and can't tell you

  • It measures your tasks, not the skill in general. Lift depends on the prompts and checks you write. Publish your evals.yaml next to any number you share.
  • Small samples are noisy. Every rate comes with a 95% interval (Wilson for pass rates, Newcombe for the lift). Below 5 runs per variant there's no verdict at all, and below 10 the report says the numbers are rough.
  • Results depend on the agent and model. Reports record the model that actually ran. In our runs, Claude Code read a library's source, noticed that a skill's docs were stale and ignored them (examples/stale-docs); a weaker model may not.
  • Trigger detection is per agent and heuristic. Claude Code has an explicit Skill tool call. Codex reads skills with ordinary shell commands, so a "trigger" there means a command touched the skill's folder.
  • The openai-compatible runner is an approximation. It pastes SKILL.md into the system prompt instead of letting the agent discover and load it, so it can't measure trigger rate and overstates how often a skill is "used". Reports label it.
  • Token deltas include cache reads, which are cheap. Look at the cost delta when the agent reports cost (Claude Code does).
  • llm_judge is a model's opinion. It's cross-vendor by default, but still noisy; prefer deterministic checks.
  • Agents run real commands. Runs happen in temp directories, but agents run without permission prompts, so a command could still reach other files on your machine. Test untrusted skills in a container or VM.

Leaderboard

leaderboard/ runs skillproof over a list of public skills (using evals you write, never the repo's own) and renders a markdown leaderboard.

Contributing

Contributions are very welcome; see CONTRIBUTING.md. Good first issues:

  • New runners: Gemini CLI, Cursor (cursor-agent), OpenCode, Aider
  • New assertions: tool_called (the agent used a given tool), json_schema, file_matches (regex on a file), max_cost_usd
  • skillproof compare: two versions of a skill head to head
  • Windows support and a Docker sandbox option for untrusted skills
  • A local judge: llm_judge over Chat Completions, for local models (Ollama, vLLM)

License

MIT

Metadata

Release files for skillproof 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for skillproof 0.1.0
File Size Uploaded Provenance
skillproof-0.1.0.tar.gz 342.6 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for skillproof 0.1.0
File Interpreter ABI Platform
skillproof-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 409.2 kB

Release files / skillproof-0.1.0.tar.gz

Download URL skillproof-0.1.0.tar.gz
Size 342.6 kB
Tags Source
SHA-256 checksum
How to use checksums
be3a5d7ef7a31933b58bd7cb370e3fd7fbf26532ed0e2e91785d24aea0994a7b
BLAKE2b-256 checksum
How to use checksums
27d5fe5783acc5bbea298acd0a828b1d7bc5ea7f5d9d4d951948236ceb05007d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.

Transparency log

Release files / skillproof-0.1.0-py3-none-any.whl

Download URL skillproof-0.1.0-py3-none-any.whl
Size 66.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
dc633f366637db43c8b39917a125e156f7549aa4502f9f988486dd0220606f09
BLAKE2b-256 checksum
How to use checksums
8a5c5f08131816d1da26d19495de63619d15891c6e3688dfa7bdef34907748a2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.

Transparency log

Security for skillproof 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for skillproof-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page