Reference

September 20, 2026 · View on GitHub

The loop sends state and typed questions to Jev; verdicts come back with confidence; escalations wake the LLM; generated results rejoin the loop

jev_judge

Batch every question about one state into one call — latency is flat in question count (measured: 1/4/8 questions ≈ 274 ms median), and cost amortizes across the shared state.

ParamTypeNotes
statestringeverything Jev may consider — facts, tool output, file excerpts (≤ ~30k tokens)
questions[]arrayeach: {id?, type, question, options?, levels?, criteria?}
questions[].typenoul | choice | scorenoul = probability a statement is true; choice = pick one enumerated option; score = position on an ordered list of levels
questions[].optionschoice only≥ 2 labels, or a label → meaning map
questions[].levelsscore only≥ 2 ordered level descriptions
questions[].criterianoul only, optional{true, false} meanings, to sharpen calibration
confidence_thresholdnumber, default per confidence source — 0.5 reported, 0.4 estimatedverdicts below it escalate
modelstring, optionalbackend model override
// input
{
  "state": "CI run #142: build ok, 214 tests passed, 0 failed; 1 test quarantined as flaky last week",
  "questions": [
    { "id": "passed", "type": "noul",   "question": "Did the run fully succeed?" },
    { "id": "next",  "type": "choice", "question": "Next action?",
      "options": { "merge": "everything green", "rerun": "looks flaky", "hold": "needs attention" } },
    { "id": "risk",  "type": "score",  "question": "How risky is merging now?",
      "levels": ["routine", "worth a look", "incident"] }
  ]
}
// result (shape exact, values illustrative)
{
  "verdicts": [
    { "id": "passed", "type": "noul",   "answer": 0.97, "confidence": 0.94,
      "confidenceFrom": "estimated", "escalate": false },
    { "id": "next",  "type": "choice", "answer": "merge", "confidence": 0.34,
      "confidenceFrom": "reported", "escalate": true,
      "reason": "unsure", "distribution": { "merge": 0.55, "rerun": 0.41, "hold": 0.04 },
      "hint": "Jev answered (merge) at reported confidence 0.34 < 0.5. Treat the answer as a prior, not a decision — reason it out yourself." },
    { "id": "risk",  "type": "score",  "answer": 0.8, "confidence": 0.81,
      "confidenceFrom": "reported", "escalate": false,
      "distribution": { "0": 0.35, "1": 0.5, "2": 0.15 },
      "legend": { "0": "routine", "1": "worth a look", "2": "incident" } }
  ],
  "escalated": true,
  "backend": "typesafe",
  "latencyMs": 187
}

A score answer is the distribution's expected position on your levels — 0.8 means "between routine and worth a look, closer to the latter"; legend maps indices back to your words.

jev_gate

One proposed action, one risk check.

ParamType
statestring — current task context
tool, inputthe action, verbatim
descriptionoptional intent
confidence_thresholddefault per confidence source (0.5 reported / 0.4 estimated)

Returns {decision: allow | deny | escalate, confidence, confidenceFrom, hint} — one allow/deny choice under the hood, escalate when confidence falls below the threshold. allow stays silent and falls through to your normal permission flow — the gate can never grant anything, only deny or ask — and if Jev is down it fails open. As a PreToolUse hook it spends zero LLM tokens on the allow path (a deny/ask feeds its reason back to the model — that is the point) and adds one ~100 ms round trip per gated call, so scope the matcher to tools worth gating.

The verdict contract

Every verdict: {id, type, answer, confidence, confidenceFrom, escalate, reason?, hint?, distribution?, legend?}. reason/hint appear exactly when escalate is true; distribution/legend whenever the provider returns them; confidenceFrom whenever the question actually reached Jev.

reasonwhenmeaning
writingpre-callthe step must produce new text/code — structurally the LLM's
open_endedpre-callnot expressible as noul/choice/score (nothing to enumerate)
oversizedpre-callthe state exceeds ~30k tokens — shrink it or take the questions over
unsurepost-callanswer too flat to act on; it stays in answer as a prior
unreachableon failureJev unreachable — proceed as if it didn't exist

Pre-call reasons come from a deterministic router (no request spent); each handback is a normal verdict with a hint, never an exception.

What the confidence scalar is, and where it came from. Every verdict says so itself, in confidenceFrom:

confidenceFromwho produced itescalates below
reportedJev's own confidence head, returned for choice and score answers0.5
estimatedjev-use, from the answer's own distribution: top-minus-runner-up for choice/score, 2·|p − 0.5| for noul0.4

noul answers carry no reported confidence from any provider, so they are always estimated; one batch mixing check with pick/rate therefore comes back part reported, part estimated, and each verdict is judged against its own number. Through the Vercel gateway the head arrives out-of-band in providerMetadata.typesafe.confidence, keyed by question id.

The two are the same scale read two ways, measured over 318 live choice/score answers (bench/RESULTS.md): on a two-option question they agree to the wire's 2-decimal rounding, and on three or more the margin reads a median 0.05 (up to 0.17) lower, because it also subtracts however the losing mass splits. Hence the lower bar for the estimate. An explicit confidence_threshold covers every verdict whatever its source, and always wins.

Library

import { Jev, check, pick, rate } from "jev-use";

const jev = new Jev();                 // backend resolved from the environment

const { answers, verdicts } = await jev.judge(state, {
  next: pick("Next action?", { merge: "all green", rerun: "looks flaky", hold: "needs attention" }),
  risk: rate("How risky?", ["routine", "worth a look", "incident"]),
  passed: check("Did the run fully succeed?"),
});
answers.next;   // { answer: "merge", confidence: 0.93, confidenceFrom: "reported", escalate: false }

const verdict = await jev.gate(state, { tool: "Bash", input: { command } });
verdict.decision;   // "allow" | "deny" | "escalate"

Both halves of the handoff are in the surface. Before writing a question, route says whether the step is Jev-shaped at all — no client, no key, no call:

import { route } from "jev-use";

route({ producesContent: true, enumerable: true });    // { to: "llm", reason: "writing" }
route({ producesContent: false, enumerable: false });  // { to: "llm", reason: "open_ended" }
route({ producesContent: false, enumerable: true });   // { to: "jev" }

After the call, every answer carries the other half — escalate, reason, hint, and Jev's answer kept as a prior (the table above).

The three builders write the three primitives — checknoul, pickchoice, ratescore — and the wire vocabulary stays exactly that. answers is keyed by the names you asked under; verdicts is the same verdicts in the order you asked them, alongside escalated, backend, model, latencyMs and usage.

new Jev({ backend: "mock" }) judges with no key at all, and new Jev({ backend: myBackend }) takes any JevBackend (tests, custom transports). Defaults set on the client — confidenceThreshold, model — are overridable per call: jev.judge(state, questions, { model }).

Configuration

SettingDefaultMeaning
JEV_BACKENDauto-detecttypesafe | openrouter | vercel | mock
TYPESAFE_API_KEY / OPENROUTER_API_KEY / AI_GATEWAY_API_KEYprovider credential; auto-detected in this order
JEV_MODELprovider default (jev-latest)model override
JEV_GATE_THRESHOLDper confidence source (0.5 / 0.4)hook-gate escalation threshold, for both sources at once

Provider dialects: TypeSafe and OpenRouter share the native wire shape (OpenRouter's decisions endpoint is alpha and may move), carrying confidence on the answer itself; Vercel's gateway renames noulboolean, moves the model into a header, drops the legend echo, and relays the confidence head in providerMetadata.typesafe.confidence instead — a map keyed by question id, with boolean answers absent from it. All three are normalized by the adapters, provenance included; wire shapes are pinned by fixture tests against documented formats — jev-use doctor is the live check.

CLI

jev-use install [claude|codex|pi]   wire the server into your harness via its own CLI (all found, if no target)
jev-use serve                 stdio MCP server
jev-use hook gate             PreToolUse hook adapter (Claude Code / Codex)
jev-use judge ['{...}']       one-shot JudgeRequest from argv or stdin
jev-use doctor                backend resolution + one live round trip