---
title: "Noul (yes/no) questions"
type: concept
tags: [noul, primitives, probability, yes-no, thresholds]
created: 2026-09-17
updated: 2026-09-21
confidence: high
sources:
  - raw/docs/primitives__noul.md
  - raw/docs/api.md
  - raw/docs/primitives.md
  - raw/docs/model-jaggedness__jev-1.13.md
jev_version: "jev-1.13.0"
sdk_python: "0.7.1"
summary: "Noul asks one yes/no question and returns a single number, the probability that the answer is yes; criteria are optional true/false clarifications."
---

# Noul (yes/no) questions

> **TL;DR** `{"type": "noul", "instructions": "<yes/no question or statement>"}`, with an optional `criteria: {"true": "...", "false": "..."}`. The answer is `{"type": "noul", "noul": <0..1>}` — the probability that the answer is **yes**. There is no `confidence` field on a Noul answer. Threshold `noul` in your code when you need a boolean.

## When to use / when not to use

Use a Noul when the answer is yes or no: does this message ask for a refund, does this resume mention distributed systems, does this comment contain personal data.

- If the answer is one of several options → [[concepts/choice]].
- If it's a position on a spectrum → [[concepts/score]].
- Comparison of all three: [[guides/choosing-a-primitive]].

Example questions from raw/docs/primitives__noul.md and raw/docs/primitives.md:

```
"Is the customer requesting a refund?"
"Does this resume mention experience with distributed systems?"
"Does the message contain personally identifiable information?"
"Is the customer asking for a human agent?"
```

## Probability-that-yes semantics

A Noul answer is a single number, `noul`, **the probability that the answer is yes**. It ranges from 0 to 1.

- Near 1 → a strong yes.
- Near 0 → a strong no.
- Near 0.5 → the model gives yes and no similar probability.

Phrase the instruction so that a high probability means "yes", so the returned answer is unambiguous in its meaning. Most often you will threshold `noul` into a boolean when your code needs a hard decision.

### Why there is no `confidence`

Upstream now states the reason explicitly (raw/docs/primitives__noul.md, 2026-09-21): "A Noul's probability distribution has only two outcomes, yes and no, so the single `noul` value describes it completely." A Choice or Score spreads probability over several options or levels, and `confidence` summarizes that spread; there is nothing to summarize for a two-outcome distribution. The number is the answer and the certainty in one.

### 0.5 is not "medium"

0.5 does **not** mean a medium amount of the thing you asked about. A Noul value runs 0 to 1, but it is not a scale of the thing asked about — it is the probability the answer is yes. "Is the candidate strong in Python?" asked as a Noul, against a four-level Score ("no experience / some familiarity / regular use in a job / deep expertise"), on the same four resumes:

| Candidate | Noul: "Is the candidate strong in Python?" | Score: "How much Python experience does the candidate have?" |
|---|---|---|
| My experience is in Java and Go. I have not used Python. | 0.03 | 0.0 (No experience) |
| I have used Python occasionally for small scripts alongside my main Java work. | 0.14 | 1.0 (Some familiarity) |
| I used Python every day for two years in my last job, mostly data pipelines. | 0.81 | 2.05 (Regular use in a job) |
| I have written Python daily for eight years, including maintaining a large Django codebase. | 0.92 | 2.89 (Deep expertise) |

You can carve the 0–1 range into bands in your code, but the model never sees them, so nothing in the answer was judged against them, and the spacing between candidates is not something you chose. The Score judges each level description on its own, so every candidate lands on or near a level you wrote. If what you want is a measurement rather than a decision, that is a [[concepts/score]], not a Noul. See [[concepts/confidence]] for how Choice and Score confidence differs from a Noul probability.

## Request contract

| Field | Required | Type | Description |
|---|---|---|---|
| `type` | Yes | `"noul"` | Must be `"noul"`. |
| `instructions` | Yes | `string \| object \| array` | The yes/no question or statement to evaluate. |
| `criteria` | No | `object` with `true` / `false` | Optional `{ true, false }` descriptions clarifying what a yes and a no mean. |

Per raw/docs/api.md, `criteria.true` is "What a yes (value near 1) means" and `criteria.false` is "What a no (value near 0) means". As of the 2026-09-21 api.md, both are typed `string | object | array` (the OpenAPI `NoulCriteria` schema also allows `null`) — see [[concepts/advanced-structure]]. `instructions` is likewise `string | object | array`: start with a string, and use an object when the question needs data alongside it, such as a record to compare the state against, or when part of the question is built by your code (see [Structured instructions](#structured-instructions) below).

In the JavaScript SDK both parameters are optional: `noul(instructions?, criteria?)`, with `instructions` defaulting to `null` ([[reference/javascript-sdk]]).

### Example request — one Noul with criteria, one without

```json
{
  "state": "I have asked three times now. Can I please just talk to a real person?",
  "model": "jev-latest",
  "questions": {
    "is_human_escalation": {
      "type": "noul",
      "instructions": "Is the customer asking for a human agent?"
    },
    "is_repeat_contact": {
      "type": "noul",
      "instructions": "Has the customer contacted support about this before?",
      "criteria": {
        "true": "Mentions a prior attempt, ticket, or that they have asked before",
        "false": "No sign of any previous contact"
      }
    }
  }
}
```

### Response

```json
{
  "model": "jev-1.13.0",
  "answers": {
    "is_human_escalation": {
      "type": "noul",
      "noul": 0.99
    },
    "is_repeat_contact": {
      "type": "noul",
      "noul": 0.93
    }
  },
  "usage": {
    "input_tokens": 360,
    "output_tokens": 39
  }
}
```

A Noul answer carries only `type` and `noul`. There is no `probabilities` map and no `confidence`. Both answers here are close to 1: the customer says "talk to a real person", so `is_human_escalation` is 0.99; "I have asked three times now" matches the `true` description of `is_repeat_contact`, so it is 0.93.

### Python SDK

Verbatim from raw/docs/primitives__noul.md (note `NoulCriteria`, not a plain dict — the upstream page now shows the typed helper):

```python
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)
    print(response.answers["is_repeat_contact"].noul)
```

### Reading the number against real messages

Recorded `jev-1.13.0` answers to `is_human_escalation` for different customer messages (raw/docs/primitives__noul.md):

| State | `noul` |
|---|---|
| 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 (0.26). "Are you a bot?" hints at wanting a human without asking for one, and the model splits almost evenly (0.40). Those are the messages a threshold in your code has to decide.

## Writing a Noul question

- **One yes/no question per Noul.** "Is the customer angry and asking for a refund?" makes the model judge two things at once and the value means less. Ask two Nouls and combine them in code.
- **Phrase it so a high value means yes.** "Does the message contain personal data?" is clear; "Is the message free of personal data?" inverts the meaning and code that reads it later will get it backwards.
- **A statement works as well as a question.** For "The customer is requesting a refund", a value near 1 means the statement is true. Try both phrasings on your own data.
- **Make the yes/no boundary unambiguous.** "Does this candidate have any Python experience?" works because "any" leaves no middle ground.
- **Optional `criteria`.** The instruction is enough for most Nouls; when the boundary is subtle, add `true` and `false` descriptions. Try your questions with and without `criteria` and keep whichever gives better answers on your documents.
- **Keep `true` meaning yes.** Per [[concepts/jaggedness-jev-1-13]], a Noul where `true` maps to "no" and `false` maps to "yes" performs worse. Treat the criteria as an extension of the instruction and align the two.
- **Ask many Nouls per call.** For a checklist of conditions, ask one Noul per condition in one request and let the code decide what the combination means. Questions are evaluated in parallel, so "adding Nouls barely changes the response time" (raw/docs/primitives__noul.md) — see [[patterns/fan-out]].

Structured `true`/`false` objects (a definition plus examples on each side) are shown in [[concepts/advanced-structure]] and in [[guides/writing-instructions-and-criteria]].

## Structured instructions

`instructions` can be an object with the question in one field and data it refers to in the others; the question names the data field in backticks. Upstream's worked example compares an arriving resume (in `state`) against candidate records that might be the same person — one Noul per record, all in one request, with the question ids generated from the database IDs:

```json
{
  "type": "noul",
  "instructions": {
    "potential_duplicate": {
      "name": "Jon Smith",
      "location": "Oakland, CA",
      "last_employer": "Google"
    },
    "question": "Is the resume for the same person as `potential_duplicate`?"
  }
}
```

Recorded answers: `same_as_record_18` (name spelled differently, same location and employer) 0.74; `same_as_record_42` (same name, different city and employer) 0.09; `same_as_record_77` (similar name, same location, different employer) 0.08. Threshold each value in your code and send the middle values to a person.

Building the questions from records (verbatim, raw/docs/primitives__noul.md):

```python
from typesafe_sdk import Noul, TypeSafeClient

SAME_PERSON = "Is the resume for the same person as `potential_duplicate`?"


def duplicate_questions(candidates: list[dict]) -> dict[str, Noul]:
    """One Noul per candidate record, all asking the same question."""
    return {
        f"same_as_record_{candidate['id']}": Noul(
            instructions={
                "potential_duplicate": {
                    "name": candidate["name"],
                    "location": candidate["location"],
                    "last_employer": candidate["last_employer"],
                },
                "question": SAME_PERSON,
            },
        )
        for candidate in candidates
    }


def find_duplicates(resume: dict, candidates: list[dict]) -> list[str]:
    with TypeSafeClient() as client:
        response = client.system_one(
            model="jev-latest",
            state={"resume": resume},
            questions=duplicate_questions(candidates),
        )
    return [
        question_id
        for question_id, answer in response.answers.items()
        if answer.noul > 0.7
    ]
```

[[cookbooks/sde-cascade]] uses the same shape to verify an extracted record: every field gets the same battery of questions, with the question text in `main_question` and per-field `field_spec` and `extracted_field` properties.

## Using the number in code

Pick the threshold from the cost of being wrong (raw/docs/primitives__noul.md): 0.5 when yes and no are equally easy to act on; higher when acting on a false yes is expensive (paging someone, issuing a refund); lower when missing a true yes is expensive (failing to flag a safety issue). Values in the middle can go to a person instead of either code path — the same three-way split [[concepts/confidence]] describes for Choice and Score. Upstream's worked routing example uses `YES = 0.8` and `NO = 0.2`, sends anything between them to review, and routes the rest.

The threshold is yours to pick and belongs in your code, not in the prompt. Two cautions from [[concepts/jaggedness-jev-1-13]]:

- **Don't carry a threshold tuned on a Noul over to a Choice.** A Choice over options is *relative* (which option wins), while each Noul is *absolute* and can be low for all of them.
- **Don't expect arithmetic identities between separate questions.** On the ticket "I was charged twice for the same order. Can someone look into this?", the question and its negation asked as two Nouls returned:

| `refund` | `not_refund` | Sum |
|---|---|---|
| 0.72 | 0.47 | 1.19 |

  `P(noul)` and `1 - P(not noul)` are not directly comparable.

For a counting use case, ask one Noul per item and add up the thresholded answers in code rather than asking for a count — the worked snippet is in [[concepts/jaggedness-jev-1-13]].

## Gotchas

- No `confidence` field exists on a Noul answer; a value near 0.5 is the only "uncertain" signal you get.
- 0.5 means split probability, not a medium quantity.
- Vague predicates ("strong", "important", "recent") make the number uninterpretable. State the exact condition ("Does the resume state that the candidate has used Python at work?").
- A Noul and a yes/no Choice on the same text can disagree sharply. Example from [[concepts/jaggedness-jev-1-13]] on "I'm not happy with the fit. What are my options here?": Noul `noul` 0.22 versus Choice `probabilities["yes"]` 0.01 with `confidence` 0.97.

## Related

- [[concepts/primitives]] — the three types and how to batch them
- [[concepts/choice]], [[concepts/score]] — the other two primitives
- [[guides/choosing-a-primitive]] — decision table
- [[guides/writing-instructions-and-criteria]] — phrasing and negation pitfalls
- [[concepts/advanced-structure]] — structured `true`/`false` criteria
- [[concepts/confidence]] — why Noul has no separate confidence
- [[reference/http-api]] — wire contract
- [[cookbooks/classifying-rag-passages]] — Noul as a relevance filter

## Sources

- raw/docs/primitives__noul.md (https://docs.typesafe.ai/primitives/noul)
- raw/docs/api.md (https://docs.typesafe.ai/api)
- raw/docs/primitives.md (https://docs.typesafe.ai/primitives)
- raw/docs/model-jaggedness__jev-1.13.md (https://docs.typesafe.ai/model-jaggedness/jev-1.13)
