The eval pipeline at a glance¶
The harness is a set of composable skills that hand off to each other in order:
setup → analyze → dataset → run → review / optimize, with mlflow available at
any point once a run exists. Each skill reads the same eval.yaml and writes to the
same runs directory, so you can stop, edit, and resume anywhere.
The steps¶
| Order | Skill | Does | Required? |
|---|---|---|---|
| 0 | /eval-setup |
Installs deps, configures MLflow, verifies API keys, sets the runs dir | Optional — deps auto-install via the SessionStart hook |
| 1 | /eval-analyze |
Reads a skill (or your docs) and generates eval.yaml + eval.md |
Required — produces the config everything else reads |
| 2 | /eval-dataset |
Populates dataset.path with test cases matching your schema |
Required — unless you already have cases |
| 3 | /eval-run |
Prepares workspaces, executes headlessly, collects, scores, reports | Required — the core loop |
| 4a | /eval-review |
Interactive human review of results; proposes config/skill changes | Optional |
| 4b | /eval-optimize |
Automated refine-and-rerun loop (composes with /eval-run) |
Optional |
| ⟳ | /eval-mlflow |
Sync dataset, log run results, push/pull trace feedback | Optional — any time after a run |
| ✓ | /eval-check |
Whole-harness health check (overlap between skills, hooks, CLAUDE.md) | Optional |
| ⇄ | /eval-compare |
Compare results across models/runs into one HTML report | Optional — after ≥2 runs |
| σ | /eval-anova |
DoE sweep over a config matrix + ANOVA/Pareto on the runs | Optional — after ≥2 conditions |
/eval-setup is genuinely optional
Dependencies live in an isolated venv that the plugin's SessionStart hook creates
automatically, and agent_eval is exposed to scripts via symlinks. Reach for
/eval-setup when you want to configure MLflow tracking, point at a remote server,
troubleshoot a ModuleNotFoundError, or change the runs directory.
The flow¶
flowchart LR
setup["/eval-setup<br/><i>optional</i>"] --> analyze["/eval-analyze<br/>eval.yaml"]
analyze --> dataset["/eval-dataset<br/>test cases"]
dataset --> run["/eval-run<br/>execute + score + report"]
run --> review["/eval-review<br/>human feedback"]
run --> optimize["/eval-optimize<br/>auto-refine"]
review -.-> run
optimize -.-> run
run -. any time .-> mlflow[("/eval-mlflow<br/>track")]
review -. any time .-> mlflow
optimize -. any time .-> mlflow
%% Mark optional steps with a dashed border, NOT a fill. Material forces node
%% label text to a theme-adaptive color (white in dark mode) inside a closed
%% shadow DOM, so a light fill would render white-on-light text. Leaving the
%% fill to the theme keeps the label readable; the dashed stroke marks them.
classDef opt stroke:#9e9e9e,stroke-width:1.5px,stroke-dasharray:4 3;
class setup,review,optimize,mlflow opt;
The dashed loops back to /eval-run are the improvement cycle: review or optimize a run,
change the skill or config, then re-run to measure the delta (often against the prior run
with --baseline <run-id>).
Skills auto-invoke and compose¶
The skills are not just a checklist — they call each other through the Skill tool when a
prerequisite is missing:
/eval-runbootstraps/eval-analyze. If noeval.yamlis found during config discovery,/eval-rundetects what's available (skills inskills/, or docs for prompt mode) and invokes/eval-analyzefor you before continuing./eval-runchains into/eval-mlflow. Whenmlflow.experimentis set ineval.yaml,/eval-runlogs the run automatically as its final step./eval-optimizedrives/eval-run. The optimization loop composes with/eval-runvia theSkilltool on each iteration./eval-anovafans out/eval-run. It runs/eval-runonce per matrix cell, then hands the runs to/eval-compareto render the comparison (with the ANOVA/Pareto section)./eval-runprompts for/eval-dataset. Ifdataset.pathhas no cases, it stops and suggests generating them.
The backend is a flag, not a step
Local, Harbor (containers), and EvalHub are selected with
/eval-run --runner <local|harbor|evalhub> — the same eval.yaml runs unchanged on
all three. The execution substrate never lives in the config.
Where results land¶
Each run writes to $AGENT_EVAL_RUNS_DIR/<eval-name>/<run-id>/, where:
AGENT_EVAL_RUNS_DIRdefaults toeval/runs(configure it via/eval-setupor the environment variable).<eval-name>comes from the config, so runs for different suites never collide.<run-id>defaults toYYYY-MM-DD-<model>(override with--run-id; append a suffix like-v2to keep a previous run's results).
$AGENT_EVAL_RUNS_DIR/
└── <eval-name>/
└── <run-id>/
├── run_result.json # exit_code, duration_s, token usage, cost_usd, permission_denials
├── stdout.log # full skill stdout (stream-json)
├── stderr.log
├── collection.json # per-case artifact counts
├── summary.yaml # judges, per_case, and pairwise sections
├── analysis.md # interpreted results (Recommendation first)
├── report.html # the scored HTML report
├── hooks/ # lifecycle hook logs (if hooks: configured)
└── cases/
└── <case-id>/
├── stdout.log # per-case log (case mode)
├── artifacts/ # files matching outputs[].path
└── _modified/ # in-place file edits (auto-detected via git diff)
Clean artifacts between runs
Skills write to the project directory, not the workspace, so stale artifacts can
contaminate results. /eval-run runs a preflight check and reports DIRTY when it
finds leftovers or an existing <run-id> — clean them or pick a new run-id before
proceeding.
See the runs directory reference for the full artifact
layout and summary.yaml fields.
Where to go next¶
-
Run it end to end
Walk the skill-mode pipeline from analyze to report on a real skill.
-
Understand each step
Deep dives on the individual skills and what they configure.
-
Read the config
Every key that these skills read from and write to
eval.yaml. -
Improve a skill
Feed a run into human review or the automated optimization loop.