AI coding

Grading Agent Output That Has No Assert: Rubrics and Judges

Antonio Evans10 min read

A split scene: on the left, a stack of printed agent transcripts with a red pen circling a paragraph, a sticky note reading 'claim not in source'; on the right, a simple rubric table with three rows (Grounding, Tone.
Key points

Key points

  1. 1When a deterministic assert cannot capture behavior, replace pass/fail with a rubric: a short list of criteria, each with observable anchors at two or three levels.
  2. 2A judge model scores output against the rubric, but the judge is only trustworthy after you sample outputs and compare its scores with human labels.
  3. 3Anchors, not the score range, do the work: vague 1 to 5 scales drift between runs, while observable descriptions hold agreement steady.
  4. 4Keep a person in the loop whenever a wrong pass is expensive, the rubric is still being written, or judge and human disagree.

Start by deciding that an assert cannot carry this test

Most agent failures that matter are not boolean. A coding agent can open a pull request that compiles, passes tests and still misses half the ticket, because an agent can pass a suite by editing the tests or by writing assertions that check nothing: a weakened assertion like toBeDefined() in place of toHaveLength(2) is exactly the failure mode review checklists are built to catch in the SpecStory review checklist. Research on AI-generated tests points the same way: a suite can reach roughly 85% coverage while killing only 57.3% of injected mutants, so most single-fault bugs went undetected in the Nimble Approach mutation experiment.

The security literature gives you the scale of the problem. One evaluation of code from five models found that almost half of the generated snippets contained bugs that were often impactful in the CSET report, and a widely cited analysis puts 45% of AI code with security flaws and 62% with design flaws or vulnerabilities per Security Journey. None of these are expressible as a single assert. "The endpoint validates input at the boundary and does not leak internal error details" is a judgment, not a string comparison.

So the decision rule is: if you can write an assert, write the assert. Reserve rubrics for output where several correct answers exist, where the quality dimension is tonal, structural or strategic, or where the harm comes from omission (a missing access check, a skipped validation step) rather than from a wrong value. Google's reviewer guidance is a useful frame here: the reviewer asks whether a change improves overall code health, not whether it is perfect in Google's review standard. That is inherently a graded judgment, which is why a rubric maps onto it better than a boolean.

Build the rubric: criteria first, anchors second, scale last

A rubric is a scoring guide that names the dimension, the scale and anchor descriptions that pin each level to something observable. Without anchors the judge interprets "1 to 5" however it likes on each call and the scores become noise. With anchors, the same rubric produces similar scores across runs, and agreement between judge and human rises from roughly 0.4 to about 0.78 Cohen's kappa as rubrics go from one line to anchored five-level scales, as reported in the Future AGI rubric glossary. Note the direction of that number: the model upgrade was not what moved agreement. The rubric was.

Practical construction order:

  1. Write the criteria from failures, not from virtues. Pull twenty real agent outputs, including at least five you rejected. For each rejection, ask what specifically was wrong. That sentence becomes a criterion. Criteria derived from "what good looks like" tend to overlap; criteria derived from "what went wrong" tend to be independent.
  2. Make each criterion atomic. One criterion should be scorable without deciding another. Bundling "accurate and well-toned" into one line forces the judge to average two independent judgments and hides which one failed.
  3. Write three anchors per criterion, not five. Three levels (fails, partial, meets) are enough resolution for a decision and far cheaper to keep distinct. Five levels invite hair-splitting that human labelers cannot reproduce consistently either.
  4. Anchor on observables. "Cites a source for each factual claim" beats "is well researched." "Names the files changed and why" beats "is clear."
  5. Allow an explicit abstain. A CANNOT_ASSESS verdict is preferable to forcing a score when the output lacks the information the criterion needs; the Autorubric framework treats that uncertainty handling as a first-class feature in the arXiv paper on unified rubric evaluation.

A worked example for a coding agent's pull request, which you can adapt:

CriterionFails (0)Partial (1)Meets (2)
Scope disciplineEdits files unrelated to the task with no stated reasonUnrelated edits present but explainedEvery changed file serves the task or has a documented reason
Test integrityDeletes, skips or weakens a test or lint ruleAdds tests that would pass on broken codeAdds tests that fail when the named behavior breaks
Failure handlingSwallows errors or logs without contextHandles the common path, misses one failure modeErrors stop the action or are reported with useful context
Access controlAdds a path with no server-side checkCheck exists but is incompleteAuth checks run server-side on every new path, no secret in the diff

That table is essentially a graded version of the checks teams already run before merging: requirement fidelity, no weakening of quality gates, auth on protected paths and dependency provenance each appear as pass conditions in the Metacto pre-merge checklist in the Metacto standards guide. The rubric does not invent new standards; it makes the existing ones scorable at volume.

Weights matter more than people expect. Give a negative criterion (penalize hardcoded secrets, penalize test tampering) a weight large enough that it can fail the whole output on its own, or it will be averaged away by three passing cosmetic criteria. A simple rule that holds up: any criterion where a single failure is unacceptable becomes a gate, not a weighted score.

Run the judge, and write the judge prompt like production code

The judge is a model with a rubric in its prompt and a structured response format. Promptfoo's llm-rubric assertion is a concrete reference implementation: it sends the output and the criteria to a grading model and asks for JSON with a reason, a score between 0 and 1 and a pass boolean in the promptfoo documentation. Whether you use that tool or your own wrapper, three properties matter.

Structured output with a reason. Require the reason before the score in the output schema. A judge that commits to a score and then rationalizes it behaves differently from one that states evidence and then grades. Reasons also give you the audit trail a human reviewer needs when they disagree.

One call per criterion. Scoring all criteria in one pass encourages the judge to let a strong impression on criterion A bleed into criterion B. Per-criterion atomic evaluation exists specifically to prevent that conflation per the rubric framework paper.

A panel when the decision is expensive. Run three judges, ideally from different model families, and aggregate by majority or weighted vote. Ensembles with configurable aggregation (majority, weighted, unanimous, any-vote) are a documented mitigation, not an exotic technique in the same framework. The cost is three calls per output; the benefit is that judge-specific quirks (verbosity preference, self-preference for outputs from the same family) partially cancel.

Two biases to guard against explicitly. Verbosity bias means longer answers score higher for reasons unrelated to quality, so either penalize length in the rubric or tell the judge to ignore it. Position bias matters when you compare two outputs side by side; shuffling the order across runs is the standard mitigation per the same paper. A practical defense is boring and effective: include two or three outputs in your calibration set that are long, fluent and wrong, and confirm the judge scores them low.

If you are running multiple judge models, a single gateway helps keep credentials and prompt versions consistent; Quanta Inference exposes text, image and embedding models behind one OpenAI-compatible key, which is convenient when the judge and the system under test are different model families (https://quantaintelligence.ai/inference).

Calibrate the judge against humans, and keep calibrating

Calibration is the whole exercise. A judge that agrees with no human is a random number generator with good manners, and its scores will look stable while measuring nothing. The operational target is inter-rater reliability, not raw accuracy: Cohen's kappa corrects for agreement that happens by chance, which raw accuracy inflates on imbalanced labels per Galileo's calibration guide.

A calibration loop that fits in a week:

  1. Label a stratified sample by hand. Take 50 to 100 outputs, deliberately over-sampling the rare and risky categories rather than sampling at random, because random sampling hides tail failures per Galileo. Have two people label independently so you can see how much humans agree with each other. That number is your ceiling; a judge cannot beat human-to-human agreement by much.
  2. Score the same sample with the judge. Compare per criterion, not just overall. One criterion with weak agreement is a rubric problem, not a judge problem, nine times out of ten.
  3. Rewrite the anchors where they disagree. If two humans disagreed, the anchor is ambiguous. If humans agreed and the judge did not, the anchor is probably fine and the judge prompt needs the disambiguation, or the judge is too weak for the task.
  4. Record kappa per criterion and keep the trend. A judge can agree closely with reviewers at first and drift silently after a provider-side model update; one older model version update caused a 9.6% accuracy drop for one prompt and a 5.1% increase for another on the same task per Galileo citing the arXiv study. Dashboards stay green while the measurement quietly stops meaning anything.

Rough thresholds from practice: a criterion at or above 0.6 kappa is usable for triage; between 0.4 and 0.6, use it only to route outputs to a human, never to auto-pass; below 0.4, do not ship the criterion, rewrite it. And remember that a clean single run proves nothing: one team reported a hallucination fix as verified at zero of five failures, then re-ran under harder conditions and got 66.7% in the freeCodeCamp account of spec-verify. Judge calibration has the same property. One comparison against one golden set is a snapshot, not a guarantee.

Where a person still has to look

The judge gives you scale. The human sample tells you whether the scale is real. Automating past that point is how teams ship a green dashboard over a real regression. Three conditions should force a human review regardless of judge score:

High cost of a wrong pass. Money movement, auth, PII, infrastructure and public APIs belong to an accountable human, even when the judge approves. The consistent position across the security literature is that review cannot rest solely on the automated layer per CSET, and practitioners recommend feeding corrections back into the review process rather than treating any single check as final per the Cloud Security Alliance writeup. A useful framing is that AI output can be treated as pre-reviewed only when your system can prove it, and the merge decision stays with a human on trust-boundary changes per Metacto.

Rubric still being written. While a criterion's anchors are under revision, its judge scores carry no weight. During that period, the human labels are the ground truth and the judge is being tested, not trusted.

Judge and human disagree. Log every disagreement with the output, the criterion, the judge's reason and the human's reason. Route it to the rubric owner weekly. A recurring disagreement pattern is the fastest rubric-improvement signal you will get.

For code specifically, a graded rubric does not replace reading the diff. The human checks that require context the judge does not have are the ones review guides emphasize: does the change do what the task asked, is there a named owner who can explain each file, is the design appropriate to this codebase rather than merely idiomatic per Google's what-to-look-for guide and the reviewer guide's emphasis on design and functionality covered in the BSSw summary of Google's guidance. Depth also degrades with volume: a long diff gets less scrutiny per line, which is why splitting agent-authored changes before grading them is worth the friction.

A practical sampling rule that balances cost and risk: auto-pass outputs that clear every gate; send outputs that fail any gate to a human; and send a random 5% of clean passes to human review anyway, because that sample is how you catch a judge that has drifted. When that 5% starts disagreeing, widen the sample until it agrees again.

A worked pass from zero to a calibrated judge

Here is the sequence with numbers, for a team of four engineers and a support bot that writes replies and a coding agent that opens pull requests.

  • Week one. Collect 60 past outputs, 20 of them known-bad. Write four criteria for the support bot and four for the coding agent, each with three anchors. This is one engineer-day per rubric and the highest-leverage day in the project.
  • Week two. Two engineers independently label all 60. Their own agreement lands around 0.7 to 0.8 kappa; anything below 0.6 means the anchors are still ambiguous and you rewrite before touching a judge.
  • Week three. Score the same 60 with a single judge, per criterion. Expect one or two criteria in the 0.4 to 0.6 band. Rewrite those anchors, rescore. Add a second judge only for criteria that stay below 0.6 or that gate on risk.
  • Week four. Turn on the pipeline: gate criteria fail the output automatically; scored criteria route to a human queue when below threshold; 5% of clean passes sampled for review. Cost per output is one or three judge calls, which is trivial compared with the human time you were spending on every change.
  • Ongoing. Weekly disagreement review, monthly recalibration on a fresh stratified sample, and a kappa trend chart per criterion. When the trend bends, recalibrate before you widen automation.

The insight that most teams miss is where the effort belongs. Recalibration looks like a pipeline review and front-loading the rubric looks like a delay, but the rubric and the anchors are the measurement instrument, and every hour spent on them buys more agreement than any judge swap. When the judge and the code under test come from the same model family, a panel of judges from a different family reduces self-preference; Quanta Inference is one way to reach several model families through a single prepaid, dollar-priced key (https://quantaintelligence.ai/inference).

Where the rubric work touches domain judgment outside your team's expertise, for example health, legal or financial review criteria, the criterion set is often better drafted with someone who already does that review. Quanta Experts read your context and hand back finished deliverables, which is a reasonable fit for a first-draft rubric plus anchor set on a domain your engineers do not own (https://quantaintelligence.ai/agents).

FAQ

FAQ

Can a judge model replace a test suite?

No. Deterministic asserts remain the right tool wherever behavior is pinnable, and they are cheap and stable. The rubric handles the residue that an assert cannot express: tonal quality, multi-step plan soundness, omission of a required control. Treat them as two layers of one stack, deterministic checks first, judged criteria second.

How many criteria should a rubric have?

Between three and seven. Below three, the score collapses into a single impression. Above seven, criteria start overlapping and labelers lose consistency. If you need more coverage than seven criteria give, split the rubric by output type rather than adding lines to one rubric.

What agreement do I need before trusting the judge?

Set the human-to-human agreement first and use it as the ceiling. A per-criterion Cohen's kappa at or above 0.6 is workable for triage; between 0.4 and 0.6, route to a human rather than auto-pass; below 0.4, rewrite the anchors. These are working thresholds, not universal constants, and the source guidance stresses per-criterion tracking over an aggregate score per Galileo.

How do I stop a judge from double-counting criteria?

Require one judge call per criterion and forbid the judge from referencing other criteria in its reasoning. This per-criterion atomic evaluation is a documented mitigation for conflation per the rubric framework paper. If two criteria still move together across your calibration sample, merge them; correlated criteria add cost without adding signal.

What should I log for every judged output?

The output itself, the rubric version, the judge model and prompt hash, the per-criterion score, the judge's reason, and any human label. Without the version and prompt hash you cannot tell a real quality change from a rubric or judge change, and without the reason a human reviewer has nothing to agree or disagree with.

Sources

Sources

  1. Code review checklist for AI-generated code - SpecStory
  2. Why Mutation Testing Is Essential for Trustworthy AI -
  3. Cybersecurity Risks of AI-Generated Code | Center for Security ... - CSET
  4. The Security Risks of AI-Generated Code: Common Risks & Fixes
  5. The Standard of Code Review | eng-practices - Google
  6. What Is a Rubric in LLM Evaluation?
  7. A Unified Framework for Rubric-Based LLM Evaluation
  8. AI-Generated Code Review Checklist: 2026 Standards for Teams - Metacto
  9. LLM Rubric
  10. How to Calibrate Your LLM Judge With Human Annotations
  11. How to Stop Letting AI Agents Fake Their Own Tests
  12. Understanding Security Risks in AI-Generated Code | CSA
  13. What to look for in a code review | eng-practices - Google
  14. Google Guidance on Code Review - Better Scientific Software (BSSw)
About the author

About the author

Antonio Evans
Antonio Evans

Antonio Evans is the founder of Quanta Intelligence and a fractional CTO who builds HIPAA-ready AI systems for health and life-science companies. Before that he was technical co-founder of FriendsEAT for twenty years, CEO of Socialdraft, and a data engineer at IBM. Antonio lives with his dog Santino in Lisbon, Portugal.

All articles