Fundamentals9 min read

What Is Jev? A System One Model, Explained Without the Marketing

Jev returns typed decisions and probability distributions instead of text. Here is the mental model, what actually comes back, and who should care.

The one-sentence version

Jev is a model you send a blob of context to, along with a set of typed questions, and it returns typed answers plus probability distributions. It does not write anything. There is no prose in the response, no JSON to coax out of a code fence, no schema-validation retry loop. You get values your code can branch on.

TypeSafe AI calls this a "System One" model, after Kahneman's fast-and-intuitive System 1. The name is doing marketing work, but the underlying category distinction is real: this is a model built to produce decisions for software rather than text for humans.

The cheapest way to understand it is the framing the founder used on Hacker News during the launch thread: the three primitives map onto three constructs you already write every day.

  • Choice is a match statement. One option out of a set you define.
  • Score is a sort key. A position along an ordered rubric you define.
  • Noul is an if statement. A yes/no proposition.

That is the whole API surface. Everything else is how you structure the input and what you do with the numbers.

The request

One endpoint: POST https://api.typesafe.ai/v1/systemone, with Authorization: Bearer <API_KEY>. The body has exactly three fields.

{
  "state": "Help! My payouts have been failing for 3 days.",
  "model": "jev-latest",
  "questions": {
    "is_urgent": {
      "type": "noul",
      "instructions": "Does this convey urgency?"
    }
  }
}

state is the content being judged. It can be a plain string, a JSON object, or an array of text values. Jev is text-only right now; images, audio and video are not supported.

questions is a map. You pick the keys. This is worth internalizing because it trips people up: the question keys are never sent to the model. A key named refund_requested communicates nothing to Jev. It is a handle for your code to pull the answer back out with. All of your actual evaluation logic goes in instructions and criteria.

model selects which version answers. jev-latest is the SDK default and currently resolves to jev-1.13.0. The response reports the versioned ID that actually answered, so you can log it.

The three primitives in detail

Choice — pick one option

criteria is a map from option name to a description of that option, or null when the name speaks for itself. You get up to 255 options in a single Choice.

from typesafe_sdk import Choice, TypeSafeClient
 
with TypeSafeClient() as client:
    response = client.system_one(
        state="My running shoes arrived in the wrong size. Can I swap them for a size 10?",
        questions={
            "department": Choice(
                instructions="Which team should handle this?",
                criteria={
                    "returns": "Exchanges, wrong or damaged items",
                    "shipping": "Delivery status, delays, lost packages",
                    "billing": "Charges, invoices, payment problems",
                },
            ),
        },
    )
 
    print(response.answers["department"].choice)

Back comes choice (the highest-probability option), probabilities (every option mapped to a float, summing to 1), and confidence.

Both the option names and their descriptions are sent to the model, so the descriptions are load-bearing. Write them to separate the options from each other rather than to sound tidy.

Score — position on a rubric

criteria is an ordered array of level descriptions. At least two, and the API accepts up to ten. Level numbers are array positions starting at 0.

import { score, TypeSafeClient } from "@typesafe-ai/sdk";
 
const client = new TypeSafeClient();
const response = await client.systemOne({
  state: "The export button crashes the settings page in Safari. It works in Chrome, but a few of our customers only use Safari.",
  questions: {
    bug_severity: score("How severe is the reported issue?", [
      "Cosmetic; no impact to functionality",
      "Broken or degraded feature, but workaround exists",
      "Blocking issue; no workaround exists",
    ]),
  },
});
 
console.log(response.answers.bug_severity.score);

The returned score for that example is 1.43, with probabilities of 0.57 on level 1 and 0.43 on level 2.

This is the single most misread field in the API. score is an expectation, not a label. It is each level number multiplied by its probability, summed: 0 × 0.0 + 1 × 0.57 + 2 × 0.43 = 1.43. It lands between levels, and different distributions produce the same number. A score of 1.0 might mean all the probability sat on level 1, or that it split evenly between levels 0 and 2. Read probabilities alongside it when the distinction matters.

Score answers also return legend, mapping each level number back to its description, which saves you keeping the array in sync on the client.

A concrete warning from TypeSafe's own jaggedness notes for jev-1.13: do not use a Score expectation to reconstruct an exact numeric magnitude by interpolating between levels. Thresholding is fine. Interpolating to recover "the real number" is not.

Noul — a probability the answer is yes

The odd name is the odd one out in the API. criteria is optional here, and if supplied it is an object with true and false keys describing what each side means.

from typesafe_sdk import Noul, NoulCriteria, TypeSafeClient
 
with TypeSafeClient() as client:
    response = client.system_one(
        model="jev-latest",
        state="I have asked three times now. Can I please just talk to a real person?",
        questions={
            "is_human_escalation": Noul(
                instructions="Is the customer asking for a human agent?",
            ),
            "is_repeat_contact": Noul(
                instructions="Has the customer contacted support about this before?",
                criteria=NoulCriteria(
                    true="Mentions a prior attempt, ticket, or that they have asked before",
                    false="No sign of any previous contact",
                ),
            ),
        },
    )
    print(response.answers["is_human_escalation"].noul)

A Noul answer contains exactly one field: noul, a number from 0 to 1. No probabilities, and critically no confidence. That is not an oversight. A two-outcome distribution is fully described by a single value, so there is nothing for a separate confidence statistic to add.

The other Noul trap: the value is the probability that the proposition is true, not a measurement of degree. Asking "Is this candidate strong in Python?" and reading 0.5 as "medium skill" is wrong; 0.5 means the model splits evenly on whether "strong" applies. If you want degree, use a Score with levels you wrote. TypeSafe's docs show this directly, with a candidate who used Python daily for two years scoring 0.81 on the Noul and 2.05 on a four-level Score.

The response

{
  "model": "jev-1.13.0",
  "answers": {
    "department": {
      "type": "choice",
      "choice": "billing",
      "probabilities": { "billing": 0.88, "technical": 0.12, "sales": 0.0 },
      "confidence": 0.81
    }
  },
  "usage": { "input_tokens": 318, "output_tokens": 34 }
}

Answers come back keyed by the ids you chose. usage reports input_tokens and output_tokens separately, which matters because only one of them is billed.

Why there are no strings

The absence of string output is not a product decision about keeping responses tidy. It is the architectural constraint the whole model is built around, and it is what makes the pricing and latency claims possible. That deserves its own post, but the short version: sequential output has to be generated token by token, one conditioned on the last. A fixed-shape distribution over a known option set does not. Every question in a request is evaluated in parallel against the same state.

The practical consequence you feel immediately is that adding questions is nearly free in latency. Every question sees the same state, is evaluated independently, and cannot contaminate another's answer. There is no context rot from stacking twelve questions into one call, because they are not sharing a context window with each other's outputs. You pay input tokens for the extra question text and that is it.

This makes a pattern rational that would be wasteful with an LLM: ask questions you probably will not need. TypeSafe calls it speculative fan-out. Ask for the ticket category, and also the bug severity, and also whether reproduction steps are present, and also whether a refund was requested. If the category comes back feature_request, your code ignores the bug fields. You saved a round trip in the cases where you needed them and paid a handful of tokens in the cases where you did not.

What it is bad at

An honest guide has to front-load this, because the failure modes are specific and documented. From TypeSafe's own jaggedness page for jev-1.13:

  • It reads literally. Scoping words, negations and implied conditions are taken at face value. The doc's own advice is sharp: if you look at a wrong answer and find yourself explaining what you really meant, that explanation is the missing half of your instruction.
  • It is not a calculator. Counting is unreliable and the error grows with the size of the thing being counted. Keep arithmetic in code.
  • Dates are read as text, not ordered quantities. Extract the components with the model; compare them in your own code.
  • Accuracy falls as the state fills with irrelevant detail. Filter before you send.
  • State is not treated as hostile. Content written to steer the classification can move the answer. TypeSafe says it expects to improve here, which is a fair way of saying prompt injection through state is a live concern today.

None of these are disqualifying. All of them are things you want to know before you put it in a pipeline.

Who this is actually for

Jev fits when you have a judgment call inside a control flow. Ticket routing, content moderation gates, relevance filtering ahead of a RAG retrieval, extracting a categorical field from messy text, scoring records for a priority queue, deciding whether an agent's next step warrants a human check. The shape of a good fit is: a knowledgeable person could make this call in about a second given the right context, and my code needs to branch on the result.

It does not fit when you need generation, extended reasoning, or a chain of dependent inferences. It is not an LLM replacement, and TypeSafe does not claim it is. Where it competes is the layer of an application where people currently call a large model, ask for JSON, and then write a parser and a retry loop around it.

If you take one habit from this post, take decomposition. The failure mode that wastes the most time is asking one broad question and getting a mushy, low-confidence answer. "Rate this startup pitch" is a bad Jev question. Market size, technical feasibility and differentiation as three separate questions, weighted in your own code, is a good one. When your priorities change you edit a coefficient instead of rewriting a prompt.

Next: why Jev is not an LLM and why that is the whole point, and what the "it can't hallucinate" claim actually covers.