Leia

Leia Evaluate Reference

leia evaluate is the entry point for agent evaluation work. It emits a versioned report and runs each discovered evaluate block body as ordinary Leia code under the evaluation harness.

The command parses Leia source, runs existing AI dialect source validation, and discovers evaluate "case name" { ... } blocks. For each case it executes the file’s top-level setup code and then the evaluate body, so normal Leia assertions such as assert(result == "ok") determine whether the case passes. It reports local TODO lines as informational findings and summarizes evaluate blocks, selected cases, executed cases, assertions, metrics, and LLM usage. During the run, the harness-only eval module can load JSONL corpora, run named subcases, record metrics, inspect LLM usage, enforce local budgets, run bounded judge turns, skip fixtures, and fail gates without adding more syntax.

leia evaluate --json path/to/script.leia
leia evaluate --json --report eval-report.json path/to/project
leia evaluate --format=text path/to/project
leia evaluate --format=html --report eval-report.html path/to/project
leia evaluate --gate examples/evaluate
leia evaluate --parallel=8 examples/evaluate
leia evaluate --baseline baseline.json --regression-threshold 0.05 examples/evaluate
leia evaluate --compare baseline.json current.json --format=text
leia evaluate --list --filter "refund flow" examples/evaluate
leia evaluate --filter "refund flow" examples/evaluate
leia evaluate --replay records/agent.records.json examples/evaluate/agent_replay.leia
leia evaluate --update-golden records/agent.records.json examples/evaluate/agent_replay.leia

--list discovers evaluate cases without executing their bodies. Listed cases use status listed, keep source metadata and assertion metadata, and do not consume LLM replay fixtures.

--filter TEXT runs only cases whose name, source path, or case id contains TEXT. Discovery counts still include all evaluate blocks so dashboards can distinguish “not discovered” from “discovered but filtered out”.

LLM record/replay uses the same JSON fixture format as the public llm package:

Flag Meaning
--format json\|text\|html Select the report renderer.
--report FILE Write the rendered report to FILE instead of stdout. --output FILE is accepted as an alias. The command still exits non-zero when the report status is failed.
--gate Explicit CI gate mode. The current command already exits non-zero for failed reports; the flag documents that intent in scripts.
--parallel N Execute selected top-level evaluate blocks with up to N workers. Discovery and report merging stay deterministic. LLM record, replay, and update-golden modes run serially so fixture turn order remains stable.
--baseline FILE Load a previous JSON evaluate report and attach a comparison section to the current report.
--compare OLD NEW Compare two existing JSON evaluate reports without executing source. The comparison is attached to NEW and rendered with the selected --format.
--regression-threshold N Allow bool pass-rate regressions up to N when --baseline is used. Summary pass-rate and bool metric pass-rate regressions beyond the threshold fail the report. Number and string metrics are compared but not treated as regressions because directionality is metric-specific.
--replay FILE Use FILE as a deterministic provider transcript. Request mismatches, exhausted replay, or unconsumed turns fail the report.
--record FILE Run against the configured provider and write observed turns to FILE.
--update-golden FILE Run against the configured provider and explicitly rewrite FILE as the new replay transcript.

The three LLM fixture modes are mutually exclusive. The explicit --llm-replay and --llm-record spellings are also accepted when a script or dashboard wants the fixture type in the flag name.

When --record or --update-golden is used, the command still writes the global fixture requested by the flag. It also writes per-case fixtures under a stable sibling directory such as agent.records.cases/001-refund-flow.records.json and reports each path as cases[].llm.record_path. These per-case fixtures are for debugging and future replay workflows; the global fixture remains the canonical replay input.

Harness module

leia evaluate installs an eval module only while it executes evaluate blocks. Ordinary script execution does not install this module.

The cost and money keys below are evaluate harness and report metrics only. They let evaluation jobs gate on provider-reported cost metadata when available; they do not make money accounting a stable script-level AI budget dimension.

Function Meaning
eval.case(id, fn) Run one named subcase. Runtime errors inside fn fail that subcase and return (false, err) without stopping later subcases.
eval.metric(name, value) Record a bool, number, string, nil, or JSON-like metric on the active subcase, or on the outer evaluate block when no subcase is active.
eval.load_jsonl(path) Load a JSON Lines corpus relative to the evaluate source file.
eval.skip_if(cond, reason) Mark the active subcase skipped when cond is truthy. Returns whether it skipped.
eval.fail_if(cond, message) Stop the current evaluate block when cond is truthy.
eval.usage() Return current case LLM usage: turns, input_tokens, output_tokens, tokens, latency_ms, cost, tool_calls, stream_events, and errors.
eval.budget(table) Fail the current case when current LLM usage exceeds a positive limit in table. Supported keys are turns, tokens, input_tokens, output_tokens, latency_ms, cost, and money.
eval.judge(options) Call llm.turn(options) for judge-style scoring. If absent, it defaults max_tokens to 200 and budget to {tokens: 512, turns: 1}. It records judge_cost, judge_input_tokens, judge_output_tokens, and judge_tokens metrics when provider usage is available.

Example:

evaluate "refund classifier corpus" {
    cases := eval.load_jsonl("refund_cases.jsonl")
    for _, row := range cases {
        eval.case(row.id, func() {
            result, err := classify_refund(row.input)
            assert(err == nil)
            eval.metric("correct", result.action == row.expected)
            eval.budget({tokens: 5000, cost: 0.02})
        })
    }
}

The JSON report is versioned with schema_version: 1 and includes:

Field Meaning
phase Currently runtime-minimal.
status ok or failed. Syntax, validation, and case runtime errors make the report fail.
started_at UTC RFC3339 timestamp for the evaluation run.
runtime Leia and Go runtime metadata: version, OS/arch, and build VCS fields when available.
summary File, parse, TODO, selected/skipped case, pass/fail/list, assertion, duration, and pass-rate counts.
llm Optional LLM metadata: mode, fixture paths, loaded/replayed/remaining/recorded turn counts, aggregate turn count, streamed event count, provider errors, input/output tokens, latency, and cost.
inputs Per-input file status.
cases Evaluate blocks with case_id, name, source path, range, status, per-case started_at, duration, optional per-case llm stats and llm.record_path, raw metrics, subcases, assertions, and diagnostics.
metrics Top-level summaries aggregated from eval.metric values. Bool metrics report pass rate, true, and false counts. Number metrics report mean, min, and max. String metrics report category counts.
comparison Optional baseline comparison with summary pass-rate deltas and metric deltas.
findings TODO, IO, lex, parse, AI syntax, case runtime, and replay-drift findings. Replay mismatch findings include stable JSON details.expected and details.actual request summaries plus case_id/case_name attribution when the drift happened inside an evaluate case; exhausted and unconsumed replay findings include turn/count details.
notes Explicit scope notes so callers do not confuse this with full eval scoring.

Ordinary script execution still treats evaluate blocks as runtime no-ops. The evaluation harness only changes leia evaluate.

Summary fields are intentionally dashboard-friendly. evaluate_blocks is the number of discovered blocks before filtering. cases_selected is the number that matched --filter and were listed or executed. cases_skipped is the number filtered out. cases_passed and cases_failed count executed cases; cases_listed counts --list results. pass_rate is cases_passed / (cases_passed + cases_failed) and is 0 when no case ran.