TL;DR — Most steps inside an AI agent are decisions (route, classify, approve, escalate), not generations. We've been paying LLM prices and LLM latency to have a text generator describe those decisions as JSON. Jev, TypeSafe AI's "System One" model, takes a different approach: you give it program state and typed questions, and it returns typed answers with probabilities your code can branch on directly. It doesn't replace your LLM. It gives your system a cheap, fast decision layer — and forces a healthy separation between model judgment and application policy.
1. The problem: we use a generator for everything
For the last few years, the industry has optimised one question: how do we make models generate better answers? Bigger models, longer context, better reasoning, tool use, retrieval, memory. It worked.
But look closely at what a production agent actually spends its calls on:
- Which route should this request take?
- Which tool should run next?
- Is this output safe to send?
- Is this incident critical?
- Should a human see this?
- Is candidate A better than candidate B?
None of these need prose. They are bounded judgments with a known answer space. Yet the default implementation looks like this:
You are a support-routing assistant.
Read the message and return JSON with: intent, priority, fraud_risk, escalate.
Return ONLY valid JSON.
{ "intent": "billing", "priority": "high", "fraud_risk": "low", "escalate": false }
It works — until it doesn't. Around that one call you end up writing:
- JSON parsing and schema validation
- Enum checks (
"High"vs"high"vs"urgent") - Retry logic for malformed output
- Some heuristic for "was the model actually sure?"
- And only then, the business logic you cared about
The model produced a string. Your software wanted a decision. Everything in between is glue code, latency and cost.
2. What Jev is
Jev is the first public model from TypeSafe AI, launched on 15 September 2026. TypeSafe calls it a System One model — a nod to Kahneman's fast, intuitive "System 1" thinking, as opposed to slow, deliberate "System 2" reasoning.
The key facts, as of writing:
| Interface | Program state + a map of typed questions → typed answers with probabilities |
| Output | No text generation. All questions are evaluated in parallel in a single pass |
| Training | A method TypeSafe calls Reinforcement Learning for Calibrated Decisions (RLCD) |
| Pricing | $0.042 per million input tokens; output tokens are free |
| Latency | TypeSafe reports ~70–500 ms end to end |
| Access | Hosted API (early access), also available via Vercel AI Gateway. No public weights |
Two honest caveats before we go further:
- "Cannot hallucinate" means the output always matches your schema. It does not mean the answer is always right. Jev can still be wrong — it just can't be wrong in an unparseable way.
- Most published speed/cost multipliers come from TypeSafe's own evals. Treat them as directional and benchmark on your own data.
The mental model shift is simple:
LLM: input ──► generated text ──► parser ──► validator ──► decision
Jev: state + question ──► typed decision + probability ──► your code
The best one-line description I've seen: Jev is a smart if statement. Ordinary code branches on things it can compute (order.total > 100). It breaks down when the condition is a judgment (is this customer about to churn?). That's the gap Jev fills.
3. The three primitives
Jev exposes exactly three question types:
| Primitive | Asks | Returns |
|---|---|---|
| Choice | Pick one option from a set (up to 255) |
choice, probabilities, confidence
|
| Score | Place the state on an ordered scale |
score, probabilities, confidence
|
| Noul | Is this statement true? |
noul — a probability between 0 and 1 |
Here's all three against a single IT ticket, using the official Python SDK:
pip install typesafe-sdk # Python >= 3.10
export TYPESAFE_API_KEY="ts_..."
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient
client = TypeSafeClient() # reads TYPESAFE_API_KEY, defaults to jev-latest
ticket = (
"Since this morning my laptop won't connect to the corporate network. "
"Home Wi-Fi works fine. I have a board presentation in 40 minutes."
)
response = client.system_one(
state=ticket,
questions={
"category": Choice(
instructions="Which category best describes the root issue",
criteria={
"wifi": "Wireless connectivity problems",
"vpn": "VPN or remote-access tunnel problems",
"hardware": "Physical device or peripheral failure",
"auth": "Login, password, MFA or certificate problems",
"other": "Anything else",
},
),
"severity": Score(
instructions="Business impact of this issue",
criteria=[ # ordered: index 0 = lowest
"Cosmetic, no work blocked",
"Degraded, workaround exists",
"Blocked, single user",
"Blocked, time-critical or many users",
],
),
"time_sensitive": Noul(
instructions="The user has a hard deadline within the next few hours",
),
},
)
cat = response.answers["category"]
print(cat.choice, round(cat.confidence, 2))
print(cat.probabilities) # full distribution
print(response.answers["severity"].score) # e.g. 3.0
print(response.answers["time_sensitive"].noul) # e.g. 0.97
A representative category answer looks like this:
{
"type": "choice",
"choice": "vpn",
"confidence": 0.61,
"probabilities": { "vpn": 0.68, "wifi": 0.19, "auth": 0.09, "hardware": 0.02, "other": 0.02 }
}
Notice what you get that a JSON-emitting LLM doesn't give you cleanly: the whole distribution. The model isn't saying "VPN." It's saying "VPN is most likely, Wi-Fi is a real alternative, and I'm not very sure." That is exactly the signal your system needs to decide what happens next.
Under the hood, the question is part of the input. Conceptually, Jev is estimating
— which is why it can handle inputs it has never seen. It doesn't need to have memorised "VPN drops when I leave the office"; it needs to recognise what that sentence resembles, relative to the options you gave it.
4. Probability ≠ confidence ≠ action
This is the most important section in the post, so I'll be precise.
- Probability — how much mass the model puts on each option.
- Confidence — a single 0–1 number TypeSafe derives from the shape of that distribution. A winner at 0.68 with a runner-up at 0.19 yields a lower confidence than a winner at 0.68 against a flat field.
- Action — what your system does. This is not the model's job.
A good architecture lets the model judge and lets code own the policy:
┌──────────────────┐
State ───────►│ Jev │ judgment
│ choice + probs │
└────────┬─────────┘
▼
┌──────────────────┐
│ Policy layer │ your code, your thresholds
└────────┬─────────┘
┌──────────┼──────────┐
▼ ▼ ▼
Execute Confirm Escalate
In code, make the policy explicit, typed and scaled to the cost of being wrong:
from dataclasses import dataclass
from enum import Enum
class Action(Enum):
EXECUTE = "execute"
CONFIRM = "confirm"
ESCALATE = "escalate"
@dataclass(frozen=True)
class Policy:
"""Thresholds are business decisions, not model outputs."""
execute_at: float
confirm_at: float
def decide(self, confidence: float) -> Action:
if confidence >= self.execute_at:
return Action.EXECUTE
if confidence >= self.confirm_at:
return Action.CONFIRM
return Action.ESCALATE
# Cheap-to-reverse actions get permissive policies; expensive ones get strict ones.
POLICIES = {
"tag_ticket": Policy(execute_at=0.70, confirm_at=0.40),
"route_to_team": Policy(execute_at=0.85, confirm_at=0.60),
"issue_refund": Policy(execute_at=0.97, confirm_at=0.90),
}
action = POLICIES["route_to_team"].decide(response.answers["category"].confidence)
Why does this matter beyond tidiness?
- Auditability. When someone asks "why did the system auto-refund this?", the answer is a versioned threshold in code, not a vibe inside a prompt.
- Changeability. Risk appetite changes quarterly. Moving a number in a config is cheaper than re-prompting and re-evaluating a model.
- Ownership. Product and risk teams can own thresholds without touching model code.
Model = judgment. Code = policy. System = action.
5. Pattern 1 — The router
Routing is where I'd start with Jev in almost any multi-agent system. Every request has to go somewhere, and every request pays for that decision.
from typesafe_sdk import Choice, TypeSafeClient
client = TypeSafeClient()
AGENTS = {
"support": "Product issues, how-to questions, account access",
"billing": "Charges, invoices, refunds, payment methods",
"security": "Suspected fraud, compromised accounts, phishing reports",
"engineering": "Bug reports with technical detail, API errors, outages",
"sales": "Pricing, upgrades, new contracts",
}
ROUTE_POLICY = Policy(execute_at=0.80, confirm_at=0.55)
def route(message: str) -> tuple[str | None, Action]:
answer = client.system_one(
state=message,
questions={
"agent": Choice(
instructions="Which specialist agent should own this request",
criteria=AGENTS,
)
},
).answers["agent"]
action = ROUTE_POLICY.decide(answer.confidence)
if action is Action.EXECUTE:
return answer.choice, action
if action is Action.CONFIRM:
# Ambiguous between a couple of agents: ask one clarifying question.
top_two = sorted(answer.probabilities, key=answer.probabilities.get, reverse=True)[:2]
return None, action # e.g. "Is this about a charge, or a bug in checkout?" using top_two
return None, action # hand to a human or a stronger LLM router
Two practical notes:
-
Write criteria like a job description, not a label.
"billing"alone is weak; "Charges, invoices, refunds, payment methods" gives the model something to match against. - Use the runner-up. When confidence is middling, the top two options tell you exactly which clarifying question to ask.
This scales in a way prompt-based routers don't: a Choice supports up to 255 options, so routing across a large tool or agent catalogue remains one call.
6. Pattern 2 — Parallel decisions over one state
Real workflows rarely need one judgment. An incident needs a category, a priority, a security flag and an escalation decision. The LLM approach is often five prompts (or one fragile mega-prompt). With Jev, the state stays constant and the questions vary — and they're all answered in one parallel pass.
┌── category
├── priority
Incident ─── Jev ───┼── security_relevant
└── customer_facing
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient
client = TypeSafeClient()
TRIAGE_QUESTIONS = {
"category": Choice(
instructions="Primary technical domain of the incident",
criteria={
"network": "Connectivity, DNS, load balancers, VPN",
"compute": "Hosts, containers, capacity, crashes",
"data": "Databases, storage, replication, data quality",
"identity": "SSO, IAM, certificates, access",
"application": "Application errors and regressions",
},
),
"priority": Score(
instructions="Priority based on business impact and urgency",
criteria=["P4 - minimal", "P3 - moderate", "P2 - high", "P1 - critical"],
),
"security_relevant": Noul(
instructions="The incident may involve a security breach or unauthorised access",
),
"customer_facing": Noul(
instructions="External customers are currently affected",
),
}
def triage(incident_text: str) -> dict:
answers = client.system_one(state=incident_text, questions=TRIAGE_QUESTIONS).answers
return {
"category": answers["category"].choice,
"category_confidence": answers["category"].confidence,
"priority": int(answers["priority"].score), # index into the ordered criteria
"security_relevant": answers["security_relevant"].noul >= 0.30, # low bar: missing one is costly
"customer_facing": answers["customer_facing"].noul >= 0.50,
}
Notice the asymmetric thresholds on the two Noul questions. A false negative on security_relevant is far more expensive than a false positive, so the bar to flag is deliberately low. That's policy — and it lives in code, where it belongs.
Adding a sixth question costs a few extra input tokens and, per TypeSafe, barely moves latency. Adding a sixth LLM prompt costs another round trip.
7. Pattern 3 — Guardrails on agent actions and outputs
Anywhere an agent is about to do something with consequences, a fast decision model is a natural checkpoint. A community project, jev-guard, applies exactly this to coding-agent tool calls — rating each one as deny, ask, or allow.
import json
from typesafe_sdk import Choice, Noul, TypeSafeClient
client = TypeSafeClient()
def review_tool_call(tool: str, args: dict, task: str) -> str:
state = json.dumps({"user_task": task, "tool": tool, "arguments": args}, indent=2)
answers = client.system_one(
state=state,
questions={
"verdict": Choice(
instructions="Should this tool call run without human review",
criteria={
"allow": "Read-only or clearly within the user's stated task",
"ask": "Plausibly intended but has side effects worth confirming",
"deny": "Destructive, irreversible, or unrelated to the task",
},
),
"exfiltration_risk": Noul(
instructions="The call could send secrets or private data outside the system",
),
},
).answers
# Hard rule first: code, not the model, owns the non-negotiables.
if answers["exfiltration_risk"].noul >= 0.20:
return "deny"
verdict = answers["verdict"]
if verdict.choice == "allow" and verdict.confidence >= 0.90:
return "allow"
if verdict.choice == "deny":
return "deny"
return "ask" # anything uncertain defaults to a human
The same shape works for output guardrails — before an LLM's reply reaches a customer, ask "Does this response disclose internal-only information?" and route anything above your threshold to review.
The design principle: fail toward the human. Every ambiguous path in that function ends in "ask", never in "allow".
8. Pattern 4 — The confidence-gated cascade
This is where probability becomes economically meaningful. Don't send every request to your most expensive model. Send it there when the cheap model is unsure.
Request
│
▼
Jev
┌─────────┴─────────┐
confident uncertain
│ │
▼ ▼
Accept Stronger LLM
┌────────┴────────┐
confident uncertain
│ │
▼ ▼
Accept Human review
from dataclasses import dataclass
from typing import Callable, Literal
from typesafe_sdk import Choice, TypeSafeClient
client = TypeSafeClient()
@dataclass
class Verdict:
label: str
confidence: float
source: Literal["jev", "llm", "human"]
def cascade_judge(
rubric: str,
candidate: str,
llm_judge: Callable[[str, str], Verdict], # your stronger model, same label space
jev_gate: float = 0.85,
llm_gate: float = 0.80,
) -> Verdict:
state = f"RUBRIC:\n{rubric}\n\nCANDIDATE RESPONSE:\n{candidate}"
first = client.system_one(
state=state,
questions={
"verdict": Choice(
instructions="Does the candidate satisfy the rubric",
criteria={
"pass": "Meets every requirement in the rubric",
"fail": "Misses or violates at least one requirement",
},
)
},
).answers["verdict"]
if first.confidence >= jev_gate:
return Verdict(first.choice, first.confidence, "jev")
second = llm_judge(rubric, candidate)
if second.confidence >= llm_gate:
return second
return Verdict("needs_review", second.confidence, "human")
What the early research says
This isn't just a whiteboard pattern. A September 2026 paper, JEV-as-a-Judge: Accept When Confident, Escalate When Unsure, compared Jev against sixteen generative and reward-model judges with blinded human adjudication. Their findings, summarised:
- On everyday preference and evidence-grounded factuality, Jev landed within ~3 points of their strongest LLM judge at well under 1% of its cost.
- The gap widened on tasks that require checking a derivation or resisting a well-written wrong answer.
- Crucially, much of the gap sat in Jev's low-confidence decisions — and a cascade that accepted confident verdicts and escalated the rest kept ~99% of the stronger judge's accuracy at lower cost.
But a second study is a useful reality check. Jev vs. LLMs as Rubric Judges found that while Jev's confidence did rank its own errors, the LLM judges repeated most of Jev's most confident mistakes. When errors are correlated, escalation can't fix them — and the cascade's accuracy gain over the best single judge was small.
My takeaway as an engineer: cascades are a strong cost play. They are not automatically an accuracy play. Whether they help accuracy depends on how independent your two judges' failure modes are — and you only learn that by measuring on your data.
9. The economics, worked through
Let's put numbers on an enterprise scenario: 100,000 IT incidents a month, each needing five decisions (category, priority, assignment group, security flag, escalation).
⚠️ Illustrative only. The LLM prices below are a hypothetical mid-tier model ($3 / M input, $15 / M output). Plug in your own contract rates and measured token counts.
Option A — LLM, one prompt per decision
Input: 100,000 incidents × 5 calls × 500 tokens = 250M tokens × $3/M = $750
Output: 100,000 incidents × 5 calls × 60 tokens = 30M tokens × $15/M = $450
Total ≈ $1,200 / month
Option B — Jev for all five, LLM only for the uncertain tail
Jev: 100,000 × 1 call × ~600 tokens (state + 5 questions) = 60M × $0.042/M = ~$2.50
Tail: assume 5% escalated → 5,000 LLM calls
input 5,000 × 700 tokens = 3.5M × $3/M = $10.50
output 5,000 × 300 tokens = 1.5M × $15/M = $22.50
Total ≈ $36 / month
The inference bill is almost a rounding error either way at this volume. The real business case is elsewhere:
- Latency. Five sequential LLM calls at a few seconds each vs. one sub-second parallel call changes what you can put in a synchronous user flow.
- Engineering cost. No parsing, no retry-on-malformed-JSON, no enum drift. That's maintenance you stop paying for.
- Scale ceiling. At fractions of a cent per decision, running judgment over every record in a table becomes a batch job, not a budgeting conversation.
- Controllability. Explicit confidence plus explicit thresholds gives risk and compliance something concrete to sign off on.
And the 5% escalation rate is an assumption. Measure your coverage at your chosen threshold before you promise anyone a number.
10. Calibration is the whole game
A probability is only useful if it's calibrated: when the model says 0.9, it should be right about 90% of the time. If 0.9 means "right 60% of the time", every threshold in your policy layer is lying to you.
TypeSafe trains specifically for calibration (that's what RLCD targets), but that's a claim to verify, not assume. Before any Jev decision goes to production, run a labelled evaluation set through it and look at two things:
- Reliability — accuracy within each confidence bucket.
- Selective accuracy vs. coverage — at each threshold, how many items you'd automate, and how accurate those would be.
from collections import defaultdict
def reliability_table(results: list[tuple[float, bool]], bins: int = 5) -> None:
"""results: (confidence, was_correct) pairs from a labelled eval set."""
buckets: dict[int, list[bool]] = defaultdict(list)
for conf, correct in results:
buckets[min(int(conf * bins), bins - 1)].append(correct)
print(f"{'bucket':<12}{'n':>6}{'accuracy':>10}")
for b in range(bins):
hits = buckets.get(b, [])
lo, hi = b / bins, (b + 1) / bins
acc = f"{sum(hits) / len(hits):.2f}" if hits else "-"
print(f"{lo:.1f}-{hi:.1f}".ljust(12) + f"{len(hits):>6}{acc:>10}")
def coverage_curve(results: list[tuple[float, bool]], thresholds=(0.5, 0.7, 0.8, 0.9, 0.95)) -> None:
"""For each threshold: share of items automated, and accuracy on that share."""
total = len(results)
print(f"{'threshold':<11}{'coverage':>10}{'sel. accuracy':>15}")
for t in thresholds:
kept = [ok for conf, ok in results if conf >= t]
cov = len(kept) / total
acc = sum(kept) / len(kept) if kept else float("nan")
print(f"{t:<11}{cov:>10.1%}{acc:>15.1%}")
Then pick thresholds from the curve based on business cost — for example, "the lowest threshold at which selective accuracy is ≥ 98%" for an automated action. That turns a debate about model quality into a concrete trade-off: how much do we automate, at what error rate?
The full metric set I'd track: accuracy, precision/recall/F1 per class, calibration, selective accuracy, coverage, p50/p95 latency and cost per 1,000 decisions. And, always: what happens to the items the model is unsure about? A strong system has a deliberate answer.
11. Where Jev is the wrong tool
Jev is not an LLM replacement, and pretending otherwise will hurt you. Reach for a generative or reasoning model when you need:
- Long-form or creative writing — articles, emails, stories
- Code generation
- Open-ended reasoning — novel designs, multi-step derivations
- Explanations — anything where the user needs to read why
- Complex multi-document synthesis
Also be wary when:
- The answer space isn't known up front. Jev chooses between options you define; it won't invent a sixth category.
- The judgment needs step-by-step verification. The research above shows this is exactly where the gap to strong LLMs is largest.
- Inputs are adversarial. A persuasively written wrong answer is a known weak spot — design your guardrails accordingly.
The sweet spot is narrow and extremely common: given this state, make one bounded judgment — thousands of times.
12. The real shift: from one model to composable intelligence
The framing "Jev vs. LLMs" is the wrong debate. The interesting architecture is composition:
AI SYSTEM
│
┌──────────────┬───────┴───────┬──────────────┐
▼ ▼ ▼ ▼
Generate Decide Retrieve Act
LLM Jev Search Tools
└──────────────┴───────┬───────┴──────────────┘
▼
Deterministic policy (code)
▼
Humans — where uncertainty and
consequences demand it
Look at an agent loop through this lens and most of it turns out to be decisions:
What should I do next? → decision
Which tool? → decision
Did the tool succeed? → decision
Should I retry? → decision
Should I ask the user? → decision
Should I stop? → decision
Write the final answer → generation
Once decisions come back as typed values instead of paragraphs — true, "vpn", 0.73, confidence = 0.91 — models stop being conversational endpoints and start being composable components in ordinary software. The question stops being "which model is smartest?" and becomes "which kind of intelligence belongs at this step of the workflow?"
- Generative models create.
- Decision models judge.
- Deterministic code governs.
- Tools act.
- Humans stay in the loop where it matters.
Final thought
The industry spent the last few years teaching machines to speak. The next phase may be about teaching them to choose — and, just as importantly, to say how sure they are when they do.
Jev isn't an LLM killer, and it isn't another chatbot. It's a credible first version of a probabilistic decision layer for the agentic stack. The engineering question it raises is the one I think matters most right now:
How do we compose different kinds of intelligence into systems that know when to generate, when to decide, when to ask — and when to stop?
If you're building agents, that's worth an afternoon with the playground and a labelled eval set.
Resources
- Introducing System One Models & Jev — TypeSafe AI
- TypeSafe docs & quickstart
- JEV-as-a-Judge: Accept When Confident, Escalate When Unsure (arXiv 2609.26550)
- Jev vs. LLMs as Rubric Judges (arXiv 2609.29769)
- jev-guard — tool-call guardrail example
Code samples target the documented typesafe-sdk Python interface as of September 2026. The API is in early access — check the docs for changes before shipping.
Top comments (0)