Home/The future of LLM review
Stop paying a model to write essays your rules never read.
A rule needs one label and a confidence. A chat model bills you for a page of thinking and prose to get there. Point the rules' questions at a context decision model such as Jev or SubQ Snap and the review bill drops by two orders of magnitude.
Context decision model (CDM): given the context and a fixed list of answers, it returns one of those answers and a calibrated certainty — never free text. Jev from TypeSafe[3] and SubQ Snap from Subquadratic[4] are two; Talooner works with either.
One question, two answers
Where the tokens go
A rule asks: does this change match its guide? The chat model thinks, explains, hedges, and finally says the word the rule needed. Same question to a decision model: the word and a confidence, nothing else. Real time, from TypeSafe's latency figures for Jev.[3]
Chat LLM · Claude Fable 5.1
0.0 s
- output tokens
- 0
- used by the rule
- 0
- this question
- $0.200
Typed decision · CDM
0.000 s
{
"verdict": "mismatch",
"confidence": 0.94
}The answer is the decision: a label the rule matches on and a calibrated confidence it can gate on. Nothing to parse, nothing to throw away.
- output tokens
- 0
- billed output
- free
- this question
- —
The math
Run your own numbers
List prices: Claude at $2–$10 per million input tokens and $10–$50 per million output[1]; Jev at $0.042 per million input[2] and SubQ Snap at $0.05[4], with no charge for output. The input is the same diff either way. The gap is the output: a chat model bills every token of thinking and prose, a typed decision bills none.
Claude Fable 5.1
$1,650
a month · $19,800 a year
Jev (TypeSafe)
$5.04
a month · $60.48 a year
SubQ Snap (Subquadratic)
$6.00
a month · $72.00 a year
At least 275× cheaper · $19,728 saved a year
27% of the chat bill is output — tokens the rule never reads.
inputoutput, discarded
// questions a month
2,000 PRs × 3 = 6,000
// Claude Fable 5.1: $10 in, $50 out per 1M tokens
(20,000 × 10 + 1,500 × 50) / 1M = $0.275
$0.275 × 6,000 = $1,650
// Jev (TypeSafe): $0.042 in per 1M, output free
20,000 × 0.042 / 1M = $0.00084
$0.00084 × 6,000 = $5.04
// SubQ Snap (Subquadratic): $0.05 in per 1M, output free
20,000 × 0.05 / 1M = $0.00100
$0.00100 × 6,000 = $6.00
Assumes the same token count for the same input on both models; tokenizers differ by a few percent.
Same rules, typed answer
Better review, not just cheaper review
Confidence is a gate, not a vibe
Every answer carries a calibrated confidence. Rules act on a sure one and send an unsure one to a human — no regex over “I think this might be…”.
Nothing to parse
The answer arrives as the type the rule asked for. No prose to scrape, no JSON the model forgot to close.
75× faster
0.114s against 8.566s per decision in TypeSafe's benchmark for Jev; Subquadratic reports SubQ Snap reading 100K tokens in under a second — a check finishes before the CI job has printed its banner.
enrich "Check changed code against its guide" {
for records where type == "code_unit" and attr "unit.important" == true
tool "llm" "review" { // model: any CDM — a typed answer, not prose
doc attr "unit.doc_content"
diff attr "unit.diff"
}
update attr "unit.llm_result" from result.verdict
update attr "unit.llm_confidence" from result.confidence
}
// Confident mismatch: block. Unsure: a human decides.
strict rule "Block documented mismatch" {
for records where type == "code_unit"
and attr "unit.llm_result" == "mismatch"
and attr "unit.llm_confidence" >= 0.8
do block "pr.merge"
}
rule "Escalate an unsure answer" {
for records where type == "code_unit"
and attr "unit.llm_confidence" < 0.8
do comment "pr" "Not sure about {attr.unit.name} — a code owner should look"
}The rule doesn't change when the model does — only what comes back. The confidence threshold is policy, written once, applied on every commit.
A decision model on your code
Ask about the code that matters, not the whole PR
Mark the security-relevant parts of your repo once. On every PR, each changed part goes to the decision model on its own, with its own question. The input is smaller, less of your code leaves for the API, and the answer is a label your rules can act on.
Mark the code
Each entry in .talooner/architecture.yaml makes a path a code unit. On every PR, each changed unit becomes its own record: its name, only its part of the diff, and the doc it should follow, read from the base branch.
# .talooner/architecture.yaml — each entry is a code unit
- path: app/policies/
kind: service
doc_ref: docs/authorization.md # who may do what, in prose
- path: app/controllers/concerns/authentication.rb
kind: controller
doc_ref: docs/authentication.mdAsk a multiple-choice question
A CDM is a typed classifier: it returns one label from the list you give it, plus a calibrated certainty. So each question is multiple choice — ideally with a safe answer, a needs-a-look answer and a dangerous one.
app/policies/
How does this change who can do what?
app/policies/ + its doc
Does it follow the documented rules?
authentication code
What does it do to authentication?
any unit
Are credentials in the code?
controllers, queries
Where does user input go?
Turn the answer into a rule
The label and the certainty become facts on the unit, and rules gate on both. A policy that widens access — and the model is sure of it — holds the merge for the security team. Answers you list under clear (at 0.9 certainty or more) skip the LLM review for that unit; anything else still gets it.
enrich "CDM: how does this policy change access?" {
for records where type == "code_unit" and attr "unit.name" starts_with "app/policies/"
stale_after 1 hour
tool "cdm" "decide" {
state concat("Authorization policy ", attr "unit.name",
"\n\nDocumented rules:\n", attr "unit.doc_content",
"\n\nDiff:\n", attr "unit.diff")
choices "no change to access, narrows access, widens access, removes a check"
clear "no change to access, narrows access" // these skip the LLM review
}
update attr "unit.access_change" from result.chosen
update attr "unit.access_certainty" from result.certainty
update attr "unit.review_cleared" from result.cleared
}
rule "Security review when a policy widens access" {
for records where type == "code_unit"
and (attr "unit.access_change" == "widens access" or attr "unit.access_change" == "removes a check")
and attr "unit.access_certainty" >= 0.85
requires "review.security_team"
do block "pr.merge"
do require "review.security_team"
do comment "pr" "CDM: {attr.unit.name} {attr.unit.access_change} (certainty {attr.unit.access_certainty})"
priority CRITICAL
}Where a chat model still fits
The decision model picks who needs a closer look. The LLM explains — only there.
A typed decision doesn't write the review comment. So let the decision model ask first, and hand the chat model only the units it flags. The contributor gets one comment that names the problem, not a page of maybes on every file — and the next commit is cleared in a tenth of a second.
PR #142 · @maya1 commit
- app/policies/item_policy.rb
- app/policies/report_policy.rb
- app/policies/invoice_policy.rb
talooner commented
item_policy.rb widens access: any signed-in user can edit any item. The doc says only owners do.
cdm · decide0.11 s each
How does this change who can do what?
item_policy.rb
widens access0.94
report_policy.rb
no change to access0.97
invoice_policy.rb
narrows access0.91
→ llm: item_policy.rb widens access (0.94). Explain it against the doc?
llm · reviewidle
Waiting for the decision model to flag something.
Two files never reached the chat model: no tokens, no prose to skim.
@mayawaiting
Oh, that's a real one.
a1b2c3d
checks running
One comment that names the line, not a page of maybes.
One run, in the right order
The review step is gated on the decision model's answer. Talooner runs a step after every step that writes what it reads, so the decision, the explanation and the rule all land in the same evaluation. The gate can be a label, a certainty, or a named condition built from both.
- Advisory only. Its answers can hold a PR, never approve one.
- Cached per commit. Each call runs once per commit and counts against your LLM quota.
- Bounded input. A large unit's diff can be truncated — smaller units get better answers.
// The CDM answered "widens access"? Only then does the chat model run.
enrich "LLM explains what the CDM flagged" {
for records where type == "code_unit"
and attr "unit.access_change" == "widens access"
stale_after 1 hour
tool "llm" "review" {
unit attr "unit.name"
doc attr "unit.doc_content"
diff attr "unit.diff"
decider "none"
}
update attr "unit.llm_result" from result.verdict
update attr "unit.llm_explanation" from result.explanation
}
rule "Explain the hold in the PR" {
for records where type == "code_unit" and attr "unit.llm_result" == "mismatch"
do block "pr.merge"
do comment "pr" "{attr.unit.name}: {attr.unit.llm_explanation}"
}Try typed review on your own PRs.