AI Development Guide

TypeSafe Primitives in 2026: Choice, Score and Noul Explained in Depth

Everything built on TypeSafe is made of three question shapes asked in parallel and combined by ordinary code. This guide covers how each one is configured, what comes back, and the documented traps in between.

Distk Editorial Sep 2026 13 min read

TypeSafe exposes three question types. Choice picks one option from a map of up to 255 you define, returning the chosen option, the full probability distribution and a confidence value. Score rates the state against an ordered array of 2 to 10 level descriptions, returning a probability-weighted position that can land between levels, plus a legend and confidence. Noul answers a yes or no question with a single number from 0 to 1 and has no confidence field, because one number fully describes a two-outcome distribution. All three mix in a single request, run in parallel and in isolation, and both instructions and criteria accept strings, objects, arrays or null. Questions can point at parts of a structured state using a backticked dot-and-index path, and a second request is only justified when your code could not build it without the first answer.

What Are the TypeSafe Primitives in 2026?

TypeSafe's primitives are small, typed building blocks that come in pairs. A question defines one judgment for the model to make about a state, and its answer is the typed value that comes back. There are three question types in 2026: Choice, which picks one option from a set; Score, which rates the state on ordered levels you write; and Noul, which returns the probability that a statement is true.

Every question has an ID you choose, a type, and instructions. Choice and Score also take criteria, which define the options or the levels. Noul accepts criteria optionally, as a clarification of what yes and no mean. The ID is never sent to the model, so name it for your code.

Explain It Like I Am Five: Three Shapes of Question

Imagine you are making a quiz about a letter someone sent you. There are only three kinds of question you are allowed to write.

The first kind is tick one box. You write the boxes yourself: is this letter for the kitchen, the garage, or the garden? The model ticks exactly one. It also tells you how much it wanted to tick each of the others.

The second kind is where on the ruler. You draw a ruler and you label the marks: 0 is calm, 1 is grumpy, 2 is furious. The model puts a pin on the ruler. The pin can land between two marks, like 1.4, which means "quite a lot more than grumpy".

The third kind is yes or no, but with a twist. Instead of just saying yes, the model says how yes. 0.95 means almost certainly yes. 0.5 means it genuinely cannot tell.

That is all there is. Tick a box, point on a ruler, say how yes. Everything clever people build with this is just lots of those three questions asked at once, and then ordinary computer code adding them up.

The trick is writing small questions. "Is this letter angry?" is a good question. "What should I do about this letter?" is a bad one, because that is not a quiz question, that is a decision, and decisions belong to your program.

How Do You Choose Between Choice, Score and Noul in 2026?

Pick the type that matches the shape of the answer you need, and when two seem to fit, prefer the one your code can act on directly. TypeSafe frames it exactly that way: a Choice between refund, rebook and information maps straight onto three code paths, a Score of customer frustration maps onto a threshold, and a Noul maps onto an if statement.

TypeUse it whenDocumented examplesMaps onto
ChoiceThe answer is one of a known set with no order between the optionsRouting a ticket to a department, classifying a document type, detecting a programming languageA branch per option
ScoreThe answer falls on a spectrum you can describe in stepsBug severity, customer frustration, skill levelA numeric threshold
NoulA clean yes or no where the probability itself is the signalDoes this contain personal data, is a refund being requested, does the resume mention distributed systemsA boolean after thresholding

How Does a Choice Question Work?

A Choice takes criteria as a map where each key is an option name and each value describes that option. The answer returns choice, the option with the highest probability, probabilities across every option summing to 1, and confidence for the selected option. Both the option names and their descriptions are sent to the model, so write descriptions that separate the options from each other.

{
  "state": "My running shoes arrived in the wrong size. Can I swap them for a size 10?",
  "model": "jev-latest",
  "questions": {
    "department": {
      "type": "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"
      }
    }
  }
}

Three configuration facts worth committing to memory in 2026. A Choice accepts up to 255 options, and adding options costs only a few tokens each, so give the model the full list of teams, categories or products rather than a shortlist. A description can be null when the option name is clear on its own. And you should add an other or none of the above option whenever the list might not cover every input, so the model can say that none of the others fit.

Reading a split Choice

The interesting answers are the ambiguous ones. TypeSafe documents a ticket that involves three teams: the department answer comes back as returns at 0.61 probability with billing at 0.35 because of a double charge, and the confidence lands at 0.42 to reflect the split. The documented code does not just take the top option. It routes to returns, sends billing a copy because its 0.35 share is over a 0.25 threshold, and asks the customer a clarifying question because a separate resolution answer came back at 0.20 confidence.

That is the habit to build. The full distribution is in the response for a reason, and a second option with meaningful probability is often actionable in its own right.

Structured Choice criteria

Start with a one-line description per option. When two options are similar and the model keeps confusing them, TypeSafe's documented fix is to describe each one with an object instead of a string, giving it fields for what the option covers, what belongs to a neighbouring option instead, and a few example inputs.

"criteria": {
  "return_policy": {
    "what": "Questions about the rules for returning an item",
    "not_for": "Questions about an existing return already in progress",
    "examples": ["How long do I have to return this?", "Do I pay return postage?"]
  },
  "return_status": {
    "what": "Questions about a specific return already under way",
    "not_for": "Questions about what the policy allows",
    "examples": ["Where is my refund?", "Has my return been received?"]
  }
}

The field names in that object are yours. TypeSafe is explicit that question, focus, what, not_for and examples are not part of the API and none are reserved. The model sees the names alongside the values, so use short names that label what follows.

How Does a Score Question Work?

A Score takes criteria as an ordered array of level descriptions, from the low end of the scale to the high end. A level's number is its position in that array starting at 0, so three entries are levels 0, 1 and 2. TypeSafe states that a Score should have at least two levels and that the API accepts up to 10.

{
  "state": "The export button crashes the settings page in Safari. It works in Chrome, but a few of our customers only use Safari.",
  "model": "jev-latest",
  "questions": {
    "bug_severity": {
      "type": "score",
      "instructions": "How severe is the reported issue?",
      "criteria": [
        "Cosmetic; no impact to functionality",
        "Broken or degraded feature, but workaround exists",
        "Blocking issue; no workaround exists"
      ]
    }
  }
}

The returned score is a position along your levels and can land between two of them, because it is computed as the probability-weighted sum: multiply each level number by its probability and add the results. TypeSafe's documented example returns a score of 1.43 with confidence 0.35 from probabilities of 0.0, 0.57 and 0.43, and 0 times 0.0 plus 1 times 0.57 plus 2 times 0.43 is indeed about 1.43. The answer also carries legend, which maps each level number back to the description you wrote, which is useful when logging.

Structured Score levels, and a warning about examples

When the model keeps scoring between two neighbouring levels on inputs you consider clear, TypeSafe's fix is to give each level an object with a field for what the level covers and a field of example situations, using the same field names on every level so the model can compare like with like. The documentation then publishes a table that is more honest than most vendor material, and it deserves reproducing.

Level description style (same ticket)scoreconfidence
Plain string, no examples1.430.35
Object with a matching example: "export fails in one browser but works in another"1.030.96
Object with an unrelated example: "search fails, but browsing categories still works"1.430.35

A well-chosen example concentrated nearly all the probability on one level. An unrelated example changed nothing. TypeSafe's own conclusion is the important part: higher confidence does not establish which answer is correct. Choose examples with known expected levels, then test the revised descriptions on separate inputs before keeping them. That is straightforward evaluation discipline, and it is rare to see a vendor write it down.

Splitting a judgment into several Scores

A judgment that depends on several things is best split into one question per thing, then combined in your code with weights you control. TypeSafe's documented ticket-priority example asks three Scores in one request and combines them.

def normalized(answers, question_id: str) -> float:
    """Put a score on 0 to 1 by dividing by its top level number."""
    top_level = len(TRIAGE_QUESTIONS[question_id].criteria) - 1
    return answers[question_id].score / top_level

severity       = normalized(answers, "severity")
frustration    = normalized(answers, "frustration")
report_quality = normalized(answers, "report_quality")

# A detailed report helps an engineer investigate, so it raises priority a little.
priority = 0.6 * severity + 0.3 * frustration + 0.1 * report_quality

The weights live in your code, visible and reviewable. TypeSafe's framing is that when the combined result does not match what your team would decide, you change the weights and run again rather than rewriting a prompt. This is the composite scoring pattern.

How Does a Noul Question Work?

A Noul asks a yes or no question and returns a single number from 0 to 1 that is the probability the answer is yes. Near 1 is a strong yes, near 0 a strong no, near 0.5 means the model gives yes and no similar probability. There is no separate confidence field, because a distribution over two outcomes is fully described by one number.

The documentation publishes recorded jev-1.13.0 answers for one question across several messages, and the middle of that table is where the design lesson sits.

Statenoul for "Is the customer asking for a human agent?"
Thanks, that fixed it!0.02
How do I reset my password?0.07
I need this sorted today, whatever it takes.0.26
Are you a bot?0.40
Is there any way to speak to someone about my invoice?0.84
I have asked three times now. Can I please just talk to a real person?0.99

"I need this sorted today" is urgent but never asks for a person, so it scores 0.26. "Are you a bot?" hints at wanting a human without asking, and the model splits at 0.40. Those are precisely the messages a threshold has to adjudicate, and the number is telling you something real about the input rather than failing.

Where to put the threshold

TypeSafe's documented guidance is to set the threshold by the cost of being wrong. Use 0.5 when yes and no are equally easy to act on. Raise it when acting on a false yes is expensive, such as paging someone or issuing a refund. Lower it when missing a true yes is expensive, such as failing to flag a safety issue. Values in the middle can go to a person rather than to either code path.

wants_human = response.answers["is_human_escalation"].noul > 0.9

if wants_human:
    route_to_agent(ticket)
else:
    route_to_bot(ticket)

Writing a good Noul question

Noul is not a scale

This is the misuse TypeSafe warns about most directly. A Noul value runs from 0 to 1 but is not a scale of the thing you asked about; it is the probability that the answer is yes. The documentation compares a Noul and a Score on the same four candidates.

CandidateNoul: "Is the candidate strong in Python?"Score: "How much Python experience?"
Experience in Java and Go; has not used Python0.030.0 (No experience)
Used Python occasionally for small scripts0.141.0 (Some familiarity)
Used Python daily for two years on data pipelines0.812.05 (Regular use in a job)
Python daily for eight years, maintains a large Django codebase0.922.89 (Deep expertise)

You could invent bands in the 0 to 1 range in your code, but the model never saw them, so nothing in the answer was judged against them. The Score judged each level description on its own, so every candidate landed on or near a level you wrote. If the question is really about degree, use a Score.

How Do You Ask Several Questions in One Request?

Put every question that uses the same state in one request and mix the types freely. TypeSafe evaluates them in parallel, so adding questions barely changes the response time and costs only the tokens for the extra questions. The documented phrasing is blunt: asking a question you might not need is close to free.

That makes speculative questions the default rather than an optimisation. Ask everything your code might need, including answers that only matter for some inputs, and let the code decide which to use. TypeSafe's parallel questions cookbook measured a 13-question briefing as 12.2 times cheaper and 10.0 times faster in one call than in 13 calls, with no change in the answers.

How Do You Point a Question at Part of the State?

When the state is a JSON object with several parts, name the part in the instructions with a dot-and-index path to its key, including the backtick characters around the path. TypeSafe's documented example points a question at something like support.tickets[0].message. Explicit paths make it clear which part of a structured state should inform each judgment, which matters because Jev's documented weaknesses include indirection and distraction by irrelevant content.

When Should a Question Be Structured Rather Than a String?

TypeSafe allows structure in more places than most people notice, and its advanced page lists them precisely. Both instructions and every kind of criteria description accept a string, an object, an array, or null.

FieldApplies toAccepted shape
instructionsChoice, Score, Noulstring, object, array, or null
criteria values (option descriptions)Choicestring, object, array, or null
criteria entries (level descriptions)Scorestring, object, array, or null
criteria.true and criteria.falseNoulstring, object, array, or null

TypeSafe gives two reasons to reach for structure. It helps with clarity when a question has several parts, because the keys are labelled. And it helps when a question needs supporting data, because a schema, a taxonomy or a database row is already JSON, so you can pass the relevant subfields instead of serialising them into a string template.

"instructions": {
  "potential_duplicate": {
    "name": "John Smith",
    "location": "Oakland, California",
    "last_employer": "Google"
  },
  "question": "Is the resume for the same person as `potential_duplicate`?"
}

The question sits in one field and the data it refers to sits in others, referenced by name in backticks. Because that data comes from your code, it can change per request without touching the wording of the question.

When Does One Question Genuinely Depend on Another?

Rarely, and TypeSafe says so plainly: two requests are the exception, not the rule. Questions in the same request are independent, and one answer does not become context for another. A real dependency exists only when your code cannot build the second request until it has the first answer, because it needs that answer to fetch more data for the state, to decide what the state is made of, or to pick the next question's options.

The documentation names the three cookbooks that make a second request for a legitimate reason: skill suggestion ranks 182 skills in one request then fetches the full text of the top three and judges them again against better evidence; structure recovery asks whether each line break split a sentence, merges lines into blocks, then classifies blocks that did not exist until the first answer; and hierarchical classification uses each Choice answer to decide which options the next request offers.

The default in 2026

If the second request's questions could have been asked against the original state, ask them in the first request and let the code ignore the ones it does not need. A second round trip costs latency that parallel questions do not.

What Are the Common Mistakes With Primitives in 2026?

Key Takeaways for 2026

Distk helps teams write the questions, which is the part that actually determines whether this works, and put them in one reviewable file alongside the thresholds.

Sources

TypeSafe Primitives in 2026: FAQs

What is the difference between Choice, Score and Noul?

Choice picks one option from a fixed set with no order between them. Score rates the state on ordered levels you write and can return a value between levels. Noul answers a yes or no question with the probability that the answer is yes. When two fit, pick the one your code can act on directly.

How many options can a Choice question have?

Up to 255. TypeSafe recommends giving the full list rather than a shortlist because each option costs only a few tokens, and adding an other or none of the above option when the list might not cover every input.

How many levels can a Score have?

At least two, and the API accepts up to 10. Levels are numbered by their position in the criteria array starting at 0, and the returned score is the probability-weighted sum across them, so it can land between two levels.

Why does a Noul have no confidence value?

Because a Noul's probability distribution has only two outcomes, yes and no, so the single noul value describes it completely. Choice and Score spread probability across several options or levels, and confidence summarises that spread.

Can a Noul be used as a 0 to 100 scale?

No. The value is the probability that the answer is yes, not a measure of degree. TypeSafe publishes a comparison showing that a Score with written levels places candidates on levels you defined, while a Noul only tells you how likely a single proposition is.

When should I make two TypeSafe requests instead of one?

Only when your code cannot build the second request without the first answer, for example because it must fetch more data, decide what the state contains, or pick the next question's options. Otherwise ask everything in one call and ignore the answers you do not need.

The questions are the product

Most TypeSafe projects succeed or fail on how the questions are written, not on the integration. Distk helps teams write, test and version them, with the thresholds in one reviewable place.

Start the conversation →