Arbiter
An LLM-as-a-judge evaluation engine: score any JSON content against free-text criteria or reusable weighted rubrics, run continuous evaluation sessions with ground-truth change detection, compare candidates head-to-head, and close the quality loop with calibration, appeals, admin overrides, and A/B judge experiments. The judge routes to Anthropic or any OpenAI-compatible endpoint, and client API keys pass through per request without being stored. Every example on this page was executed against a live server.
When to reach for it: scoring game rounds against designer-authored rules, judging competition submissions against rubrics, continuous support-conversation quality monitoring, content moderation with explainable per-criterion reasoning, regression-testing LLM output quality.
When not to: decisions that need a human approver belong to Human-in-the-Loop; reaching agreement on a fact across multiple independent reporters belongs to Consensus.
Concepts
- Rubrics are reusable scoring templates: 1-50 weighted criteria, each
with a score type (
numeric01,numeric0100,numeric_range,boolean,categorical) and an aggregation strategy (weighted_mean,minimum,maximum,custom_formula). Updates auto-increment the version. - Sessions are stateful evaluation contexts: a judge configuration, an
optional rubric (pinned to the rubric version current at session
creation), and cached ground-truth and content payloads. Lifecycle:
created->active<->paused->completed. - Evaluations are judged results: per-criterion scores with reasoning, an aggregate score, a confidence value, and token usage. They come from a one-shot call (no setup) or from a session.
- Ground truth is optional reference data the judge compares content against. Pushes are SHA-256 hashed so you can detect change.
- The judge runs on an LLM provider per request; keys, providers, modes, comparison, calibration, and experiments are covered on Evaluation and the judge.
Rubrics
snug arbiter rubric create --file rubric.json
snug arbiter rubric get --rubric-id rub_ArScpbNKvZhjMtxgfMsm
snug arbiter rubric list --search support
snug arbiter rubric update --rubric-id rub_ArScpbNKvZhjMtxgfMsm --file rubric-v2.json
snug arbiter rubric delete --rubric-id rub_ArScpbNKvZhjMtxgfMsmrubric.json holds the criteria and aggregation:
{
"name": "docs4-support-quality",
"description": "Scores support replies for accuracy and tone",
"criteria": [
{ "name": "accuracy", "description": "Does the reply match the policy in the ground truth?",
"weight": 0.7, "score_type": "numeric01", "range_min": null, "range_max": null },
{ "name": "tone", "description": "Is the reply professional and empathetic?",
"weight": 0.3, "score_type": "numeric01", "range_min": null, "range_max": null }
],
"aggregation": "weighted_mean"
}Create returned "version": 1; an update (even description-only) bumped it
to "version": 2.
Sessions
Over HTTP, POST /api/v1/arbiter/sessions returns the envelope:
curl -X POST -H "Authorization: Bearer $SNUG_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "docs4-cs-monitor", "rubric_id": "rub_ArScpbNKvZhjMtxgfMsm",
"judge": {"provider": "anthropic", "mode": {"kind": "single"}}}' \
http://localhost:4000/api/v1/arbiter/sessions{
"status": 201,
"msg": "Created",
"data": {
"session_id": "ses_KMLbJMbHZePFDYbyhhGE",
"name": "docs4-cs-monitor",
"status": "created",
"rubric_id": "rub_ArScpbNKvZhjMtxgfMsm",
"rubric_version": 2,
"created_at": "2026-08-28T06:32:11.133873Z"
}
}Lifecycle and management from the CLI:
snug arbiter session start --session-id ses_KMLbJMbHZePFDYbyhhGE
snug arbiter session pause --session-id ses_KMLbJMbHZePFDYbyhhGE
snug arbiter session resume --session-id ses_KMLbJMbHZePFDYbyhhGE
snug arbiter session stop --session-id ses_KMLbJMbHZePFDYbyhhGE # -> completed, terminal
snug arbiter session list --status active
snug arbiter session update --session-id ses_... --file rename.json # PATCH, partial
snug arbiter session delete --session-id ses_... # also deletes its evaluationsFeed a session by pushing ground truth and content, then trigger an evaluation (details). Ground-truth pushes are hashed for change detection:
snug arbiter session push-gt --session-id ses_KMLbJMbHZePFDYbyhhGE --file gt.json{ "accepted": true, "changed": true,
"hash": "sha256:389cdcb9...", "previous_hash": null }The second push of the identical payload returned "changed": false with
previous_hash equal to hash.
Results, appeals, and overrides
snug arbiter eval list --session-id ses_... --status completed
snug arbiter eval get --evaluation-id eval_...
snug arbiter eval appeal --evaluation-id eval_... --reason "Score too low"
snug arbiter eval override --evaluation-id eval_... --file scores.json --reason "Correct per policy"
snug arbiter stats --session-id ses_...An appeal flips the evaluation to appealed status for human follow-up.
Override requires a platform admin token: it replaces the scores, recomputes
the aggregate, and records who overrode and why, keeping the previous
aggregate on the record. A non-admin attempt fails with
403 insufficient_permissions, and appealing a missing evaluation is
404 evaluation_not_found - both reproduced live.
stats returns score statistics (mean, median, stddev, percentiles), a
status breakdown, token usage, and drift detection against a rolling
baseline; with too few evaluations, drift reports "insufficient evaluations to establish baseline" and alert: false.
Behaviors and gotchas
All reproduced against the live server:
- Lifecycle is enforced - pausing a session still in
createdfails with400 invalid_state_transition("cannot move session from Created to Paused"). Start it first. - Rubric delete-protection counts only active sessions - the delete
failed with
409 rubric_in_usewhile a referencing session wasactive, but succeeded once that session waspaused. Treat deleting a still-referenced rubric as unsafe regardless of session state. - Evaluating a session with no pushed content fails with
400 invalid_evaluation("no content available for session"). Ground truth is optional; content is not. multijudge mode requirescount >= 1-{"kind": "multi", "count": 0}is rejected at session creation with400 invalid_session.- Per-request judge endpoint overrides are off by default - a session
with
judge.endpointset is accepted at creation but evaluation fails with400 invalid_request("per-request endpoint override is disabled") until the server enablesARBITER_ALLOW_ENDPOINT_OVERRIDEand allowlists the host. - Owner isolation - another user reading your session gets
403 insufficient_permissions, and list endpoints only return your own resources. - Stored but not yet acted on -
triggers,result_webhook_url,key_provider_url, and theground_truth_source/content_sourceconfigs are persisted on the session, but no poller runs, no triggers fire, and no result webhooks are delivered yet. Push data explicitly.
Limits and configuration
Default judge model and provider endpoints, judge timeout and retries, the
per-session evaluation cap (10000 by default), drift windows, idempotency
TTL, the experiment worker, and endpoint-override allowlisting are all set
via ARBITER_* variables - see the
Arbiter CONFIG reference.
Reference
- Evaluation and the judge - providers, keys, judge modes, one-shot evaluation, comparison, calibration, experiments
- Arbiter API - every endpoint, callable
- Related: Human-in-the-Loop for human judgment, Consensus for fact quorums