Start here

Where a decision model fits in an application

A decision model belongs at the points in an application where code needs an answer from a fixed set and the input is text a rule cannot read. TypeSafe's design guide gives the rule for everything around it: keep control flow, deterministic rules and side effects in code. This page turns that rule into a map. It names the four patterns, the three anti-patterns TypeSafe warns about, and the test we use to sort every product moment into a decision (route, gate, score, verify) or a generation, which still needs a language model.

Published September 22, 2026. Editorial.

Key takeaways

  • Code owns the flow. The model answers one narrow question, and code decides what to do with the answer, applies the fixed rules, and performs the action.
  • A decision model sits between hard-coded rules, which cannot read a sentence, and an agent loop, where every extra turn is another chance to go wrong.
  • Sort every product moment with one question: does the answer come from a fixed list? If yes it is a decision and suits Jev; if it produces new text it is generation.
  • The three anti-patterns are broad questions that hide several judgments, hidden context that relies on model memory instead of the state, and agent loops where plain code would do.
  • Reveneau designs the decision points of an agent this way first, so the language model is called only where text has to be written.

The support-ticket example in TypeSafe's design guide is a good place to start because it is ordinary. A ticket arrives. Code checks the deterministic things first: is the customer on a paid plan, is the account locked, has this ticket ID been seen before. Then the state is built with only the fields that matter, seven atomic questions are asked in one request, and the answers are combined with weighted rules and confidence gates in code [1]. Every step where a fixed rule has the answer stays a fixed rule. The model reads the sentence that no rule can read.

That is the whole design, and the rest of this page is about applying it without drifting back into the two designs it replaces.

The rule and why it holds

TypeSafe's guide states it in one line: "Keep control flow, deterministic rules, and side effects in code" [1]. Control flow means the order things happen in and the branches taken. Deterministic rules are anything with a fixed right answer, such as a plan check or a date comparison. Side effects are actions with consequences outside the program: a database write, a payment, an email.

The rule holds because a decision model, by design, does none of those things. Jev takes a state and a map of named questions, answers each in parallel and in isolation, and returns typed answers with probabilities [2]. It never writes text, never calls a tool and never decides what to ask next. So the only place the flow can live is in code, and the design question becomes where in that flow a narrow question is worth asking.

There is a second reason we hold to it, which is auditability. When a probability comes back and code compares it with a threshold, both the input and the decision rule are visible in a code review and in a log. When a language model is asked to "decide and then act", the reason for the action is somewhere inside a paragraph of prose. The first is a system you can explain to a customer or a regulator. Our AI agents in production guide makes the same argument about the agent as a whole, and this page applies it to each decision inside one.

Between hard-coded rules and an agent loop

Teams arrive at a decision model from one of two directions.

Some come from hard-coded rules. Keyword matches, regular expressions (text patterns), lookup tables. These are fast, cost nothing per request and behave the same way every time, and they cannot read "I want to return the second one, the first was fine". A Choice question over the possible intents reads that sentence and returns a probability per option, with a confidence number that says how concentrated the probability is [3].

Others come from an agent loop: a language model with tools, deciding what to do next, turn after turn. That design can read anything. TypeSafe's guide names the cost: every loop iteration is another opportunity for the run to go wrong, and much of what the loop decides could have been plain code [1]. A decision model gives you the reading in one request, with code in charge before and after. TypeSafe's coding-agents page draws the same line from the other side: Jev "is not a drop-in replacement" for the model that runs a coding agent (it cannot be swapped in where a model writes code), and it is used inside an application to route, to score on a rubric and to check whether a statement is true of a document before an action is taken [4].

The way we say it to a team: keep the rules you have, keep the language model for the text it has to write, and put a decision model at each point where the two meet.

The four patterns

TypeSafe's patterns page names four, and this guide gives each one a page or a section [5].

Intent routing classifies a request and sends it to the right handler: deterministic code, a specialist model or a human. Intent routing with Jev covers the Choice question over handlers, the 255-option limit, and the two-stage approach when the list is long.

Confidence-gated routing uses the confidence number as a second axis. TypeSafe's confidence page says to act automatically at 0.9 and above and to route to a human, ask for clarification or use a different system below 0.5, with a different gate for each consequence [6]. Confidence-gated routing turns that into a table.

Composite scoring breaks a complex judgment into atomic scores and combines them with weights in code. TypeSafe's example scores a candidate on four dimensions from 0 to 4, normalises each to 0 to 1 and applies a weight set per role, so the final number has a formula anyone can read [7].

Speculative fan-out asks every question you might need in one call and lets code discard the answers it does not use, because questions run in parallel and extra ones add tokens and almost no time [8]. A ticket that turns out to be a feature request simply has its bug-severity answer ignored.

The three anti-patterns

TypeSafe's guide names three, and we see all three in code we are asked to fix [1].

Broad questions. "Is this spam?" hides several judgments behind one answer, so a wrong answer cannot be traced and a threshold cannot be tuned. The fix is three questions: does the message ask for credentials, does the sender name mismatch the address, does it promise a reward. Each is inspected on its own and combined in code.

Hidden context. A question that relies on what the model remembers about the world rather than on what is in the state. The guide says to give each question only the context it needs, and warns that unrelated detail acts as a distraction. The jaggedness page confirms the cost: "Accuracy falls as the state grows with content unrelated to the decision" [9]. Put the facts in the state, and keep out what the question does not need.

Agent loops. A while loop around a language model where a sequence of plain code with one decision in the middle would do. The guide's point is that the loop is where control leaves the code. If the steps are known, write them as steps.

The map: decision or generation

The tool we use in a design session is a two-column list. Walk every moment in the product where a model is called or could be, and ask one question: does the answer come from a fixed list?

If it does, it is a decision. Four kinds cover almost everything. Route: which team, handler, model or workflow takes this. Gate: does this action run now, wait for a confirmation, or go to a person. Score: how urgent, how risky, how complete, how satisfied, on a scale described in words. Verify: is this statement true of this document, does this extracted value match the source, did this reply follow the instruction. Each is a Choice, a Score or a Noul question, and each used to be a prompt that asked a language model for JSON.

If the answer is new text, code or an image, it is generation, and it stays with a language model: the reply to the customer, the summary, the diff, the draft.

Most products end up with a language model in the middle and decisions on both sides of it. Before the generation: route the request, screen the input. After it: screen the output, check it against the source, decide whether to send. TypeSafe's guardrail cookbook is that exact shape, one request per message in each direction [10], and Guardrails for an LLM app with one Jev request explains it step by step. Extraction and verification with Jev covers the verify column, where the mistake teams make most often is expecting the model to write a value out rather than pick one.

A worked example

Take an agent that handles refund requests by email. The moments, in order:

The email arrives. Code parses the sender, matches the order ID with a regular expression and looks up the order. All deterministic.

Is this a refund request, a return, a complaint, or something else? A Choice question. Route.

Is the message trying to get the assistant to ignore its instructions? A Noul question, asked in the same request. Screen.

How angry is the customer, on a 0 to 3 scale described in words? A Score question, same request. Score.

Given the answers, code decides: refund requests under the policy limit for a customer in good standing go to the automatic path, everything else waits. Deterministic rule.

The automatic path calls a language model to write the reply. Generation.

Before sending, a Noul question asks whether the reply promises anything the policy does not allow, and a Choice question checks which order the reply refers to against the order in the state. Verify.

Code sends the email and records the refund. Side effects.

Three Jev requests, one language-model call, and every branch in code. The decisions are logged with their probabilities, so when one goes wrong the fix is a threshold or a question, and the change is a line of code.

What this changes about the build

Reveneau builds agents and product features with the decision points designed first. Before any prompt is written, we list the moments, sort them into decision or generation, and write the questions and thresholds for the decisions as code with the acceptance criteria beside them. The language model is called at the places that remain. Our own eval suite is graded the same way, with Jev answering Noul and Score questions written from the acceptance criteria, and Evals with Jev explains that setup. Everything that follows in this guide is one of those decision points in detail, and Where Reveneau fits on the pillar page says what we do and do not claim about it.

Best for

  • A product moment whose answer comes from a fixed list: a route, a gate, a score or a check.
  • Replacing a language-model prompt that asks for JSON and then parses it.
  • Screening text before and after a language model writes a reply.
  • Teams that want every automated decision to be a probability compared with a threshold in code.

Avoid if

  • The moment produces new text, code or an image; that is generation and needs a language model.
  • A rule already has the right answer; keep the rule.
  • The decision needs facts that are not in the state, such as world knowledge or a live lookup; fetch them into the state first.
  • The answer depends on counting, date arithmetic or comparing two numbers; do those in code.

Check before you decide

  • Every branch in the flow is in code and no model call decides what to call next.
  • Each question asks one thing, and the combination rule is arithmetic you can read.
  • The state for each question holds only the fields the question needs.
  • Every decision is logged with its probabilities and the action taken.

Common questions

What does keep control flow in code mean in practice?

Every branch, loop and ordering decision is written as ordinary code, and the model is called only to answer a narrow question at a point where the code needs it. The model returns a probability, code compares it with a threshold and takes the branch. TypeSafe's design guide states the rule as keeping control flow, deterministic rules and side effects in code, and its own support-ticket example does deterministic checks first, asks seven questions in one request, then combines them in code.

How is a decision model different from a rules engine?

A rules engine applies fixed conditions to structured fields and cannot read free text. A decision model reads the text and returns a probability for a fixed question, which code then treats like any other condition. The two combine: rules handle everything with a known answer (plan status, limits, dates), the decision model handles the sentence no rule can parse, and the combination lives in code. Keep the rules you already have.

When is an agent loop the wrong design?

When the steps are known in advance. TypeSafe's guide lists agent loops as an anti-pattern because each iteration is another chance for the run to go wrong and much of what the loop decides could be plain code. If you can write the sequence as steps with one or two decisions in the middle, write it that way and ask a decision model at those points. Keep a loop for work whose steps are unknown until the model sees the result.

What is a broad question and why does it fail?

A question that hides several judgments behind one answer, such as is this spam. When the answer is wrong there is no way to see which judgment failed, and there is no single threshold to tune. TypeSafe's fix is to ask the parts separately (does it ask for credentials, does the sender mismatch, does it promise a reward), inspect each probability on its own, and combine them with weights in code. Each part can then be tested and adjusted independently.

What is hidden context?

Relying on what the model knows from training instead of on what is in the state. TypeSafe's guide says to give each question only the context it needs, and its jaggedness page says accuracy falls as the state grows with unrelated content. The practical rule is two-sided: fetch every fact the decision needs into the state before asking, and leave out every field the question does not use, because unrelated detail acts as a distraction.

How do I tell a decision from a generation?

Ask whether the answer comes from a fixed list. Which handler, should this run, how urgent, and is this claim supported all do, and they map to Choice, Noul and Score questions. Write the reply, summarise this and draft the code produce new text and need a language model. Sort every model call in the product into those two columns before writing a prompt; most products end with a language model in the middle and decisions on both sides.

Can one Jev request hold several question types?

Yes. TypeSafe's introduction says all three types can be mixed in one call and every question is evaluated in parallel and in isolation, so adding questions barely changes the response time. That is what makes speculative fan-out affordable: ask the route, the screen and the score in one request, and let code ignore the answers that the route makes irrelevant. Extra questions still cost tokens, so keep the state to what they need.

What should be logged for each decision?

The question ID, the model version from the response, every probability returned, the threshold applied and the action taken. With those five fields a wrong decision can be traced to a question wording, a threshold or a model change, and a labelled set can be rebuilt from production traffic. Without the probabilities you only know what happened, and you cannot tell whether the model was confident and wrong or uncertain and unlucky.

Does using a decision model remove the need for a language model?

No. Every place the product writes text still needs one. What changes is the number of calls and where they sit: routing, screening, scoring and verification move to typed questions, and the language model is called at the points where a reply, a summary or a change has to be written. TypeSafe's own coding-agents page says Jev cannot replace the model behind a coding agent.

Where does a decision model go in an existing product?

Start at the prompts that ask a language model for JSON and then parse it. Each of those is a decision dressed as a generation, and it is the first candidate for a Choice, Score or Noul question. Then look at the branches where a person reads text and picks a queue, which are routes. Then the places an agent acts without a check, which are gates. Add one at a time with a logged probability and a threshold you can tune.

How does Reveneau design the decision points in an agent?

Before any prompt is written, we list every moment where a model is or could be called, sort each into decision or generation, and write the decisions as questions with thresholds in code, with the acceptance criteria next to them. Deterministic rules and side effects stay in code. The language model is called only where text has to be written. Our own eval suite is graded the same way, with Jev answering questions written from the criteria.

References