evaluating-skill-recall · git:20261018.9800f92 · 2026-10-18 · sha256 7184a0511eec6e74

evaluating-skill-recall git:20261018.9800f92A

Immutable. This exact content never changes and is served at /api/v1/blob/7184a0511eec6e74.

---
name: evaluating-skill-recall
description: Writes, runs and adjudicates the python-complexity skill's recall evaluation (skills/evals/, scripts/skill_eval.py). Use when adding or changing evaluation items, rerunning after a skill release or for another model, or deciding whether a model or the answer key is right where they disagree.
---

# Evaluating Skill Recall

`make skills-recall` asks 100 single-operation questions with and without the
skill and scores the stated bounds. [skills/evaluations.md](../../../skills/evaluations.md)
describes the method and the result files; read it first. This skill covers the
judgement the scripts cannot make: what makes an item answerable, and who is
wrong when a model and a key disagree.

## Write an Item

An item is only as good as its wording. A key that is right for the page's row
can still be wrong for the question as asked, and a capable model will answer
the question as asked.

- **Name every size variable the bound may use.** `lst[i:j]` needs i and j listed
  as variables (`i = the slice start; j = the slice stop`), or models bound it by
  n. Leave out variables the key does not use: `dq[i]` asked at the costliest
  index has only n.
- **State the condition that makes the key tight.** `s.rfind(sub)` reaches
  Θ(n·m) only while Θ(n) start positions remain, which fails at m = n, so the
  item requires `len(sub) <= len(s) / 2`; `heapq.merge` needs r >= 2 inputs for
  O(r + m log r); `x in range(n)` takes its O(1) arithmetic path only for an
  exact `int` or `bool`, and searches for anything else.
- **Match the case to what can happen.** An insertion that can resize the list
  allocates O(n) in the worst case, so ask for the amortized bound.
- **Give callbacks a cost and a result size.** The prompt's cost model makes a key
  function or predicate O(1); an item whose subject is a callback, such as
  `functools.reduce(operator.add, strs)`, says what it does. A key function that
  returns large objects changes `sorted()`'s space.
- **Keep the question free of the key.** The prompt shows the operation,
  variables and case. The key and the accepted spellings go only to the judge,
  and the quoted row to neither.
- **Quote the row.** `source.row` must be text from the page, specific enough to
  include the bound; `tests/test_skill_eval.py` fails when it leaves the page.
  A row prefix with no complexity cells pins nothing.
- **Check a derived key against CPython**, in `/cpython` at the version the
  prompt asks about (`PYTHON` in the script), not only against the page. A
  page bound can be incomplete for one version: with the digit limit disabled,
  `int(str)` on 3.14 switches to a `decimal`-based algorithm above 2,000,000
  digits that the int page does not describe.
- **List only equivalent spellings in `accept`**, equivalent under the item's own
  conditions with every size variable at least 1. A difference of variables is
  not covered: the slice items allow i == j, where O(j - i) is not tight, so a
  reworded slice item should require i < j. `O(n + m)` is not `O(n)` for a
  search unless the item bounds m by n.

Run `uv run pytest tests/test_skill_eval.py` after editing `items.yaml`. A
common item needs a `usage` name present in `skills/evals/usage.json`; the test
pins the item count, so update it when the set grows on purpose.

## Run It

```bash
make skills-recall MODEL=sonnet RUNS=3   # all 100 items: 600 agent runs
make skills-recall MODEL=haiku PILOT=1   # the 20 items marked `pilot: true`
```

A full Sonnet 5.5 run with `--concurrency 6` (the script's default is 4) cost
$52 and took 33 minutes; Haiku 5.5 cost $32.

- Try a new or reworded item with
  `uv run python scripts/skill_eval.py run --case '<id>' --runs 1` before a full
  run; `PILOT=1` covers only the items already marked as pilots.
- Do not run `make check` at the same time: its timing tests flake under the
  load.
- Stop a run by the PIDs of `claude plugin eval` and `skill_eval.py` from `ps`.
  `pkill -f` with a pattern that also appears in the shell's own command line
  kills that shell.
- A stopped run leaves read-only directories under `/tmp/claude-eval-*`;
  `chmod -R u+rwx` them before deleting, and delete the partial results
  directory.

## Adjudicate the Results

Read every section of the run's `summary.md` below the summary table.

- **A model disagrees with the key.** Settle it from the CPython source before
  deciding. If the model is right, the cause is one of three: the item's
  wording admits its answer (narrow the item for the next run, and add the
  spelling to `accept` if it is equivalent); the key was derived wrongly from a
  correct page (correct the key); or the page is wrong (report the page defect;
  the key follows the corrected page). If the key is right, the miss stands.
- **Judge rulings to review.** A judge pass on a spelling outside `accept` is
  added to `accept` once it is confirmed equivalent. A judge fail on an exact
  match is already scored as a pass; the judge rejects a small share of exact
  matches.
- **Worse with the skill.** Read the reply and its `tool_calls`. A regression
  where the skill's page carries a caveat outside the item's conditions is a
  real result, not a key defect.
- After changing keys or `accept`, run
  `uv run python scripts/skill_eval.py report <results dir>` for every stored
  run. It rewrites `summary.md` without calling a model and discards judge
  verdicts given under criteria that have since changed, so on those bounds
  only listed spellings pass: check the affected items' answers for equivalent
  spellings still missing from `accept`. `meta.json` keeps the digests the run
  started with.

## Keep Runs Comparable

A change to `PROMPT`, `PYTHON` or the rendered question of any item a run
selects changes that run's `questions_sha256`, and a run under the old
questions can no longer be compared with a new one. Batch such changes, then
rerun every model you compare. A change to keys or `accept` only needs `report`.

Commit each run's results directory with the change that produced it. Its
`meta.json` records the model IDs, judge, skill digest and question and
criteria digests. Two runs are comparable when everything except the one
thing being compared agrees: the model when comparing models, the skill digest
when comparing skill releases.