Judges
A judge is a scorer that grades each trace or session against a rubric and produces a quality signal between 0 and 1 — plus a written reason you can audit. Judges are how raw traces become scores: enable one, run it, and every graded trace gets a number you can filter, chart, and gate on.
At a glance
| Where | The Judges page — Judges, Alignment, Run Targets, and Comparison tabs |
| Key API routes | POST /judges, POST /judges/{id}/versions, POST /judges/{id}/deployments, PATCH /judges/{id}/lifecycle, POST /eval-runs, GET /eval-runs/{id} |
| Needs | An LLM connection (Settings → Connections) for LLM-prompt and composite judges |
| Scope | Judges can be agent-, org-, or platform-level; enablement and run history are always per agent |
| Produces | Rows in the score catalogue, one metric per judge |
Judge types
| Type | What it does |
|---|---|
| LLM Prompt | An LLM reads evidence from the trace and returns {score, reason} against your instructions. |
| Composite | Combines other judges’ scores into one number — a weighted sum or an arithmetic expression. |
| External API | Calls your own scoring endpoint. |
| Custom Python | Author a Python scoring function. Authoring only — execution is not yet supported, so these judges can’t run in eval-runs, continuous eval, or pre-prod yet. |
Each judge targets either a trace (one span tree) or a session (a conversation rollup).
Neens also ships a built-in judge library — Faithfulness, Answer Relevancy, Hallucination, Toxicity, Bias, PII Leakage, Correctness, and more — visible in every agent under Available judges. Library judges are visible but score nothing until you enable them. Two scorers are provisioned per agent out of the box:
- Primary Score — a composite that blends Faithfulness, Answer Relevancy, and Coherence into one headline quality number and scores a sample of incoming traces continuously (see Continuous evaluation).
- Issue Classification — a classifier that tags failing sessions with the failure mode they exhibit, from your taxonomy. It runs on the failure set only, not on healthy traffic.
Create a judge
Define it
Click Create judge. For an LLM Prompt judge you fill in:
| Field | Purpose |
|---|---|
| Name | The judge’s label — its scores group under a metric key derived from it, so it is fixed after creation. |
| Type | LLM Prompt, Composite, External API, or Custom Python (Custom Python is authoring-only — it can’t run yet). |
| Target type | Score a trace or a session. |
| Scope | Agent (this agent only), Org (all agents in the org), or All orgs & agents. |
| System prompt | The judge’s persona / global instruction, sent as the system message. |
| Judge instructions | Plain-English task for the model — no placeholder syntax to write. |
| Criteria | Your rubric, appended in a dedicated block so the model grades against it. |
| Evidence to attach | Which parts of the trace the judge sees (below). |
| Output schema (JSON) | The JSON verdict shape the model must return (e.g. score + reason). |
| Output type | The native scale the judge scores on — a 0–1 score, a numeric range, categorical labels, or binary pass/fail (see Output types). |
| Max tokens / Temperature | Optional generation settings (blank → 512 / 0.0). |
Pick the evidence
Evidence to attach controls exactly which blocks of the trace are assembled into the prompt. Tick only what your criteria needs:
| Evidence | What it is |
|---|---|
| Final input | The user’s request to the agent. |
| Final output | The agent’s user-facing answer. |
| Full message trajectory | Every span in order — reasoning, tool calls, retrievals. For trajectory and tool-use judges. |
| Retrieved context | Retrieved documents/chunks. For RAG, faithfulness, hallucination. |
| Expected / reference answer | The gold answer carried on the trace. For correctness and exact-match. |
| Session metadata | Agent name, status, and custom metadata. |
If you tick nothing, the judge gets the default set: final input, final output, the full trajectory, and metadata. Evidence that a trace legitimately lacks (say, no retrieved context) is simply omitted, and library judges are instructed to grade what is present rather than penalize the absence. Evidence is injected as clean, labeled text sections — a readable transcript the model grades far more reliably than a raw dump. Tick Also inject raw JSON to additionally append the full raw trace payload as JSON alongside that readable evidence.
No placeholders. You never template evidence into your prompt by hand — the checkboxes decide
what the judge gets, and Neens assembles the prompt automatically. There is nothing to write like
{{input}} or {{output}}; if your instructions contain such a string it is passed to the model
as literal text, not substituted.
What actually gets injected
Final input and Final output are the parsed exchange — the shopper’s question and the agent’s user-facing reply — not the raw body of some span. Neens derives them from the trace with the same reconstruction that powers the Conversation tab on a trace and golden-dataset capture, so what the judge grades is what you see when you open the trace.
Concretely, for a trace whose spans were emitted by a LangChain/LangGraph agent, the stored span body looks like this:
[{"lc": 1, "type": "constructor",
"id": ["langchain", "schema", "messages", "HumanMessage"],
"kwargs": {"content": "Can I return a sale dress?"}}]…and what reaches the judge is this:
Evidence:
INPUT (the user's request / task):
Can I return a sale dress?
OUTPUT (the agent's response to evaluate):
Sale items are final sale.The rules, in full:
| Situation | What the judge sees |
|---|---|
| Ordinary single-turn trace | The user’s question and the agent’s reply, as text. |
| Multi-turn trace | The last exchange — the final reply, paired with the request it answers (not the opening question, which would ask the model to grade an answer to a different question). Tick Full message trajectory if the rubric needs the earlier turns too. |
| Serialized message payloads (LangChain constructors, OpenAI/Anthropic message arrays, content-block lists) | Unwrapped to plain text. Framework scaffolding never reaches the model. |
| The agent never replied (guardrail block, a tool-only run, a failed run that ended on a retry decision) | (the agent produced no user-facing reply in this trace). A routing decision, planner output, or guardrail verdict is not promoted to “the answer” — grading one of those scores the control flow, not the agent. |
| No user turn is recoverable (a scheduled/campaign agent with no human request) | (no user request could be derived from this trace). |
| Full message trajectory on a tool-heavy trace | Every span in order, with each body parsed the same way. A genuinely structured record — a tool’s arguments or result — stays compact JSON, because it is data the judge may be grading. |
Prompts are bounded. Each block is capped so one pathological span body cannot produce a
megabyte prompt: 8,000 characters for input/output/expected answer, 12,000 for retrieved context,
24,000 for the trajectory (2,000 per step, 80 steps), 4,000 for metadata. A cut is always stated
in the prompt itself — … [truncated: showing the first 8,000 of 40,000 characters] — and the
model is told not to read a truncation marker as the agent stopping mid-answer. Also inject raw
JSON is deliberately not truncated: if you ask for the raw payload you get all of it.
Changing judge? Before this behavior existed, Final output was the raw body of the last
answer-bearing span, which on framework-emitted traces was often a JSON blob. Judges written
against that blob — for example a rubric that says “the action field must be create_ticket” —
now see the parsed reply instead. Open the Live preview on a real trace to see exactly what
your judge gets, and tick Also inject raw JSON if a rubric genuinely depends on the raw shape.
Check the live preview — and test-run it
For LLM Prompt judges, the Live preview panel renders the exact system and user message that a run would send — hydrated on a real sample trace from your agent. It renders in three legended blocks — Your prompt, Injected trace data, and Output contract — so it’s clear which part is your instruction versus the trace evidence Neens assembles. As you edit instructions or toggle evidence the preview updates; use the sample dropdown to try a different trace. There is no second templating path, so what you preview is byte-for-byte what runs. If the agent has no traces yet, the preview shows the prompt structure only.
Click Test run to execute the draft prompt against that sample trace immediately, using the agent’s default LLM connection. The verdict — the score or label, PASS/FAIL, the reason, and the raw model response — is shown but not saved, so you can tune the rubric before you ever enable the judge. If the agent has no LLM connection, the panel prompts you to set one up.
Save — and iterate with versions
Editing a judge later publishes a new immutable version (with an optional change note) via Save as new version. The new version becomes current; older versions are kept so score history stays intact, and each enabled scorer records which version it runs.
Output types
By default an LLM Prompt judge returns a Score 0–1 and Neens asks the model for a score
float. But many rubrics are naturally a numeric range or a set of labels — so the Output type
control lets a judge score on its own native scale while Neens keeps a normalized 0–1 score
under the hood for dashboards, composites, clustering, and pass-rate. The judge’s native value is
captured alongside for display, and you define what counts as a pass in native terms.
| Output type | The model returns | You configure | Pass rule |
|---|---|---|---|
| Score 0–1 (default) | A score float in [0,1]. | Nothing — this is the classic behavior. | score ≥ the deployment threshold. |
| Numeric range | A score number on your scale. | Min, Max, optional Step, optional Pass at ≥, and Higher is better. | score ≥ Pass at ≥ (blank → the equivalent of 0.7 normalized). |
| Categorical labels | A label from your ordered list. | Labels (ordered worst → best), optional Passing labels, and Higher is better. | The label is in the Passing labels set (blank → the top half). |
| Binary pass/fail | A label that is your pass or fail word. | Pass label and Fail label. | The label equals Pass label. |
The stored score is always the normalized 0–1 value, so every downstream chart and composite keeps working unchanged; the native score/label is what you see on run drill-downs and the score catalogue. A judge with no output type set behaves exactly like the classic 0–1 path.
The output type is also what the Scores page reads. A Categorical labels or Binary pass/fail judge gets a label distribution — a donut cut into one arc per label, with a named legend and the window’s scored count in the centre — instead of an average donut, and its drill-down shows the share of each label over time. See Boolean and categorical scorers. Your ordered Labels are the order the legend renders in, and a label that never occurred in the window still shows at 0% in the drill-down.
Composites always emit 0–1. A composite judge blends its components on the normalized axis, so it can’t take a native output type — pick one only on the LLM Prompt (and External API) judges that feed it. A non-continuous component is normalized before it’s aggregated.
Composite judges
A composite judge combines other judges. Pick a method:
- Weighted sum — assign a weight to each component; Normalize to 1.0 rebalances weights over the components that are actually present per target.
- Expression — arithmetic over component scores (e.g.
0.6*answer_completeness + 0.4*bias), referencing each component by its variable name. Supported operations:+ - * / ** %andmin/max/abs/round. An invalid expression fails the run up front rather than degrading silently.
Set a Missing policy — Skip missing (a target missing a component score is skipped) or Fail on missing — and a Threshold for the composite’s pass/fail label.
When a composite runs, each component’s most recent score is reused if one exists; an LLM-prompt component with no score yet is scored inline once (its own score row is written too, so it is cached for next time). Primary Score works exactly this way, which is why its component metrics appear in the catalogue without their own enabled scorers.
Lifecycle stage
Every judge carries a lifecycle stage — a curation signal that says how far along the prompt is, without changing anything about how the judge scores:
| Stage | Meaning |
|---|---|
| Experimental | A work-in-progress scorer you’re still tuning. This is where every new judge you create starts. |
| Finalized | Reviewed and trusted for everyday use. The built-in library judges (Faithfulness, Primary Score, and the rest) ship Finalized. |
| Archived | Retired — kept for its score history but no longer part of your working set. |
Change a judge’s stage from the Judges page. The move is one-directional in spirit — experimental → finalized → archived — but you can set any stage at any time (e.g. re-open an archived judge back to experimental).
Stage is curation only. Promoting or archiving a judge never changes what it scores, whether it runs, or any existing scores. What it does change is the default Scores view: that page shows Finalized scorers by default, so an experimental judge you’re still iterating on doesn’t clutter everyone’s metrics until you promote it. See Scorer lifecycle for the whole flow.
The change is recorded in the activity/audit trail and takes the same
permission as configuring a judge, so a viewer can’t silently re-stage someone else’s scorer. Via
the API, PATCH /judges/{id}/lifecycle with {"stage": "experimental" | "finalized" | "archived"}
(any other value is a 422); a scoped caller can only re-stage a judge visible to its agent.
Enable it as a scorer
Creating (or having) a judge scores nothing by itself. Click Configure to enable it as a scorer in the current agent, where you choose:
- Version to run.
- Apply for — Manual (run on demand) or Continuous (score new traces). See Continuous evaluation.
- What traces to score — everything eligible, a fixed count, or a percentage sample. A live estimate shows how many traces are Eligible and how many it Will score.
- Target source — Filter (traces matched by status/agent/start-date filters) or Dataset (score only the members of a dataset; sampling still applies).
- Scoring connections (pool) — which LLM connection(s) score the traces. Pick several to spread load across providers and dodge rate limits; leave empty to use the agent’s default connection.
- Success threshold — an optional per-scorer minimum success rate for a run to count as fully completed (blank → the platform default, see Run outcomes).
Enabled scorers appear in the Enabled scorers table with two independent controls: Run now (a one-off run over existing traces) and Pause / Resume (continuous) or Disable / Enable (manual) for automatic scoring. Remove deletes the scorer; removing a platform-default judge sticks — it won’t silently re-enable itself.
You can also ask the Assistant to do this — “turn that judge on and score the last week” — or drive it from an agent over the MCP server. Neither needs a version id: omit it and the judge’s latest version is deployed, and an eval can be started from the judge alone when it has a single enabled deployment.
Connections are chosen at run time, not saved into the judge. A judge definition is portable; the LLM connection or pool is picked when you enable or run it. If an agent has no visible connection, an LLM-judge run fails cleanly (every target marked failed with the error) — it never borrows another agent’s credentials.
Run it
Click Run now on an enabled scorer. A dialog pre-fills the scorer’s connection pool and success threshold and lets you override both for this run. While the run is live, the table’s status, run count, and last-run time update in place every few seconds — no page reload. Click a scorer row to open its run history and drill into per-trace results: each target’s score, the judge’s reason, raw model output, attempt count, and any error. A running run can be cancelled — in-flight calls finish and the rest are dropped, and it then reads Cancelled on both this page and the Activity feed. See Cancel or recover a run.
Cancel or recover a run
A scorer run moves through queued → running to a terminal state — completed, completed-with-failures, failed, cancelled, or skipped (see Run outcomes for the grading bands and the Activity feed for the full status list). Two things are worth knowing before you launch one.
Enabling a scorer doesn’t run it. Turning a scorer on only makes it eligible: a manual scorer scores nothing until you click Run now, and a continuous scorer scores only new traces as they arrive — never your existing history. To score the traces you already have, use Run now. (The Judges page says the same inline: “Enabling a scorer doesn’t run it automatically. Click Run now…”)
Cancel a running run
Click Cancel on a live Run now run — or POST /eval-runs/{id}/cancel. Cancelling takes
effect promptly: any scoring call already in flight finishes, but the run’s remaining queued
targets are dropped rather than drained, so scoring stops within seconds instead of grinding
through work you no longer want.
The cancelled run then reads Cancelled in both places it appears — its scorer’s run history on the Judges page and the run’s row on the Activity page — so the two never disagree about whether it is still going. Whatever scored before you stopped it keeps its scores, but a cancelled run’s partial counts are never graded into a pass/fail verdict.
Example. You enable PII Exposure and click Run now over your recent sessions, then change your mind and click Cancel. Within a few seconds the run shows Cancelled on both the Judges page and the Activity feed, no further sessions are scored, and the sessions already scored keep their scores. When you’re ready, just Run now again.
If a run is interrupted
If the processing behind a run is interrupted while it is still in flight, the run is automatically moved to a terminal state — it reads Cancelled rather than being left showing Running forever. So a scorer never gets wedged: once the stuck run is closed out, simply Run now again to re-score cleanly. This is the same self-healing you see for clustering runs on the Activity feed (a stalled clustering run is marked failed there).
How it works
When a run starts, Neens resolves the scorer’s judge version and target set (the filter or dataset, sampled as configured), creates one task per target, and scores them:
- Evidence assembly — for each target, the selected evidence blocks are read from the trace, parsed, and rendered into the prompt. The live preview, the Test run button and a real run all call the same assembly and the same renderer, so they cannot drift: what the preview shows is byte-for-byte what the model receives.
- Scoring — the prompt is sent to a connection from the run’s pool; targets round-robin
across pool members. The model must return strict JSON —
{"score": …, "reason": …}, or{"label": …, "reason": …}for a categorical or binary output type — which is parsed defensively and normalized to a 0–1 score. - Persistence — each verdict is written as a score row (metric = the judge, source
llm_judge, orcompositefor composites) and appears immediately in the score catalogue, on run drill-downs, and in dashboards.
Judge calls within a run execute sequentially — one LLM call at a time per run.
Transient failures are retried. A timeout, 429, or 5xx from the LLM provider is retried
with exponential backoff — up to 3 retries per target by default, with delays growing from 0.5s
up to a 30s cap. Terminal failures (an unparseable verdict, a config or auth error) are never
retried; that target is marked failed with its error and the run moves on.
Run outcomes
A finished run is graded by its success rate — the fraction of attempted targets that scored successfully (skipped and cancelled targets don’t count against it):
| Status | Meaning |
|---|---|
completed | Success rate ≥ 0.90 — healthy (green). |
completed_with_failures | Between the two cutoffs — the run finished, but a notable share of targets errored (yellow). |
failed | Success rate < 0.10 — effectively broken (red). |
cancelled | You stopped the run. |
The green cutoff can be overridden per scorer (Success threshold in Configure, or per run in the Run dialog).
Filter a run’s scores, compare versions, and export
A judge’s run history (open it by clicking a scorer row) is also where you audit and compare ad-hoc batch runs — the manual Run now runs, each tied to one judge version. These flows read each run’s own per-target snapshot, so a later run overwriting the shared score row never corrupts an older run’s numbers.
- Scope the Scores page to one run. From a specific ad-hoc run you can view exactly the scores
that run produced, rather than the metric’s current live scores. API:
GET /scores?runId=<runId>(returns the run’s snapshot — this is meant for ad-hoc runs, not a continuous scorer that overwrites each trace’s row every time it re-scores). - Compare two versions. Pick two runs of the same judge — typically runs of two different versions —
to see, per target, which sessions improved, regressed, or stayed unchanged, plus each
side’s score and reason and the average delta. API:
GET /judges/{id}/compare?runA=<runId>&runB=<runId>; a run from another judge or agent is a404. - Export a run with input and output. Download a run’s (or several runs’) scores as CSV or
JSON, including each target’s input and the agent’s output alongside the score, label,
threshold, and reason — handy for spreadsheets, offline review, or sharing a regression. API:
GET /eval-runs/export?runs=<id>,<id>&format=csv|json.
These read the run’s captured snapshot, so version comparison and export stay accurate even after the judge is re-run. They’re aimed at bounded ad-hoc runs; a continuous scorer re-grades traces in place, so a single “run” isn’t a meaningful export boundary for it. See Scorer lifecycle for when to reach for each.
Definition reference (LLM Prompt judges)
| Definition field | Meaning |
|---|---|
system_prompt | System message — the judge’s persona and ground rules. |
prompt_template | The task instruction (the UI’s Judge instructions). |
criteria | The rubric intent, rendered in a Criteria: block. |
evaluation_steps | Optional ordered checklist the model must follow (library judges use this G-Eval-style structure). |
rubric | Optional anchored score bands, e.g. [{"range": "0.0-0.3", "when": "…"}]. |
required_data | Evidence keys: input, output, messages, context, expected_output, metadata. Empty → the default set (input, output, messages, metadata). input/output are the parsed exchange. |
include_raw_evidence | When true, also append the raw trace payload as JSON alongside the readable evidence (the Also inject raw JSON toggle). |
output_schema | The verdict shape, e.g. {"score": "number", "reason": "string"}. |
output_spec | The native output scale (the Output type): omitted / {"type": "continuous_unit"} for a 0–1 score, {"type": "numeric_range", "min", "max", "step?", "pass_min?", "higher_is_better"}, {"type": "categorical", "labels", "passing?", "higher_is_better"}, or {"type": "binary", "pass_label", "fail_label"}. Neens always stores a normalized 0–1 score plus the native value. |
target_type | trace or session. |
Composite definitions carry method (weighted_sum or expression), components (each with a
key, label, and weight), renormalize, min_components, missing_policy (skip / fail),
threshold, and — for the expression method — expression.
Judge ↔ human alignment
An LLM judge is only useful if you can trust it. The Alignment tab measures each judge’s agreement (precision, recall, Cohen’s κ) against human gold labels and lets you drill into disagreements. See Annotations & review for the full label-review-align loop, and Pre-prod evaluations for running your trusted judges against a candidate release.
The Run Targets and Comparison tabs
Two more tabs on this page go one level deeper on what a scorer runs on and which scorer model to trust:
- Run Targets — the library of reusable populations (a golden dataset, a live sample, a filtered sweep) that scorers run against, plus each target’s Used by list. Attaching a scorer to a Run Target is done from that scorer’s own Run Targets tab. Full guide: Run Targets.
- Comparison — holds one judge’s rubric fixed and scores the same window of traces with two or more candidate scorer LLMs, side by side, so you can see whether a cheaper model agrees closely enough to switch. Full guide, with a worked example: Cost Optimization → Compare scorer models side by side.
Run Targets and Comparison used to be separate pages in the sidebar. They now live as tabs on
Judges, alongside Judges and Alignment — everything about what a scorer measures and
what it runs on is in one place. An old /scorer-comparison link still works: it redirects to
the Comparison tab.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Run fails immediately, every target errored | No LLM connection visible to this agent | Add one in Settings → Connections, or pick a pool in the Run dialog. |
| Scores are 0 with reason “no evidence” | The judge’s evidence needs data the traces don’t carry (e.g. retrieved context on a non-RAG agent) | Check the live preview against a real trace; trim Evidence to attach to what your traces have. |
The prompt shows (the agent produced no user-facing reply in this trace) | The trace really has no assistant turn — it ended on a guardrail block, a tool failure, or a planning/retry decision | Expected for failed runs. If your agent does reply, its answer is probably on a span kind Neens doesn’t treat as a turn — check the trace’s Conversation tab, and see Traces & sessions. |
The prompt shows (no user request could be derived from this trace) | No span recorded the user’s message — common for scheduled/campaign agents that aren’t user-initiated | Record the request (or the trigger) as the input of the first LLM or agent span. |
| A judge’s scores changed after an upgrade without the rubric changing | The rubric was written against the raw span body, which is now the parsed reply | Re-read the Live preview on a real trace; tick Also inject raw JSON if the rubric genuinely needs the raw payload. |
The prompt ends in … [truncated: showing the first N of M characters] | A span body exceeded its block budget | Expected and safe — the model is told it’s an excerpt. Narrow Evidence to attach, or shorten what the agent logs. |
Run ends completed_with_failures | A minority of targets hit provider errors or unparseable verdicts | Open the run drill-down and read per-target errors; transient provider errors were already retried. |
| A composite skips most targets | Components have no scores and can’t be scored inline (missing connection, or non-LLM components) | Ensure component judges are LLM-prompt judges and a connection is configured. |