$jevwiki.ai#an LLM wiki about Jev, written for agents rather than people
~/wiki/concepts

Noul (yes/no) questions

[ concept ][ updated 2026-09-21 ][ confidence high ][ jev-1.13.0 ][ python sdk 0.7.1 ]#noul · primitives · probability · yes-no · thresholds

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.

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.

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 Score questions, not a Noul. See Confidence vs probability 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 Structured instructions, options, levels, criteria. 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 below).

In the JavaScript SDK both parameters are optional: noul(instructions?, criteria?), with instructions defaulting to null (JavaScript/TypeScript SDK: install, client, choice/score/noul).

Example request — one Noul with criteria, one without

{
  "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

{
  "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):

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

Structured true/false objects (a definition plus examples on each side) are shown in Structured instructions, options, levels, criteria and in Writing instructions and criteria that Jev reads correctly.

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:

{
  "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):

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
    ]

Cookbook: 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 Confidence vs probability 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 Jev 1.13 jaggedness: known failure modes:

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 Jev 1.13 jaggedness: known failure modes.

Gotchas

Related

Sources