---
title: "What is a typed answer? Choice, Score and Noul explained"
description: "A typed answer is a value from options you defined, with a probability on each. See a Choice, a Score and a Noul route one support ticket in an interactive sim."
published: 2026-11-04
updated: 2026-11-04
canonical: https://protrailblazer.com/posts/jev-typed-answers/
tags: [typesafe, jev, system-one, typed-answers, confidence-routing, structured-output, demo, developer-tools]
---

# What is a typed answer? Choice, Score and Noul explained

**Answer:** A typed answer is a value from a set of options the developer wrote down, with a probability on each option, returned by a System One model instead of generated text. Three shapes cover most questions: a Choice picks one option, a Score places an item on ordered levels, and a Noul gives the probability of yes.

The interactive below runs an airline support desk. One customer message goes out as one request with three questions, three typed answers come back, and eight lines of code pick a route. Let the stream run for a few messages, then tap a preset to pin it, drag any probability bar, and watch the confidence and the route change with no new request.

*Interactive demo: [open the jev typed answers demo](https://protrailblazer.com/demos/jev-typed-answers/)*

## What you're seeing

The **Message** window holds the current ticket, twelve preset chips, the state the request carries (the message plus one line of refund policy), and a disclosure with the full request JSON and the curl line. The three windows on the right are the answers, one per question. `refund_requested` is a Noul with one bar, the probability of yes. `request_type` is a Choice with three bars, one per option, and the selected option in red. `frustration` is a Score with three bars, one per level, plus a gauge showing where the score position lands between 0 and 2. Under each set of bars sits the answer JSON in the documented shape, the one line of code that reads it, and the confidence worked out from the bars.

The **Policy** window is the part that runs in your code. Eight lines, with the live numbers substituted in and the line that fired washed in red. Under it are the four thresholds as sliders and a tally of the five routes. The live stats top right count requests, answers (three per request), the share of tickets that routed with no person involved, and the last route.

A few things to try:

- **Pin message 1 and drag the refund bar down.** The other two bars rescale so the window still sums to 1, the confidence drops with the top probability, and at 0.70 the route flips from refund to confirm because the ACT line fires. The label on the window changes to "Edited by you" and a Restore button appears.
- **Pin message 7 and switch the Score toggle to "score at least 1.5".** The sarcastic honeymoon message splits the Score between calm and very angry, so the position lands at 1.00 and the threshold on position misses it. The threshold on the level 2 probability catches it.
- **Move the NO slider from 0.30 up to 0.50 with message 6 pinned.** The Noul sits at 0.52, so the ticket still routes to confirm. Push NO past it and the same ticket goes to a human because the Choice and the Noul now disagree. The REQUESTS counter never moves: the tally re-sorts from stored answers.
- **Drag any bar to 100 percent, then to an even split.** Confidence reads 1.000 and then 0.000 for both the Choice and the Score, which is the whole range of the documented formulas.
- **Open the Request disclosure.** The IDs you see on the windows (refund_requested, request_type, frustration) are keys you choose. They are not sent to the model, which only reads the instructions and criteria.

## What is a typed answer?

A typed answer is a value from options the developer defined, with a probability for each option. It is not generated text. A System One model such as TypeSafe's Jev reads a state (a string or a JSON object) and answers typed questions about it. You write the options. The model returns a probability on each one. Nothing comes back that your code has to parse, and nothing comes back that a model could have written differently on a second try.

The three shapes in the sim cover most questions an application asks about its own data:

- **A Choice** picks one option from a set you name. The answer carries the chosen option, the probability of every option (they sum to 1), and a confidence.
- **A Score** places the item on ordered levels you describe, numbered from 0. The answer carries a probability per level, a confidence, and a score, which is each level number multiplied by its probability and added up. It can land between two levels.
- **A Noul** is the probability that the answer to a yes or no question is yes. It has no confidence field. A value near 0.5 means the model cannot tell. It does not mean medium.

All three ride in one request. The state is shared, and each question is evaluated independently and in parallel, so adding a question adds criteria text to the request rather than a round trip. Here is the request the sim builds for message 1, in the shape the [TypeSafe docs](https://docs.typesafe.ai/) describe as of 2026-11-04:

```
POST https://api.typesafe.ai/v1/systemone
{
  "model": "jev-latest",
  "state": {
    "ticket_message": "My flight was cancelled. Can I get a refund?",
    "refund_policy": "Cancelled flights are eligible for a full refund."
  },
  "questions": {
    "refund_requested": {
      "type": "noul",
      "instructions": "Does `ticket_message` request a refund?"
    },
    "request_type": {
      "type": "choice",
      "instructions": "What is the main request in `ticket_message`?",
      "criteria": {
        "refund": "The customer wants money returned.",
        "rebooking": "The customer wants a replacement flight.",
        "information": "The customer is asking for information only."
      }
    },
    "frustration": {
      "type": "score",
      "instructions": "How frustrated does the customer appear in `ticket_message`?",
      "criteria": [
        "Calm and neutral.",
        "Concerned but civil.",
        "Very angry or using strong language."
      ]
    }
  }
}
```

And the three answers the sim shows for that message when pinned, one per question ID:

```
{
  "refund_requested": { "type": "noul", "noul": 0.930 },
  "request_type": {
    "type": "choice",
    "choice": "refund",
    "probabilities": { "refund": 0.880, "rebooking": 0.080, "information": 0.040 },
    "confidence": 0.820
  },
  "frustration": {
    "type": "score",
    "score": 0.23,
    "legend": { "0": "Calm and neutral.", "1": "Concerned but civil.", "2": "Very angry or using strong language." },
    "probabilities": { "0": 0.800, "1": 0.170, "2": 0.030 },
    "confidence": 0.655
  }
}
```

The Choice picked refund because it had the highest probability. The Score came back at 0.23, which is 0 times 0.800 plus 1 times 0.170 plus 2 times 0.030: mostly calm, with a little weight on concerned. The Noul is a single number and that is all it is.

## Structured output vs typed answers: what is the difference?

Structured output fixes the shape of the text. A typed answer removes the text. Structured output from a language model, whether by JSON mode or by the grammar tricks in the [constrained decoding sim](https://protrailblazer.com/posts/constrained-decoding-json/), guarantees that what the model writes will parse. It says nothing about whether the model was sure. If you ask such a model to add a "confidence": 0.8 field to its JSON, that number is generated text like the rest of the object, written one token at a time by the same process that wrote the sentence before it.

A typed answer is a distribution over options you defined, not a value the model composed. The probability of "refund" is what the model assigns to that option, and the confidence is a function of those probabilities that you can recompute yourself. That is why every number in the sim is live: drag a bar and the confidence, the fired line and the route all fall out of the arithmetic. Whether the probabilities deserve trust is a separate question, and the calibration of a model is something you measure on your own data. The shape, though, is guaranteed by construction.

## How is confidence calculated?

Confidence is arithmetic on the probabilities, and each shape has its own formula. The sim implements the two documented formulas exactly and checks them against the reference values from the docs on every load:

```
// Choice: how far the top probability sits above an even split.
function choiceConfidence(p) {
  const n = p.length;
  return (Math.max(...p) - 1 / n) / (1 - 1 / n);
}

// Score: probability-weighted distance from the most likely level,
// against an even spread.
function scoreConfidence(p) {
  const n = p.length, m = p.indexOf(Math.max(...p));
  const spread = p.reduce((s, pi, i) => s + pi * Math.abs(i - m), 0);
  const even = p.reduce((s, _, i) => s + Math.abs(i - (n - 1) / 2), 0) / n;
  return Math.max(0, 1 - spread / even);
}

// Noul: no API field. If code wants one, use the distance from 0.5.
function noulMargin(p) { return Math.abs(2 * p - 1); }
```

The Choice formula only looks at the top probability. The documented examples make the point: (0.6, 0.3, 0.1) and (0.6, 0.2, 0.2) both give 0.4, because the runner-up never enters the calculation. A confidence of 0.5 means a top probability of 0.75 with two options and 0.55 with ten, so the same confidence can mean quite different probabilities depending on how many options you wrote.

The Score formula is different because levels are ordered, so a split between neighbors is less uncertain than a split between the ends. The docs' two reference cases show it: probabilities (0, 0.5, 0.5) give a Score confidence of 0.25, and (0.5, 0, 0.5) give 0. The Choice formula would give 0.25 for both, which is why a Score has its own. In the sim, message 1's frustration answer (0.800, 0.170, 0.030) has a spread of 0.230 around level 0, the even spread for three levels is 0.667, and the confidence is 1 minus 0.230 divided by 0.667, which is 0.655.

A Noul has no confidence field. The value already says how sure the model is: 0.93 is a confident yes, 0.04 is a confident no, and 0.52 is "cannot tell". If code wants a single number for that, the distance from 0.5 is what the docs suggest, and the sim shows it as a margin next to the bar.

## How does code route on the answers?

The model supplies the judgments. Thresholds in code turn them into a route. The whole policy in the sim is this:

```
if   p(frustration = 2) >= ANGRY            -> human
elif p(request_type.choice) < ACT           -> confirm
elif request_type.choice == "refund":
     if   refund_requested >= YES           -> refund
     elif refund_requested <= NO            -> human   (the two answers disagree)
     else                                   -> confirm
elif request_type.choice == "rebooking"     -> rebook
else                                        -> reply

ANGRY 0.4 · ACT 0.7 · YES 0.7 · NO 0.3   (example values, tune on real data)
```

Each threshold sits where the docs say a threshold belongs for that shape. A Noul is thresholded on the value itself: 0.7 and above reads as yes, 0.3 and below reads as no, and those two are the documented starting points. The band between them gets its own outcome in code, which here means asking the customer one question. A Choice is thresholded on the probability of the option the code is about to act on, not on the confidence, because 0.42 on "refund" is a weak reason to issue a refund however the other 0.58 is spread. A Score is thresholded on the probability of one level when the decision depends on that level, which is the ANGRY line. The ACT and ANGRY values are examples, not documented defaults.

Because the thresholds live in code, moving one re-routes every ticket already judged with no new request. The [preflight gate sim](https://protrailblazer.com/posts/typesafe-before-claude-code/) uses the same property to re-sort a queue of dev requests, and the [confidence-gated companion](https://protrailblazer.com/posts/jev-confidence-gated-companion/) shows what happens to a game character when the gate is set too loose or too tight.

## Three messages worth pinning

**Message 6** ("I was told I might be owed a refund for the cancelled flight?") is the unsure case. The Choice says refund at 0.78, comfortably over ACT. The Noul sits at 0.52. If code rounded that to yes, a hedged question would trigger a refund; if it rounded to no, the Choice and the Noul would look like a contradiction and the ticket would go to a human. Neither is right. The band between NO and YES exists so that "cannot tell" becomes "ask", and the route is confirm.

**Message 7** ("Thanks so much for cancelling my honeymoon flight. Really great work.") is why the Score window has a toggle. Sarcasm reads as calm on the surface and furious underneath, and the simulated answer puts 0.49 on level 0, 0.02 on level 1 and 0.49 on level 2. The score position is 1.00, right on "concerned but civil", a level the model gave almost no weight to. A policy that tests the position against 1.5 sends this ticket on to the Choice branch and ends at confirm. A policy that tests the level 2 probability against 0.4 sends it to a human. The confidence on this answer is 0, which is the formula doing its job: a split between the ends is the least certain shape a three-level Score can have.

**Message 8** ("Loved the new safety video, by the way.") is not a request at all. A Choice always picks one option, even when nothing fits, and here it picks "information" at 0.55 with the rest spread over the other two. The only signal that something is off is the low probability on the pick, which is what the ACT line catches. The docs recommend a cleaner fix: a companion Noul that asks whether any of the options applies, so code can tell "no option fits" from "a close call between two options". That is the same lesson as the LLM as judge pattern taught the [level judging sim](https://protrailblazer.com/posts/jev-generate-and-judge-levels/): ask narrow questions, one per thing you need to know, and let code combine them.

> **info:** Nothing in the demo calls Jev. Each of the twelve messages carries an authored distribution for each question and a clarity value. When the stream feeds a message, seeded noise scaled by 1 minus clarity is added to each distribution and the result is renormalized, so two loads play out identically and clear messages barely move while ambiguous ones wobble. A pinned preset shows its authored distribution with no noise. Everything derived (the choice, the score, both confidences, the Noul margin, the fired line, the route and the tally) is computed from the probabilities on screen with the documented formulas, and the eight reference values from the docs are asserted on every load. The request and answer shapes, the confidence formulas and the 0.7 and 0.3 starting thresholds come from the TypeSafe docs as read on 2026-11-04.

## Frequently asked questions

### What is a System One model?

A System One model answers typed questions about an input instead of generating text. You pass a state and one or more questions, each a Choice, a Score or a Noul with your own options or levels, and it returns a probability on each option plus a confidence where the shape has one. TypeSafe's Jev is the model the sims on this site are built against. The point of the design is that the answer is a judgment your code can compare, not a sentence your code has to read.

### How do you get a confidence score from an AI model?

From a chat model, asking it to rate its own confidence gives you generated text, a number it wrote rather than one it measured. From a System One model, every answer comes with the probability of each option, and confidence is a documented function of those probabilities. For a Choice it is the top probability scaled against an even split, for a Score it is one minus the spread around the most likely level, and for a Noul the value itself is the measure. Because the formulas are public, you can recompute the number from the probabilities and check it, as the sim does.

### What does model confidence mean?

In a typed answer, confidence means how far the probabilities are from an even split, measured in a way that suits the shape. It is not a promise that the answer is right. A Choice with 0.75 on one of two options has confidence 0.5; a Choice with 0.55 on one of ten also has confidence 0.5. Whether a 0.75 is right three times in four is a question of calibration, which you test by comparing the model's probabilities with real outcomes on your own data.

### How do you route on model confidence?

Put the threshold on the number the decision depends on. For a yes or no question, threshold the Noul value itself, with 0.7 and 0.3 as documented starting points and a separate outcome for the band between. For a Choice, threshold the probability of the option your code is about to act on. For a Score, threshold the probability of the level that matters when the decision hinges on that level, because the score position averages across levels and can hide a split. Give the uncertain cases their own route, such as asking one question or handing off to a person, rather than rounding them.

### Is a typed answer the same as structured output?

No. Structured output constrains the text a language model generates so it parses as JSON or matches a schema. A typed answer is a distribution over options you defined, returned by a model that does not generate text at all. Structured output guarantees shape; a typed answer guarantees shape and gives you a probability per option that you can threshold, which structured output cannot provide except as more generated text.

### Why does a Noul have no confidence field?

Because the value is already the whole answer. A Noul of 0.93 is a confident yes, 0.04 is a confident no, and 0.5 is maximum uncertainty. A separate confidence would just restate the distance from 0.5, which is what the docs suggest computing in code if you need a single number. The one thing to remember is that 0.5 means unsure, not a medium amount of yes.

## Key takeaways

- A typed answer removes the text, not just its shape. Structured output constrains what a model writes; a typed answer is a probability on options you defined, so there is nothing to parse and nothing a second run could phrase differently.
- Confidence is arithmetic on the probabilities, and each shape has its own formula. A Choice only looks at the top probability, a Score measures spread around the most likely level, and a Noul has no confidence field at all.
- A Noul near 0.5 means unsure, not medium. Give that band its own outcome in code instead of rounding it to yes or no.
- A Score position can hide a split. Two peaks at level 0 and level 2 average to 1.0, so threshold on the probability of the level that matters when the decision depends on that level.
- The model never decides the route. Thresholds in code do, which is why moving a slider re-routes everything with no new request, and why those thresholds need tuning on your own data rather than copying from a demo.
