Skip to main content
Test whether an agent loads your skill on the right prompts, then compare task results with and without that skill. The TypeScript and Python source CLIs run this locally; no EvalGate credential or hosted request is required. Use a build that includes the skill command; source availability does not establish npm or PyPI release availability.
The command runs your runner once for each case, trial, and variant. It passes the prompt on stdin and sets EVALGATE_SKILL_VARIANT to with or without, plus EVALGATE_SKILL_DIR to the absolute directory. The command string is split into an executable and arguments and executed without a shell. Pipes, substitutions, redirection, and shell expansion do not execute. Put complex setup in a runner script.

Define cases and task checks

Each JSONL line has a unique case id, prompt, and expected trigger label. Include at least ten distinct paired cases; repeating one prompt ten times does not establish ten independent cases.
exit_code compares the reported task exit code with expected. includes_all requires a successful task exit and every expected substring. json_equals requires a successful task exit and structurally equal JSON, ignoring object key order. JSON numbers must be finite; integer values must be between -9007199254740991 and 9007199254740991 so TypeScript and Python compare the same values. Unsafe integers in expected JSON are refused; unsafe integers in runner output fail the task check. Encode high-precision identifiers as strings. Task checks are optional for trigger-only testing; uplift needs task checks.

Runner contract

Write exactly one JSON line to stdout:
variant must echo the requested variant. invokedSkills lists skills actually loaded by the agent. The tested skill’s name comes from its SKILL.md frontmatter name, falling back to the directory name. Mentioning the skill in an answer does not count as an invocation. The runner process itself must exit zero to deliver usable evidence; exitCode records the task’s outcome. The runner controls isolation: it must remove the skill from the agent’s view in the without variant, including global/project skills, agent configuration, caches, and prior conversation state. EvalGate rejects a without observation that reports loading the tested skill, but cannot prove your runner hid every copy. Use fresh work directories and fresh sessions for both variants. Execution defaults to a 30-second timeout and a combined 1 MB stdout/stderr cap. Set --timeout-ms (maximum 600000) and --max-bytes (maximum 10000000) explicitly when needed. Runner errors, malformed JSON, variant mix-ups, and resource-limit failures cannot produce a passing result. Error reports omit raw output, stderr, and prompts. Runner code executes with your user permissions and inherited environment; run only code you trust. On POSIX systems, EvalGate terminates the runner’s process group after every outcome, including success, to remove remaining child processes. Windows termination covers the direct runner process; descendant cleanup is not verified there. A Windows runner must stop its own child processes before exiting. These limits do not isolate filesystem or network access.

Example: Claude Code headless runner

This example uses fresh temporary project and configuration directories. Check the stream contract for your installed Claude Code version before using it for release evidence. It reads Skill tool-use records from stream JSON; it never infers skill loading from the final answer.
Authenticate Claude Code using your approved mechanism for the isolated config. Do not copy account secrets into a committed runner. The example can call a paid model; EvalGate itself performs no model call. Set up a representative fixture project inside the temporary directory when a task depends on project files.

Use recorded observations

For harnesses that cannot be scripted, record the same runner fields plus caseId and a one-based trial:
Supply exactly one observation for every (caseId, trial, variant). Duplicate, missing, extra, and unknown-case observations are refused rather than scored on a selected subset. Choose exactly one of --runner and --observations. Case and observation inputs must be regular files (no symlinks, devices, or named pipes), with at most 50 MB per file and 4 MiB per JSONL record. Inputs are read incrementally with LF or CRLF delimiters; Unicode line and paragraph separators inside JSON strings remain data. Blank lines count toward the byte limit without creating retained records. Parsing stops when the case/trial population bound is exceeded or a record fails validation.

Interpret and gate results

The report contains trigger precision, recall, F1, false/missed-trigger case ids, task pass rates for both variants, a paired difference, its seeded 95% percentile bootstrap interval, and a two-sided exact sign-test p-value. Trials are paired by case and trial; task results are averaged within each case before bootstrapping case differences, so repeated trials do not inflate the independent sample size. The sign test uses nonzero per-case differences and excludes ties. Undefined trigger metrics are null. No task checks means task: null, not zero uplift. The report includes both the SKILL.md SHA-256 and a SHA-256 of all relative filenames and bytes in the skill directory, including scripts and references. Symlinks are refused. A directory changing during execution invalidates the evidence. Keep observation files and generated reports outside that directory. --min-uplift 0.05 requires the interval’s lower bound to be at least 0.05. An ungated report describes the observations; it does not prove the skill will improve every deployment or that a nondeterministic agent always behaves alike.