Eval Datasets

View as Markdown

Everything a live evaluation runs on lives in one directory: evals/ inside your skill. This page is the contract for that directory — what goes in it, the dataset format tier3 evaluate reads, and how to generate and refine cases. You can generate a starter dataset with no API key; LLM-backed generation and refinement need a configured provider (see Providers & Credentials).

Tier 3 executes these cases through Harbor, the open-source agent evaluation framework. Native Harbor task bundles are the bring-your-own-task alternative to the evals.json dataset described below.

The evals/ directory

SkillEvaluator recognizes this tree inside your skill:

Something went wrong!

Only the dataset is required. Everything else is optional and additive:

PathPurposeCommit it?
evals/evals.jsonThe eval dataset. Without it, live evaluation cannot run.Yes — required
evals/config.ymlRun policy: attempts, thresholds, runtime env, agent models, grading mode.Yes
evals/EVAL.mdDeveloper guidance that steers dataset generation.Yes
evals/files/Input fixtures, staged into /workspace/input/ in the container.Yes
evals/environment/Custom container environment: Dockerfile, sidecars, MCP servers.Yes
evals/harbor/Bring-your-own-task native Harbor tasks — see Custom Graders & Tasks.Yes
evals/grader.py or evals/grader.shBring-your-own-grader script — see Custom Graders & Tasks.Yes
evals/results/Run output, written by tier3 evaluate. Never author it by hand.No — output only

Unrecognized files in evals/ are ignored, and the validator warns about them so typos don’t silently drop a fixture.

Dataset format

New datasets use the agentskills.io shape: a top-level object with skill_name and an evals array.

evals/evals.json
1{
2 "skill_name": "my-skill",
3 "evals": [
4 {
5 "id": "my-skill-001",
6 "prompt": "Use my-skill to do the thing",
7 "expected_output": "Expected outcome description",
8 "assertions": [
9 "Read my-skill/SKILL.md",
10 "Returned correct result"
11 ]
12 }
13 ]
14}
FieldRequiredMeaning
skill_nameYesName of the skill under evaluation.
evalsYesArray of eval cases.
idYesUnique case ID. Start with a letter or digit; ASCII letters, digits, ., _, - only; at most 128 characters. Integer IDs are accepted and canonicalized to strings.
promptYesThe user request the agent receives.
expected_outputYesOutcome-oriented description of what a successful agent produced.
assertionsNoObservable agent behaviors to verify, one per entry.
expected_skillNoSkill the agent should route to; null for negative cases.
expected_scriptNoScript the agent should run; null if none applies.
acceptable_skillsNoClosely related skills that earn partial routing credit (legacy alias: acceptable_alternates).

The dataset file can be named evals.json, evals.jsonl, evals.yaml, or evals.ymltier3 evaluate accepts all four.

Older datasets are a flat array of entries with question instead of prompt. They still run, but the validator emits a deprecation warning — migrate to the agentskills.io shape for new work.

Legacy fieldagentskills.io equivalent
question (required, with id)prompt
ground_truth (recommended)expected_output
expected_behavior (recommended)assertions
expected_skill (recommended)expected_skill (unchanged)
acceptable_alternatesacceptable_skills

Generate a dataset

create-eval-dataset writes evals/evals.json for you, grounded in your SKILL.md name, description, body, and script filenames:

Generate the full four-case dataset
$skillevaluator create-eval-dataset ./my-skill --full
FlagEffect
--fullGenerate the full 4-bucket dataset (four cases) instead of one.
--no-llmUse local templates only — no API key required.
--dry-runPreview without writing.
--forceOverwrite existing evals/evals.json.
--prompt PATHUse a developer guidance file instead of auto-detected evals/EVAL.md.
--refineRefine cases using existing or collected trajectories.
--from-results PATHWith --refine: read trajectories from a specific results directory.
--results-dir DIRECTORYWith --refine: external results root to search first.

Without an LLM provider configured, generation automatically falls back to the same templates --no-llm uses, so the command always succeeds. Exact defaults for every flag are in the CLI Reference.

You don’t have to generate a dataset up front at all: tier3 evaluate --autopilot (and validate --full or validate --autopilot) creates exactly one eval case when no evaluation source exists — LLM-generated with your configured provider, falling back to a deterministic template case when no key is available. Autopilot never overwrites an existing source; if evals/evals.json (or evals/harbor/) is already there, it runs with what you wrote.

--force overwrites evals/evals.json without a backup. --refine implies overwrite too — it produces the final version of the dataset.

The four case buckets

--full generates one case per bucket. The mix matters: the negative case protects against a skill that triggers when it shouldn’t.

BucketIntent
ExplicitThe user names the skill directly.
ImplicitThe user describes the task without naming the skill.
ContextualThe task appears inside a realistic project scenario.
NegativeA request that should not activate the skill.

Guide generation with EVAL.md

Place an EVAL.md in evals/ and the generator picks it up automatically. Three headings are parsed as structured hints; everything else is general context:

evals/EVAL.md
1## Questions
2- Sample user prompts to use as eval questions
3
4## Behaviors
5- Expected agent behaviors to verify
6
7## Notes
8- General context and constraints

Author guidance is the highest-priority input: behaviors you list here are preserved in the generated cases even when a trial agent skipped them — that is exactly the regression the eval should catch.

Refine from real runs

--refine grounds the dataset in real agent behavior. It looks for existing trajectories from a previous evaluation (checking --from-results or the latest resolved results first); if none exist, it runs the claude-code agent via Harbor to collect one, then rewrites expected_output and assertions from what the agent actually did:

Agent-refined generation
$skillevaluator create-eval-dataset ./my-skill --full --refine

Collecting a fresh trajectory needs Docker, the tier3 extra, and a configured provider. With --no-llm, refinement still works — it applies template-based updates from the trajectory instead of an LLM rewrite.

Run policy: evals/config.yml

The dataset says what to evaluate; evals/config.yml (or config.yaml) says how to run it. CLI flags always override config values.

evals/config.yml
1schema_version: 1
2
3harbor:
4 task_source: auto # auto | evals_json | native_harbor
5 custom_dockerfile_mode: preserve # preserve | rebase
6 n_attempts: 3
7 pass_threshold: 0.50
8 n_concurrent: 2
9 timeout_multiplier: 1.5
10 resources:
11 cpus: 2
12 memory_mb: 4096
13 runtime_env:
14 - SERVICE_API_TOKEN # expands to ${SERVICE_API_TOKEN} at run time
15 pre_agent_setup:
16 - service-cli auth login --token "$SERVICE_API_TOKEN"
17 - service-cli whoami >/dev/null
18 agents:
19 claude-code:
20 model: your-model-id # any model your agent CLI accepts
21
22skill_workspace:
23 mode: isolated # isolated | group
24 include: []
25
26grading:
27 mode: default # default | default_plus_custom | custom_only
KeyAcceptsEffect
schema_version1Required. Only version 1 is supported.
harbor.task_sourceauto, evals_json, native_harborWhere cases come from: auto-detect, the eval dataset, or native Harbor tasks.
harbor.custom_dockerfile_modepreserve, rebaseHow your custom Dockerfile is handled.
harbor.base_image_modereuse, rebuild, disabledShared pre-built eval base image (Docker env mode only): reuse it across tasks, force a rebuild, or ship self-contained per-task Dockerfiles. Default: disabled.
harbor.n_attemptsinteger ≥ 1Attempts per case (drives pass@k).
harbor.pass_threshold0.0–1.0Minimum score for an attempt to count as a pass.
harbor.stop_on_passbooleanStop a case’s remaining attempts after the first pass. Requires n_attempts > 1.
harbor.n_concurrentinteger ≥ 1Concurrent trials.
harbor.max_agentsinteger ≥ 1Cap on agents evaluated in one run.
harbor.timeout_multipliernumber > 0Scales task timeouts.
harbor.agent_runtime_preflightbooleanBounded one-task smoke run that checks agent runtime readiness before the full evaluation. Default: true.
harbor.agent_workdirstringWorking directory for the agent inside the container.
harbor.resourcescpus, memory_mb, storage_mbPer-container resource requests.
harbor.runtime_envlist or mappingNon-credential task values passed into the container. Prefer a list of plain names — each expands to ${NAME} from your shell; a mapping sets explicit templates. Entries that name or reference operator-owned credentials (OPENAI_API_KEY, ANTHROPIC_API_KEY, NVIDIA_API_KEY, base-URL variables, AWS credential variables) fail with a hard error. Alias: passthrough_env.
harbor.pre_agent_setupstring or listShell commands run in the container before the agent starts. Alias: setup_commands.
harbor.agents.<name>.modelstringPer-agent model override; whitespace is trimmed. claude is accepted as an alias of claude-code, but configuring both spellings in one config is an error.
skill_workspace.modeisolated, groupWhether sibling skills are visible — see Agents & Sandboxes.
skill_workspace.includelist of pathsExtra skills staged into a group workspace.
grading.modedefault, default_plus_custom, custom_onlyGrading pipeline — see Custom Graders & Tasks. Legacy spellings aces_default and aces_plus_custom are accepted and normalized to default and default_plus_custom.

Unknown keys are rejected, not ignored — a typo like n_attemps fails the config load instead of silently running with defaults. Use only one of each alias pair. Agent and provider credentials never go through runtime_env — they come only from your host environment; see the two-credential-roles section in Providers & Credentials.

Fixtures and custom environments

Two optional directories shape the world the agent works in.

evals/files/ holds input fixtures — test data, configs, sample documents. The entire directory is copied into /workspace/input/ inside the container, so prompts can reference those paths directly.

evals/environment/ customizes the container itself:

FileRules
DockerfileAt most 20 KB; the first non-comment line must be FROM. The build context is environment/ itself, so COPY paths are relative to it (COPY skills/, not COPY environment/skills/). If absent, an image is auto-generated from scripts/requirements.txt and scripts/apt-packages.txt on a python:3.12-slim base.
docker-compose.yamlSidecar services only (databases, APIs, mock servers) launched alongside the agent container. A strict allowlist applies: host port mappings are removed, and host mounts, privileged access, and unrecognized keys are rejected. Also accepted as docker-compose.yml.
mcp_servers.tomlMCP servers injected into the container. TOML array-of-tables [[mcp_servers]]; each entry needs name plus url or command. Command-based servers use transport = "stdio" (auto-inferred); URL-based servers default to sse.
SubdirectoriesCopied into the container environment (config files, seed data for sidecars). Any name except __pycache__ and .git.
evals/environment/mcp_servers.toml
1[[mcp_servers]]
2name = "my-api"
3url = "http://localhost:8080"
4
5[[mcp_servers]]
6name = "my-tool"
7command = "python /workspace/tools/server.py"
8transport = "stdio"

Validate before you run

Check the whole contract — dataset fields, case IDs, config.yml, Dockerfile rules, MCP declarations — without launching anything:

Validate the evals/ contract
$skillevaluator tier3 validate ./my-skill
FlagEffect
--jsonEmit JSON output.
--strictTreat warnings as failures.
--harbor-contractValidate the Harbor task and reward contract for BYOT/BYOG setups — see Custom Graders & Tasks.

Run it in CI or before a long evaluation: it catches an empty dataset, missing required fields, duplicate case IDs, and invalid config in seconds.

Next steps