---
title: "Primitives: Choice, Score, Noul"
type: concept
tags: [primitives, questions, answers, choice, score, noul]
created: 2026-09-17
updated: 2026-09-17
confidence: high
sources:
  - raw/docs/primitives.md
  - raw/docs/api.md
jev_version: "jev-1.13.0"
sdk_python: "0.6.0"
summary: "The three Jev question types (Choice, Score, Noul), the typed answers they return, how to pick one, and how to batch many questions in one request."
---

# Primitives: Choice, Score, Noul

> **TL;DR** A Jev request is a `state` plus a map of `questions`, each with an id you choose, a `type` (`choice`, `score`, `noul`), `instructions`, and usually `criteria`. Each question returns a typed answer under the same id: `choice`/`probabilities`/`confidence`, `score`/`legend`/`probabilities`/`confidence`, or a single `noul` probability. Ask many questions in one request — they run in parallel and cost only their own tokens.

## What primitives are

Primitives are the small, typed building blocks you compose in code. They come in pairs: a **question** defines one judgment for a [[concepts/system-one|System One model]] to make about a [[concepts/state|state]], and its **answer** is the typed value that comes back. You compose the answers in your code to make decisions.

| Type | What it answers | Returns |
|---|---|---|
| [[concepts/choice]] | Which of these options? | `choice`, `probabilities`, `confidence` |
| [[concepts/score]] | Which level? | `score`, `legend`, `probabilities`, `confidence` |
| [[concepts/noul]] | Is this true? | `noul` (0 to 1) |

You can ask one question or send several together. Every question in a request sees the same state, is evaluated independently, and returns a typed answer under the ID you chose.

## One snap judgment per question

System One models are built for fast, focused judgments. Ask for a judgment a knowledgeable person makes in a second given the right context. "Does this message convey urgency?" is a good question. "Analyze this message and determine the best course of action" is not — that needs slow reasoning, and it is a signal to break the task into small questions and compose the answers in code.

If the judgment depends on several independent factors, ask about each factor separately and combine the answers with your own logic. Instead of "rate this startup pitch", ask about market size, technical feasibility, and differentiation, then weight them in code. When priorities shift, change the value of the weights rather than rewriting a prompt.

## How a question is defined

Every question has an ID, a `type`, and `instructions`. Choice and Score also take `criteria`; Noul accepts `criteria` as an optional clarification of what yes and no mean.

- **ID** — the key you pick, such as `refund_requested`. It identifies the answer in the response. Per [raw/docs/api.md](https://docs.typesafe.ai/api), "The key is not sent to the underlying model and is not used in inference." Write the complete question in `instructions` even when the ID seems self-explanatory.
- **`type`** — one of `choice`, `score`, or `noul`.
- **`instructions`** — the question you are asking about the state. This is where your evaluation logic goes. Write it as a clear, specific question, or as a statement for the model to judge.
- **`criteria`** — the possible answers: a map of options for a Choice, an ordered list of levels for a Score, and an optional `{true, false}` description for a Noul.

```python
from typesafe_sdk import Noul

questions = {
    "refund_requested": Noul(
        instructions="Does the customer request a refund?",
    ),
}
```

## Choosing a type

- **Choice** fits when the answer is one of a known set of options with no order between them: routing a ticket to a department, classifying a document type, detecting a programming language. Give the full list, and add an `other` or `none of the above` option when the list might not cover every input.
- **Score** fits when the answer falls on a spectrum and you can describe what each point on that spectrum means: bug severity, customer frustration, skill level.
- **Noul** fits a clean yes/no question where the probability itself is the useful signal.

Use Noul for a yes/no judgment and Score to measure a position on a spectrum. A Noul value of 0.5 means the model gives yes and no equal probability; it does **not** mean "medium". For skill level, use a Score with defined levels (no experience, some familiarity, daily use, deep expertise); for a yes/no decision, define the condition clearly ("Does the resume state that the candidate has used Python at work?").

If two types both seem to fit, prefer the one whose answer your code can act on directly. See [[guides/choosing-a-primitive]] for the full decision table.

## What comes back

| Type | Answer fields | How to read it |
|---|---|---|
| Choice | `choice`, `probabilities`, `confidence` | `choice` is the selected option. `probabilities` is the distribution across every option. `confidence` summarizes how peaked that distribution is. |
| Score | `score`, `legend`, `probabilities`, `confidence` | `score` is a position along your levels and can fall between two of them. `legend` repeats the levels by number. `probabilities` is the distribution across levels. |
| Noul | `noul` | The probability that the answer is yes. Near 1 is a strong yes, near 0 a strong no, near 0.5 uncertain. Noul has no separate `confidence`. |

Two properties make these composable:

- **Every answer is constrained to the options you supplied.** The model returns a probability distribution over your options or levels, never a value outside them. Your code never has to recover a value from generated prose.
- **Every answer is independent.** One question's answer is not hidden context for another. You can add or remove questions without changing the others' results.

[[concepts/confidence]] explains how `confidence` is derived from `probabilities`. [[reference/http-api]] has the exact wire types.

## Referencing specific fields of the state

When a question is about one part of a structured state, name it in the `instructions` with a dot-and-index path to its key, **including the backticks**.

```json
{
  "ticket": {
    "subject": "Duplicate charge",
    "messages": [
      {"from": "customer", "text": "I was charged twice for order A-104. Please refund the duplicate."},
      {"from": "support", "text": "We are checking the charges."}
    ]
  },
  "order": {
    "id": "A-104",
    "charges": [
      {"amount_usd": 49, "status": "captured"},
      {"amount_usd": 49, "status": "captured"}
    ]
  },
  "refund_policy": "Duplicate charges are eligible for a refund."
}
```

```python
questions = {
    "refund_requested": {
        "type": "noul",
        "instructions": "Does `ticket.messages[0].text` request a refund?",
    },
    "policy_supports_refund": {
        "type": "noul",
        "instructions": (
            "Does `refund_policy` support the refund requested "
            "in `ticket.messages[0].text`, given `order.charges`?"
        ),
    },
}
```

## Ask multiple questions together

Send every question that uses the same state in one request; you can mix types freely. System One models evaluate every question in a request in parallel. Adding questions barely changes the response time and costs only the tokens for the extra questions, which are cheap. **Asking a question you might not need is close to free.**

```python
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient

state = {
    "ticket_message": "My flight was cancelled. Can I get a refund?",
    "refund_policy": "Cancelled flights are eligible for a full refund.",
}

with TypeSafeClient() as client:
    response = client.system_one(
        state=state,
        questions={
            "refund_requested": Noul(
                instructions="Does `ticket_message` request a refund?",
            ),
            "request_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": Score(
                instructions="How frustrated does the customer appear in `ticket_message`?",
                criteria=[
                    "Calm and neutral.",
                    "Concerned but civil.",
                    "Very angry or using strong language.",
                ],
            ),
        },
    )

print(response.answers["refund_requested"].noul)
print(response.answers["request_type"].choice)
print(response.answers["frustration"].score)
```

### Speculative questions

Ask every question your code might need, including ones whose answer only matters for some inputs, and let the code decide which answers to use — the [[patterns/fan-out|speculative fan-out]] pattern. The [[cookbooks/parallel-questions|parallel questions cookbook]] shows that batching 13 questions into one call is **11.5x cheaper and 9.6x faster** than 13 separate calls, with no change in the answers (that is the figure on the upstream primitives page; the cookbook itself prints 12.2x cheaper, 10.0x faster — different runs, same conclusion).

The number of questions in one request is limited only by the request's token budget, which the state and the questions share. Per raw/docs/primitives.md the budget is "around 32,000 tokens, roughly 150,000 characters of English text" (see [[reference/models-and-pricing]] for the authoritative limit).

### Splitting a complex judgment

A judgment that depends on several things is best split into one question per thing, combined in code with weights for relative importance — the [[patterns/composite-scoring|composite scoring]] pattern. Worked example in [[concepts/score]].

### When one question depends on another

Questions in the same request are independent: one answer does not become context for another question. Make a second request only when your code genuinely cannot build it until it has the first answer — it needs the answer to fetch more data for the state, to decide what the state is made of, or to pick the next question's options. Two requests are the exception, not the rule. Real examples: [[cookbooks/skill-suggestion]] (rank 182 skills, then fetch the full text of the top three and re-judge), [[cookbooks/autoformat]] (merge lines into blocks that did not exist before the first request), [[cookbooks/hierarchical-classification]] (each Choice answer decides the next request's options).

## Gotchas

- Question IDs are never seen by the model. Do not encode meaning in them.
- Answers are independent — do not expect one question's phrasing to constrain another's answer, and do not expect arithmetic identities to hold between them (see [[concepts/jaggedness-jev-1-13]]).
- Extra questions are cheap but not free: they still cost input and output tokens.
- More state is not better. Unrelated material in the `state` costs accuracy.

## Related

- [[guides/choosing-a-primitive]] — decision table for picking a type
- [[guides/writing-instructions-and-criteria]] — how to phrase `instructions` and `criteria`
- [[concepts/choice]], [[concepts/score]], [[concepts/noul]] — full contract per type
- [[concepts/advanced-structure]] — JSON inside `instructions` and `criteria`
- [[concepts/state]] — how to shape the input
- [[reference/http-api]] — the wire contract
- [[concepts/how-to-build]] — where in your code to call Jev
- [[reference/agent-skill]] — the skill that tells a coding agent to batch questions

## Sources

- raw/docs/primitives.md (https://docs.typesafe.ai/primitives)
- raw/docs/api.md (https://docs.typesafe.ai/api)
