archal run executes every exported scenario against hosted clones
and scores your agent’s behavior. This is the recommended way to write
scenarios: the tests live in your repo next to your agent code, get type
checking, and share the same helpers.
archal init scaffolds a starter eval file for you. This guide covers the
authoring surface and the portable JSON format eval files compile to.
Minimal example
evals/first-run.eval.ts
scenario() falls back to the
agent block in .archal.json, so your harness wiring stays in one place.
Run it:
The scenario() builder
scenario() describes one scored test.
| Field | Required | Description |
|---|---|---|
name | Yes | Human-readable title, shown in reports |
clones | Yes | Clones to start, for example ['github', 'slack'] |
task | Yes | The instruction the agent receives. This is the only field shown to the agent |
criteria | Yes | One or more criterion() or check() entries |
expected | No | The answer key for the judge. Never shown to the agent |
seed | No | Named seed per clone, for example { github: 'small-project' } |
timeout | No | Seconds before a run is killed (default 180) |
runs | No | Times to execute the scenario (default 1) |
tags | No | Labels for filtering |
agent | No | Override the agent for this scenario. Omit to use .archal.json |
archal run executes them in
export-name order.
Criteria: criterion() vs check()
Every success criterion is either an LLM judgment or a mechanical assertion.criterion() is judged by an LLM
Usecriterion() for anything that needs judgment: tone, reasoning quality,
whether an explanation makes sense. The judge reads the trace, the final clone
state, and your expected text.
{ critical: true } to make a failing criterion short-circuit the run
score to zero (and double its weight when it passes):
check() is a mechanical assertion
Usecheck() for anything a state or trace inspection can answer with no LLM
cost. It takes a structured assertion and an optional label:
type values:
| Type | Checks |
|---|---|
exists / not_exists | An entity is present or absent in clone state |
exact_count / min_count / max_count | A count of entities (needs value) |
output_contains | The agent’s final output contains a string |
trace_contains | A trace entry matches (tool name, host, text) |
no_errors | The trace has no tool errors |
negated: true to invert an assertion, and pass { label } to control how
it reads in reports. { critical: true } works the same as on criterion().
check() accepts the same structured assertions the deterministic evaluator
runs, so you never depend on a natural-language phrasing happening to parse.
Reach for criterion() when the check genuinely needs judgment.Running eval files
archal run finds scenarios in this order:
- A path you pass directly:
archal run evals/first-run.eval.ts --definition <path>for a JSON definition (see below)--task "..."for a one-off inline task- Eval files matched by the
evalsglobs in.archal.json - Markdown scenarios listed under
scenariosin.archal.json
archal init sets up discovery for you:
.archal.json
archal run --docker runs every matched eval file.
TypeScript eval files run under npx tsx, so no build step is needed.
Portable definitions (archal.scenario.v1)
An eval file is code. When you need a portable, language-neutral artifact (a machine producer emitting a regression test, or CI that has no TypeScript toolchain), use a definition file instead.archal.scenario.v1 is the JSON
form of a scenario minus the agent:
close-stale.scenario.json
--definition:
check entry carries the same assertion a
check() call would, and a criterion entry carries the judge statement. The
agent always comes from the consuming side (.archal.json or flags), so a
definition stays portable between machines.
If you already have a markdown scenario, compile it to a definition:
Choosing a format
- Eval files for scenarios you author and maintain by hand. Type checking,
co-located with your agent, the default from
archal init. - Definitions for machine-generated or interchange artifacts, and CI that
runs
archal run --definitionwith only thearchalbinary.
