Primitives: Choice, Score, Noul
TL;DR A Jev request is a
stateplus a map ofquestions, each with an id you choose, atype(choice,score,noul),instructions, and usuallycriteria. Each question returns a typed answer under the same id:choice/probabilities/confidence,score/legend/probabilities/confidence, or a singlenoulprobability. 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 System One model to make about a 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 |
|---|---|---|
| Choice questions | Which of these options? | choice, probabilities, confidence |
| Score questions | Which level? | score, legend, probabilities, confidence |
| Noul (yes/no) questions | 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, "The key is not sent to the underlying model and is not used in inference." Write the complete question ininstructionseven when the ID seems self-explanatory. type— one ofchoice,score, ornoul.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.
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
otherornone of the aboveoption 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 Choosing between Choice, Score, Noul 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.
Confidence vs probability explains how confidence is derived from probabilities. HTTP API: POST /v1/systemone and GET /v1/models 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.
{
"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."
}
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.
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 speculative fan-out pattern. The 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 Models, aliases, pricing, rate limits, context 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 composite scoring pattern. Worked example in Score questions.
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: Cookbook: Skill suggestion (rank 182 skills, then fetch the full text of the top three and re-judge), Cookbook: Structure recovery (autoformat) (merge lines into blocks that did not exist before the first request), Cookbook: 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 Jev 1.13 jaggedness: known failure modes).
- Extra questions are cheap but not free: they still cost input and output tokens.
- More state is not better. Unrelated material in the
statecosts accuracy.
Related
- Choosing between Choice, Score, Noul — decision table for picking a type
- Writing instructions and criteria that Jev reads correctly — how to phrase
instructionsandcriteria - Choice questions, Score questions, Noul (yes/no) questions — full contract per type
- Structured instructions, options, levels, criteria — JSON inside
instructionsandcriteria - State: what you send Jev — how to shape the input
- HTTP API: POST /v1/systemone and GET /v1/models — the wire contract
- How to build software with System One — where in your code to call Jev
- The typesafe-ai agent skill and Claude Code plugin — 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)