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.

Compare against

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.

1

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.md
2

Ask 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?

no change to accessnarrows accesswidens accessremoves a check

app/policies/ + its doc

Does it follow the documented rules?

matches the docnot covered by the doccontradicts the doc

authentication code

What does it do to authentication?

no changechanges session or token handlingweakens or bypasses authentication

any unit

Are credentials in the code?

noneread from config or envhard-coded credential

controllers, queries

Where does user input go?

no user inputvalidated before useunvalidated input reaches a query or shell
3

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.

  1. [1] Anthropic API pricing (Claude Fable 5.1, Opus 5.5, Sonnet 5.5)
  2. [2] TypeSafe — Jev pricing and latency
  3. [3] TypeSafe — Introducing System One models and Jev
  4. [4] Subquadratic — Introducing SubQ Snap (pricing, context, speed)