In part 1, we stopped at a familiar message: “I was charged twice”. A customer support system does not need a paragraph in reply to that message. It needs a few decisions: which team gets the ticket, how urgent it is, whether the customer wants a refund. A decision model returns exactly those decisions, as typed values with probabilities.
Part 1 stayed at the level of concepts. This post goes into one concrete JEV call. TypeSafe reports an end-to-end response time of about 70–500 ms. What happens in that window? Which fields does a request have, and which are required? What do the three question types Choice, Score and Noul return, and how should you read them? What do “parallel sampler” and “RLCD” mean, as far as the public documentation goes? And, most important for anyone writing production code: how do you turn a number like 0.62 into an action?
The answers come in that order. The first part follows the schema in the official Python SDK, with a piece of code that runs without an API key. The middle part quotes what TypeSafe says about the internals, and keeps what only comes from third-party sources separate. The last part is about limits: well-typed is not correct, the 9 failure modes TypeSafe itself lists for jev-1.13, and a list of what the company has not published as of September 30, 2026.
01One request → response loop
Every System One model sits behind a single endpoint: POST /v1/systemone. TypeSafe’s official Python SDK (typesafe-sdk, version 0.7.2, commit f078f1e from September 26, 2026) contains a models.py file generated from the API’s OpenAPI spec. This post treats that file as the “contract” for every field name below.
Request: three fields, all required
A SystemOneRequest has exactly three fields:
| Field | Type | Meaning |
|---|---|---|
state |
str, dict or list |
The content every question in the request refers to |
model |
str |
A version name or alias, for example jev-1.13.0 or jev-latest |
questions |
dict[str, Question] |
At least one question. You choose the keys, and answers come back under the same keys |
Two details in this table are worth noting right away.
First, state is shared by every question. You do not send three prompts for three questions. You send one state and three questions about it. This comes back in the section on the parallel sampler.
Second, question keys are labels on the application side. The API reference says the keys are “not sent to the underlying model”. Naming a key department or q1 does not change what the model understands. What the model reads lives in instructions and criteria.
As for model, the SDK defaults to the jev-latest alias, which currently points to jev-1.13.0. TypeSafe’s Models page recommends pinning a specific version if you have tuned confidence thresholds against it. The reason is practical: when the alias moves to a new release, the probability distribution can shift, and thresholds chosen on the old release may no longer hold. The examples in this post use jev-1.13.0.
The running example: a “charged twice” ticket
This example continues from part 1 but is more complete: a state with the ticket’s subject and message, plus three questions, one of each type.
{
"model": "jev-1.13.0",
"state": {
"subject": "Charged twice",
"message": "I was charged twice for order #4821. Please fix this ASAP, I need the money back before Friday."
},
"questions": {
"department": {
"type": "choice",
"instructions": "Which team should handle this ticket?",
"criteria": {
"billing": "Payments, invoices, duplicate charges, refunds",
"technical": "Bugs, outages, integrations",
"account": "Login, profile, account settings",
"other": "None of the above"
}
},
"urgency": {
"type": "score",
"instructions": "How urgent is this ticket?",
"criteria": ["Can wait", "Needs attention this week", "Needs attention today"]
},
"refund_requested": {
"type": "noul",
"instructions": "Is the customer asking for a refund?",
"criteria": {
"true": "The customer explicitly or implicitly asks for money back",
"false": "The customer asks for something else"
}
}
}
}
Each question has a type field (choice, score or noul). The schema uses it as a discriminator: the type tells you what shape the rest of the question must have.
Response: model, answers, usage
A SystemOneResponse has three fields, and all three are required:
{
"model": "jev-1.13.0",
"answers": {
"department": {
"type": "choice", "choice": "billing", "confidence": 0.93,
"probabilities": { "billing": 0.95, "technical": 0.02, "account": 0.02, "other": 0.01 }
},
"urgency": {
"type": "score", "score": 1.7, "confidence": 0.62,
"legend": { "0": "Can wait", "1": "Needs attention this week", "2": "Needs attention today" },
"probabilities": { "0": 0.05, "1": 0.2, "2": 0.75 }
},
"refund_requested": { "type": "noul", "noul": 0.91 }
},
"usage": { "input_tokens": 212, "output_tokens": 24 }
}
modelis the resolved version. If you sendjev-latest, this field tells you which release actually answered. Log it with every decision so you can replay and evaluate later.answersis a map keyed exactly likequestions. Each answer also has atype, and its shape depends on the question type.usageholdsinput_tokensandoutput_tokens. The schema description says “Output tokens are currently free of charge”.
The SDK’s public classes add a few conveniences on top: result.choices, result.scores and result.nouls to get answers by type, and skipping answer types the SDK does not know yet, so that a future API adding a new type does not break older code.
Figure 1 at the top of the post redraws the whole loop. Code calls the SDK, the SDK sends JSON, JEV ingests the state once and returns every probability at the same time, the response travels back as a typed object, and the last step does not belong to JEV at all: policy code reads the numbers and decides what to do. Section 5 returns to that step.
02Three question types
JEV has three question types. Picking the right type matters more than writing a clever prompt, because the type decides the answer space and how to read the probabilities. All three share two fields: instructions (the question, as text or JSON) and criteria (the answer space, in a different shape for each type).
Choice: pick one of the known options
Parameters. criteria is an option → description map. The key is the value your code gets back. The value is a semantic description that tells the model what the option means, and it can be null.
Limit. At most 255 options per Choice. The number appears on the API reference (“maximum of 255 options per Choice”), on the Choice docs page and in the launch post. For larger sets, TypeSafe’s Wikiracing demo uses two stages: narrow the list first, then run a Choice over the shorter list.
Output. A ChoiceAnswer has three fields:
choice: the option with the highest probability;probabilities: the probability of each option, summing to roughly 1;confidence: a number from 0 to 1 describing how concentrated the distribution is.
confidence is not the probability of the chosen option. TypeSafe’s docs describe it as a statistic “derived from the probabilities”: a distribution piled onto one option gives high confidence, a spread-out one gives low confidence. The text of the docs does not state an official formula. The interactive widget on the Confidence page illustrates it with:
confidence = (n · p_max − 1) / (n − 1) where n is the number of options
For the department example: n = 4, p_max = 0.95, so confidence = (3.8 − 1) / 3 ≈ 0.933, rounded to 0.93 in the sample response. The formula gives 1 when all probability sits on one option and 0 for a perfectly uniform distribution. This blog uses it only to build intuition. It is inferred from the widget’s code, not a specification, so do not write code that depends on it. If you need confidence, read the confidence field the API returns.
When to use it. Routing, intent classification, tool selection, category selection. In general: whenever your code has an enum and needs to know which value fits.
Score: place a case on an ordered scale
Parameters. criteria is an ordered list. Levels are counted by position, starting at 0. In the urgency example, "Can wait" is level 0 and "Needs attention today" is level 2. The API page says the scale “should have at least two levels; the API accepts up to 10”.
Output. A ScoreAnswer has four fields:
score: the expected score, the probability-weighted average of the levels. That is why it can be fractional;probabilities: the probability of each level;legend: the rubric you sent, mapping each level to its description.legendandprobabilitiesshare the same keys ("0","1", …). The SDK’s public classes convert these keys toint;confidence: as with Choice.
For the urgency example:
score = 0 · 0.05 + 1 · 0.20 + 2 · 0.75 = 1.7
Why return 1.7 instead of a hard level 2? Because 1.7 carries more information. A ticket at 1.7 and a ticket at 1.95 both have level 2 as their most likely level, but the second one is clearly more urgent. The expected score lets code sort cases and compare them against a threshold on a continuous axis. The SDK’s own example uses the same number: probabilities of 0.1 / 0.1 / 0.8 over three levels also give an expected score of 1.7.
Still, do not read score as a measured quantity. The jev-1.13 weak-spot docs explicitly warn against inferring precise magnitudes from score or probabilities. Use it only to compare against a threshold. Section 7 comes back to this.
When to use it. Urgency, risk, relevance, quality, frustration: things with a natural order where a discrete label would throw information away.
Noul: P(yes)
Parameters. criteria is optional and has two keys, true and false, each a description of one side. In the refund_requested example, the two descriptions help the model tell “wants money back” apart from “asks about something else”.
Output. A NoulAnswer has only one field besides type: noul, the probability of “yes”. It has no confidence and no probabilities. The Confidence page says so directly: “Noul answers don’t carry one”. That makes sense, because with two sides noul already is the whole distribution: P(no) = 1 − noul.
noulnear 1: strongly leaning “yes”;noulnear 0: strongly leaning “no”;noularound 0.5: the model is unsure.
When to use it. Guardrails, approval gates, spam or phishing signals, “does a person need to look at this?”. Noul is also a good filter: ask a Noul “is this passage relevant to the question?” over each part of the state, and keep only the relevant parts.
One place where the docs and the schema disagree
The API reference lists instructions as required for all three question types. The OpenAPI spec and the SDK make it optional. This post does not claim either side is right. The safe option is to always fill in instructions, as every example here does. The request is then valid under both readings.
03Runnable code, no API key needed
JEV is currently in early access, so code that calls the real API will not run for most readers. The SDK has no public mock client, but it does have a place to swap the transport layer: TypeSafeClient accepts a transport argument. The SDK’s own test suite uses httpx2.MockTransport at exactly that point. We do the same: a real client, a real serialized request, a real parsed response. Only the answer comes from a Python function instead of TypeSafe’s servers.
The code below uses only the SDK’s public API (TypeSafeClient, Choice, Score, Noul) and has been run with Python 3.12.3, typesafe-sdk==0.7.2, httpx2 2.13.1, pydantic 2.13.5.
# pip install typesafe-sdk==0.7.2 (pulls in httpx2 2.13.1, pydantic 2.13.5; Python 3.12)
import httpx2
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient, __version__
# Made-up response: every number below is illustrative, not output from the real JEV.
FAKE_RESPONSE = {
"model": "jev-1.13.0",
"answers": {
"department": {
"type": "choice", "choice": "billing", "confidence": 0.93,
"probabilities": {"billing": 0.95, "technical": 0.02, "account": 0.02, "other": 0.01},
},
"urgency": {
"type": "score", "score": 1.7, "confidence": 0.62,
"legend": {"0": "Can wait", "1": "Needs attention this week", "2": "Needs attention today"},
"probabilities": {"0": 0.05, "1": 0.2, "2": 0.75},
},
"refund_requested": {"type": "noul", "noul": 0.91},
},
"usage": {"input_tokens": 212, "output_tokens": 24},
}
def fake_api(request: httpx2.Request) -> httpx2.Response:
print("POST", request.url.path)
return httpx2.Response(200, json=FAKE_RESPONSE)
state = {
"subject": "Charged twice",
"message": "I was charged twice for order #4821. Please fix this ASAP, I need the money back before Friday.",
}
questions = {
"department": Choice(
instructions="Which team should handle this ticket?",
criteria={
"billing": "Payments, invoices, duplicate charges, refunds",
"technical": "Bugs, outages, integrations",
"account": "Login, profile, account settings",
"other": "None of the above",
},
),
"urgency": Score(
instructions="How urgent is this ticket?",
criteria=["Can wait", "Needs attention this week", "Needs attention today"],
),
"refund_requested": Noul(
instructions="Is the customer asking for a refund?",
criteria={
"true": "The customer explicitly or implicitly asks for money back",
"false": "The customer asks for something else",
},
),
}
# Fake api_key: the SDK only checks the key's syntax; MockTransport blocks every network request.
with TypeSafeClient(api_key="offline-demo", transport=httpx2.MockTransport(fake_api)) as client:
result = client.system_one(state, questions, model="jev-1.13.0")
department = result.choices["department"]
urgency = result.scores["urgency"]
refund = result.nouls["refund_requested"]
print("typesafe-sdk", __version__, "| model:", result.model)
print("department:", department.choice, "confidence", department.confidence)
print("urgency:", urgency.score, "confidence", urgency.confidence, "legend", urgency.legend)
print("refund_requested: P(yes) =", refund.noul)
print("usage:", result.usage)
# Policy: the bar rises with the consequence of the action (the exact numbers are this blog's example).
LOW_RISK = (0.90, 0.50) # route a ticket: cheap to fix if wrong
HIGH_RISK = (0.98, 0.75) # open a refund flow: costs money if wrong
def decide(p: float, thresholds: tuple[float, float]) -> str:
auto, review = thresholds
if p >= auto:
return "auto"
if p >= review:
return "light_review"
return "escalate"
print("route to", department.choice, "->", decide(department.confidence, LOW_RISK))
print("urgency label ->", decide(urgency.confidence, LOW_RISK))
print("open refund flow ->", decide(refund.noul, HIGH_RISK))
Output:
POST /v1/systemone
typesafe-sdk 0.7.2 | model: jev-1.13.0
department: billing confidence 0.93
urgency: 1.7 confidence 0.62 legend {0: 'Can wait', 1: 'Needs attention this week', 2: 'Needs attention today'}
refund_requested: P(yes) = 0.91
usage: input_tokens=212 output_tokens=24
route to billing -> auto
urgency label -> light_review
open refund flow -> light_review
A few things this code shows:
- There is no parsing step.
department.choiceis already a string from the option set,urgency.legendalready hasintkeys,refund.noulis already afloat. - The SDK sends what we expect. When compared, the body the SDK serializes matches the request JSON in section 1 field for field. In other words, the JSON in section 1 is what goes over the wire.
- The API key is only checked for syntax on the client side. The SDK rejects empty keys, keys with whitespace and non-ASCII keys, but does not call the server to authenticate. So a fake string like
offline-demois enough once the transport is swapped. There is no secret in the code. - The policy at the end is yours, not JEV’s. Section 5 explains where 0.9 / 0.5 and 0.98 / 0.75 come from.
04Inside JEV: parallel sampler, RLCD, calibration
TypeSafe’s launch post describes the System One stack in one sentence: “a new model architecture, parallel sampler for maximum efficiency, and training method we call Reinforcement Learning for Calibrated Decisions (RLCD)”. Almost nothing about the model architecture is public (section 8). The other two parts have a little more. For each one, this section separates two layers: what TypeSafe says verbatim, and what only comes from third-party sources.
Parallel sampler
What TypeSafe says. In the launch post’s comparison table with LLMs, JEV’s “Sampling” row reads: “Parallel. Generates all outputs in a single query. Incredibly efficient and hardware-aware.” The same post says: “Jev outputs all probabilities in parallel instead of autoregressively generating by token”. The Models page adds a detail that matters to users: “Jev ingests the state once and evaluates every question against it in parallel”.
Put together, what is certain is:
- JEV does not generate tokens sequentially to answer. Every probability comes out in one pass.
- The
stateis ingested once per request. Three questions about the same ticket do not cost three reads of the ticket. - The consequence TypeSafe reports: an end-to-end response time of “70ms-500ms”, and potentially about 40–200 times faster than LLMs “for System One shaped queries”. These are the company’s own measurements.
Why does this matter? With an LLM, even in JSON mode or with structured output, the model still generates one token at a time, each depending on the ones before, so latency grows with output length. Constrained decoding only limits which tokens are allowed at each step; it does not change sequential generation. That is this blog’s reading, consistent with the “Sequential” label TypeSafe uses for LLMs in the same table. With JEV, the number of questions in a request and the length of option names do not stretch output generation that way, because no output string is generated at all.
Nothing on the implementation. TypeSafe has not published how the parallel sampler is implemented. TypeSafe’s entire documentation (the combined llms-full.txt, downloaded on September 30, 2026) does not contain the word “sampler” once. What exists is the three sentences in the launch post.
Third-party. An independent analysis on ai.thesatyajit.com (September 18, 2026) explains this interface as “parallel constrained decoding”: read the options’ logits after a single forward pass instead of generating a string. It cites an open-source reproduction called openjev, measured on the same Qwen3.5-4B model: reading logits took 1.02 s versus 5.33 s for generating JSON, about 5.2 times faster. It also notes that around 14 reproductions of this decoding approach appeared within a few days of JEV’s launch.
The right reading: this is outsiders reproducing JEV’s interface on open LLMs. It shows the interface is implementable and faster than generating JSON. It does not prove that JEV works that way inside.
RLCD: Reinforcement Learning for Calibrated Decisions
What TypeSafe says. The launch post puts three training approaches side by side in a table. LLMs are optimized with RLHF (answers people prefer) or RLVR (solutions a program can verify). JEV is optimized with RLCD, and its “Optimizes for” row reads: “Calibrated decisions: answers with epistemically honest probabilities on System One tasks”.
The AI primer in TypeSafe’s docs adds:
- RLCD “trains TypeSafe to return decisions and calibrated probabilities instead of generated text”;
- “Pretrained language models have been adapted in two major ways. TypeSafe adds a third… TypeSafe’s training path is RLCD”.
The Models page says JEV is “trained with RLCD” and that “the same weights serve every account”, so there is no per-customer fine-tune. The launch post’s FAQ, on why a new training algorithm was needed, only criticizes the limits of RLHF and RLVR without describing how RLCD works. TypeSafe’s blog post “The Bitterest Lesson” (September 10, 2026) adds no further detail either.
In short, TypeSafe has published RLCD’s name and goal: a model trained to return decisions and honest probabilities instead of text. It has not published how: the loss, the reward, how calibration targets are built.
Third-party. The ai.thesatyajit.com analysis notes that there is no paper or specification detailed enough to reproduce RLCD. According to it, the openjev project also classifies RLCD as “not reproduced or established”. A Hugging Face repo named “Qwen-2.5-1B-RLCD” has no weights.
Calibration
RLCD targets calibration, so it is worth being precise about what calibration means.
What TypeSafe says. The AI primer defines it: “Higher probability should correspond to a greater chance that the answer is correct”. When the model says 0.2, the answer should be right about 20% of the time. At 0.8, about 80%. At 1.0, essentially always. Right after that comes the most important sentence: “These rates describe groups of predictions, not a guarantee about any single answer”. The launch post sums it up in one line: “Calibrated: higher confidence means higher accuracy”.
Why does “groups of predictions” matter? Because it says calibration is a property of a whole set of predictions, not a promise about each answer. Take 1,000 tickets where the model returns noul = 0.9 for “is this a refund request?”. If the model is calibrated, about 900 of them really are refund requests. But there is no way to know whether the particular ticket in front of you is one of the 900 or one of the other 100. Calibration helps you set thresholds and estimate error rates across the whole population. It does not make any single answer correct.
No published measurements. TypeSafe’s docs publish no calibration measurement: no ECE, no Brier score, no reliability diagram. Section 8 lists the details.
Third-party. The ai.thesatyajit.com analysis relays two external measurements. This is a secondary source: this blog has not read the originals of either one.
- An architecture analysis measured ECE 0.0313 on 1,200 MMLU questions. If accurate, that is fairly good calibration on a general-knowledge benchmark. But MMLU is not the kind of task JEV targets, and this is not TypeSafe’s measurement.
- A cascade benchmark called “calibre” on the Banking77 dataset reports that “reported confidence runs ahead of measured accuracy at every level”: reported confidence is higher than measured accuracy at every level. The same benchmark found the optimal threshold moving from 0.67 to 0.37 when the dataset changed.
These two results do not contradict each other. They lead to the same practical conclusion: calibration measured on someone else’s data does not replace calibration measured on your own traffic. That is the subject of the next section.
05From probability to action
A number like 0.62 does nothing by itself. Probabilities only become valuable when code uses them to pick a branch. This section goes from TypeSafe’s official guidance to a concrete policy.
The official guidance: three zones, thresholds by risk
The Confidence page in TypeSafe’s docs splits confidence into three zones:
- High: “Act automatically.” The model has a clear read, and you can proceed without a person.
- Medium: “Proceed with caution.” The model has a reasonable answer but is not certain.
- Low: “Do not act.” Route to a person, ask for clarification, or fall back to a different system.
The same page has a section titled “Thresholds scale with risk”. Its sample code uses 0.5 as a floor: below that, route to a person. For destructive operations, the sample requires 0.9, and the 0.5–0.9 range needs user confirmation. The closing advice: “start with conservative thresholds, test with your own data, and adjust as you observe results”.
TypeSafe presents this as a design pattern, not a standard set of thresholds. The company’s workflow evals point the same way: “we ask independent narrow questions and defer to code where possible”.
Three branches, two thresholds
Figure 2 turns that guidance into a policy. Each action has two thresholds: a high one and a low one.
p ≥high → automatic: code acts directly;- low
≤ p <high → light review: a person confirms quickly before code acts; p <low → escalate: a person handles it from scratch, or a stronger LLM is called to reason further.
p is confidence for Choice and Score, and noul for Noul. With Noul, the policy applies to the action tied to the “yes” side (here, opening a refund flow). A ticket with a very low noul simply does not enter the automatic refund flow.
The bar rises with the consequence
The most important part of Figure 2 is the “pick thresholds” step. A single threshold for every action is usually a poor design. Routing a ticket to technical by mistake costs a few minutes to route it back. Opening a refund flow by mistake costs money. Those two actions should not share a threshold.
The series’ source notes propose concrete levels: 0.98 to act automatically and 0.75 for light review, and even 0.995 for refunds. This is this blog’s analysis, not a TypeSafe recommendation. The code in section 3 uses two threshold pairs:
| Action | Cost of being wrong | High / low threshold | Where the numbers come from |
|---|---|---|---|
| Route a ticket, set priority | Cheap, easy to fix | 0.9 / 0.5 | Numbers from the Confidence page’s sample code (there, 0.9 is for destructive actions and low-risk actions only need a 0.5 floor); applying them to ticket routing is this blog’s choice |
| Open a refund flow | Costs money, hard to undo | 0.98 / 0.75 | The general threshold pair in the series’ source notes (the notes use 0.995 specifically for refunds); applying it to refunds is this blog’s choice |
Applied to the ticket example:
department: confidence 0.93 ≥ 0.9 → route tobillingautomatically;urgency: confidence 0.62 sits between 0.5 and 0.9 → set the priority label after a light review. The expected score of 1.7 leans toward “needs attention today”, but the distribution is not concentrated enough to label it automatically;refund_requested: noul 0.91 sits between 0.75 and 0.98 → light review before opening a refund. The same 0.91 would have been automatic under the ticket-routing thresholds.
The last case shows why thresholds belong to actions, not to questions: the same level of certainty can be enough for one action and not enough for another.
Choose thresholds on your own traffic
The numbers in the table are only a starting point. The real way to pick thresholds, following the series’ source notes and consistent with the Confidence page’s advice:
- Collect a labeled set from real workload: a few hundred to a few thousand cases, each with a correct answer confirmed by a person.
- Run JEV on that set and bucket the results by confidence, for example 0.5–0.6, 0.6–0.7, …
- Measure accuracy per bucket. This is the reliability curve on your data. The calibre result in section 4 shows this curve can drift away from the diagonal, and drift differently across datasets.
- Pick thresholds by cost. Each action has a cost for false positives (acting when it should not) and a cost for false negatives (not acting when it should). A good threshold minimizes total cost, not maximizes accuracy.
- Pin the model version and log it. Log each decision with
response.model, the question and rubric version, and the threshold version. When moving to a new JEV release, rerun steps 2–4 before switching traffic.
06Type safety ≠ correctness
Part 1 made this point at the conceptual level. Having seen the schema and the code, we can be more precise: what is guaranteed, where, and what is not.
“Can’t hallucinate” in the narrow sense
The launch post says JEV “can’t hallucinate”, calls finding a counterexample “mathematically impossible”, and notes that its 0% hallucination figure “is not empirical”: it is a guarantee about matching the schema. On correctness, the System One page says plainly that calibration “is measured across groups of predictions; it does not guarantee that an individual answer is correct”.
The safe production reading, following the series’ source notes: “does not hallucinate” means does not answer outside the schema. A Choice cannot return an option that is not in criteria. A Score cannot return a level outside the scale. It does not mean JEV is always right about the world.
well-typed != true
A ticket saying “The app crashed right after I paid, and now I see two charges” could be routed to technical with confidence 0.7. That answer is well-typed, since technical is a valid option. It can still be wrong for the business.
The guarantee lives on the server, not in the SDK schema
There is a detail few people notice: the SDK’s pydantic models do not check the type-safety guarantees. The test below runs with typesafe-sdk==0.7.2:
from typesafe_sdk import Choice, SystemOneResponse
# 1) a choice outside criteria, probabilities that do not sum to 1
bad = SystemOneResponse.model_validate({
"model": "jev-1.13.0",
"answers": {"department": {"type": "choice", "choice": "payments", "confidence": 0.9,
"probabilities": {"billing": 0.7, "technical": 0.7}}},
"usage": {"input_tokens": 1, "output_tokens": 1},
})
print("response validated:", bad.choices["department"].choice)
# 2) 256 options
q = Choice(criteria={f"opt_{i}": None for i in range(256)})
print("choice options accepted:", len(q.criteria))
response validated: payments
choice options accepted: 256
A response with choice = "payments" (not in the list) and probabilities summing to 1.4 still validates. A 256-option Choice is created without an error, even though the limit is 255. Likewise, the schema only requires a Score to have at least one level and sets no maximum, while the docs say the API accepts up to 10.
This does not mean the SDK is wrong. It means the “type-safe” guarantee is a server-side guarantee: the model and the API keep answers inside the answer space. The 255-option and 10-level limits come from the docs. This blog has no API key, so it has not checked which error the server returns when those limits are exceeded.
The practical consequence, as this blog’s analysis: if your code receives responses through an intermediate layer (a cache, a queue, another service, or an adapter that runs an LLM behind the same interface), do not assume choice ∈ criteria just because the object went through pydantic. An assert answer.choice in criteria line is very cheap.
Separate questions are not guaranteed to agree
A third class of error sits between questions. The jev-1.13 weak-spot docs give an example: ask two separate Noul questions about the same state, and “refund” gets 0.72 while “not refund” gets 0.47. Logically, the two should add up to 1. Here they sum to 1.19.
Each question is evaluated independently. Nothing in the model forces answers to respect logical invariants between them. If your business logic needs such an invariant, code has to enforce it, for example by asking about one side only and computing the other.
To sum up: type safety removes one class of errors, errors in the shape of the output. Calibration helps manage the second class, picking a wrong but valid answer, by telling you when to be suspicious. The third class, contradictions between questions, belongs to code.
07The 9 jev-1.13 failure modes
TypeSafe maintains a “model jaggedness” page per version, listing where the model performs poorly. The jev-1.13 page (marked “Last reviewed 2026-09-17”) lists 9 failure modes. The Math and Numbers entry has 3 sub-items: Counting, Numeric representations, Math using score. Counting every heading on the page gives 12, but that double-counts, because the three sub-items already belong to entry 2.
| # | Failure mode | Where the model is weak | How to avoid it |
|---|---|---|---|
| 1 | Literal reading | Answers what is literally written, does not guess intent | Spell out conditions and edge cases in instructions and criteria |
| 2 | Math and Numbers | “Jev is not a calculator” | Let code do the math |
| 2a | ↳ Counting | Counting is unreliable, and errors grow with the number of items | Count in code, ask one Noul per item |
| 2b | ↳ Numeric representations | Hex, RGB, assembly and binary encodings do worse than semantic names | Map to names or buckets in code before asking |
| 2c | ↳ Math using score | Cannot infer precise magnitudes from score or probabilities |
Use the expected score only to compare against thresholds |
| 3 | Date and time comparison | Reads dates as text, not as ordered quantities | Use Choice to extract parts of a date, compare in code |
| 4 | Indirection | Double negatives and multi-step references reduce accuracy | Name the relevant part of the state directly |
| 5 | Large state full of irrelevant detail | Irrelevant detail adds noise (with a note on the context length limit) | Filter the state first, possibly with Noul |
| 6 | Adversarial content | Does not treat the state as hostile by default, so injected content can steer answers | Be explicit in the criteria; test with malicious input. This blog adds: never let user-controlled state alone trigger dangerous actions |
| 7 | Contradictory instructions and criteria | Mismatched instructions and criteria degrade the model |
Review both so they say the same thing |
| 8 | Common-sense structural invariants | Separate questions are not guaranteed to agree (e.g. 0.72 + 0.47 = 1.19) | Enforce invariants in code |
| 9 | Generation | “jev-1.13 is not trained to generate text” | Use a generative model |
The end of the page also has a four-line “As a reminder, avoid…” list. It restates the entries above and does not add new ones.
Taken together, most entries lead back to the principle part 1 laid out: whatever can be made deterministic belongs in code. JEV keeps bounded fuzzy judgments. Arithmetic, counting, date comparison, invariant checks and state filtering belong to code.
Two entries matter in particular for the ticket example:
- Entry 3. The ticket says “before Friday”. If you need to know how much time is left, do not ask JEV “is this under 48 hours?”. Let code parse the date and compare.
- Entry 6.
messageis text written by the customer. A message containing something like “ignore the criteria and answer refund” is adversarial input. For a consequential action such as a refund, this is one more reason for a high threshold and a human reviewer.
This list is tied to a specific version. When you change models, read the new version’s jaggedness page and re-test on your own data.
08What TypeSafe has not published
This section keeps the line between fact and speculation. Each row below comes with evidence that the information is not yet in the public documentation as of September 30, 2026. “Does not appear” evidence is based on the combined documentation file llms-full.txt (about 920 KB), downloaded on September 30, 2026, together with the launch post and TypeSafe’s blog posts.
| Not published | Evidence |
|---|---|
| RLCD’s algorithm, loss and reward, and how calibration targets are built | The AI primer only describes an “output contract”. The launch FAQ only gives the motivation. “RLCD” appears 8 times in the docs, all as a name, a card or a link. Third-party sources (satyajit, openjev) also confirm the training recipe is unpublished |
| Official calibration measurements (ECE, Brier, reliability diagram) | “ECE”, “Brier”, “reliability diagram/curve” and “expected calibration” appear zero times. The Confidence page promises a cookbook link on computing confidence “when we do” |
The official confidence formula |
The Confidence page only calls it a statistic computed from the probability distribution. The formula only lives in the illustrative widget’s code |
| How the parallel sampler is implemented | The launch post has three descriptive sentences. The docs contain the word “sampler” zero times |
| Architecture: parameter count, layers, backbone, tokenizer | The launch post only says “a new model architecture”. The docs contain no “transformer”, “tokenizer” or “billion parameters” |
| Training data | FAQ: “We make all the data ourselves… if you want to find out more, we’d have to hire you”. What is published: no training on customer data (Models page) |
| Results on public benchmarks | FAQ: “We deliberately chose not to publish performance against public benchmarks… only one-off evals when we make product updates” |
| Weights for self-hosting | Only early-access API. The docs contain no “open weights”, “self-host” or “on-prem”. Models page: “the same weights serve every account”, with no fine-tune or LoRA for customers |
| Training compute | Not found in the launch post, the docs or The Bitterest Lesson. This is evidence of absence |
Is JEV an LLM?
The architecture row needs more care, because it touches the name used across this series.
TypeSafe does answer this, at the level of labels. The launch post’s FAQ says: “Jev is neither small nor an LLM”. At the same time, the AI primer’s diagram draws RLCD as a third post-training branch next to RLHF and RLVR, starting from “pretrained language models”.
These two statements do not necessarily conflict. One reading is that JEV may start from a pretrained model but, after RLCD, is no longer an “LLM” in the sense of a text-generating model. But that is a reading, not something TypeSafe has published. This blog quotes both verbatim and draws no conclusion about the internal architecture. Part 1 quotes this FAQ line too; what has not been published is what JEV is built from and what is inside it.
What is certain is that the series’ framing of a new branch beside LLMs now has direct support from TypeSafe itself. And the series still does not claim that JEV is “really a classification head on an LLM”, “an encoder-only transformer” or “an LLM with the decoder removed”. The open-source reproductions in section 4 do not prove any of that either.
09Summary
From the outside, a JEV call is simple: one state, a few typed questions, a set of probabilities. Most of the value, and most of the risk, lies in how you ask the questions and how you read the numbers that come back.
What to take away:
- a request has three required fields,
state,modelandquestions; a response hasmodel,answersandusage, all required. Question keys are application-side labels and are not sent to the model. Pinjev-1.13.0and logresponse.model; - Choice picks one of up to 255 options and returns
choice,probabilitiesandconfidence. Score returns an expected score, possibly fractional, plus alegend, over 2–10 levels per the docs. Noul only returnsnoul= P(yes), with noconfidence; - the sample code runs offline with
MockTransportandtypesafe-sdk==0.7.2, using the public API and no API key; real answers need an early-access key; - JEV ingests the state once and returns every probability in parallel instead of generating tokens. How the parallel sampler is implemented has not been published. Explanations such as “reading logits” come from third parties;
- RLCD is the training method for “calibrated decisions”. TypeSafe has published the goal, not the method. Calibration is a property of a whole set of predictions, not a guarantee for any single answer;
- from probability to action: three zones, automatic, light review and escalate; the bar rises with the consequence of the action; and pick thresholds on your own traffic;
- well-typed is not correct. The type-safety guarantee lives on the server; the SDK schema does not check
choice ∈ criteria. Separate questions are not guaranteed to agree; - jev-1.13 has 9 failure modes, and Math and Numbers has 3 sub-items. Most lead back to one principle: whatever can be made deterministic belongs in code;
- as of September 30, 2026, TypeSafe has not published the RLCD recipe, calibration measurements, the architecture, the data, the compute or the weights. TypeSafe has said JEV is “neither small nor an LLM”.
The next post, part 3, steps out of the model to look at what surrounds it: the JEV ecosystem. That means TypeSafe’s official repositories (the Python and JavaScript SDKs, and the adapter that runs an LLM behind the System One interface), community projects, and open or self-hosted models imitating JEV’s interface, such as the openjev project mentioned in section 4. It also covers how to read community claims without confusing “reproduced the interface” with “knows what is inside”. If you have not read part 1, Decision Models: A New Branch Beside Generative LLMs explains why this class of model exists in the first place.
References
Official TypeSafe AI sources
- TypeSafe AI — Introducing System One Models & Jev, September 15, 2026 (the architecture, parallel sampler and RLCD stack; the Sampling and Frontiers comparison tables; 70–500 ms latency; “can’t hallucinate”, “not empirical”; FAQ on data, public benchmarks and “neither small nor an LLM”)
- TypeSafe AI — API reference (request fields, question keys not sent to the model, the 255-option limit and 2–10 Score levels,
instructions) - TypeSafe AI — Models (
jev-1.13.0, thejev-latestalias, ingesting the state once, “trained with RLCD”, “the same weights serve every account”) - TypeSafe AI — Choice (Choice and the 255-option limit)
- TypeSafe AI — Confidence (high / medium / low zones, “Thresholds scale with risk”, Noul has no confidence, the widget illustrating the confidence formula)
- TypeSafe AI — Machine learning primer (RLCD versus RLHF and RLVR; the definition of calibration, “groups of predictions”)
- TypeSafe AI — System One (the System One concept, state and questions)
- TypeSafe AI — Jev 1.13 jaggedness (9 failure modes and 3 sub-items, the 0.72 + 0.47 = 1.19 example)
- TypeSafe AI — The Bitterest Lesson, September 10, 2026
- TypeSafe AI — typesafe-sdk-python, version 0.7.2, and
models.pyat commit f078f1e (request, question, answer and usage schema) - TypeSafe AI — Workflow evals (“we ask independent narrow questions and defer to code where possible”)
- TypeSafe AI — Full documentation as text (llms-full.txt), downloaded September 30, 2026 (used to check what does not yet appear in the docs)
Third-party sources
- ai.thesatyajit.com — JEV System One models, September 18, 2026 (the “parallel constrained decoding” explanation, the openjev reproduction at 1.02 s versus 5.33 s, RLCD not reproduced, ECE 0.0313 on MMLU, the calibre benchmark on Banking77). Not a TypeSafe source; the measurements it relays are secondary
Author’s notes
- Source notes: “JEV Deep Dive — How the System One model works” (Notion, September 30, 2026), sections 2–7 and 10–12