What you’ll learn
- How XORCISE turns one run into one number, and why that number caps at 50% without a judge.
- What each half reads, what it cannot see, and how weights are applied inside it.
- How to read
judge_statusand recover a score that was lost to configuration, without running the agent again.
The two halves
Grading runs over the sealed evidence of a finished run: the artifacts your agent submitted, the frozen OpenTelemetry trace, and the facts XORCISE recorded about the run itself. Each half produces a sub-score between0 and 1, and the overall score is their average:
0.0, that zero is still multiplied by 0.5 and added, and the deterministic half is not rescaled to compensate. A run that passes every single check reads as 50%. A reader who sees 42.5% on an unjudged run is not looking at a failed agent — they are looking at half a score. The same ceiling applies in the other direction: a mission that ships only checks and no rubric, or only a rubric and no checks, caps at 50% no matter how well the agent performs.
Why the flag alone is not enough
A flag check answers one question: did the agent end up holding the right string. It cannot tell a lucky guess from a methodical compromise, it cannot see whether the agent enumerated the target before attacking it or trampled the box on the way through, and it has nothing to say about a mission with no single correct answer. That is the half of agent evaluation people actually argue about, and it is why XORCISE spends half the score on a rubric.When the judge is the wrong tool
The judge is not objective. It is a model reading your mission’s rubric, and it inherits every weakness that implies: it grades only what the evidence shows, it answersunknown when the transcript is thin, and two providers will not agree to the decimal. Anything you can assert exactly — a flag, an artifact’s format, a turn count — belongs in checks, where it is free, repeatable, and immune to prompt length. Put in the rubric only what needs judgement.
The deterministic half
A check is a declaration in the mission that XORCISE evaluates itself. Every check is two decisions kept deliberately apart: asource and a ref say where to fetch a value from, and an op and its args say what must be true of it. XORCISE resolves the value, applies the operation, and records a verdict.
There are three sources — the agent’s submitted artifacts, statistics projected from the sealed trace, and the observed facts XORCISE recorded about the run — and four operations, covering exact equality, whole-string regex match, presence, and a numeric less-than. ref is the lookup key into the source, not the expected value; the value a check compares against lives in args. The complete vocabulary, with the exact strings and the pass and fail semantics of each operation, is on Checks and ops.
A missing ref resolves to nothing rather than raising, so the operation decides the verdict. That makes an unsubmitted artifact a clean failure instead of a crash.
Weights follow one rule with no middle ground:
- Every check declares a
weight, each greater than0and at most1, and they sum to1.0. XORCISE uses them as declared. - Or no check declares a weight, and each gets
1/n. - Mixing the two is a manifest error, rejected when the mission manifest is validated.
- A mission with no checks at all scores
0.0on this half, which caps the run at 50%.
error explaining why. Those checks consume their weight as failures, not as skips — so a check with a non-empty error is a mission-authoring bug, and it is the first column to read when a score looks unfair.
The judge half
The judge is bring-your-own-model: XORCISE ships no model and calls the OpenAI-compatible endpoint and key you configure. Each criterion in the mission’s rubric — anid, the text of the behaviour to assess, and a weight — gets its own isolated model call, so one criterion’s assessment cannot bleed into another’s. The instructions and the evidence are byte-identical across those calls, so a provider that supports prompt caching reuses the prefix.
What the judge is shown is exactly two things: every artifact the agent submitted, as name and value, and a distilled transcript of the agent’s tool actions and their outputs, merged in time order from the trace and log signals your harness exported. When no trace was sealed, the prompt says so explicitly. What the judge is not shown matters as much: it never sees the deterministic check results, the mission objective, the observed facts, the terrain map, or the run’s statistics. All agent-controlled text is fenced as untrusted and stripped of the markers that delimit the fence, so agent output cannot forge a boundary or issue instructions to the judge.
For each criterion the judge returns either a score between 0 and 1 with a one-line reason, or the verdict unknown with an explanation of why the evidence was insufficient. The sub-score is the weighted average over the criteria it could grade:
unknown are renormalised out, not scored zero. Thin evidence must not be charged to the agent as a failure. The consequence is worth knowing: if every criterion comes back unknown, the divisor is 0 and the judge half is 0.0 while judge_status still reads ok. Rubric weights are used exactly as written and need not sum to 1.0, because the division renormalises them — but there is no equal-split fallback the way there is for checks, so a rubric whose criteria all omit weight also produces a judge score of exactly 0.0 reported as ok.
XORCISE preserves judge_prompt on the result: the shared instructions and evidence block every criterion call received. When a judge score surprises you, that field is the ground truth for what the judge actually read.
A worked example
One run ofsqli-login by the agent scout. The mission declares three checks and three rubric criteria:
scout submits flag and notes, never submits creds, and completes the run. A judge model is configured.
All three checks declare weights, so XORCISE uses them as declared:
hygiene dropped out of the average rather than scoring zero. Had it counted as a zero, the judge half would have been 0.66 instead of 0.9429 — a swing of 0.14 on the overall. The result records the reason on judge_detail: 1 of 3 criteria had insufficient evidence (unknown).
0.0:
scout did exactly the same work in both runs. The 47 points between them are the judge’s absence, nothing else — which is why conditions.judge_model and judge_status are the fields to read before comparing any two scores.
When the judge half is missing
Every result carriesjudge_status, and judge_detail alongside it. Read those two before you read breakdown.judge.
Two more shapes are worth recognising. An overall score of
0.0 whose judge_detail begins grading failed: is XORCISE’s defensive fallback: grading itself raised, usually against a mission installed before a newer validation rule, so the run records a zero rather than wedging forever. Refresh the installed copy with xorcise mission pull <id>, then re-evaluate. And spans_truncated above 0 means the judge read abridged span bodies, which is worth knowing before you trust a low judge score on a run with enormous tool output.
In every one of these cases the fix is to re-evaluate, never to run the agent again. The evidence is already sealed and re-grading replays it against your current settings.

A graded result — a different run from the worked example above. The scorecard always separates the two halves, so you can see which one carried the score.
What a result contains
Each judge criterion is a self-contained snapshot: its text and weight are copied from the rubric as it was graded, so a later version of the mission does not rewrite an old result.
Conditions
The result’s conditions record the context a score was produced under: the agent’s disclosedmodel, the judge_model actually used, the run’s budget_seconds, the sandbox_ref of the mission image, the agent_version and mission_version snapshotted when the run was created, and how much intel the run received. Two scores are comparable only when these agree — and judge_model is null whenever the judge was unconfigured or degraded, which is the most common reason a comparison is not valid. Intel is provenance for you; grading never reads it.
Statistics
Token and timing statistics live on their own endpoint rather than on the grade: input, output, cache and reasoning tokens with a computed total; counts of model calls, tool calls, findings and errors; and timing, including elapsed seconds and the longest single tool call. Cost estimation is not shipped, so it reports nothing. These numbers are reported by the agent’s own harness, so they are display and comparison data only — never an observed fact, and never a grading input. XORCISE also renders the same result as a single self-contained Markdown or HTML file, which is the shareable form of a run.How a run ends and what gets graded
A run persists exactly two states:created from the moment it exists, and terminal once it is over. There is no separate running state on the wire. What distinguishes one ending from another is terminal_trigger:
A partial result is still graded normally over whatever evidence exists — the scores are real and reflect only the work that was finished, and only the agent’s own completion counts as a genuine result against it. Note the asymmetry in the last row: a
deploy_failed run records a normal-looking, near-zero result that is not flagged partial, so check the trigger before concluding the agent performed badly.
Sealing and grading are deliberately split. Completion returns immediately; XORCISE then waits a short drain window for the agent’s last spans to arrive, freezes the trace, grades both halves, folds the statistics, and tears the environment down.
While that runs, asking for the result returns
202 with the status grading — normally for a few seconds, longer when the judge is working. That is not a hang. Polling the result also re-drives grading if it was lost to a restart, and XORCISE sweeps any terminal-but-ungraded run once on each start.What grading costs
The deterministic half is free and local. The judge half calls your model provider once per rubric criterion, and the evidence prefix those calls share is identical, so a provider with prompt caching charges the bulk of it once. Cost scales with transcript size, not run count. Two settings bound that size. A per-span cap,judge_span_max_tokens, defaults to 2000 and trims each span to a head-and-tail window with a marker in between — every span survives, so no criterion loses an action. A pre-flight ceiling, judge_transcript_max_tokens, defaults to disabled: the local token count is an estimate, so XORCISE prefers to let the call go through and surface the provider’s real error verbatim rather than reject a prompt that would have fit.
That is the cost of grading one run. A playbook benchmark grades every cell of a model × mission × runs matrix, so a 150-run playbook means 150 gradings — each costing what this section describes — on top of 150 agent sessions.
Read a result
Both interfaces show the same graded result. Replace<run_id> with the id XORCISE printed when the run was created.
- CLI
- Web UI
--verbose for the per-check and per-criterion breakdown, or --json for the whole result as a document you can script against. Both flags are documented in the CLI reference.grading status instead of a score — wait a few seconds and ask again.
Re-evaluate a run
Re-grading replays the run’s already-sealed evidence — the same artifacts, the same frozen trace, the same observed facts — against your current settings. It does not re-run the agent and it cannot change what the agent did. This is the fix for a judge that was unconfigured, rejected, or out of context when the run first ended.- CLI
- Web UI
- REST
From the terminal, run:It polls until the fresh grade lands unless you pass
--no-wait.mission_version in the conditions before treating two scores as the same measurement.
Next steps
Configuration
Set the judge model that produces half of every score.