Scorers and command scorers
Scoring in evalctl is about what an agent actually produced β files written, diffs, exit codes, and text β not just the text of a response. Scorers are attached to a suite and can be required (they gate pass/fail) or advisory.
Built-in scorers
Add a built-in scorer by name:
evalctl scorer add demo --name exact --required --json
Built-ins include exact match, regex, JSON comparison, and numeric threshold
scorers. Cases declare expectations β e.g. case add --expect-json β and the
scorer grades the resulting workspace and output against them.
Command scorers
A command scorer runs your own binary to produce a verdict:
evalctl scorer add demo --name command --id judge --argv "python3 scorer.py"
The scorer process receives three environment variables and emits one JSON verdict on stdout:
| Variable | Meaning |
|---|---|
EVALCTL_CASE_FILE |
the materialized case JSON |
EVALCTL_OUTPUT_FILE |
the runnerβs captured response |
EVALCTL_WORKSPACE |
the per-case workspace to inspect |
Verdicts are captured once
A command scorer is executed once, at run time. Its normalized verdict is
stored under cases/<case_id>/scorers/<id>.json. Reports and artifact replay read
that artifact and never re-execute the scorer binary β which is exactly why a
copied run directory stays reportable with no access to your original tooling.
A per-case scorer failure surfaces as E_SCORER_CASE_FAILED on the
score_json surface; it is reportable case data, not a command-level error. See
Error and exit codes.
Safety
Command scorers run arbitrary local code, exactly like runners, and are
covered by the same W_UNSANDBOXED_RUNNER warning. evalctl does not sandbox
them. See Security posture.
LLM-as-judge scoring is a roadmap item, not a shipped command scorer type. Any model-based judgment today is your own binary emitting a JSON verdict.