Generate a config (/eval-analyze)¶
/eval-analyze reads what you want to evaluate — a skill's SKILL.md (following any
sub-skills it calls) or a custom analysis prompt — and writes a complete eval.yaml.
It caches its findings in an eval.md next to the config so re-runs are cheap.
What it produces
eval.yaml— the config/eval-runneeds: anexecutionblock, a natural-languagedataset.schema,outputs, judges,models, and thresholds.eval.md— a cached narrative of the analysis with askill_hashso the skill isn't re-read until it changes.
Two modes¶
The analysis mode is decided by which flag you pass.
Reads the skill deeply — SKILL.md, its scripts, and its sub-skill chain — then
generates a config that tests the skill's outputs.
Produces execution.skill (case or batch mode) plus judges that check output
quality.
Executes a custom analysis prompt that defines what to evaluate and how — used for non-skill evals such as agentic documentation testing.
Produces execution.prompt (case mode) plus capability-style rubric judges, and
typically a generation block for synthetic
datasets. Prompt-mode configs often set runner.workspace_mode: repo and
permissions.deny — see Prompt-mode specifics.
| Aspect | Skill analysis | Prompt analysis |
|---|---|---|
| Flag | --skill my-skill |
--prompt <path> |
| Analyzes | SKILL.md, scripts, sub-skills |
Docs, patterns, APIs (prompt-defined) |
| Executes | execution.skill |
execution.prompt |
| Mode | case or batch |
case only |
| Dataset | Schema-based (input/output fields) | Generated (generation.strategy) |
| Judges | Output-quality checks | Capability rubrics |
| Question it answers | Does my skill work? | Can agents use my docs? |
Flags¶
| Flag | Required | Default | Description |
|---|---|---|---|
--skill <name> |
no | auto-detect | Which skill to analyze |
--config <path> |
no | auto-discover | Output path for the generated config |
--prompt <path> |
no | none | Custom analysis prompt (non-skill evals) |
--update |
no | false |
Fill in missing sections only; preserve your edits |
--assess |
no | false |
Assess all skills and recommend which need evals (ignores --skill; writes no config) |
# Explicit skill, custom output path
/eval-analyze --skill my-skill --config eval/my-skill/eval.yaml
# Refresh a config after the skill changed, keeping hand edits
/eval-analyze --skill my-skill --update
Auto-triggered when the config is missing
You don't always call it directly. /eval-run invokes /eval-analyze
automatically when no eval.yaml exists, so the pipeline bootstraps itself.
Batch assessment (--assess)¶
Not sure which skills are even worth evaluating? /eval-analyze --assess skips config
generation entirely and instead profiles every skill in the project — its tools,
script count, whether it already has an eval, and a short body excerpt — then
classifies each one:
- RECOMMENDED — non-deterministic, quality-sensitive output where judges catch regressions linters can't
- OPTIONAL — some judgment involved but straightforward; worth evals if heavily used
- SKIP — deterministic output or a thin wrapper; a linter or unit test suffices
- EXISTS — already has an eval config
It's the natural first step in a repo with several skills: assess once, then run
/eval-analyze --skill <name> on the ones it recommends. --assess ignores --skill
and writes no config — the verdicts are guidance, not a generated eval.yaml.
The core principle: observe, don't assume¶
Every field name, file pattern, and directory path in the generated
eval.yamlmust come from reading actual files. If you can't point to a specific file or field you observed, don't put it in the config.
This is why the analysis reads a complete sample case (every file in it) before
writing dataset.schema, and why the outputs section describes what the pipeline
actually produces rather than a placeholder. A vague schema ("input files and
references") forces judges to guess; a specific one
("input.yaml with a 'prompt' field; reference.md gold output") lets them write
outputs["..."] knowing what to expect.
Mark external systems
Fields whose values must exist in an external system (Jira keys, repo URLs,
channel IDs) should be tagged [EXTERNAL: System] in the schema. That tells
/eval-dataset to emit TODO_ placeholders instead of fabricating realistic but
invalid values.
Recursive sub-skill reading¶
Skill analysis follows the sub-skill chain — Skill tool calls and /skill-name
references — until it reaches the skills that produce the final artifacts. It reads
each sub-skill's SKILL.md to trace the full pipeline (typically 2–5 levels, capped
at 5 to avoid circular references).
flowchart TD
A["/eval-analyze --skill my-skill"] --> B["Read my-skill/SKILL.md"]
B --> C{Calls sub-skills?}
C -->|"/sub-a, /sub-b"| D["Read each sub-skill SKILL.md"]
D --> C
C -->|"leaf skills reached"| E["outputs = what the whole pipeline produces"]
E --> F["Write eval.yaml + eval.md"]
The outputs block therefore describes what the entire pipeline emits, not just what
the top-level orchestrator returns.
The Skill tool needs explicit permission in headless mode
If the skill's allowed-tools frontmatter lists Skill, the analyzer adds
"Skill" to permissions.allow. Without it, nested skill calls fail silently
when /eval-run executes headlessly.
eval.md caching and skill_hash freshness¶
The analysis is expensive, so it's cached. eval.md sits in the same directory as
eval.yaml and carries YAML frontmatter:
---
skill: my-skill
analyzed_at: 2026-07-16T10:00:00Z
skill_hash: a1b2c3d4e5f6 # sha256 of SKILL.md, first 12 hex chars
---
Before doing any work, /eval-analyze checks freshness:
flowchart TD
S["Start"] --> C{"eval.yaml exists<br/>and not --update?"}
C -->|no| FULL["Full analysis"]
C -->|yes| V["validate_eval.py memory eval.md"]
V --> F{"FRESH and config complete?"}
F -->|yes| DONE["Report up to date, exit — no work"]
F -->|"STALE / INCOMPLETE"| FULL
"Fresh and complete" means the skill_hash still matches the current SKILL.md
and the config has a non-empty dataset.schema, at least one outputs entry with
a schema, at least one judge, and models.skill set. If any of that is missing (for
example an older config predating a restructure), it re-analyzes even when the hash
matches.
The hash tracks only the top-level SKILL.md
If a sub-skill changes but my-skill/SKILL.md does not, the hash still matches
and the cache is considered fresh. Run /eval-analyze --skill my-skill --update to
force a refresh in that case.
What --update preserves¶
--update is for regenerating without clobbering hand edits. It only adds top-level
keys that don't exist yet — it never rewrites your existing judges, schemas,
thresholds, or permissions.
With --update |
Behavior |
|---|---|
Existing judges, dataset.schema, thresholds |
Kept as-is |
Missing top-level key (e.g. no outputs) |
Added |
--skill differs from the config's recorded skill |
Asks you — never silently overwrites |
Prompt-mode specifics¶
Prompt-mode configs use the same config surface (they still need models, judges,
and thresholds) with two additions the analyzer applies when the agent must navigate
the real repository:
runner:
workspace_mode: repo # agent explores docs/ai-docs at real paths
permissions:
deny: # test-cheating guard (prompt-mode only)
- path: "eval/"
tools: ["Read", "Edit", "Grep", "Glob"]
reason: "Cases contain answer keys and prior run results"
- path: "eval.yaml"
tools: ["Read", "Edit", "Grep"]
- path: "eval.md"
tools: ["Read", "Edit", "Grep"]
- path: "tmp/"
tools: ["Read", "Edit", "Grep", "Glob"]
Deny rules are prompt-mode only
Skill evals run in an isolated /tmp workspace, so they omit permissions.deny
entirely. deny rules exist to stop a repo-navigating prompt-mode agent from
reading its own answer key.
After generation¶
The skill validates what it wrote, then reports next steps:
Errors (broken file references, absolute paths, missing modules) block; warnings (an empty dataset you haven't populated yet, judges to add later) are surfaced but don't.
- No test cases yet? Run
/eval-datasetto generate them. - Cases exist? Run
/eval-runto execute and score.
See also¶
- The eval.yaml schema — every field the analyzer writes
- Execution model — case vs batch, skill vs prompt
- Skill vs prompt mode — which mode fits your target
- The full pipeline — where analyze fits end to end