Deep dives / One Pass, No Prose
One Pass, No Prose
A chatbot writes an answer. A decision model picks one.
A typed decision model takes some text plus a fixed question and returns a probability for each allowed answer. No prose, one forward pass, nothing to parse. Pick a situation below and compare the two kinds of answer. The decision-model numbers are real: the same six requests ran through five models (four on Oct 5, 2026; Clef 27B on Oct 9).
A chatbot (generative LLM)
IllustrativeSubject: Checkout broken Hi, every customer who tries to pay on our store has been getting a 502 at the payment step for the last hour. We are losing orders. Account: acme-retail (Scale plan).
This sounds like an outage affecting payments, so I'd route it to the technical team. It's urgent since customers can't check out, and the severity is probably major or critical. I'm about 90% confident on the routing.
You get sentences. Your code has to find the answer in them, and the "90% confident" is more generated text.
Jev (hosted)
Our run3 questions answered in one request: 432 ms measured, TypeSafe's API, round trip from a home connection. Oct 5, 2026.
Look for the dashed side quest (side quest) links. Each opens a short explainer of one basic idea in a side panel. They are also collected in a row at the end of each chapter.
Chapter 01 · Foundations
Two ways to answer a question
Both kinds of model are built from the same parts. The difference is what happens at the very end: one keeps writing words, the other scores a list of options you gave it. That difference decides the latency, the cost and what you can do with the answer.
The same question, two machines
Writing versus picking
We'll follow one support ticket through both kinds of model.
-
The requestA chatbot's answerA decision model's viewWhat comes backSame request, two models
Illustrative Our run · Oct 5, 2026state Subject: Checkout broken / Hi, every customer who tries to pay on our store has been getting a 502 at the payment step for the last hour. We are losing orders. Account: acme-retail (Scale plan). questions team billing · technical · sales · other urgent yes · no severity no impact · minor · major · critical
This sounds like a payment outage, so the technical team should own it. It is urgent because customers cannot check out, and I would call the severity major or critical. I'm about 90% confident on the routing.
…502 at the payment step for the last hour… Which team should own this? [slot] billing [slot] technical [slot] sales [slot] other [slot] urgent? [slot] severity…
Jev (hosted)
team · choice
billing 0.00technical 1.00sales 0.00other 0.00urgent · noul
yes 0.94no 0.06severity · score
no impact 0.00minor 0.00major 0.01critical 0.99Base Laya (local)
team · choice
billing 0.28technical 0.62sales 0.04other 0.05urgent · noul
yes 0.20no 0.80severity · score
no impact 0.00minor 0.04major 0.85critical 0.11One ticket, three questions, every allowed answer listed. About 45 tokens, written one at a time. Each one is a separate pass through the model. Highlighted: what your code has to dig out, a hedge, and a confidence that is only text. Each [slot] is read by the scoring head. Softmax runs over each question's options, not the whole vocabulary. Jev, one request, 432 ms round trip. Three answers, each with a full distribution. Base Laya, 43 ms on a laptop. Right team, but "not urgent" at 0.80 and "major" rather than "critical".
01 / 06
The request
Every request has three parts: the state (what the model looks at), one or more questions, and the options you allow for each. This exact request ran through five models for this page.
-
The requestA chatbot's answerA decision model's viewWhat comes backSame request, two models
Illustrative Our run · Oct 5, 2026state Subject: Checkout broken / Hi, every customer who tries to pay on our store has been getting a 502 at the payment step for the last hour. We are losing orders. Account: acme-retail (Scale plan). questions team billing · technical · sales · other urgent yes · no severity no impact · minor · major · critical
This sounds like a payment outage, so the technical team should own it. It is urgent because customers cannot check out, and I would call the severity major or critical. I'm about 90% confident on the routing.
…502 at the payment step for the last hour… Which team should own this? [slot] billing [slot] technical [slot] sales [slot] other [slot] urgent? [slot] severity…
Jev (hosted)
team · choice
billing 0.00technical 1.00sales 0.00other 0.00urgent · noul
yes 0.94no 0.06severity · score
no impact 0.00minor 0.00major 0.01critical 0.99Base Laya (local)
team · choice
billing 0.28technical 0.62sales 0.04other 0.05urgent · noul
yes 0.20no 0.80severity · score
no impact 0.00minor 0.04major 0.85critical 0.11One ticket, three questions, every allowed answer listed. About 45 tokens, written one at a time. Each one is a separate pass through the model. Highlighted: what your code has to dig out, a hedge, and a confidence that is only text. Each [slot] is read by the scoring head. Softmax runs over each question's options, not the whole vocabulary. Jev, one request, 432 ms round trip. Three answers, each with a full distribution. Base Laya, 43 ms on a laptop. Right team, but "not urgent" at 0.80 and "major" rather than "critical".
02 / 06
A chatbot writes, one piece at a time
A generative LLM reads the request and predicts the next token (side quest), then the next, until it decides to stop. Each piece is a weighted dice roll, and each one is a separate trip through the model.
-
The requestA chatbot's answerA decision model's viewWhat comes backSame request, two models
Illustrative Our run · Oct 5, 2026state Subject: Checkout broken / Hi, every customer who tries to pay on our store has been getting a 502 at the payment step for the last hour. We are losing orders. Account: acme-retail (Scale plan). questions team billing · technical · sales · other urgent yes · no severity no impact · minor · major · critical
This sounds like a payment outage, so the technical team should own it. It is urgent because customers cannot check out, and I would call the severity major or critical. I'm about 90% confident on the routing.
…502 at the payment step for the last hour… Which team should own this? [slot] billing [slot] technical [slot] sales [slot] other [slot] urgent? [slot] severity…
Jev (hosted)
team · choice
billing 0.00technical 1.00sales 0.00other 0.00urgent · noul
yes 0.94no 0.06severity · score
no impact 0.00minor 0.00major 0.01critical 0.99Base Laya (local)
team · choice
billing 0.28technical 0.62sales 0.04other 0.05urgent · noul
yes 0.20no 0.80severity · score
no impact 0.00minor 0.04major 0.85critical 0.11One ticket, three questions, every allowed answer listed. About 45 tokens, written one at a time. Each one is a separate pass through the model. Highlighted: what your code has to dig out, a hedge, and a confidence that is only text. Each [slot] is read by the scoring head. Softmax runs over each question's options, not the whole vocabulary. Jev, one request, 432 ms round trip. Three answers, each with a full distribution. Base Laya, 43 ms on a laptop. Right team, but "not urgent" at 0.80 and "major" rather than "critical".
03 / 06
Then you have to read it
Your code now has to find the answer inside a sentence. It might hedge ("major or critical"), and the confidence is a phrase it wrote, not a measurement. Why "90% confident" isn't a probability (side quest)
-
The requestA chatbot's answerA decision model's viewWhat comes backSame request, two models
Illustrative Our run · Oct 5, 2026state Subject: Checkout broken / Hi, every customer who tries to pay on our store has been getting a 502 at the payment step for the last hour. We are losing orders. Account: acme-retail (Scale plan). questions team billing · technical · sales · other urgent yes · no severity no impact · minor · major · critical
This sounds like a payment outage, so the technical team should own it. It is urgent because customers cannot check out, and I would call the severity major or critical. I'm about 90% confident on the routing.
…502 at the payment step for the last hour… Which team should own this? [slot] billing [slot] technical [slot] sales [slot] other [slot] urgent? [slot] severity…
Jev (hosted)
team · choice
billing 0.00technical 1.00sales 0.00other 0.00urgent · noul
yes 0.94no 0.06severity · score
no impact 0.00minor 0.00major 0.01critical 0.99Base Laya (local)
team · choice
billing 0.28technical 0.62sales 0.04other 0.05urgent · noul
yes 0.20no 0.80severity · score
no impact 0.00minor 0.04major 0.85critical 0.11One ticket, three questions, every allowed answer listed. About 45 tokens, written one at a time. Each one is a separate pass through the model. Highlighted: what your code has to dig out, a hedge, and a confidence that is only text. Each [slot] is read by the scoring head. Softmax runs over each question's options, not the whole vocabulary. Jev, one request, 432 ms round trip. Three answers, each with a full distribution. Base Laya, 43 ms on a laptop. Right team, but "not urgent" at 0.80 and "major" rather than "critical".
04 / 06
A decision model reads once and scores
A decision model reads the whole request in one forward pass (side quest). Each option has a marked slot, and a small head scores every slot. A softmax (side quest) turns the scores into percentages that add up to 100.
-
The requestA chatbot's answerA decision model's viewWhat comes backSame request, two models
Illustrative Our run · Oct 5, 2026state Subject: Checkout broken / Hi, every customer who tries to pay on our store has been getting a 502 at the payment step for the last hour. We are losing orders. Account: acme-retail (Scale plan). questions team billing · technical · sales · other urgent yes · no severity no impact · minor · major · critical
This sounds like a payment outage, so the technical team should own it. It is urgent because customers cannot check out, and I would call the severity major or critical. I'm about 90% confident on the routing.
…502 at the payment step for the last hour… Which team should own this? [slot] billing [slot] technical [slot] sales [slot] other [slot] urgent? [slot] severity…
Jev (hosted)
team · choice
billing 0.00technical 1.00sales 0.00other 0.00urgent · noul
yes 0.94no 0.06severity · score
no impact 0.00minor 0.00major 0.01critical 0.99Base Laya (local)
team · choice
billing 0.28technical 0.62sales 0.04other 0.05urgent · noul
yes 0.20no 0.80severity · score
no impact 0.00minor 0.04major 0.85critical 0.11One ticket, three questions, every allowed answer listed. About 45 tokens, written one at a time. Each one is a separate pass through the model. Highlighted: what your code has to dig out, a hedge, and a confidence that is only text. Each [slot] is read by the scoring head. Softmax runs over each question's options, not the whole vocabulary. Jev, one request, 432 ms round trip. Three answers, each with a full distribution. Base Laya, 43 ms on a laptop. Right team, but "not urgent" at 0.80 and "major" rather than "critical".
05 / 06
What you get back
Always one of your options, a probability for every option, and no text to parse. Three questions were answered in the same pass: the team, whether it's urgent, and a severity score. Jev put 0.99 on "critical" and 0.94 on "urgent".
-
The requestA chatbot's answerA decision model's viewWhat comes backSame request, two models
Illustrative Our run · Oct 5, 2026state Subject: Checkout broken / Hi, every customer who tries to pay on our store has been getting a 502 at the payment step for the last hour. We are losing orders. Account: acme-retail (Scale plan). questions team billing · technical · sales · other urgent yes · no severity no impact · minor · major · critical
This sounds like a payment outage, so the technical team should own it. It is urgent because customers cannot check out, and I would call the severity major or critical. I'm about 90% confident on the routing.
…502 at the payment step for the last hour… Which team should own this? [slot] billing [slot] technical [slot] sales [slot] other [slot] urgent? [slot] severity…
Jev (hosted)
team · choice
billing 0.00technical 1.00sales 0.00other 0.00urgent · noul
yes 0.94no 0.06severity · score
no impact 0.00minor 0.00major 0.01critical 0.99Base Laya (local)
team · choice
billing 0.28technical 0.62sales 0.04other 0.05urgent · noul
yes 0.20no 0.80severity · score
no impact 0.00minor 0.04major 0.85critical 0.11One ticket, three questions, every allowed answer listed. About 45 tokens, written one at a time. Each one is a separate pass through the model. Highlighted: what your code has to dig out, a hedge, and a confidence that is only text. Each [slot] is read by the scoring head. Softmax runs over each question's options, not the whole vocabulary. Jev, one request, 432 ms round trip. Three answers, each with a full distribution. Base Laya, 43 ms on a laptop. Right team, but "not urgent" at 0.80 and "major" rather than "critical".
06 / 06
Same interface, very different models
Base Laya, a 421-million-parameter open encoder, got the same request. It picked the right team at 0.62 and called the ticket not urgent. Both models are "typed", but one has learned what the options mean and one hasn't. The probabilities are only as useful as the training behind them, which is what calibration (side quest) measures.
Where the difference lives
Same engine, different last step
Most of the model is the same in both cases: a stack of layers that turns words into vectors that capture meaning. The only real change is the readout at the end, plus how it was trained.
-
Inside a transformer
Four layers shown; real models have 24 to 64. Each layer refines every token's vector using the others. Chatbot ending: the last vector scores every token in the vocabulary, then one is sampled. Repeat per word. Decision-model ending: read the vectors at the option slots and score just those. Who each token can see. This is the main architectural difference between an encoder like Laya and a decoder like Qwen.
01 / 04
The shared engine
Both start as a transformer: dozens of layers that pass a vector (a list of numbers) for every token, each layer refining it using the tokens around it. By the top layer, each vector carries the meaning of its token in context.
-
Inside a transformer
Four layers shown; real models have 24 to 64. Each layer refines every token's vector using the others. Chatbot ending: the last vector scores every token in the vocabulary, then one is sampled. Repeat per word. Decision-model ending: read the vectors at the option slots and score just those. Who each token can see. This is the main architectural difference between an encoder like Laya and a decoder like Qwen.
02 / 04
Ending 1: predict the next word
A chatbot takes the last vector and scores every token in its vocabulary, around 150,000 to 260,000 of them, then samples one. Repeat for every word it writes.
-
Inside a transformer
Four layers shown; real models have 24 to 64. Each layer refines every token's vector using the others. Chatbot ending: the last vector scores every token in the vocabulary, then one is sampled. Repeat per word. Decision-model ending: read the vectors at the option slots and score just those. Who each token can see. This is the main architectural difference between an encoder like Laya and a decoder like Qwen.
03 / 04
Ending 2: score your options
A decision model ignores the vocabulary. It takes the vectors sitting at your option slots and scores only those, so the softmax runs over 4 options, not 260,000 tokens. That's the whole trick of a "typed" model.
-
Inside a transformer
Four layers shown; real models have 24 to 64. Each layer refines every token's vector using the others. Chatbot ending: the last vector scores every token in the vocabulary, then one is sampled. Repeat per word. Decision-model ending: read the vectors at the option slots and score just those. Who each token can see. This is the main architectural difference between an encoder like Laya and a decoder like Qwen.
04 / 04
Two ways to read the request
Laya is an encoder: every token can look at every other token, before and after it. Qwen, the base of Jeeves and Clef, is a decoder: each token only looks backwards, which is what you need to write text. Encoder vs decoder (side quest)
Why this matters to an inference bill
One pass, not sixty
Serving a model has two phases: prefill, which reads the prompt, and decode, which writes the answer. A decision model is a prefill-only workload.
-
Passes through the modelWhat you payWhere the time goesAsk everything at once
Chat model
Thetechnicalteamshouldownthisticket.Itisurgentbecausecustomerscannotcompletecheckout,andtheseverityismajororcritical.Iwouldrouteitimmediatelyandpagetheon-callengineerforthepaymentservice.37passes960 msillustrative timeDecision model
prefill → score optionsbilling 0.00technical 1.00sales 0.00other 0.0001passes0 ms432 msmeasured (Jev)$1.26per day, decision model at $0.042/M in, $0 out$8.10per day, small LLM at $0.15/M in + 60 tokens out at $0.60/M6.4×ratioList prices as of Oct 2026: TypeSafe Jev $0.042 per million input tokens; a 9B to 31B open model on a serverless catalog typically $0.09–$0.15 in and $0.40–$0.60 out. Your numbers will differ; the shape won't.
ChatDecisionprefill: parallel, compute-bound, batches welldecode: sequential, memory-boundone stateteamurgentseverityrefund due?SLA breached?needs engineer?customer tiersentimentduplicate?languagePII present?escalate?reply templateChat model: one prefill pass, then one pass per output token (illustrative 25 ms each). The answer still has to be parsed. Decision model: the same prefill, then the option slots are read. Jev's measured round trip for all three questions was 432 ms; Clef 27B hosted 1,255 ms; Clef Flash on a laptop 5,569 ms; Laya 43 ms. Input tokens are the whole bill. Fewer, better-chosen tokens in the state is the main cost lever. Bars are schematic. A chat request spends most of its wall time in decode; a decision request has no decode. Thirteen questions, one state, one pass. The questions are scored together, so the marginal question is nearly free.
01 / 05
A chat answer is a loop
Reading the ticket is one pass. Then the model writes a 48-token answer, and every token is another pass that reads the whole context again. In the figure, each chip is a pass. The timings are illustrative, but the shape is exact.
-
Passes through the modelWhat you payWhere the time goesAsk everything at once
Chat model
Thetechnicalteamshouldownthisticket.Itisurgentbecausecustomerscannotcompletecheckout,andtheseverityismajororcritical.Iwouldrouteitimmediatelyandpagetheon-callengineerforthepaymentservice.37passes960 msillustrative timeDecision model
prefill → score optionsbilling 0.00technical 1.00sales 0.00other 0.0001passes0 ms432 msmeasured (Jev)$1.26per day, decision model at $0.042/M in, $0 out$8.10per day, small LLM at $0.15/M in + 60 tokens out at $0.60/M6.4×ratioList prices as of Oct 2026: TypeSafe Jev $0.042 per million input tokens; a 9B to 31B open model on a serverless catalog typically $0.09–$0.15 in and $0.40–$0.60 out. Your numbers will differ; the shape won't.
ChatDecisionprefill: parallel, compute-bound, batches welldecode: sequential, memory-boundone stateteamurgentseverityrefund due?SLA breached?needs engineer?customer tiersentimentduplicate?languagePII present?escalate?reply templateChat model: one prefill pass, then one pass per output token (illustrative 25 ms each). The answer still has to be parsed. Decision model: the same prefill, then the option slots are read. Jev's measured round trip for all three questions was 432 ms; Clef 27B hosted 1,255 ms; Clef Flash on a laptop 5,569 ms; Laya 43 ms. Input tokens are the whole bill. Fewer, better-chosen tokens in the state is the main cost lever. Bars are schematic. A chat request spends most of its wall time in decode; a decision request has no decode. Thirteen questions, one state, one pass. The questions are scored together, so the marginal question is nearly free.
02 / 05
A decision is the first pass, and that's it
The decision model does the prefill, reads the option slots, and stops. There is no decode phase. In our runs Jev answered the ticket's three questions in one request in 160 to 430 ms over the network; a 9B open model on a laptop took about 0.6 s; a 0.4B encoder took 26 to 54 ms.
-
Passes through the modelWhat you payWhere the time goesAsk everything at once
Chat model
Thetechnicalteamshouldownthisticket.Itisurgentbecausecustomerscannotcompletecheckout,andtheseverityismajororcritical.Iwouldrouteitimmediatelyandpagetheon-callengineerforthepaymentservice.37passes960 msillustrative timeDecision model
prefill → score optionsbilling 0.00technical 1.00sales 0.00other 0.0001passes0 ms432 msmeasured (Jev)$1.26per day, decision model at $0.042/M in, $0 out$8.10per day, small LLM at $0.15/M in + 60 tokens out at $0.60/M6.4×ratioList prices as of Oct 2026: TypeSafe Jev $0.042 per million input tokens; a 9B to 31B open model on a serverless catalog typically $0.09–$0.15 in and $0.40–$0.60 out. Your numbers will differ; the shape won't.
ChatDecisionprefill: parallel, compute-bound, batches welldecode: sequential, memory-boundone stateteamurgentseverityrefund due?SLA breached?needs engineer?customer tiersentimentduplicate?languagePII present?escalate?reply templateChat model: one prefill pass, then one pass per output token (illustrative 25 ms each). The answer still has to be parsed. Decision model: the same prefill, then the option slots are read. Jev's measured round trip for all three questions was 432 ms; Clef 27B hosted 1,255 ms; Clef Flash on a laptop 5,569 ms; Laya 43 ms. Input tokens are the whole bill. Fewer, better-chosen tokens in the state is the main cost lever. Bars are schematic. A chat request spends most of its wall time in decode; a decision request has no decode. Thirteen questions, one state, one pass. The questions are scored together, so the marginal question is nearly free.
03 / 05
What you pay
Every hosted decision endpoint we found bills input tokens only; output is free because there is none. At Jev's $0.042 per million, a day of tickets costs cents.1 Drag the sliders. The comparison line uses a small general LLM's list price plus a short answer.
-
Passes through the modelWhat you payWhere the time goesAsk everything at once
Chat model
Thetechnicalteamshouldownthisticket.Itisurgentbecausecustomerscannotcompletecheckout,andtheseverityismajororcritical.Iwouldrouteitimmediatelyandpagetheon-callengineerforthepaymentservice.37passes960 msillustrative timeDecision model
prefill → score optionsbilling 0.00technical 1.00sales 0.00other 0.0001passes0 ms432 msmeasured (Jev)$1.26per day, decision model at $0.042/M in, $0 out$8.10per day, small LLM at $0.15/M in + 60 tokens out at $0.60/M6.4×ratioList prices as of Oct 2026: TypeSafe Jev $0.042 per million input tokens; a 9B to 31B open model on a serverless catalog typically $0.09–$0.15 in and $0.40–$0.60 out. Your numbers will differ; the shape won't.
ChatDecisionprefill: parallel, compute-bound, batches welldecode: sequential, memory-boundone stateteamurgentseverityrefund due?SLA breached?needs engineer?customer tiersentimentduplicate?languagePII present?escalate?reply templateChat model: one prefill pass, then one pass per output token (illustrative 25 ms each). The answer still has to be parsed. Decision model: the same prefill, then the option slots are read. Jev's measured round trip for all three questions was 432 ms; Clef 27B hosted 1,255 ms; Clef Flash on a laptop 5,569 ms; Laya 43 ms. Input tokens are the whole bill. Fewer, better-chosen tokens in the state is the main cost lever. Bars are schematic. A chat request spends most of its wall time in decode; a decision request has no decode. Thirteen questions, one state, one pass. The questions are scored together, so the marginal question is nearly free.
04 / 05
Where the time goes
Prefill runs the prompt's tokens in parallel, so it is compute-bound and batches well. Decode is sequential and memory-bound: each token re-reads the cache. A decision model never enters the memory-bound phase, which is why the fastest entries on the independent board sit at 6 to 40 ms on a single GPU.2,10 Prefill vs decode (side quest)
-
Passes through the modelWhat you payWhere the time goesAsk everything at once
Chat model
Thetechnicalteamshouldownthisticket.Itisurgentbecausecustomerscannotcompletecheckout,andtheseverityismajororcritical.Iwouldrouteitimmediatelyandpagetheon-callengineerforthepaymentservice.37passes960 msillustrative timeDecision model
prefill → score optionsbilling 0.00technical 1.00sales 0.00other 0.0001passes0 ms432 msmeasured (Jev)$1.26per day, decision model at $0.042/M in, $0 out$8.10per day, small LLM at $0.15/M in + 60 tokens out at $0.60/M6.4×ratioList prices as of Oct 2026: TypeSafe Jev $0.042 per million input tokens; a 9B to 31B open model on a serverless catalog typically $0.09–$0.15 in and $0.40–$0.60 out. Your numbers will differ; the shape won't.
ChatDecisionprefill: parallel, compute-bound, batches welldecode: sequential, memory-boundone stateteamurgentseverityrefund due?SLA breached?needs engineer?customer tiersentimentduplicate?languagePII present?escalate?reply templateChat model: one prefill pass, then one pass per output token (illustrative 25 ms each). The answer still has to be parsed. Decision model: the same prefill, then the option slots are read. Jev's measured round trip for all three questions was 432 ms; Clef 27B hosted 1,255 ms; Clef Flash on a laptop 5,569 ms; Laya 43 ms. Input tokens are the whole bill. Fewer, better-chosen tokens in the state is the main cost lever. Bars are schematic. A chat request spends most of its wall time in decode; a decision request has no decode. Thirteen questions, one state, one pass. The questions are scored together, so the marginal question is nearly free.
05 / 05
Ask everything at once
Because the state is read once and the questions are scored together, you should ask every question you might need in one call. TypeSafe's GDPR cookbook reports 13 questions in one call as 12 times cheaper and 10 times faster than 13 separate calls.9 The ticket example already does this with three.
Side quests · foundations
Scroll sidewaysFoundations
Tokens
Models don't see words. They see tokens: common words whole, rarer words in pieces. "checkout" might be one token and "acme-retail" four. Prices, context limits and speeds are all counted in tokens. A rule of thumb for English is about ¾ of a word per token.
Foundations
Scores to percentages: softmax
A model's raw outputs are scores called logits, which can be any number. Softmax turns them into percentages that add to 100: it raises e to each score, then divides by the total. Every point of score difference is a factor of about 2.7. Drag the scores to see it.
Foundations
One forward pass
A forward pass is one trip of the input up through every layer. A chatbot does one pass per token it writes, so a 60-token answer is 60 passes. A decision model does one pass and reads the answer off the top. That is why Laya takes milliseconds and why a reasoning decision model, which writes tokens first, is back to many passes.
Foundations
Prefill vs decode
Prefill processes the whole prompt at once and builds the KV cache; it is parallel and compute-bound. Decode produces the answer one token at a time, re-reading the cache each step; it is sequential and memory-bandwidth-bound. Serving stacks tune the two differently (chunked prefill, speculative decoding, disaggregation). A decision model is all prefill, so its latency floor is set by compute and its throughput by batch size.
Foundations
Encoder vs decoder
An encoder lets every token look both ways, which is good for understanding a fixed input. A decoder only lets each token look backwards, which is what lets it write text one token at a time. Both can be turned into decision models; they just put the option slot in different places.
Foundations
Why "I'm 90% confident" isn't a probability
When a chatbot writes "90%", it is predicting what text should come next, not measuring how often it is right. Studies find these stated numbers are sometimes reasonable and sometimes not; you only know by testing on your data. In our email tests Claude Haiku's stated confidence was better calibrated than GPT-4.1-mini's actual token probabilities, which shows how little the number's origin tells you.
Foundations
Calibration
A model is calibrated when its 80%-sure answers are right about 80% of the time. Group answers by confidence and compare to the actual hit rate. Bars under the diagonal mean overconfident. It is a property of many answers, never a guarantee about one.
Chapter 02 · Under the hood
Three ways to build one
Nearly every entry on the independent board is one of three designs: a small encoder with option slots, a chatbot model with a new scoring head, or an unchanged LLM read at a single token. Here is what each does with a request, using open models whose code we could read.
Design 1 of 3
An encoder that never writes
Laya: 421 million parameters, built on ModernBERT-large. It runs on a laptop in about 20 to 50 ms per request. It was never a chatbot. The same design appears in Lavoir, Julia-1 and the mmBERT entries.
-
Laya's view of the request
[CLS]Subject:Checkoutbroken…502atthepaymentstep…Whichteam?[MASK]billing[MASK]technical[MASK]sales[MASK]otherbilling 0.28technical 0.62sales 0.04other 0.05T = 0.5
billing 0.17technical 0.82sales 0.00other 0.01T = 1
billing 0.28technical 0.62sales 0.04other 0.05T = 2
billing 0.30technical 0.45sales 0.12other 0.13The ticket, the question and the four options in one row. The four outlined [MASK] chips are the answer slots. Every token's vector is updated using every other token, in both directions, across 28 layers. Base Laya's real output for the ticket. Right answer, but only 62% sure, with 'billing' and 'other' close behind. The same scores at different temperatures. The order never changes; only how spread out the bars are. Base Laya on our jobs: 0.62 on keyword buckets (majority guess 0.54), 0.36 on intent, 0.61 on email category, 0.50 on the seven-way form sort. Fine-tuned on about 1,300 keyword labels: 0.89.
01 / 05
The request becomes one row of tokens
Laya lays out the state, the question and every option in one row. Each option gets a
[MASK]token in front of it. In normal BERT training,[MASK]means "guess the missing word". Laya reuses it as a slot where the answer will be read. -
Laya's view of the request
[CLS]Subject:Checkoutbroken…502atthepaymentstep…Whichteam?[MASK]billing[MASK]technical[MASK]sales[MASK]otherbilling 0.28technical 0.62sales 0.04other 0.05T = 0.5
billing 0.17technical 0.82sales 0.00other 0.01T = 1
billing 0.28technical 0.62sales 0.04other 0.05T = 2
billing 0.30technical 0.45sales 0.12other 0.13The ticket, the question and the four options in one row. The four outlined [MASK] chips are the answer slots. Every token's vector is updated using every other token, in both directions, across 28 layers. Base Laya's real output for the ticket. Right answer, but only 62% sure, with 'billing' and 'other' close behind. The same scores at different temperatures. The order never changes; only how spread out the bars are. Base Laya on our jobs: 0.62 on keyword buckets (majority guess 0.54), 0.36 on intent, 0.61 on email category, 0.50 on the seven-way form sort. Fine-tuned on about 1,300 keyword labels: 0.89.
02 / 05
Everything reads everything
Because Laya is an encoder, each slot can look at the whole request in both directions: the ticket, the question, and its own option text. By the top layer, the vector at each slot summarizes "how well does this option fit this ticket".
-
Laya's view of the request
[CLS]Subject:Checkoutbroken…502atthepaymentstep…Whichteam?[MASK]billing[MASK]technical[MASK]sales[MASK]otherbilling 0.28technical 0.62sales 0.04other 0.05T = 0.5
billing 0.17technical 0.82sales 0.00other 0.01T = 1
billing 0.28technical 0.62sales 0.04other 0.05T = 2
billing 0.30technical 0.45sales 0.12other 0.13The ticket, the question and the four options in one row. The four outlined [MASK] chips are the answer slots. Every token's vector is updated using every other token, in both directions, across 28 layers. Base Laya's real output for the ticket. Right answer, but only 62% sure, with 'billing' and 'other' close behind. The same scores at different temperatures. The order never changes; only how spread out the bars are. Base Laya on our jobs: 0.62 on keyword buckets (majority guess 0.54), 0.36 on intent, 0.61 on email category, 0.50 on the seven-way form sort. Fine-tuned on about 1,300 keyword labels: 0.89.
03 / 05
Score each slot, then softmax
A small scorer turns each slot's vector into one number. Softmax over just this question's options gives the percentages. These are base Laya's real numbers for the ticket: right team, 62% sure, with "billing" and "other" not far behind.
-
Laya's view of the request
[CLS]Subject:Checkoutbroken…502atthepaymentstep…Whichteam?[MASK]billing[MASK]technical[MASK]sales[MASK]otherbilling 0.28technical 0.62sales 0.04other 0.05T = 0.5
billing 0.17technical 0.82sales 0.00other 0.01T = 1
billing 0.28technical 0.62sales 0.04other 0.05T = 2
billing 0.30technical 0.45sales 0.12other 0.13The ticket, the question and the four options in one row. The four outlined [MASK] chips are the answer slots. Every token's vector is updated using every other token, in both directions, across 28 layers. Base Laya's real output for the ticket. Right answer, but only 62% sure, with 'billing' and 'other' close behind. The same scores at different temperatures. The order never changes; only how spread out the bars are. Base Laya on our jobs: 0.62 on keyword buckets (majority guess 0.54), 0.36 on intent, 0.61 on email category, 0.50 on the seven-way form sort. Fine-tuned on about 1,300 keyword labels: 0.89.
04 / 05
Temperature: one dial for confidence
Laya ships overconfident, so its scores are divided by a fitted number called the temperature (side quest) before the softmax. A higher temperature spreads the bars out, a lower one sharpens them. The winner never changes, only how sure it sounds.13
-
Laya's view of the request
[CLS]Subject:Checkoutbroken…502atthepaymentstep…Whichteam?[MASK]billing[MASK]technical[MASK]sales[MASK]otherbilling 0.28technical 0.62sales 0.04other 0.05T = 0.5
billing 0.17technical 0.82sales 0.00other 0.01T = 1
billing 0.28technical 0.62sales 0.04other 0.05T = 2
billing 0.30technical 0.45sales 0.12other 0.13The ticket, the question and the four options in one row. The four outlined [MASK] chips are the answer slots. Every token's vector is updated using every other token, in both directions, across 28 layers. Base Laya's real output for the ticket. Right answer, but only 62% sure, with 'billing' and 'other' close behind. The same scores at different temperatures. The order never changes; only how spread out the bars are. Base Laya on our jobs: 0.62 on keyword buckets (majority guess 0.54), 0.36 on intent, 0.61 on email category, 0.50 on the seven-way form sort. Fine-tuned on about 1,300 keyword labels: 0.89.
05 / 05
The limits
Design 2 of 3
A chatbot model with a new last step
PostHog's Jeeves started from Qwen3.5-9B, an open chatbot model twenty times bigger than Laya, and rebuilt its last step. It is the best-documented example of the pattern Kev, Strands Decider and Cloudflare's Clef also follow. This is the part that answers "how do you make an LLM typed?"
-
Building Jeeves
Qwen3.5-9B · 9 billion weightsfrozenStructural markers<state> <q> <opt> </opt> <decide>Pointer head2 small matricesLoRA adaptersrank 16, on every layerTrainingSFT → RL → temperatureReasoningoptional, before deciding<state>Subject: Checkout broken …<q>team?<opt>billing</opt><opt>technical</opt>…<think>…</think><q> … <decide># model/head.py, Jeeves (complete) class PointerHead(nn.Module): def __init__(self, d_embed, d_latent=256): self.q = nn.Linear(d_embed, d_latent) # reads <decide> self.k = nn.Linear(d_embed, d_latent) # reads each </opt> self.scale = d_latent ** -0.5 self.temperature = 1.0 # fitted after training def forward(self, questions, decide): options = self.k(questions) query = self.q(decide) logits = (options @ query) * self.scale # one score per option return logits / self.temperature
SFT19,126 questions, 2 epochsRL (CISPO)9,992 questions × 8 tries, stopped at 402/624Temperature1 number, fitted on devA decoder LLM, unchanged. On its own it can only write. Outlined chips are the structural markers. Optional reasoning (dashed) goes between <think> tags, then the question is repeated before <decide>. The entire decision head. It compares one vector with each option's vector, and that's it. The 9B base stays frozen; only the small LoRA adapters and the head are trained. Bar length shows how far each stage ran: SFT to the end, RL stopped at 64% of its schedule. With reasoning on, the answer still comes from the head, but the request is back to many passes: 3–10 s on an A100 in our runs.
01 / 06
Start: an ordinary LLM
Qwen3.5-9B reads left to right and predicts the next token, like any chatbot. Its weights take about 21 GB. Everything it knows about language is already in there; it just has no way to output a probability per option.
-
Building Jeeves
Qwen3.5-9B · 9 billion weightsfrozenStructural markers<state> <q> <opt> </opt> <decide>Pointer head2 small matricesLoRA adaptersrank 16, on every layerTrainingSFT → RL → temperatureReasoningoptional, before deciding<state>Subject: Checkout broken …<q>team?<opt>billing</opt><opt>technical</opt>…<think>…</think><q> … <decide># model/head.py, Jeeves (complete) class PointerHead(nn.Module): def __init__(self, d_embed, d_latent=256): self.q = nn.Linear(d_embed, d_latent) # reads <decide> self.k = nn.Linear(d_embed, d_latent) # reads each </opt> self.scale = d_latent ** -0.5 self.temperature = 1.0 # fitted after training def forward(self, questions, decide): options = self.k(questions) query = self.q(decide) logits = (options @ query) * self.scale # one score per option return logits / self.temperature
SFT19,126 questions, 2 epochsRL (CISPO)9,992 questions × 8 tries, stopped at 402/624Temperature1 number, fitted on devA decoder LLM, unchanged. On its own it can only write. Outlined chips are the structural markers. Optional reasoning (dashed) goes between <think> tags, then the question is repeated before <decide>. The entire decision head. It compares one vector with each option's vector, and that's it. The 9B base stays frozen; only the small LoRA adapters and the head are trained. Bar length shows how far each stage ran: SFT to the end, RL stopped at 64% of its schedule. With reasoning on, the answer still comes from the head, but the request is back to many passes: 3–10 s on an A100 in our runs.
02 / 06
Step 1: mark the structure
Every request is wrapped in markers:
<state>,<q>,<opt>…</opt>and a final<decide>. They reuse rare tokens Qwen already has. PostHog found plain words like "State:" worked worse. Why special tokens (side quest) -
Building Jeeves
Qwen3.5-9B · 9 billion weightsfrozenStructural markers<state> <q> <opt> </opt> <decide>Pointer head2 small matricesLoRA adaptersrank 16, on every layerTrainingSFT → RL → temperatureReasoningoptional, before deciding<state>Subject: Checkout broken …<q>team?<opt>billing</opt><opt>technical</opt>…<think>…</think><q> … <decide># model/head.py, Jeeves (complete) class PointerHead(nn.Module): def __init__(self, d_embed, d_latent=256): self.q = nn.Linear(d_embed, d_latent) # reads <decide> self.k = nn.Linear(d_embed, d_latent) # reads each </opt> self.scale = d_latent ** -0.5 self.temperature = 1.0 # fitted after training def forward(self, questions, decide): options = self.k(questions) query = self.q(decide) logits = (options @ query) * self.scale # one score per option return logits / self.temperature
SFT19,126 questions, 2 epochsRL (CISPO)9,992 questions × 8 tries, stopped at 402/624Temperature1 number, fitted on devA decoder LLM, unchanged. On its own it can only write. Outlined chips are the structural markers. Optional reasoning (dashed) goes between <think> tags, then the question is repeated before <decide>. The entire decision head. It compares one vector with each option's vector, and that's it. The 9B base stays frozen; only the small LoRA adapters and the head are trained. Bar length shows how far each stage ran: SFT to the end, RL stopped at 64% of its schedule. With reasoning on, the answer still comes from the head, but the request is back to many passes: 3–10 s on an A100 in our runs.
03 / 06
Step 2: bolt on a pointer head
A tiny add-on, two small matrices, compares the model's vector at
<decide>with the vector at each option's</opt>. A closer match gives a higher score. This is the whole head, from the Jeeves source. Pointer head vs classifier (side quest) -
Building Jeeves
Qwen3.5-9B · 9 billion weightsfrozenStructural markers<state> <q> <opt> </opt> <decide>Pointer head2 small matricesLoRA adaptersrank 16, on every layerTrainingSFT → RL → temperatureReasoningoptional, before deciding<state>Subject: Checkout broken …<q>team?<opt>billing</opt><opt>technical</opt>…<think>…</think><q> … <decide># model/head.py, Jeeves (complete) class PointerHead(nn.Module): def __init__(self, d_embed, d_latent=256): self.q = nn.Linear(d_embed, d_latent) # reads <decide> self.k = nn.Linear(d_embed, d_latent) # reads each </opt> self.scale = d_latent ** -0.5 self.temperature = 1.0 # fitted after training def forward(self, questions, decide): options = self.k(questions) query = self.q(decide) logits = (options @ query) * self.scale # one score per option return logits / self.temperature
SFT19,126 questions, 2 epochsRL (CISPO)9,992 questions × 8 tries, stopped at 402/624Temperature1 number, fitted on devA decoder LLM, unchanged. On its own it can only write. Outlined chips are the structural markers. Optional reasoning (dashed) goes between <think> tags, then the question is repeated before <decide>. The entire decision head. It compares one vector with each option's vector, and that's it. The 9B base stays frozen; only the small LoRA adapters and the head are trained. Bar length shows how far each stage ran: SFT to the end, RL stopped at 64% of its schedule. With reasoning on, the answer still comes from the head, but the request is back to many passes: 3–10 s on an A100 in our runs.
04 / 06
Step 3: adapt, don't retrain
Retraining 9 billion weights is expensive. Instead, LoRA (side quest) freezes them and trains small add-on matrices beside each layer, so only a sliver of the model learns. Together trained its Tev1-4B this way for about $17.8,17
-
Building Jeeves
Qwen3.5-9B · 9 billion weightsfrozenStructural markers<state> <q> <opt> </opt> <decide>Pointer head2 small matricesLoRA adaptersrank 16, on every layerTrainingSFT → RL → temperatureReasoningoptional, before deciding<state>Subject: Checkout broken …<q>team?<opt>billing</opt><opt>technical</opt>…<think>…</think><q> … <decide># model/head.py, Jeeves (complete) class PointerHead(nn.Module): def __init__(self, d_embed, d_latent=256): self.q = nn.Linear(d_embed, d_latent) # reads <decide> self.k = nn.Linear(d_embed, d_latent) # reads each </opt> self.scale = d_latent ** -0.5 self.temperature = 1.0 # fitted after training def forward(self, questions, decide): options = self.k(questions) query = self.q(decide) logits = (options @ query) * self.scale # one score per option return logits / self.temperature
SFT19,126 questions, 2 epochsRL (CISPO)9,992 questions × 8 tries, stopped at 402/624Temperature1 number, fitted on devA decoder LLM, unchanged. On its own it can only write. Outlined chips are the structural markers. Optional reasoning (dashed) goes between <think> tags, then the question is repeated before <decide>. The entire decision head. It compares one vector with each option's vector, and that's it. The 9B base stays frozen; only the small LoRA adapters and the head are trained. Bar length shows how far each stage ran: SFT to the end, RL stopped at 64% of its schedule. With reasoning on, the answer still comes from the head, but the request is back to many passes: 3–10 s on an A100 in our runs.
05 / 06
Step 4: teach it, then sharpen it
First, examples with answers (19,126 questions). Then reinforcement learning (side quest): 8 attempts per question, rewarding good decisions. They stopped early, at step 402 of 624, because past that point the probabilities got overconfident. Last, one temperature fitted on held-out data.
-
Building Jeeves
Qwen3.5-9B · 9 billion weightsfrozenStructural markers<state> <q> <opt> </opt> <decide>Pointer head2 small matricesLoRA adaptersrank 16, on every layerTrainingSFT → RL → temperatureReasoningoptional, before deciding<state>Subject: Checkout broken …<q>team?<opt>billing</opt><opt>technical</opt>…<think>…</think><q> … <decide># model/head.py, Jeeves (complete) class PointerHead(nn.Module): def __init__(self, d_embed, d_latent=256): self.q = nn.Linear(d_embed, d_latent) # reads <decide> self.k = nn.Linear(d_embed, d_latent) # reads each </opt> self.scale = d_latent ** -0.5 self.temperature = 1.0 # fitted after training def forward(self, questions, decide): options = self.k(questions) query = self.q(decide) logits = (options @ query) * self.scale # one score per option return logits / self.temperature
SFT19,126 questions, 2 epochsRL (CISPO)9,992 questions × 8 tries, stopped at 402/624Temperature1 number, fitted on devA decoder LLM, unchanged. On its own it can only write. Outlined chips are the structural markers. Optional reasoning (dashed) goes between <think> tags, then the question is repeated before <decide>. The entire decision head. It compares one vector with each option's vector, and that's it. The 9B base stays frozen; only the small LoRA adapters and the head are trained. Bar length shows how far each stage ran: SFT to the end, RL stopped at 64% of its schedule. With reasoning on, the answer still comes from the head, but the request is back to many passes: 3–10 s on an A100 in our runs.
06 / 06
Step 5 (optional): think, then decide
Jeeves can write reasoning first, then re-read the question and decide with the pointer head. That brings back a decode phase, so latency jumps from about 0.3 s to 3 to 10 s. The independent board excludes anything over 1,000 ms, which is why reasoning decision models like Jeeves and GLiDE are not on it.2
Design 3 of 3
Any chat model, read at one token
Four of the top nine entries on the independent board publish no weights of their own. They are a prompt plus a readout on a stock open model. You can do the same through any API that returns log-probabilities.
-
The forced-letter readout
Shape illustrative Our run · Oct 5, 2026Ticket: Subject: Checkout broken … 502 at the payment step for the last hour … Which team should own this? Answer with one letter. A. billing B. technical C. sales D. other max_tokens=1, temperature=0, logprobs=true, top_logprobs=20
B 1.0e+0A 2.0e-4The 1.0e-4D 1.0e-4C 4.0e-5Technical 3.0e-5** 2.0e-5b 1.0e-5A · billing 0.00B · technical 1.00C · sales 0.00D · other 0.00An ordinary prompt. The readout is the only unusual part. Top alternatives at the answer position (shape illustrative; the real list is model-specific). Keep the four letters, drop the rest, renormalize. GPT-4.1-mini on the ticket, 2,747 ms for two questions in two calls. 1.00 on technical. Letter bias, missing logprobs in reasoning modes, one generation per question, and one output token billed each time. On the board: Decider chat (Gemma-4-31B, no training) 57.33; simple-jev (Qwen3.8-27B, no training) 55.74; the hosted leader 57.91.
01 / 05
Letter the options
Render the question with lettered options and ask for one letter. The prompt is ordinary; the trick is in how you read the reply.
-
The forced-letter readout
Shape illustrative Our run · Oct 5, 2026Ticket: Subject: Checkout broken … 502 at the payment step for the last hour … Which team should own this? Answer with one letter. A. billing B. technical C. sales D. other max_tokens=1, temperature=0, logprobs=true, top_logprobs=20
B 1.0e+0A 2.0e-4The 1.0e-4D 1.0e-4C 4.0e-5Technical 3.0e-5** 2.0e-5b 1.0e-5A · billing 0.00B · technical 1.00C · sales 0.00D · other 0.00An ordinary prompt. The readout is the only unusual part. Top alternatives at the answer position (shape illustrative; the real list is model-specific). Keep the four letters, drop the rest, renormalize. GPT-4.1-mini on the ticket, 2,747 ms for two questions in two calls. 1.00 on technical. Letter bias, missing logprobs in reasoning modes, one generation per question, and one output token billed each time. On the board: Decider chat (Gemma-4-31B, no training) 57.33; simple-jev (Qwen3.8-27B, no training) 55.74; the hosted leader 57.91.
02 / 05
Read the distribution, not the text
At the answer position the model has a score for every token in its vocabulary. Ask the API for logprobs (side quest) and keep only the letters A to D. Renormalize them and you have a probability per option, from one decode step.11
-
The forced-letter readout
Shape illustrative Our run · Oct 5, 2026Ticket: Subject: Checkout broken … 502 at the payment step for the last hour … Which team should own this? Answer with one letter. A. billing B. technical C. sales D. other max_tokens=1, temperature=0, logprobs=true, top_logprobs=20
B 1.0e+0A 2.0e-4The 1.0e-4D 1.0e-4C 4.0e-5Technical 3.0e-5** 2.0e-5b 1.0e-5A · billing 0.00B · technical 1.00C · sales 0.00D · other 0.00An ordinary prompt. The readout is the only unusual part. Top alternatives at the answer position (shape illustrative; the real list is model-specific). Keep the four letters, drop the rest, renormalize. GPT-4.1-mini on the ticket, 2,747 ms for two questions in two calls. 1.00 on technical. Letter bias, missing logprobs in reasoning modes, one generation per question, and one output token billed each time. On the board: Decider chat (Gemma-4-31B, no training) 57.33; simple-jev (Qwen3.8-27B, no training) 55.74; the hosted leader 57.91.
03 / 05
What it looks like for real
GPT-4.1-mini on the ticket, read this way: 1.00 on "technical". Right answer, with a confidence that leaves nothing to sort on. Across our tests its probabilities were badly overconfident (calibration error 0.12 to 0.40), which is the usual result of chat post-training.
-
The forced-letter readout
Shape illustrative Our run · Oct 5, 2026Ticket: Subject: Checkout broken … 502 at the payment step for the last hour … Which team should own this? Answer with one letter. A. billing B. technical C. sales D. other max_tokens=1, temperature=0, logprobs=true, top_logprobs=20
B 1.0e+0A 2.0e-4The 1.0e-4D 1.0e-4C 4.0e-5Technical 3.0e-5** 2.0e-5b 1.0e-5A · billing 0.00B · technical 1.00C · sales 0.00D · other 0.00An ordinary prompt. The readout is the only unusual part. Top alternatives at the answer position (shape illustrative; the real list is model-specific). Keep the four letters, drop the rest, renormalize. GPT-4.1-mini on the ticket, 2,747 ms for two questions in two calls. 1.00 on technical. Letter bias, missing logprobs in reasoning modes, one generation per question, and one output token billed each time. On the board: Decider chat (Gemma-4-31B, no training) 57.33; simple-jev (Qwen3.8-27B, no training) 55.74; the hosted leader 57.91.
04 / 05
The catches
Models prefer some letters regardless of content, so shuffle options and average.12 Reasoning modes often don't return logprobs. Each question is a separate generation unless you batch cleverly. And it is still one decode step on a chat endpoint, so it costs an output token and a scheduling slot.
-
The forced-letter readout
Shape illustrative Our run · Oct 5, 2026Ticket: Subject: Checkout broken … 502 at the payment step for the last hour … Which team should own this? Answer with one letter. A. billing B. technical C. sales D. other max_tokens=1, temperature=0, logprobs=true, top_logprobs=20
B 1.0e+0A 2.0e-4The 1.0e-4D 1.0e-4C 4.0e-5Technical 3.0e-5** 2.0e-5b 1.0e-5A · billing 0.00B · technical 1.00C · sales 0.00D · other 0.00An ordinary prompt. The readout is the only unusual part. Top alternatives at the answer position (shape illustrative; the real list is model-specific). Keep the four letters, drop the rest, renormalize. GPT-4.1-mini on the ticket, 2,747 ms for two questions in two calls. 1.00 on technical. Letter bias, missing logprobs in reasoning modes, one generation per question, and one output token billed each time. On the board: Decider chat (Gemma-4-31B, no training) 57.33; simple-jev (Qwen3.8-27B, no training) 55.74; the hosted leader 57.91.
05 / 05
Why it matters if you run open models
On the board, "Decider chat" on Gemma-4-31B scores 57.33 with no training, 0.6 points under the leader.2 The same base model is on most serverless providers today, so a near-leader decision model can be a prompt and a readout on an endpoint you may already use. A good decision model is as much a serving recipe as it is a set of weights.
Side quests · under the hood
Scroll sidewaysUnder the hood
Temperature scaling
Divide every score by one number, T, before the softmax. T above 1 spreads the probabilities out; below 1 sharpens them. The winner never changes. You fit T on held-out data so the confidence matches the real hit rate. Laya's model card reports its calibration error falling from 0.47 to 0.08 after a refit. Refit on your own traffic whenever the model, the wording or the inputs change.
Under the hood
Fine-tuning vs prompting
Prompting changes what you tell the model; its weights stay the same. Fine-tuning changes the weights using labeled examples, so the model itself learns your categories. It costs labels and a training run, and the result only knows that task. Our keyword fine-tune took about 1,300 labels and under seven minutes on a free GPU; about 400 labels were enough to match the hosted model on that one job.
Under the hood
LoRA
Instead of changing a big weight matrix, LoRA learns two thin matrices whose product is added to it. Jeeves uses rank 16: each adapter is 16 columns wide against thousands, so only a small fraction of the model is trained and the original stays intact. A one-epoch rank-16 LoRA on Qwen3.8-27B scored 54.67 on the board after 167 minutes on one H200.
Under the hood
Reinforcement learning, briefly
Supervised training shows the right answer. Reinforcement learning lets the model try (Jeeves: 8 attempts per question), scores the attempts, and pushes toward the better ones. For decision models the score can reward honest probabilities, not just right answers. It can also overshoot: Jeeves stopped early when its confidence started inflating. TypeSafe calls its version RLCD; nobody outside has reproduced it.
Under the hood
Why special tokens
The head needs exact, reliable places to read from: the end of each option, and the moment of decision. Rare tokens the model almost never saw make clean, unambiguous anchors. Plain words like "Option:" also appear in normal text, which muddies them. PostHog measured this and kept the rare tokens.
Under the hood
Pointer head vs classification head
A classic classifier has one fixed output per category, so adding a category means retraining. A pointer head scores whatever options appear in the request by comparing vectors, so the same model handles 4 options today and 7 tomorrow. Laya does the same with its [MASK] slots and one shared scorer. Clef uses a small joint head that scores all of a request's questions together.
Under the hood
Logprobs and letter bias
Many OpenAI-compatible servers (vLLM included) can return the log-probability of the sampled token and the top alternatives at each position. Force a one-letter answer and the letters' probabilities at that position form a real distribution over your options. Two catches: models prefer some letters regardless of content, so shuffle and average; and chat post-training tends to make the probabilities overconfident, so recalibrate on your labels.
Chapter 03 · Our tests
What we measured
Eight models, five jobs, one method, three weeks. Jobs: sorting search queries into a client's four buckets, labeling search intent, categorizing inbox email, flagging email that needs a reply, and sorting a client's inbound contact forms (1,290 real submissions: five models reach 0.80–0.84 on a seven-way sort with 90–99% of real leads caught, while a yes/no spam gate failed for all of them). Every model was scored the same way on sets it had never seen. The answer keys for everything except the keyword set were made by a frontier model working blind, not by people, so "correct" there means "agrees with the frontier model".
Hosted decision model (Jev)
The default for a new decision
- Accuracy
- About level with GPT-4.1-mini and Claude Haiku on every job
- Calibration
- Best on almost every job (error 0.04–0.09)
- Handles alone
- 70–91% of items at 90% accuracy
- Cost
- About 2¢ per 1,000 decisions; 29¢ for everything we ever sent it
Open 27B (Clef, hosted on Workers AI)
The first open model that matched the leader on email
- 0.870 vs Jev's 0.878 on "needs me", inside the interval; catches more real asks (recall 0.79 vs 0.68); calibration error 0.029, the best on email. Category within noise of Jev
- Search intent
- Ties its 9B sibling for the closest spreads of all eight (JS 0.053–0.055; Jev 0.102)
- Keyword buckets
- 0.760, two points under Jev, within noise on 300 items
- Speed and cost
- 0.6–2 s round trip hosted (median 641 ms); about 7¢ per 1,000 at $0.24 per million input. Locally: an 18 GB download and 2–6 s per item on a MacBook. Its 9B sibling Clef Flash is the laptop option: best intent spreads, cautious on email (recall 0.45)
General LLM with logprobs (GPT-4.1-mini)
Works, costs more, trusts itself too much
- Accuracy
- Level with Jev when the question is good
- Calibration
- Overconfident: error 0.12–0.40
- Cost and speed
- 5–12 times Jev per decision; 0.6–1.8 s per item. Claude Haiku: 60–200 times, with a stated confidence that was better calibrated on email
Accuracy on the most-confident share
The chart that matters
-
Accuracy on the most-confident share · "needs me", 246 emailsPlain wording vs precise wording · same 246 emails
Our data · Sep 15 to Oct 9, 2026Jev 0.878Clef 27B (hosted) 0.870Clef Flash (local) 0.846GPT-4.1-mini 0.829Claude Haiku 4.5 0.825Base Laya 0.756The first 5% is left out because it's too few emails to read. Answer key: a frontier model labeling blind, with a second pass on uncertain items. Figures after each name: accuracy on all 246.
Jev 0.65 → 0.88Clef 27B (hosted) 0.85 → 0.87Clef Flash (local) 0.83 → 0.85GPT-4.1-mini 0.75 → 0.83Claude Haiku 4.5 0.72 → 0.83Base Laya 0.60 → 0.76Accuracy with the plain wording (left bar) and the precise wording (right bar) of the same question, per model.
01 / 07
Sort by confidence, keep the top
Sort 246 fresh emails from most to least confident and track accuracy as you include more of them. A useful model stays high on the left. That left edge is the share of work you could hand to it.
-
Accuracy on the most-confident share · "needs me", 246 emailsPlain wording vs precise wording · same 246 emails
Our data · Sep 15 to Oct 9, 2026Jev 0.878Clef 27B (hosted) 0.870Clef Flash (local) 0.846GPT-4.1-mini 0.829Claude Haiku 4.5 0.825Base Laya 0.756The first 5% is left out because it's too few emails to read. Answer key: a frontier model labeling blind, with a second pass on uncertain items. Figures after each name: accuracy on all 246.
Jev 0.65 → 0.88Clef 27B (hosted) 0.85 → 0.87Clef Flash (local) 0.83 → 0.85GPT-4.1-mini 0.75 → 0.83Claude Haiku 4.5 0.72 → 0.83Base Laya 0.60 → 0.76Accuracy with the plain wording (left bar) and the precise wording (right bar) of the same question, per model.
02 / 07
The line to beat
The dashed line is what always answering "no" scores: only 47 of 246 emails needed the owner, so a model has to beat 0.809 to be worth anything. Beat the base rate (side quest)
-
Accuracy on the most-confident share · "needs me", 246 emailsPlain wording vs precise wording · same 246 emails
Our data · Sep 15 to Oct 9, 2026Jev 0.878Clef 27B (hosted) 0.870Clef Flash (local) 0.846GPT-4.1-mini 0.829Claude Haiku 4.5 0.825Base Laya 0.756The first 5% is left out because it's too few emails to read. Answer key: a frontier model labeling blind, with a second pass on uncertain items. Figures after each name: accuracy on all 246.
Jev 0.65 → 0.88Clef 27B (hosted) 0.85 → 0.87Clef Flash (local) 0.83 → 0.85GPT-4.1-mini 0.75 → 0.83Claude Haiku 4.5 0.72 → 0.83Base Laya 0.60 → 0.76Accuracy with the plain wording (left bar) and the precise wording (right bar) of the same question, per model.
03 / 07
Jev and Clef 27B
Jev and Clef 27B hold above 0.9 for about the most confident 90%.
-
Accuracy on the most-confident share · "needs me", 246 emailsPlain wording vs precise wording · same 246 emails
Our data · Sep 15 to Oct 9, 2026Jev 0.878Clef 27B (hosted) 0.870Clef Flash (local) 0.846GPT-4.1-mini 0.829Claude Haiku 4.5 0.825Base Laya 0.756The first 5% is left out because it's too few emails to read. Answer key: a frontier model labeling blind, with a second pass on uncertain items. Figures after each name: accuracy on all 246.
Jev 0.65 → 0.88Clef 27B (hosted) 0.85 → 0.87Clef Flash (local) 0.83 → 0.85GPT-4.1-mini 0.75 → 0.83Claude Haiku 4.5 0.72 → 0.83Base Laya 0.60 → 0.76Accuracy with the plain wording (left bar) and the precise wording (right bar) of the same question, per model.
04 / 07
The open 9B
The open 9B is cautious and never clears the baseline's noise.
-
Accuracy on the most-confident share · "needs me", 246 emailsPlain wording vs precise wording · same 246 emails
Our data · Sep 15 to Oct 9, 2026Jev 0.878Clef 27B (hosted) 0.870Clef Flash (local) 0.846GPT-4.1-mini 0.829Claude Haiku 4.5 0.825Base Laya 0.756The first 5% is left out because it's too few emails to read. Answer key: a frontier model labeling blind, with a second pass on uncertain items. Figures after each name: accuracy on all 246.
Jev 0.65 → 0.88Clef 27B (hosted) 0.85 → 0.87Clef Flash (local) 0.83 → 0.85GPT-4.1-mini 0.75 → 0.83Claude Haiku 4.5 0.72 → 0.83Base Laya 0.60 → 0.76Accuracy with the plain wording (left bar) and the precise wording (right bar) of the same question, per model.
05 / 07
GPT-4.1-mini
GPT-4.1-mini's curve is nearly flat because its confidence is nearly always 1.0: there is nothing to sort on.
-
Accuracy on the most-confident share · "needs me", 246 emailsPlain wording vs precise wording · same 246 emails
Our data · Sep 15 to Oct 9, 2026Jev 0.878Clef 27B (hosted) 0.870Clef Flash (local) 0.846GPT-4.1-mini 0.829Claude Haiku 4.5 0.825Base Laya 0.756The first 5% is left out because it's too few emails to read. Answer key: a frontier model labeling blind, with a second pass on uncertain items. Figures after each name: accuracy on all 246.
Jev 0.65 → 0.88Clef 27B (hosted) 0.85 → 0.87Clef Flash (local) 0.83 → 0.85GPT-4.1-mini 0.75 → 0.83Claude Haiku 4.5 0.72 → 0.83Base Laya 0.60 → 0.76Accuracy with the plain wording (left bar) and the precise wording (right bar) of the same question, per model.
06 / 07
Base Laya
Base Laya hands almost everything onward.
-
Accuracy on the most-confident share · "needs me", 246 emailsPlain wording vs precise wording · same 246 emails
Our data · Sep 15 to Oct 9, 2026Jev 0.878Clef 27B (hosted) 0.870Clef Flash (local) 0.846GPT-4.1-mini 0.829Claude Haiku 4.5 0.825Base Laya 0.756The first 5% is left out because it's too few emails to read. Answer key: a frontier model labeling blind, with a second pass on uncertain items. Figures after each name: accuracy on all 246.
Jev 0.65 → 0.88Clef 27B (hosted) 0.85 → 0.87Clef Flash (local) 0.83 → 0.85GPT-4.1-mini 0.75 → 0.83Claude Haiku 4.5 0.72 → 0.83Base Laya 0.60 → 0.76Accuracy with the plain wording (left bar) and the precise wording (right bar) of the same question, per model.
07 / 07
Wording moved results more than model choice
The chart uses the second wording of the "needs me" question. With the first, plainer wording, every model failed. Jev went from 0.650 to 0.878 on one rewrite (McNemar p ≈ 7×10⁻¹⁰). Treat question text like code.
Chapter 04 · Testing
How to test one of these properly
Accuracy alone doesn't tell you whether a model saves work. What matters is how much you can hand off at an error rate you can live with, what happens to the rest, and what each decision costs.
Try it: confidence routing
Pick a model and drag the threshold. Emails at or above it are handled automatically; the rest go to a person. Each dot is one of the 246 emails, most confident first.
At 0.90, Jev handles about nine in ten emails and gets them right about nine times in ten. Lower the bar and watch the wrong answers arrive. Switch to GPT-4.1-mini and notice that the threshold barely does anything.
Risk-coverage curves (side quest) ECE (side quest) Beat the base rate (side quest)
Jev: 0.878 accuracy on all 246; 281 ms per email in our run; about $0.017 per 1,000.
The testing ladder
The steps a careful test goes through. We've done 1 through 6 on four tasks; the rules for steps 3 to 5 were written down before scoring.
Agreement between labelers (side quest) Data leakage (side quest)
- Write the decision downOne decision, its allowed answers, what each answer triggers, and who owns the definitions. Always include an "other" or "unclear" option.
- Build an answer key the model never seesA few hundred real items, labeled by people or by a frontier model working blind. Say which. Measure agreement between two labelers first; low agreement means the definitions need work, not the model.
- Split before you tuneSeparate tuning and test sets; keep related items together. Touch the test set once. Our "needs me" wordings were tuned on 182 older emails and scored on 246 fresh ones.
- Beat the trivial baselinesAlways-guess-the-common-answer, a few rules, and a cheap trained classifier if you already have labels. One published test found an embedding model plus logistic regression beating a zero-shot decision model by ten points on Banking77.
- Check confidence, not just accuracyDo the 80%-sure answers come out right about 80% of the time? Fit a cutoff on half the items and score it on the other half.
- Design the cascadeRules first, the cheap model next, a stronger model for what it escalates, a person last. Measure whether the second model's errors differ from the first's; if they share mistakes, escalation repairs nothing.
- Shadow modeRun it beside the current process without acting on it. Log the state, the question version, every probability and the model version. The log becomes your calibration data and your next training set.
- Roll out reversiblyStart with actions you can undo (tags, queue order, a confirmation prompt), one decision at a time, with a kill switch. Fail closed: if the decision service is down, escalate everything.
The options, side by side
Inference costs per 1,000 decisions of about 300 input tokens, from published list prices and our runs. Labels and someone's time to maintain the thing usually cost more.
| Option | Cost / 1k | Latency | Needs | Confidence | Data stays with you |
|---|---|---|---|---|---|
| Hosted decision model (Jev) | ~$0.013 | 160–500 ms round trip | An API key | Best we measured; still verify on your data | No (zero-retention on enterprise plans) |
| Open 27B decision model on your provider (AutoJev / pplx-decider, Rune, Decider) | $0.012–0.045 at $0.04–0.15 / M input | ~100 ms on one server GPU (board median) | A provider that serves it, or a 48–96 GB GPU | Board ECE 0.02–0.12 depending on the model | Yes, on dedicated capacity |
| Clef / Clef Flash on Workers AI | $0.07 (measured) / $0.027 | Clef 27B: 0.6–2 s round trip measured, median 641 ms. Vendor medians 209 / 39 ms on their GPUs | A Cloudflare account with the Workers AI permission | Measured: the 27B level with Jev on email and better calibrated there; Flash cautious | Cloudflare says it doesn't store or train on requests |
| Small encoder you fine-tune (Laya) | ~$0 compute | 20–50 ms on a laptop | ~400+ labels, minutes of training | Accurate on one narrow job; weak everywhere else | Yes |
| LLM you already use, read with logprobs | $0.05–0.25 | 0.6–1.8 s per question | A prompt and an API that returns logprobs | Overconfident; shuffle letters and recalibrate | Depends on the endpoint |
| LLM with JSON output, no logprobs | $1–4 (Haiku) | 0.8–3.5 s | A prompt | Stated, not measured | No |
Sources: TypeSafe, Cloudflare Workers AI, Perplexity and OpenRouter pricing pages; OpenAI and Anthropic pricing; the Decision Index 0.2.1 board; our runs (Oct 2026).1,2,7
Side quests · testing
Scroll sidewaysTesting
Risk-coverage
Coverage is the share of items you let the model handle. Risk is the error rate among those. Raising the threshold lowers both. The curve tells you, for example, "we can automate 90% at 10% error" or "50% at 3% error". Pick the point your process can live with, on tuning data, then confirm it once on test data.
Testing
ECE
Expected Calibration Error: group predictions by confidence, measure the gap between average confidence and actual accuracy in each group, and average the gaps weighted by group size. 0 is perfect. The independent board shows it but doesn't score it; the leader sits at 0.074, several open 27B entries at 0.01–0.02. A low ECE doesn't mean accurate: a model that is always 50% sure and right half the time scores 0.
Testing
Beat the base rate
If 81% of emails don't need you, always answering "no" scores 0.81. If 79% of an agent's tool calls are benign (as one team found on real traffic), always answering "allow" scores 0.79. A question whose accuracy loses to a constant is broken, however reasonable it reads. Report the base rate next to every accuracy.
Testing
Agreement between labelers
Have two people label the same 100 items without seeing each other's answers. Raw agreement is the share they match on. Cohen's kappa corrects for agreement by chance. If two people only agree 70% of the time, no model will reliably beat 70% against either of them. Fix the definitions first. Two frontier-model passes on our emails agreed 95% of the time, which says the key is consistent, not that it is right.
Testing
Data leakage
Leakage is when test items sneak into training, so the score looks better than reality. Every benchmark on the public board is public, which is why the maintainers count flagged items as wrong and why author-reported numbers routinely sit 20 to 40 points above board numbers. Keep your own test set frozen, hashed and private.
Chapter 05 · Where it slots in
A decision layer between information and an action
These models don't replace generation, retrieval or judgment. They sit at the points in a system where something has to be chosen: gate, route, rank, triage, verify. The model is the easy part. What makes it pay off is everything around it.
Before a tool call
Gate
"Could this command destroy data?" in 30–300 ms before an agent runs it. Jev and the open 9B both flagged our force-push; base Laya did not. Pause above a probability you set; log every call.
Before generation
Route
Pick the model tier, the reasoning effort, or the endpoint for a prompt. Every model in our run sent the one-line rewrite to "small". OpenRouter's Jev Router and Databricks' ai_decide do this in production.
After generation
Judge
Is this answer supported by the passage? On-policy? On-voice? A CMU study kept 99% of a frontier judge's accuracy at 57% of the cost by escalating only low-confidence verdicts.15
At intake
Triage
Tickets, forms, email, alerts: team, urgency, severity, needs-a-human. One request answers all of them. Jev decided 91% of our fresh emails alone at 0.90 accuracy.
In bulk
Label
Decisions over whole tables: MotherDuck's SQL function labeled 100K rows at 89% in 40 s for about 50 cents.16 This is a batch workload: throughput matters, latency doesn't.
Between steps
Verify
Extraction checks, citation checks, entity matches. Let a deterministic rule reject; let the probabilistic judge only lower confidence or request review, never overrule the rule.
Get your house in order
The things that separate a useful pilot from a demo. Tick what's true for a decision you're considering; this page remembers your ticks in this browser only.
How these go wrong
- Grading against the teacherAgreeing with the model that made the labels measures imitation, not truth. Score against a key that never fed training.
- Fuzzy questions"Is this urgent?" fails. "Does this ticket need a response within the hour?" works. Name the boundary cases in the option descriptions.
- Mistaking decisive for correctA typed model always returns a valid option. It can still be confidently wrong, especially on inputs unlike its training. "No hallucinations" means the shape is valid, nothing more.
- Answer shoppingRephrasing until the model is confident selects the luckiest wording, not the right answer. Average across phrasings instead, then refit the cutoff.
- Averages that hide the costly misses90% overall can still mean missing most of the rare, expensive cases. Weight errors by what they cost, and check each class.
- Copying thresholds between modelsA 0.9 from one model is not a 0.9 from another. Every vendor says so; our data agrees. Refit on your traffic when you swap.
Sources and method
Companion briefing, with the market, the Decision Index, our studies and what to do about it: Decision models.
Model internals come from the projects' own code and READMEs. Our numbers come from a test program (Sep 15 to Oct 9, 2026): five jobs, eight models, one scoring method, with the answer keys and their provenance stated on the companion field guide. The six worked examples on this page are invented situations run on Oct 5, 2026 through TypeSafe's hosted API, Clef Flash on Ollama 0.35.1 on a MacBook, base Laya on the same machine, and GPT-4.1-mini with logprobs. Clef 27B was added on Oct 9, 2026 through Cloudflare Workers AI, with a local re-run on the same MacBook for timing. Latencies are what we observed, not vendor figures, unless marked.
First published Oct 5, 2026; updated Oct 9, 2026. Evidence marks: filled for our own runs, half for a vendor or author figure, hollow for something read from a project's code or README, dashed for an illustration.
- 1 TypeSafe: introducing System One models and Jev · Sep 15, 2026
- 2 Decision Index (board 0.2.1) · generated Sep 28, 2026
- 3 Decision Index reproduction kit
- 4 PostHog/jeeves (README, model/head.py)
- 5 Laya model card
- 6 Laya rl_common.py
- 7 Cloudflare: Clef and Clef Flash · Oct 1, 2026
- 8 Together: train your own Jev for $17
- 9 TypeSafe: parallel questions cookbook
- 10 Parasail: prefill vs decode in LLM inference
- 11 OpenAI cookbook: using logprobs
- 12 LLMs are not robust multiple choice selectors (Zheng et al.) · 2023
- 13 On calibration of modern neural networks (Guo et al.) · 2017
- 14 Just ask for calibration (Tian et al.) · 2023
- 15 CMU judge-cascade study
- 16 MotherDuck: prompt_jev
- 17 LoRA (Hu et al.) · 2021