Agent evals: datasets, runs, and pass-rate gates
An eval run replays a fixed dataset of inputs against an agent version and lets an LLM judge score every response. Because the dataset and the rubric stay pinned, two runs compare cleanly — which is what makes a pass rate safe to gate a rollout on. This guide walks the full loop: declare a dataset, run it, triage the failures, and use the result as a gate. Endpoint paths below are relative. Send them againsthttps://api.orbit.devotel.io/api/v1. All routes need
agents:read / agents:write scope.
1. What an eval run expects
Every run binds three things, and the run row records all three so results stay attributable:- A dataset — up to 1,000 rows of
{ input, expected_output, metadata }. The runner sends eachinputto the agent and the judge compares the agent’sactual_outputagainst yourexpected_output. - A rubric — one of
correctness,helpfulness,safety,groundedness(faithfulness against retrieved knowledge-base context), orcustom(your ownrubric_prompt, required and up to 8,000 characters when you pickcustom). - A judge model — the clause model that grades each row. Omit
judge_modelto inherit the default (claude-haiku-4-5-20251001); when you pin one it must be an approved Claude model id or the run is rejected at submit with a422.
2. Declare a dataset
Create datasets from the Agents → your agent → Evals → Datasets page, or withPOST /agents/{agentId}/evals/datasets. Two event sources feed a
dataset; use both.
Scripted rows
Write rows by hand when you know the case you want covered — a canonical support question, a regression you just fixed, an edge case from a ticket.201 with the dataset id and row_count. At least one
row is required; the cap is 1,000 per dataset.
Promoted production conversations
The richer source is live traffic. One turn of a captured conversation’s execution trace becomes one{ input: user prompt, expected_output: assistant response } row:
include_non_success_turns defaults to true — on purpose, because
capturing the turns where the agent’s audit outcome was a failure is exactly
what you want in the golden set. Narrow the window with limit (defaults to
the most recent turns, max 200).
Read back what you have with GET /agents/{agentId}/evals/datasets (list)
and GET /agents/{agentId}/evals/datasets/{dsId} (rows). Dataset names and
rows are editable later — rename a set, fix a mislabeled
expected_output, or prune a stale case.
3. Run a suite
Start a run from the Evals → Runs page or withPOST /agents/{agentId}/evals/runs:
202 with status: "pending" — the run executes in the
background. Poll GET /agents/{agentId}/evals/runs/{runId} until the
status flips to completed (or failed), then read the aggregate
passed_rows, failed_rows, and avg_score on the same object.
GET /agents/{agentId}/evals/runs lists recent runs; filter with
?status=completed.
Pick a rubric that matches the failure you are guarding against. For a
knowledge-grounded agent, groundedness is the stronger check — it catches
answers that read well but are not in the retrieval context.
4. Read the failures
The run detail returns aresults array (up to 1,000 rows) with, per row:
actual_output, the judge’s score, a boolean passed, and
judge_reasoning, plus latency_ms and cost_cents. Triage in this order:
- Filter to
passed: false. Each failed row carries the exactdataset_row_id, so you always know which input regressed. - Read
judge_reasoningbefore touching the prompt. The judge names which rubric item failed — “no refund timeline given” is actionable; “score 62” alone is not. - Check
error_message. A row that errored (runtime fault, not a judge reject) shows the cause in the row itself; fix the fault before treating it as a quality regression. - Compare against the baseline run.
GET /agents/{agentId}/evals/runs/compare?run_a_id=<old>&run_b_id=<new>reports the same verdict on the same rows — it rejects with a400 EVAL_COMPARE_DATASET_MISMATCHif the two runs were not run against the same dataset.
5. Gate on a pass-rate threshold
Each run carries athreshold (0–100, default 70): the judge score a row
must reach to count as passed. Set it at submit time and keep it pinned
across runs so pass rates move only because the agent moved.
Wire the gate like this:
- Run the pinned dataset against the candidate version.
- Read
passed_rows/total_rows; require the pass rate to clear your gate (for example, all rows passed, or at least 90% on a noisy rubric). - Promote only then — via the canary ladder in the rollout pipeline guide, never by hand.
GET /agents/{agentId}/evals/runs/{runId} until completed, then assert
passed_rows / total_rows clears the gate before promoting. The threshold
stays a row-level judge cutoff; the gate asserts on the aggregate.
6. Running the eval loop from code
Every step above is scriptable — the same CI gate described in step 5 is a sequence of five calls. Each step below ships cURL, TypeScript, and Python side by side, matching the language floor in How request samples work. The TypeScript form uses the SDK’s untypedrequest method (the typed resources
do not yet expose eval helpers); the Python form uses plain requests.
1 — Create the dataset
201 Created — the full envelope, data plus meta:
201 Created
2 — Import a conversation
Enrich the same dataset with turns captured from production traffic:3 — Submit the run
202 Accepted with status: "pending" — the run
executes in the background and you poll for the terminal state:
202 Accepted
4 — Poll until the run completes
PollGET /agents/{agentId}/evals/runs/{runId} until status flips to
completed (or failed), then read the aggregate passed_rows /
failed_rows that the gate asserts on:
5 — Compare runs
Score the candidate against the frozen baseline (step 6), then gate on the verdict:400 — the error envelope
matches the shape in Error codes:
400 Error
7. Holdout vs experiment
Keep a holdout when you experiment.- Freeze a baseline. Save the current production prompt as a version and never edit it. Run the pinned dataset against that version once and keep the run id as your reference.
- Score experiments against the holdout. An experiment splits live
traffic between the control version (variant A) and the candidate
(variant B). Run the same dataset against both versions and use
GET /agents/{agentId}/evals/runs/comparefor the verdict — never compare a candidate against a dataset that drifted since the baseline was scored. - Delete stales. A baseline run on a dataset you have since edited is a false holdout. Re-run the baseline after any dataset change, and record both run ids on the experiment.
8. Failure modes
- A run stuck at
pending. The background runner never picked it up — re-submit and checkGET /agents/{agentId}/evals/runs?status=failedfor anerror_messageon the sibling rows. A run that never terminates almost always carries its cause on the failed run detail. - A rubric that passes trivially. If every row passes at 100 no matter
the input, the rubric is not discriminating — usually a
customprompt that grades “was an answer given” instead of a policy criterion. Rewrite the criterion against something the agent can actually violate. - Drift under traffic. The dataset ages as your product changes: a baseline you froze six weeks ago no longer covers today’s top contact reasons. Promote fresh failing conversations into the golden set regularly (everything in step 2) rather than growing a second dataset.
- Compare rejects with
EVAL_COMPARE_DATASET_MISMATCH. The two runs were scored on different datasets — both must run against the same dataset id for a row-level verdict. - A
422forcustomwith no prompt.rubric_promptis required whenrubric_nameiscustom; the run is rejected at submit so a miswritten gate never silently runs.
See also
- Agent versions — save the versions runs bind to.
- Continuous production evals — keep scoring a deterministic slice of live traffic.
- Simulation & Regression Eval Suite — scripted scenario gate for multi-turn conversations.
- Safely Roll Out an AI Agent — the canary ladder this gate feeds.
- Agent evaluation endpoints — the full route reference.
- How request samples work — the cURL/Node/Python floor the scripted loop above follows.
- Error codes — the envelope shape
EVAL_COMPARE_DATASET_MISMATCHarrives in.