Building AI Agents with Claude Bonus · Model Landscape
Bonus Module ~70 min + lab Intermediate

Jev: The Cheap Judge That Can Still Cost You

Jev answers typed questions about your agent's evidence in a few hundred milliseconds for fractions of a cent. This module is about the question that price hides: where in a Claude Code build→verify→fix loop should a cheap judge sit, and when does adding one make the whole system worse?

🔗 Based on

Udaykiran Estari, “Jev + Claude Code: The Cheap Judge That Can Still Cost You” (Medium, September 2026, Part 3 of a series). The facts were cross-checked against TypeSafe’s API reference, Vercel’s integration guide, and the independent evaluations cited in Sources.

Jev launched in early access on 15 September 2026. Prices, limits and model versions below are as published that month, so check the API reference before relying on them.

Learning Objectives

  • Explain what a System One model like Jev is, and how it differs from a generative LLM such as Claude
  • Write Jev’s three question types (noul, choice, score) and read their typed answers, including the difference between probability and confidence
  • Explain why a schema guarantees the shape of a decision but never its correctness
  • Compare four placements of a judge in a coding-agent loop, and compute how a hard gate caps end-to-end recall
  • Design a shadow-mode rollout that earns a judge its authority one region at a time
  • Wire Jev into Claude Code as an advisory hook that can never block the agent

Prerequisites: M05 (tool use), M18 (evaluation). M22C (quality gates) and M26 (hooks) help with the last two sections.  |  Level: Intermediate

What Jev Is — A Model That Decides Instead of Writes

💡 Analogy: The Triage Nurse and the Attending Physician

Picture an emergency department with one senior physician. Before triage existed, every patient waited for the physician, whether they had a broken leg or a paper cut. The physician spent the night on paper cuts, and the patient with chest pain waited behind them.

A triage nurse fixes that. In ninety seconds the nurse asks a fixed set of questions and assigns a category: immediate, urgent, can wait. The nurse never diagnoses or treats anyone. The job is to decide, quickly and cheaply, who the physician sees first. But a triage protocol has a failure mode that every hospital designs around: the patient who looks fine and isn’t. Good hospitals never let triage send anyone home. Triage orders the queue; it does not empty it.

Jev is the triage nurse, and Claude is the physician. Jev reads the evidence a coding agent produces (test logs, diffs, run history) and answers a few fixed questions with numbers attached. Claude does the diagnosis and the fix. The rest of this module is about the patient who looks fine: the real regressionA bug that a code change introduces into something that used to work. Here: a test that passed before this diff and fails because of it. that Jev reads as a flaky testA test that sometimes passes and sometimes fails on the same code, usually because of timing, ordering or shared state. Rerunning it often “fixes” it, which is exactly why a real bug that looks flaky is dangerous..

🔧 Technical Definition

Jev is the first System One modelTypeSafe AI’s name for a class of models built for fast, structured decisions that software consumes directly. The name borrows from psychology’s “System 1”: fast, intuitive judgement, as opposed to slow, deliberate reasoning. from TypeSafe AI, released in early access on 15 September 2026. You send it two things: a state (the evidence, as a string or a JSON object) and a set of typed questions. It returns one typed answer per question, with probabilities attached. It never returns prose.

Claude produces text one token at a time, and each token depends on the last. That is autoregressive generationGenerating output one token at a time, where each new token is conditioned on every token before it. It is how Claude writes, and why long answers take longer.. TypeSafe says Jev instead uses parallel sampling: it produces all of its answers in one pass, because an answer is a category or a number, not a sentence. That is where the speed comes from.

There is one more difference. TypeSafe trains Jev for calibrationA model is calibrated when its stated probabilities match reality: of all the answers it gives with probability 0.8, about 80% should be right. A calibrated 0.55 is useful information; an overconfident 0.99 is not.: the goal is that its probabilities mean what they say. A calibrated 0.55 tells your code “genuinely unsure”, and your code can act on that.

The published characteristics

PropertyValueSource
Price$0.042 per 1M input tokens; output tokens are not chargedTypeSafe, Vercel model page
Latency70–500 ms end to endTypeSafe launch post
Context64k tokens per request; 32k for the state plus the longest single questionVercel guide, TypeSafe docs
EndpointPOST https://api.typesafe.ai/v1/systemoneTypeSafe API reference
Other ways inVercel AI GatewayVercel’s hosted proxy that serves many providers’ models behind one API and one bill. You reach Jev there through the Vercel AI SDK rather than TypeSafe’s native endpoint. (typesafe-ai/jev), Cloudflare Workers AI (typesafe/jev), OpenRouterVendor docs
Does not doWrite code, explain its reasoning, run multi-step investigationsTypeSafe docs
📈 Why It Matters

Here is the arithmetic that makes Jev tempting. A CIContinuous integration: the automated system that builds the code and runs the test suite on every change, before anyone merges it. pipeline that fails 2,000 tests a day, each with about 1,500 tokens of logs and diff, sends Jev 3 million input tokens a day. At $0.042 per million, that is about 13 cents a day. Sending the same 2,000 failures to Claude Sonnet 5 at $2 per million input tokens costs about $6 in input alone, before thinking and output tokens. That is a gap of roughly 48×. The rest of this module explains why the gap is real and still the wrong number to optimise.

A cheap decision is only useful if your code can read it. So before asking where Jev belongs, look at exactly what it returns and what those numbers mean.

The Three Question Types

Each question you send to Jev has an id you choose, a type, some instructions, and usually some criteria. Jev evaluates every question independently and in parallel. Adding a question never changes the answer to another one.

TypeYou sendYou get backLimits
noul (yes/no)Optional {"true": ..., "false": ...} descriptionsnoul: probability of “yes”, from 0 to 1—
choiceA map of option → descriptionchoice, probabilities for every option, confidence2–255 options
scoreAn ordered list of level descriptions, low to highscore (probability-weighted), legend, probabilities, confidence2–10 levels

A real response for the three questions this module uses throughout looks like this:

{ "model": "jev-synthetic-replay", "answers": { "is_regression": { "type": "noul", "noul": 0.14 }, "failure_type": { "type": "choice", "choice": "flaky", "probabilities": { "regression": 0.10, "flaky": 0.84, "environment": 0.03, "unknown": 0.03 }, "confidence": 0.58 }, "severity": { "type": "score", "score": 0.55, "legend": { "0": "Low: ...", "1": "Medium: ...", "2": "High: ..." }, "probabilities": { "0": 0.55, "1": 0.35, "2": 0.10 }, "confidence": 0.16 } }, "usage": { "input_tokens": 342, "output_tokens": 0 } }

This is the lab’s replay response for test F10, with the legend abridged. It follows the documented response shape, but the numbers are synthetic, not a recorded Jev response. A live response names a real model version, such as jev-1.13.0.

Probability vs. confidence: two different numbers

These are easy to mix up, and the policy code later depends on keeping them apart.

  • Probability is about one outcome. "flaky": 0.84 means Jev puts 84% on “flaky”.
  • Confidence is about the whole distribution: how concentrated it is, from 0 to 1. TypeSafe says it is derived from the probabilities but does not publish the formula. Two answers can both pick “flaky” at 0.84 and still differ in confidence, depending on how the remaining 16% is spread.
  • Score is a probability-weighted mean. In the example it is 0.55: between Low (0) and Medium (1), leaning Low. It is not a class label, so read probabilities if you need one.

Vercel’s guide adds a practical wrinkle: probabilities are rounded, so a distribution can sum to 0.99. Never check sum == 1.

⚠️ Same model, two vocabularies

The Medium article’s code uses the Vercel AI SDK (experimental_evaluate, AI SDK 7.0.105 or later). That SDK names things differently from the native API, and copying a field name from one into code for the other fails quietly:

  • Yes/no type: native "noul" → Vercel "boolean"
  • Yes/no answer: native answers.x.noul → Vercel answers.x.probability
  • Confidence: native answers.x.confidence → Vercel result.providerMetadata?.typesafe?.confidence?.x
  • Model id: native "jev-latest" or a pinned "jev-1.13.0" → Vercel "typesafe-ai/jev"

Writing good questions

The Vercel guide gives three rules, and all three apply here:

  1. Ask atomic questions, ones a person could answer in seconds. Combine the answers in code, not in the question.
  2. Describe options; don’t just label them. “The diff plausibly causes the observed failure” gives the model far more to work with than “yes”.
  3. Send only the relevant state. Every token of state is billed and dilutes the evidence. A diff hunkOne contiguous block of changed lines in a diff, with a few lines of context around it. Sending only the hunk that touches the failing code keeps the state small and on topic. beats the whole diff.
A typed answer is guaranteed to be well-formed. The next section is about what it is not guaranteed to be.

“Typed” Prevents Malformed Answers, Not Wrong Ones

TypeSafe markets Jev as a model that “can’t hallucinate”. That is true in a narrow and useful sense. If a question allows only regression, flaky, environment and unknown, Jev cannot invent a fifth category, wrap its answer in an apology, or return JSON your parser chokes on. A whole class of production bugs goes away: the ones where a generative model answers "Probably flaky, but…" and your if statement never matches.

What typing cannot do is make the chosen category correct. Jev can pick flaky, a perfectly valid option, for a test that fails because the diff removed a lock. Vercel’s guide says it plainly: the schema constrains the answers but doesn’t guarantee they’re correct. Arize’s independent analysis makes the same distinction: a schema-correct classification can still be wrong.

This matters because of what happens after the wrong answer. If Jev’s answer is one signal among several, a wrong answer is a weak signal that other checks can outvote. If Jev’s answer decides whether Claude ever sees the failure, a wrong “flaky” means the stronger model never gets the chance to catch it.

❌ Common Misconceptions
  • “It can’t hallucinate, so it’s always right.” It can’t produce a malformed answer. It can produce a wrong one.
  • “A low regression probability means the change is safe.” It means Jev leans away from “regression” on this evidence. Absence of a signal is not proof of correctness.
  • “It’s 50× cheaper, so the system is 50× cheaper.” The judge’s bill is rarely the expensive part. A missed regression that ships costs more than a year of Jev calls.
  • “Calibrated means accurate.” Calibrated means the probabilities are honest. A calibrated judge that says 0.55 is telling you it doesn’t know, and your policy has to handle that.
  • “It beat Sonnet on a benchmark, so it will improve my loop.” The benchmark compared judges on one task. Your loop is a different system (see The Published Numbers).
So Jev can be wrong in a well-formed way. Whether that is harmless or dangerous depends entirely on where it sits in the loop.

Where Jev Sits in a Build → Verify → Fix Loop

A coding agent like Claude Code works in a loop: edit the code, run the checks, read what failed, fix it, check again. The tempting design drops Jev into the middle. Deterministic tools run, Jev cheaply screens whatever fails, and Claude only investigates what Jev considers important.

The order of the stages is the most important design decision in this module. Compilers, linters, type checkers, test runners, security scanners and invariant checks come first, and they stay authoritative wherever they can give a deterministic verdict. A type error is a type error; no model should be asked to “judge” it. Jev belongs after those tools, working on the evidence they produce, never in their place. And the step after Jev is not Claude directly but policy code: ordinary, testable code that turns Jev’s numbers into an action.

The loop, one failing test at a time
1 · Build / editClaude changes code
2 · Deterministic checkstests, types, lint, scanners
3 · Evidencelogs + diff + run history
4 · Jevtyped signals, ~ms
5 · Policy codererun / investigate / review
6 · Claude investigatesthe selected evidence
7 · FixClaude edits again
8 · Re-verifyback to step 2

Press play to follow one failure through the loop.

📈 Why It Matters

Every stage you move ahead of a deterministic check trades a certain answer for a probable one. A 50-millisecond type checker is right every time and costs nothing. Putting a model before it saves nothing and adds a way to be wrong. Jev earns its place only on the evidence the deterministic tools cannot classify, such as “is this failure caused by the diff, or by a slow CI runner?”

“Put Jev in front of Claude” actually describes four different systems. Each one gives Jev’s mistakes different consequences.

Four Architectures, Four Different Costs of Being Wrong

PatternWhat Jev controlsMain benefitMain risk
Hard gateWhether Claude sees an item at allMaximum call reductionFalse negativesA real problem the judge scored as “not a problem”. Here, a genuine regression Jev called flaky or environmental. become invisible
Soft prioritizerOrder and urgencyFaster attention, nothing discardedLow-ranked items may still be neglected
Independent signalOne input to deterministic policyCombines semantic judgement with explicit rulesMore policy and monitoring work
Uncertainty routerWhich ambiguous items escalateAutomates clear cases, keeps a fallbackThresholdsThe cut-off values your policy code compares Jev’s probabilities against, such as “investigate when P(regression) ≥ 0.75”. Choosing them sets the trade-off between misses and false alarms. need calibrating on your data

Why a hard gate caps the whole pipeline

RecallOf all the real regressions, the share that got caught. 90% recall means 1 real regression in 10 slipped through. is the share of real regressions that get caught. With a hard gate in front, a regression is caught only if both stages catch it: the gate has to pass it and the agent has to recognise it. Ignoring correlated errors, the probabilities multiply:

end-to-end recall = gate recall × downstream recall = 0.90 × 0.97 = 0.873 (87.3%)

That example is illustrative, not a Jev benchmark. The point it makes is structural. The 97%-capable downstream model didn’t get worse. The system got worse, because 10% of the real regressions never reached it. The gate’s recall is a ceiling on everything after it. Drag the sliders to see how fast that ceiling bites:

Recall multiplication calculator
90%
97%
End-to-end recall 87.3%: per 1,000 real regressions, 127 ship.

The same 24 failures, two placements

This is the lab’s data: 24 CI failures from a fictional order-tracking service, 10 of them real regressions. The Jev answers are the lab’s synthetic fixtures. Watch where each failure lands, especially F10, a release-blocking race condition that Jev reads as flaky.

Hard gate vs. uncertainty router
Claude investigates
Rerun (passed)
Never seen by Claude

Chip colours: amber = real regression, red = real HIGH-severity regression, green ✓ = regression Claude caught, grey = flaky/environment.

Under the hard gate, Claude sees 9 failures and the pipeline catches 7 of the 10 regressions: 70%. The uncertainty router sends Jev’s unsure cases to Claude and reruns its confident “flaky” and “environment” cases, escalating any rerun that fails again. F10’s rerun fails, so it reaches Claude. The router catches 9 of 10, the same as sending everything to Claude, with 14 Claude calls instead of 24.

The soft prioritizer deserves one warning. It keeps everything visible, so on paper its recall equals the baseline. But if the queue is cut at the same 9 items the gate allowed, because the reviewer ran out of time, it catches exactly what the gate caught. A queue nobody drains is a gate with extra steps.

Optimise expected total cost, not the API bill

expected total cost = model calls + retries and review + integration operations + false-positive work + expected cost of false negatives

If the workflow processes 10,000 items and one false negative can ship a serious regression, saving a few thousand model calls is not automatically an improvement. In the lab, with illustrative costs of $5,000 for a missed high-severity regression and $500 for a medium one, the hard gate saves $0.035 in API calls and adds $5,500 in expected losses.

💰 The cheapest component is not the cheapest system

In the lab, Jev screens all 24 failures for about $0.0003. Claude Sonnet 5 investigating all 24 cost $0.056 in a live run: 506 input and 132 output tokens per failure on average. The judge’s price was never the expensive part. The only question that matters is what its mistakes cost after they leave it.

The architectures show what could go wrong. The published numbers show how well Jev actually judges, as long as you read them for what they measured.

What the Published Numbers Actually Show

Ben Greenberg: Jev vs. Claude on one bounded decision

Greenberg took 102 archived submissions and ran each one three times, for 306 decisions. Every decision applied a fixed policy to an evidence packet and picked one of three states: satisfied, not satisfied, or insufficient evidence. The post reports:

  • Accuracy: Jev scored 100.0% with a choice question plus four diagnostic noul questions. Claude Sonnet 5 with high reasoning scored 99.0%. Jev’s choice question on its own made 2 errors in 306. Rebuilding the policy in code from the four nouls did worst, at 94.1%.
  • Median latency: 378 ms for Jev, 3,554 ms for Sonnet
  • Cost per 10,000 evaluations: about $2.27 vs. $129.74, roughly 57×
  • Expected Calibration Error (ECE)How far a model’s stated confidence is from its actual accuracy, averaged across confidence levels. 0 is perfectly calibrated; lower is better.: 0.037 for Jev vs. 0.058 for Sonnet. Sonnet’s few errors sat in its 0.9–1.0 confidence band. Both of the choice-only Jev errors sat at 0.2–0.3.

Two lessons come out of this for system design. First, a policy hand-assembled from several signals is not automatically better than a well-posed single question; the hand-built one was the weakest setup in the post. Keep policy code simple, and measure it like everything else.

Second, and most useful, is calibration. An error made at 0.25 confidence can be routed to review. An error made at 0.95 confidence goes straight through any threshold. Greenberg calls his setup “a deliberately unfair place to make a sweeping model comparison”, and notes that Jev does not replace the parts of his workflow that need code analysis and written explanations.

Arize: Jev as an LLM-as-judge replacement

Arize compared Jev with LLM judgesAlso called LLM-as-judge: using a general-purpose language model to grade or classify another system’s output, usually with a rubric in the prompt and an explanation in the reply. Covered in M18. on hallucination detection. As summarised in its published write-up, Jev matched Claude Opus 5 at 87% accuracy on a held-out set after each judge’s decision threshold was tuned, at roughly 1/300 of the cost and 23× the speed. Arize describes the evidence as promising but early and task-dependent. It also names the biggest loss: Jev gives no explanation, so its suggested pattern is to run Jev on every trace and re-run a sample of the failures through an LLM judge for the reasoning.

⚠️ What these results do not show

Both are judge-vs-judge comparisons on one bounded task. Neither measures a coding-agent loop with Jev in front of Claude. “Jev matched Sonnet at classifying X” says nothing about the recall of a pipeline where Jev decides what Sonnet gets to see. Jev’s price and latency are broadly attractive, but its accuracy has to be established separately for each question, dataset, threshold and position in your workflow.

Time to build it. The screening layer below keeps Jev in its lane: it produces signals, and plain code owns every routing decision.

A Runnable Screening Layer

One function sends a failing test’s evidence to Jev and returns one of three actions. The Python and Node.js tabs call the native REST API directly. The third tab shows the same logic through the Vercel AI SDK, which is the form the source article uses, so you can see the naming differences side by side.

  1. Chunk 1, the questions. What: three atomic questions: a noul, a four-way choice that includes unknown, and a three-level score. Why: the unknown option gives Jev an honest way to say “not enough evidence” instead of forcing a guess into another bucket. Gotcha: the criteria are sentences, not labels. “The failure is probably flaky, environmental, or unrelated” is a much better “false” than “no”.
  2. Chunk 2, the call. What: one POST with a bearer token and a 10-second timeout, retrying only on 429 (rate limited) and 529 (overloaded). Why: those are the two statuses TypeSafe documents as retryable. A 422 is your bug, and retrying it just burns time. Gotcha: in Node, fetch has no timeout of its own, hence AbortSignal.timeout().
  3. Chunk 3, failing toward scrutiny. What: any Jev failure (network, auth, timeout) returns review-ambiguous-case. Why: a judge that is down must never turn into a judge that says “fine”. Gotcha: a bare except: return "rerun" here would silently turn every Jev outage into suppressed failures.
  4. Chunk 4, the policy. What: three thresholds on three different numbers: regression probability, choice confidence, and the chosen option’s probability. Why: the policy lives in code, where you can read it, test it and version it. Gotcha: the thresholds are examples. A security gate, a flaky-test router and a docs scorer need different cutoffs, chosen from labelled cases like your own.
"""Screen one failing CI test with Jev. Python 3.10+; pip install "httpx>=0.27"; export TYPESAFE_API_KEY=..."""
import os
import time

import httpx

API_URL = "https://api.typesafe.ai/v1/systemone"
MODEL = "jev-latest"  # pin e.g. "jev-1.13.0" once your thresholds are tuned against it

# --- Chunk 1: atomic questions; criteria DESCRIBE each option ---
QUESTIONS = {
    "is_regression": {
        "type": "noul",
        "instructions": "Did the current code change probably cause this test failure?",
        "criteria": {"true": "The diff plausibly causes the observed failure.",
                     "false": "The failure is probably flaky, environmental, or unrelated to the diff."},
    },
    "failure_type": {
        "type": "choice",
        "instructions": "What best explains this failure?",
        "criteria": {"regression": "The current diff introduced a product defect.",
                     "flaky": "The test is nondeterministic or timing-sensitive.",
                     "environment": "Infrastructure or configuration caused the failure.",
                     "unknown": "The evidence is insufficient to choose another category."},
    },
    "severity": {
        "type": "score",
        "instructions": "How strongly should this failure block the change?",
        "criteria": ["Low: non-blocking or clearly unrelated",
                     "Medium: plausible issue requiring more evidence",
                     "High: likely release-blocking regression"],
    },
}


class JevError(RuntimeError):
    pass


# --- Chunk 2: one call, retry only what the API says is retryable ---
def ask_jev(state: dict, attempts: int = 3) -> dict:
    key = os.environ.get("TYPESAFE_API_KEY")
    if not key:
        raise JevError("TYPESAFE_API_KEY is not set")
    body = {"model": MODEL, "state": state, "questions": QUESTIONS}
    for attempt in range(1, attempts + 1):
        try:
            r = httpx.post(API_URL, json=body, timeout=10.0,
                           headers={"Authorization": f"Bearer {key}"})
        except httpx.HTTPError as e:
            if attempt == attempts:
                raise JevError(f"network error: {e}") from e
        else:
            if r.status_code == 200:
                return r.json()["answers"]
            if r.status_code not in (429, 529) or attempt == attempts:
                raise JevError(f"HTTP {r.status_code}: {r.text[:200]}")
        time.sleep(0.5 * 2 ** attempt)
    raise JevError("unreachable")


def route(state: dict) -> str:
    # --- Chunk 3: a judge that is down fails toward MORE scrutiny ---
    try:
        a = ask_jev(state)
    except JevError as e:
        print(f"[jev] unavailable, escalating: {e}")
        return "review-ambiguous-case"

    # --- Chunk 4: policy in code. EXAMPLE thresholds -- tune on your own labelled cases ---
    p_reg = a["is_regression"]["noul"]
    ft = a["failure_type"]
    type_prob = ft["probabilities"].get(ft["choice"], 0.0)
    if p_reg >= 0.75 and ft.get("confidence", 0.0) >= 0.60 and type_prob >= 0.70:
        return "investigate-with-claude"
    if p_reg <= 0.20 and ft["choice"] in ("flaky", "environment") and type_prob >= 0.75:
        return "rerun-and-collect-evidence"
    return "review-ambiguous-case"


if __name__ == "__main__":
    failure = {
        "testName": "test_sla_credit_concurrent_updates",
        "stdout": "FAILED tests/test_sla.py::test_sla_credit_concurrent_updates\n"
                  "assert po.credit_total == 2\n  where po.credit_total = 4",
        "stderr": "",
        "diffHunk": "-    with po_lock(po.id):\n-        apply_credit(po)\n+    apply_credit(po)",
        "recentRuns": [{"passed": True, "commit": "a41c"}, {"passed": False, "commit": "b7e2"}],
    }
    print(route(failure))
// Screen one failing CI test with Jev. Node 18+ (built-in fetch). TYPESAFE_API_KEY=... node screen.mjs
const API_URL = "https://api.typesafe.ai/v1/systemone";
const MODEL = "jev-latest"; // pin e.g. "jev-1.13.0" once your thresholds are tuned against it

// --- Chunk 1: atomic questions; criteria DESCRIBE each option ---
const QUESTIONS = {
  is_regression: {
    type: "noul",
    instructions: "Did the current code change probably cause this test failure?",
    criteria: {
      true: "The diff plausibly causes the observed failure.",
      false: "The failure is probably flaky, environmental, or unrelated to the diff.",
    },
  },
  failure_type: {
    type: "choice",
    instructions: "What best explains this failure?",
    criteria: {
      regression: "The current diff introduced a product defect.",
      flaky: "The test is nondeterministic or timing-sensitive.",
      environment: "Infrastructure or configuration caused the failure.",
      unknown: "The evidence is insufficient to choose another category.",
    },
  },
  severity: {
    type: "score",
    instructions: "How strongly should this failure block the change?",
    criteria: [
      "Low: non-blocking or clearly unrelated",
      "Medium: plausible issue requiring more evidence",
      "High: likely release-blocking regression",
    ],
  },
};

class JevError extends Error {}
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

// --- Chunk 2: one call, retry only what the API says is retryable ---
async function askJev(state, attempts = 3) {
  const key = process.env.TYPESAFE_API_KEY;
  if (!key) throw new JevError("TYPESAFE_API_KEY is not set");
  for (let attempt = 1; attempt <= attempts; attempt++) {
    let r;
    try {
      r = await fetch(API_URL, {
        method: "POST",
        headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
        body: JSON.stringify({ model: MODEL, state, questions: QUESTIONS }),
        signal: AbortSignal.timeout(10_000), // fetch has no timeout of its own
      });
    } catch (e) {
      if (attempt === attempts) throw new JevError(`network error: ${e.message}`);
    }
    if (r) {
      if (r.status === 200) return (await r.json()).answers;
      if (![429, 529].includes(r.status) || attempt === attempts) {
        throw new JevError(`HTTP ${r.status}: ${(await r.text()).slice(0, 200)}`);
      }
    }
    await sleep(500 * 2 ** attempt);
  }
  throw new JevError("unreachable");
}

export async function route(state) {
  // --- Chunk 3: a judge that is down fails toward MORE scrutiny ---
  let a;
  try {
    a = await askJev(state);
  } catch (e) {
    if (!(e instanceof JevError)) throw e;
    console.error(`[jev] unavailable, escalating: ${e.message}`);
    return "review-ambiguous-case";
  }
  // --- Chunk 4: policy in code. EXAMPLE thresholds -- tune on your own labelled cases ---
  const pReg = a.is_regression.noul;
  const ft = a.failure_type;
  const typeProb = ft.probabilities?.[ft.choice] ?? 0;
  if (pReg >= 0.75 && (ft.confidence ?? 0) >= 0.6 && typeProb >= 0.7) return "investigate-with-claude";
  if (pReg <= 0.2 && ["flaky", "environment"].includes(ft.choice) && typeProb >= 0.75) {
    return "rerun-and-collect-evidence";
  }
  return "review-ambiguous-case";
}

const failure = {
  testName: "test_sla_credit_concurrent_updates",
  stdout: "FAILED tests/test_sla.py::test_sla_credit_concurrent_updates\nassert po.credit_total == 2\n  where po.credit_total = 4",
  stderr: "",
  diffHunk: "-    with po_lock(po.id):\n-        apply_credit(po)\n+    apply_credit(po)",
  recentRuns: [{ passed: true, commit: "a41c" }, { passed: false, commit: "b7e2" }],
};
console.log(await route(failure));
// Same policy through the Vercel AI SDK (ai >= 7.0.105) and Vercel AI Gateway.
// npm i ai ; vercel link && vercel env pull   (local OIDC tokens expire after 12h -- re-pull on 401)
// NOTE the different vocabulary: "boolean" not "noul", .probability not .noul,
// confidence under providerMetadata, model id "typesafe-ai/jev".
import { experimental_evaluate as evaluate } from "ai";

export async function route(failure) {
  let result;
  try {
    result = await evaluate({
      model: "typesafe-ai/jev",
      state: failure,
      questions: {
        isRegression: {
          type: "boolean",
          instructions: "Did the current code change probably cause this test failure?",
          criteria: {
            true: "The diff plausibly causes the observed failure.",
            false: "The failure is probably flaky, environmental, or unrelated to the diff.",
          },
        },
        failureType: {
          type: "choice",
          instructions: "What best explains this failure?",
          criteria: {
            regression: "The current diff introduced a product defect.",
            flaky: "The test is nondeterministic or timing-sensitive.",
            environment: "Infrastructure or configuration caused the failure.",
            unknown: "The evidence is insufficient to choose another category.",
          },
        },
      },
      providerOptions: { gateway: { zeroDataRetention: true } },
    });
  } catch (e) {
    console.error(`[jev] unavailable, escalating: ${e.message}`);
    return "review-ambiguous-case";
  }

  const { isRegression, failureType } = result.answers;
  const confidence = result.providerMetadata?.typesafe?.confidence?.failureType ?? 0;
  const typeProb = failureType.probabilities?.[failureType.choice] ?? 0;

  if (isRegression.probability >= 0.75 && confidence >= 0.6 && typeProb >= 0.7) {
    return "investigate-with-claude";
  }
  if (isRegression.probability <= 0.2 && ["flaky", "environment"].includes(failureType.choice) && typeProb >= 0.75) {
    return "rerun-and-collect-evidence";
  }
  return "review-ambiguous-case";
}

With the lab’s fixture for this failure (noul 0.14, flaky at 0.84, confidence 0.58), the output is:

rerun-and-collect-evidence
✔️ What Just Happened?

Jev looked at a test that fails because the diff removed a lock, and called it flaky with a regression probability of 0.14. The policy did exactly what it was told and routed it to a rerun. Nothing in this function is wrong; the judge was wrong in a well-formed way.

That is why the next step matters so much. Behind a hard gate, this failure is gone. Behind an uncertainty router, the rerun fails again (it is a real race), the case escalates, and Claude reads the diff and finds the lock. The code is identical; only the architecture decides the outcome.

You can now build any of the four architectures. The only honest way to choose between them is to run them all against cases where you already know the answer.

Measure the System You Are Actually Deploying

Model accuracy alone is not enough. Before changing the loop, freeze a set of historical cases with known outcomes. Then run the complete variants against it, not just the judge:

  1. Claude-only baseline: every eligible item goes straight to the current coding model.
  2. Jev-only judgement: is the bounded question itself one Jev answers well?
  3. Jev hard gate → Claude: only Jev-positive items reach the coding model.
  4. Jev soft priority → Claude: everything stays visible, but Jev sets the order.
  5. Jev uncertainty router: clear cases follow policy, and ambiguous ones escalate.
  6. Cheaper general-model baseline: before adding a whole new component, try a smaller Claude tier such as Haiku 4.5 in the same position.

For every variant, capture:

  • End-to-end recall and precisionOf everything the pipeline flagged as a regression, the share that really was one. Low precision means engineers waste time on false alarms., not just the judge’s standalone accuracy
  • False negatives broken down by severity. One missed high-severity regression outweighs ten missed cosmetic ones.
  • The percentage of items escalated to Claude
  • Total input and output tokens across the whole path
  • Median and p95 latencyThe latency that 95% of requests beat. The slow tail is what users and CI timeouts feel, even when the median looks fine.
  • Retries, timeouts, and what happens when the model is unavailable
  • Human-review volume
  • The estimated cost of a missed defect, not only the API cost

The lab’s Step 3 does exactly this for four of the variants. Its output, in replay mode with recorded agent verdicts, is:

architecture to Claude reached recall precision API $ expected total $ claude-only 24 100% 90% 90% 0.056 500.06 hard gate 9 80% 70% 100% 0.021 6,000.02 soft priority 24 100% 90% 90% 0.056 500.06 soft priority (cut at 9) 9 80% 70% 100% 0.021 6,000.02 uncertainty router 14 100% 90% 90% 0.033 500.63 Gate recall x agent recall = 0.80 x 0.90 = 0.72 (observed end-to-end: 0.70)
✔️ What Just Happened?

The hard gate has the best precision on the table (100%) and the worst outcome. Precision only looks at what was flagged; it cannot see what was dropped. Reporting a gated pipeline’s precision alone would make it look like an upgrade. The multiplication line predicts 0.72 and observes 0.70. They differ because errors correlate: the cases a judge finds hard are often the ones an agent finds hard too. That is why you measure instead of just multiplying.

🔬 Measured: the same comparison with real Claude verdicts

The table above uses recorded verdicts, so its numbers are reproducible. On 2026-09-25 we reran it with --live-agent: Claude Sonnet 5 investigated for real, while Jev stayed on the synthetic fixtures. The run made 24 calls and cost $0.056.

architecture to Claude recall precision API $ claude-only 24 100% 91% 0.056 hard gate 9 80% 89% 0.021 uncertainty router 14 100% 91% 0.033

Real Claude was a better agent than the stand-in: it caught every regression it saw, and made one false positiveSomething flagged as a problem that isn’t one. Here: a failure Claude called a regression when it was really environmental. It costs an engineer’s time, not a shipped bug.. The hard gate still scored 80%, because F09 and the high-severity F10 never reached Claude. Improving the agent behind a gate cannot raise recall above the gate’s own. The gate is the ceiling. (Only Claude’s verdicts were measured here. The Jev answers remain synthetic.)

🎓 Cert Tip: evaluation and guardrails

This is the exam’s evaluation domain in a new setting. Expect questions that contrast a component metric (a judge’s accuracy, a guardrail’s precision) with a system metric (end-to-end recall), and that ask which is the right acceptance criterion. The answer is almost always the system metric, measured on a frozen, labelled set. See M18 for eval-set design and M17 for why guardrails escalate to a human instead of silently passing.

Measurement tells you whether Jev helps in your loop. Experience with the model tells you where to look first.

When Jev Is a Strong Fit, and When It Isn’t

Jev is most compelling when a decision is narrow, repeated, measurable and reversible:

  • Classifying a failure as regression, flaky, environment or unknown
  • Prioritising a large queue of test failures or review comments
  • Routing clear cases while escalating uncertain ones
  • Scoring evidence against an explicit rubric
  • Producing typed signals for deterministic policy
  • Running an eval on every trace, where an LLM judge would only be affordable on a sample

It is a weaker fit when the task needs repository-wide causal reasoning, arithmetic, multi-step investigation, weighing contradictory evidence, an explanation, or a generated fix. Those remain jobs for deterministic tools, a capable coding model such as Claude, or a human reviewer.

📈 Why It Matters

“Reversible” is the word teams skip. Rerunning a test Jev called flaky is reversible: if it fails again, you escalate and have lost a few CI minutes. Auto-merging a change Jev scored low-risk is not reversible, because the regression is in production before anyone looks. Use the same model with the same accuracy in the first place and never in the second.

Given all that, how should Jev enter a real loop? Slowly, and without authority at first.

The Rollout to Trust: Start in Shadow Mode

Do not begin with a hard gate. Begin with shadow modeRunning a new component beside the live system on real traffic, logging what it would have done, without letting it change a single outcome. You compare its would-be decisions with what actually happened.: run Jev beside the existing workflow without changing any route. Promote it one region at a time, only when the disagreement data says it has earned that authority.

Promotion ladder: each rung needs evidence
1
Shadow

Jev runs on every failure; routes are unchanged. Log probabilities, choices, confidence, latency, cost.

exit when: enough volume to measure each region
2
Study disagreements

Join the log with final outcomes. Investigate every case Jev would have sent away that was a real regression.

exit when: every false negative is explained
3
Soft prioritisation

Jev sets the order; nothing is suppressed. Claude still sees every failure.

exit when: queue is reliably drained, no regressions found late
4
Uncertainty routing

Clear cases follow policy (rerun, then escalate on repeat failure); unsure cases always escalate.

exit when: per-region miss rate is inside your budget
5
Automate a narrow region

Only where errors are rare, cheap and reversible. Keep fallbacks for low confidence, timeouts, schema changes and outages.

always: a tested rollback at every rung

Press play to climb the ladder.

The lab’s Step 4 turns a shadow log into a recommendation for each region:

Jev region cases regressions recommendation investigate-with-claude 9 8 precision 8/9: safe to PRIORITISE these for Claude now review-ambiguous-case 3 1 keep escalating: ambiguity is exactly what the stronger model is for rerun-and-collect-evidence 12 1 1 regression(s) hidden here, 1 HIGH (F10). Promote only to rerun-then-escalate; never to suppress

This is less dramatic than dropping a cheap judge into every step of the loop. It is also how you find out whether the new component saves money in your system, rather than merely having a low list price.

One place Jev can help from day one, because it holds no authority there, is inside Claude Code itself, as a hint.

Jev Inside Claude Code: An Advisory Hook

A PostToolUse hookA command Claude Code runs after a tool call finishes. It receives the call and its result as JSON on stdin, and can reply on stdout, for example to add context for Claude or, if you choose, to block. Covered in M26. runs after every tool call. The lab ships one that watches for Bash test commands. When a test fails, it asks Jev about the failure and hands Claude one line of additionalContextA field in a hook’s JSON reply whose text is added to what Claude sees next. It informs; it does not stop or rewrite anything.. That line is phrased as a hint, and Claude still decides.

{"hookSpecificOutput": {"hookEventName": "PostToolUse", "additionalContext": "Jev advisory for test_sla_credit_concurrent_updates (a cheap typed screen, NOT a verdict): looks flaky (p=0.84, confidence 0.58); P(caused by this diff)=0.14; suggested action: rerun-and-collect-evidence. Treat this as one signal. Read the diff yourself before dismissing any failure as flaky."}}

Note that the advice is wrong: this is F10 again. That is the whole design. Because the hook only advises, a wrong hint costs one sentence of context, and Claude, reading the diff, sees the removed lock. The hook deliberately never does three things:

  • Block: no "decision": "block". A cheap judge must not gate the agent.
  • Rewrite: no updatedToolOutput. Claude sees the real test output.
  • Fail shut: if Jev is down, the hook prints nothing and exits 0.
🔒 Three silent hook failures (all of which “work”)
  • Reading tool_result instead of tool_response. tool_result is a Messages API content-block type, not a hook field. The hook reads nothing and exits 0.
  • Echoing the payload to stdout “to pass it through”. Stdout is the decision channel, so a payload there is a malformed decision. Diagnostics go to stderr.
  • A flat {matcher, command} in settings.json. The real shape is event → [{matcher, hooks: [{type, command, timeout}]}]; the flat one parses and never fires.
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python \"$CLAUDE_PROJECT_DIR/labs/BONUS-jev-judge/hooks/jev_advisory_hook.py\"",
            "timeout": 15
          }
        ]
      }
    ]
  }
}

The hook itself (Python and Node.js) is in labs/BONUS-jev-judge/hooks/. For the full hook contract, see M26. For blocking gates that should block, because they are deterministic, see M22C.

Hands-On Lab: labs/BONUS-jev-judge

The lab works on 24 frozen CI failures from the fictional Northwind order-tracking service (course Domain B), in Python and Node.js. It runs free with no Jev key: without TYPESAFE_API_KEY, Jev answers come from synthetic replay fixtures in the documented response shape. Replay proves your code handles real responses. It says nothing about Jev’s accuracy, and the lab says so every time it runs.

StepYou writeYou’ll see
1 · ScreenThe Jev call and the policyF10, a real race, routed to “rerun”
2 · InvestigateOne Claude call returning a validated JSON verdictClaude naming the removed lock
3 · CompareHard gate, soft priority and uncertainty router routes70% vs. 90% end-to-end recall on identical signals
4 · ShadowPromotion rules for each region“Never suppress the rerun region”, backed by data
5 · HookNothing: read and wire upA wrong hint that costs nothing

Start with labs/BONUS-jev-judge/README.md. Everything is Tier 1: raw REST for Jev and the raw Messages API for Claude.

Knowledge Check

1. Jev’s schema allows only regression, flaky, environment or unknown. What does that guarantee?

AThat the chosen category is correct
BThat Jev will say “unknown” whenever it is unsure
CThat the answer is one of the four valid options, never malformed text
DThat the probabilities sum to exactly 1.0
Correct. Typing removes malformed answers, not mistaken ones. Jev can still pick “flaky” for a real regression.
The schema guarantees shape only. Correctness has to be measured, “unknown” is just another option Jev may or may not pick, and rounding can make probabilities sum to 0.99.

2. A hard gate passes 80% of real regressions to an agent that recognises 90% of what it sees. Ignoring correlation, what is end-to-end recall?

A90%: the agent’s recall is what counts
B72%
C80%: the lower of the two
D85%: the average
Correct. 0.80 × 0.90 = 0.72. A regression has to survive both stages, so the recalls multiply. The lab observes 0.70 because real errors correlate.
Both stages must pass the regression, so the recalls multiply: 0.80 × 0.90 = 0.72. The gate’s recall is a ceiling on everything after it.

3. The screening function can’t reach Jev (a timeout). What should it return?

A“rerun”, since most failures are flaky anyway
BNothing: skip the failure until Jev is back
CRetry forever until Jev answers
DAn action that sends the failure to more scrutiny (review / Claude)
Correct. A judge that is down must fail toward more scrutiny, never toward “fine”.
An outage must not silently become suppression. Fail toward more scrutiny: escalate to review or to Claude. Bounded retries on 429/529 are fine; unbounded ones stall the loop.

4. In the lab, the hard gate shows 100% precision and the uncertainty router shows 90%. What is the right conclusion?

APrecision can’t see what the gate dropped; compare end-to-end recall and the cost of misses
BThe hard gate is better: fewer false alarms and fewer Claude calls
CThey are equivalent, because both use the same Jev signals
DThe router is misconfigured: it should match the gate’s precision
Correct. The gate’s perfect precision hides a missed HIGH-severity regression. End-to-end recall (70% vs. 90%) and expected total cost tell the real story.
Precision only scores what was flagged. The gate never shows Claude the regressions it dropped, including the high-severity F10, so its recall is 70% against the router’s 90%.

5. Greenberg found that Sonnet’s few errors sat in its 0.9–1.0 confidence band, while Jev’s sat around 0.2–0.3. Why does that matter for a pipeline?

AIt proves Jev is more accurate than Sonnet in general
BIt doesn’t: only accuracy matters
CA low-confidence error can be caught by a threshold and escalated; a high-confidence error sails through
DIt means you can skip threshold tuning
Correct. Calibration is what makes uncertainty routing work: errors the judge is unsure about land in the “escalate” region.
It was one bounded task, so it proves nothing general. What it shows is a useful error pattern: where errors come with low confidence, a threshold can route them to review. Thresholds still need tuning on your data.

6. You wire Jev into Claude Code as an advisory PostToolUse hook on failing tests. Which of these must the hook never do? (select all that apply, then check)

Correct. A, B and D are the things it must never do. Blocking (A) or rewriting (B) hands a cheap judge authority over the agent, which is the hard gate again. Echoing the payload (D) is a malformed decision, because stdout is the decision channel. C is the whole job of the hook, and E is how it fails open.
Not quite. The hook must never do A, B or D. Blocking or rewriting gives the judge authority over the agent; stdout is the decision channel, so a payload there is a malformed decision. Adding additionalContext (C) is what it is for, and exiting 0 silently when Jev is down (E) is the correct fail-open behaviour.

7. Put the rollout of Jev for flaky-test triage in the right order. (choose a step number for each, then check)

Correct. Shadow, then disagreements, then soft priority, then uncertainty routing, and only then narrow automation. Each rung has to be earned with evidence from the one before, and every rung keeps a rollback.
The order is: 1 shadow mode, 2 study disagreements, 3 soft prioritisation, 4 uncertainty routing, 5 automate a narrow region. You can’t judge a region’s miss rate until shadow mode has logged it, and you can’t suppress anything safely until the disagreements are explained.

Your Score

0/0

Module Summary

Key Concepts

  • Jev is a System One model: state and typed questions in, typed answers with probabilities out. It is fast (70–500 ms) and cheap ($0.042 per 1M input tokens), and it writes no prose.
  • Typed ≠ correct. The schema removes malformed answers, not wrong ones.
  • Probability is not confidence. Probability is about one outcome; confidence is about the whole distribution. Good policies use both.
  • Placement decides the cost of an error. A hard gate multiplies its misses into the pipeline. An uncertainty router escalates what it is unsure of and reruns before it dismisses anything.
  • Measure systems, not judges. Use end-to-end recall, false negatives by severity and expected total cost on a frozen, labelled set.
  • Earn authority. Go from shadow, to disagreement study, to soft priority, to uncertainty routing, to narrow automation, with a rollback at every rung.
  • In Claude Code, advise; don’t gate. Jev belongs in additionalContext, never in "decision": "block".

The right question is not “Is Jev cheaper than Claude?” It is: which bounded decision should Jev own, what happens when it is uncertain, and how will you detect the cases it gets wrong?

How This Maps to the Rest of the Course

Idea hereCourse module(s)
Eval sets, end-to-end metricsM18
Escalate, don’t silently passM17
Deterministic quality gates in a coding loopM22C
Model routing on costM22
Hook contract, PostToolUseM26
Routing as a workflow patternBonus: Building Effective Agents

Sources and Further Reading

Illustrative numbers on this page (the 0.90 × 0.97 example and every Jev answer from the lab) are labelled as such. The lab’s Jev answers are synthetic fixtures, not recordings.