# The middle tier is not in the middle

> Satyajit Ghana — Head of Engineering @ Inkers Technology
> canonical: https://ai.thesatyajit.com/articles/three-tiers
> date: 2026-09-19
> tags: explainer, architecture, agents, llm, calibration
On September 18, Sydney Runkle of LangChain published [*Building a Harness with
Jev*](https://x.com/sydneyrunkle/status/2100754364545761643): tool calling and
structured outputs made LLMs safe for software, "but even with those in place,
the agent loop is still **slow** and **costly**: every decision requires another
model call." Her prescription is a division of labour — "use an LLM for
open-ended reasoning and generation, and Jev for fast, structured decisions along
the way."

The next morning [0xRicker](https://x.com/0xricker/status/2101292455391809670)
published the same idea harder, as a discipline with a name. *Jev Engineering:*
"An LLM creates the work → Jev decides what happens next → code executes the
decision. That split is the whole discipline."

Read together they describe a three-tier stack, and the natural way to hear it is
as a spectrum: deterministic scripts at one end, reasoning models at the other,
decision models in between. Work slides down from the expensive end as it gets
easy enough.

In fairness to both authors: neither actually writes the word. Runkle says
"complement"; 0xRicker says "layer." The spectrum is the reading, not the text,
and it is the reading I keep hearing repeated — which is why it is worth testing
rather than the posts themselves.

I spent a day reading the systems people have actually built this way. The
three-tier split is real and the evidence for it is better than either post
claims. The spectrum is not. On the properties that make you choose one tier over
another, the middle is not between the ends — on two of them it is below both,
and that is the single most load-bearing fact about building with these models.

## Their own rule is a type test, not a difficulty test

Notice what the sorting rule actually says. From step one of the Jev Engineering
guide:

> If the operation creates text, it stays with the LLM. If it picks, scores, or
> answers yes or no, it goes to Jev. An exact rule, such as stopping after ten
> actions, belongs in code, not in either model.

There is no difficulty in that test anywhere. Nothing about how hard the decision
is, how much context it needs, how much judgment it takes. The test is entirely
about the **shape of the output**: one value determined by the input, one of *k*
known options, or an unbounded string.

That is a type signature, not a position on a line. And a type signature does not
have a middle. `() => 1`, `() => (0..k)` and `() => string` are three different
things, and the reason a decision model exists is not that it is a weaker
reasoner — it is that it returns the second type natively, with a trained
probability on it, which neither of the others does.

So: six properties people cite when they explain the split. Three tiers. Which of
them actually rise from left to right?

<TierAxes />

Three do. Output cardinality, cost and latency all climb — those are the
properties the spectrum reading is built on, and on those it is fine.

Three do not, and the three that do not are the ones you design against.

## The axis where the middle is below both ends

An earlier piece here documented the sharpest number in this whole story:
Jev [scores 0 of 100](/articles/jev-scores-zero) on relational choice — questions
where the content of one option decides another — on a suite where the same model
scores 100% on rule judgment and 98% on ARC-Challenge. Not low. Zero, across a
hundred attempts, far below what you can reach by guessing.

That number has now been independently restated three more times by people who,
as far as I can tell, have never seen it.

The Jev Engineering guide, explaining how to batch questions for speed, states it
as an engineering rule:

> **The one rule: questions cannot read one another's answers.** If a decision
> depends on a fresh search result, run the search first.

Stanley — a Jev-first coding agent I'll come back to — states it as a limitation
of its own product, in the list of things its report tells you it did not check:

> hunks are judged individually; intent spread across unlinked hunks is not
> modeled

And the xArm7 control harness states it by construction: it never asks the model
for a movement, only for four separate signs. More on that below.

Put the axis on the picture and it is a V. Deterministic code sees every option at
once — that is what a program *is*. A reasoning model sees every option at once,
because they are all tokens in one context. The decision model, alone in the
stack, evaluates each option in isolation and cannot form a relation between them.

A quantity that is at its minimum in the middle column is not "in between." On
relational reach, the middle tier is not a compromise between code and reasoning;
it is strictly worse than both, and no amount of making it cheaper or faster
moves it.

## What actually moved: Stanley, read line by line

[stanley-code](https://github.com/devagrawal09/stanley-code) is the best evidence
I found, because it is a whole agent built on the three-way split and it is small
enough to read: 11,199 lines of source across 55 files, with 5,881 lines of tests.
Its own summary of itself is the thesis in one sentence: "Each workflow is
deterministic code that gathers bounded evidence and asks Jev small fixed-choice
questions about it; code, not a model, makes the decisions."

I counted what is delegated. Across ten built-in workflows, Stanley asks Jev
exactly **23 questions** (22 distinct names; one is reused), every one of them a
`noul`, `choice` or `score` over criteria fixed at author time. Not one of them is open-ended. Against that,
its eight policy blocks hold **69 hard-coded constants** — 41 bare probability
thresholds, two more aliased to a shared one, 25 caps and a minimum count — and
those constants, not the model, decide what every answer means.

Here is one request going through the router, drawn from the real source.

<StanleyRoute />

The router is 160 lines and I am going to quote most of the part that matters, because the
whole argument of this piece is in it. Repository at commit `85f39e7`,
`src/cli/router.ts`. First, what the model is allowed to answer:

```ts
// src/cli/router.ts — the option set is the registered workflows, and nothing else
const criteria: ChoiceCriteria = {};
for (const candidate of context.candidates) {
  const routing = dependencies.redaction.json(candidate.routing);
  criteria[candidate.id] = routing.value;
  redactions += routing.count;
}
criteria[CANNOT_TELL] = CANNOT_TELL_WHEN;
const labels = Object.keys(criteria);

const frame = createFrame({
  template: "route-intent@1",
  // …scope, provenance…
  state: { request: redacted.text, context: { /* diff, input, capabilities */ } },
  questions: { route: choice(ROUTING_INSTRUCTIONS, criteria) },
  parse(answers) {
    expectKeys(answers, ["route"]);
    return readChoice(answers, "route", labels);   // outside labels ⇒ throw
  },
});
```

One question. Its criteria are workflow ids the registry produced, plus the router's own
`cannot_tell`. The request text is redacted before it is sent and the answer is parsed
against the same `labels` array that built the question.

The shape is a sandwich, and it is worth being precise about which slice is which.
Before the call, deterministic code computes facts (is there a diff? is stdin a
failure log?) and each workflow's `available` gate turns those facts into hard
constraints on what may even appear as an option. After the call, three fixed
tests decide whether the answer counts: `minConfidence: 0.6`, `minProbability:
0.55`, `minMargin: 0.15`.

The `minMargin` test is the one I'd point at. It is not a confidence check —
confidence is already checked separately. It is a check that the top two options
are far enough apart, and it exists because a calibrated distribution is the
*only* thing the model returns. The tier below has to read the shape of that
distribution to recover what the tier above would have said in words.

Here is the whole of it — every line between the model returning and the router returning:

```ts
// src/cli/router.ts — everything that happens to the answer
const answer = result.value;
const selected = answer.choice;
const selectedProb = answer.probabilities[selected] ?? 0;
const alternatives = labels
  .filter((label) => label !== selected)
  .map((label) => answer.probabilities[label] ?? 0);
const margin = selectedProb - Math.max(...alternatives);
let outcome: string = selected;
let reason: RoutingDecision["reason"] = "selected";
if (selected === CANNOT_TELL) reason = "cannot_tell";
else if (context.capabilities[selected] !== true) {
  outcome = CANNOT_TELL;                                  // the override
  reason = "unavailable";
} else if (
  answer.confidence < ROUTING_POLICY.minConfidence ||     // 0.6
  selectedProb < ROUTING_POLICY.minProbability ||         // 0.55
  margin < ROUTING_POLICY.minMargin                       // 0.15
) {
  outcome = CANNOT_TELL;
  reason = "model_uncertain";
}
```

`selected` and `outcome` are separate fields, and only `outcome` is acted on. The three
constants are declared right above with a comment that says what they are: *"These are fixed
product thresholds, not configuration."* If the model picks a workflow that the
deterministic gate already marked ineligible, the pick is discarded. The model's
answer is an input to a decision that code makes. That is not a tier in the middle
of a pipeline; it is a subroutine.

The same pattern runs all the way down. In the `check` workflow, nine regexes find
skip markers (`it.skip`, `@pytest.mark.xfail`, `#[ignore]`, `t.Skip`…), one finds
assertions, and a whitespace-squash equality finds formatting-only hunks. Those
fire before any model call and are reported as findings with `source:
"deterministic"`. Jev is then asked four bounded questions per source hunk — how the hunk
relates to the task on a 4-level scale, which of seven kinds of change it is, and
two yes-or-nos — and code turns those into flags at `0.7`, `0.7`, `0.5`, `0.6`.
Jev never writes a review comment. The README says so as a design statement: "Jev
does not write free-form review comments."

## The one place work actually moves between tiers

Everything above is a static split. Stanley has the only mechanism I found where
work *migrates*, and it moves in the direction the whole argument predicts —
upward first, then permanently down.

When a request matches no workflow, Stanley delegates to [Pi](https://www.npmjs.com/package/@earendil-works/pi-coding-agent),
a general coding agent, with a 600-second hard limit. That is the reasoning tier,
used as a fallback rather than a default. It reports the agent's account of what
it did and states in `notChecked` that Stanley did not verify it — the tier is
used, and explicitly not trusted.

Then the interesting part. In the background, Stanley queues an improvement job
and starts a detached worker that asks an agent to write **a Stanley workflow for
that kind of request** — deterministic code plus `judge()` calls, in the documented
contract. The candidate is validated by the same loader that loads real workflows, and the
record of that validation is a five-field struct:

```ts
// src/workflows/improve.ts — what a promotable candidate has to survive
export interface CandidateChecks {
  /** The candidate directory contained exactly one loadable workflow. */
  readonly loaded: boolean;
  readonly quarantined: readonly string[];
  readonly duplicateId: boolean;
  /** Repository paths the agent changed outside its candidate directory. */
  readonly outsideWrites: readonly string[];
  /** Whether the router selected the candidate for the original request. */
  readonly routing: "selected" | "not_selected" | "skipped";
}

// src/adapters/improvements.ts — the verdict
status: reasons.length === 0 ? "validated" : "rejected",
```

Then it stops. Nothing activates — the module comment says so in those words:
*"Nothing is activated automatically: `promoteCandidate` moves a validated
candidate into `.stanley/workflows/` on request."* A human runs
`--promote-candidate`, and from then on "that request kind is handled by the
promoted workflow: deterministic code plus `judge`, no agent."

That is the three-tier thesis as a running loop: a reasoning model does a job
once, writes the deterministic-plus-decision version of itself, and a person
decides whether it graduates. The guardrails around it are the tell — agent-written
files that appear under `.stanley/workflows/` are moved to `.stanley/quarantine/`,
every agent subprocess runs with `STANLEY_NESTED=1` so it cannot start another
delegation, and the queue holds 20 jobs with at most two attempts each. The system
treats its own reasoning tier as the untrusted one.

Worth noting what this is not: Stanley is at `0.1.0`, unreleased, and its README
says so twice. I am describing a mechanism I read, not a result I measured.

## The workaround is the architecture

If the decision tier genuinely cannot relate two things, how does any of this
work? Diffs are relational. Robot arms are relational.

Both systems solve it the same way, and once you see it once you see it
everywhere.

<RelationSupply />

Stanley wants to know whether a hunk that looks unrelated to the task is actually
there because another hunk needs it. It cannot ask that as a relational question.
So `enabledHunks` does it deterministically: regex out the identifiers each hunk
*declares* on added lines, regex out the identifiers each hunk *references*,
intersect. `src/workflows/hunks.ts`, same commit:

```ts
// src/workflows/hunks.ts — the relation, built by regex, with no model involved
const DECLARATION =
  /\b(?:function|class|interface|type|enum|const|let|var|def|fn|func|struct|trait|module)\s+\*?\s*([A-Za-z_$][\w$]{2,})/g;
// declaredIdentifiers(hunk): DECLARATION matches on "+" lines only.
// referencedIdentifiers(hunk): every identifier on "+" and context lines.

/** For each hunk, the other hunks that reference identifiers it declares. */
export function enabledHunks(hunks: readonly Hunk[]): Map<string, string[]> {
  const references = new Map(hunks.map((h) => [h.id, referencedIdentifiers(h)]));
  const result = new Map<string, string[]>();
  for (const hunk of hunks) {
    const declared = declaredIdentifiers(hunk);
    const linked: string[] = [];
    if (declared.size > 0) {
      for (const other of hunks) {
        if (other.id === hunk.id) continue;
        const refs = references.get(other.id)!;
        if ([...declared].some((name) => refs.has(name))) linked.push(other.id);
      }
    }
    result.set(hunk.id, linked);
  }
  return result;
}
```

That produces a candidate pair — built by code, from a regex, with no model involved. Only
then does Stanley open a frame containing exactly two hunks and ask one yes-or-no: does A add
something D uses and needs?

The budget on that is the instructive part, and it is one method call:

```ts
// src/workflows/check-task.ts — a weak hunk that enables a strong one gets ONE question
const followUps: Array<{ weakId: string; dependentId: string }> = [];
for (const id of weak) {
  const dependent = (links.get(id) ?? []).find((other) => {
    const otherResult = results.get(other);
    return (otherResult?.taskRelation?.highMass ?? 0)
      >= CHECK_TASK_POLICY.enablerMinHighMass;                       // 0.5
  });
  if (dependent) followUps.push({ weakId: id, dependentId: dependent });
}
// …one enablesFrame per pair, judged, then:
if (outcome.value >= CHECK_TASK_POLICY.enables) {                    // 0.6
  result.flags = result.flags.filter((f) => f !== "weak_task_relation");
  result.flags.push("enables_linked_hunk");
}
```

`.find`, not `.filter`: only a hunk already flagged weak, already linked to a hunk already
scoring well, gets a follow-up — and it gets exactly one, against the first match. The
relational capability is manufactured by code, one pair at a time, and rationed.

The [xArm7 harness](https://github.com/openroboto-ai/jev-robot-control) does the
identical thing in a domain where it is impossible to miss. A movement is a joint
choice: X, Y, Z and the gripper together, 81 combinations, and the right value on
one axis depends on the others. The harness never asks for it. Each control cycle
is two requests — one `choice` over eight intents, then one request carrying four
independent `choice` questions, one per channel, each with three options. The
model returns four signs. `incremental_policy.py` builds those four questions in a loop and
writes the constraint into each one's instruction text: *"This question controls ONLY X …
Use ONLY this axis for this answer; do not answer for another axis."*

The executor supplies everything continuous. At commit `7a4ed8b`,
`incremental_env.py`:

```python
# incremental_env.py — the model's whole contribution is the sign
direction = {'negative': -1., 'hold': 0., 'positive': 1.}
# …
# Magnitude-only scaling near geometric targets. Never changes the selected sign.
reference = self.fruit_position + [0, 0, .002]
if intent in ('carry', 'lower', 'release'):
    reference = np.r_[PLATE[:2], RELEASE_Z if intent != 'carry' else TRAVEL_Z]
if intent in ('lift', 'withdraw'): reference = np.r_[current[:2], TRAVEL_Z]
distances = np.abs(reference - current)
sizes = np.where(distances < .012, .002, np.where(distances < .025, .004, .018))
delta = np.array([direction[motor[a]] for a in ('x', 'y', 'z')]) * sizes
target = current + delta
if np.any(target < [.20, -.30, .025]) or np.any(target > [.65, .38, .32]):
    rejection = 'Workspace bound: requested increment rejected.'
```

Three signs in; a 2, 4 or 18 mm increment, a workspace check, fixed-orientation
inverse kinematics and a 0.32-second physics step out. The comment above
`reference` is the design statement: the executor scales magnitude and *"never
changes the selected sign."*

The model chooses direction. Code chooses distance. Every relation between the
axes lives in the executor's geometry, where it can be read, tested and stepped
through.

<Figure
  src="/articles/three-tiers/fig1.png"
  alt="A three-column comparison of one apple-to-plate task. Jev 1.13, GPT-6 Astra and GPT-4.1 mini each show a final simulation frame, the final tool-centre-point coordinates, the chosen intent with a probability, and four separate columns for X, Y, Z and fingers, each with its own probability. Jev's are 98, 96, 93 and 86 percent and labelled native Jev probabilities; both GPT columns read 100 percent across the board and are labelled self-reported JSON probabilities. A sidebar gives cumulative cost of $0.0188, $5.9336 and $0.2885, decision cycles of 113, 106 and 160, and wall times of 181.8, 707.3 and 704.3 seconds."
  caption="The decomposition, drawn by the source itself: X, Y, Z and FINGERS are four separate columns with four separate probabilities, because they were four separate questions. Note the probability rows — Jev's are native model outputs; both GPT columns are self-reported and read 100% four times in a row. (openroboto-ai/jev-robot-control, media/final.png.)"
/>

## A number is not a reason, and somebody has to write the reason

The second non-monotonic axis is the one I underrated before reading this code.

Deterministic code explains itself completely: the regex is the explanation, and
you can read it. A reasoning model explains itself fluently, in prose, and that
prose is legible even though it is famously not guaranteed to be the actual cause.
The decision model returns a probability vector. That is all. It is the most
*faithful* output of the three — there is no gap between the number and what the
model did — and simultaneously the least *explanatory*, because a number does not
say why.

So the audit trail has to be built somewhere else, and in Stanley you can watch it
being built. Every finding carries `source: "deterministic" | "jev" | "policy"`,
so you can tell which tier produced it. Every frame carries `provenance` — the
file and line range the evidence came from. Every report carries `coverage`, how
much was actually examined; `parked`, the items it could not decide; `limits`,
where a cap truncated the work; and `notChecked`, a plain-English list of what it
did not do. The README's sharpest line is about exactly this: "An empty findings
list is **not** an approval."

None of that is decision-model output. All of it is deterministic bookkeeping
wrapped around decision-model output, and it is a large fraction of why Stanley is
11,000 lines rather than 2,000. The honest version of the three-tier claim has to
include this: **moving a decision down a tier does not move its explanation down
with it. You inherit the obligation to manufacture one.**

That is a cost, and it is not on anyone's comparison table.

## The multipliers, measured

TypeSafe's homepage headline is "up to 193.6x faster, up to 444.6x cheaper."
Runkle's post relays it as "up to 200x faster inference and 400x lower cost." This
site [itemized those claims](/articles/jev-system-one-models) when they were made
— self-graded workflow evals, raw data unreleased, and TypeSafe's own docs
cookbook showing 20x–114x speed against real LLM prices — so I won't re-argue it.

What is new is that there is now a matched head-to-head someone else can replay.
The xArm7 repository publishes both arms of a seed-0 trial with per-call costs and
per-call model wait times, an offline verifier that re-executes every recorded
motor command in MuJoCo, and `max_qpos_error = 0.0`. Nobody in this ecosystem has
put those numbers next to the headline. Here they are.

**Receipts.** TypeSafe's homepage headline is 'up to 193.6x faster, up to 444.6x cheaper.' The xArm7 repository is the only place in this ecosystem that publishes a matched, independently replayable head-to-head with per-call costs and per-call model wait times for both sides. Read against it, the cost multiplier is roughly the right order of magnitude and the speed multiplier is not: the measured per-call latency ratio is 4.57x, about 42 times smaller than the headline.

| quantity | Jev 1.13 | GPT-6 Astra (low) | ratio |  |
| :--- | ---: | ---: | ---: | :--- |
| model API calls | 226 | 212 | 0.94x | measured |
| total model cost | $0.018825 | $5.933624 | 315.2x | measured |
| cost per call | $0.0000833 | $0.027989 | 336.0x | measured |
| total model wait | 159.81 s | 685.75 s | 4.29x | measured |
| latency per call | 0.7071 s | 3.2347 s | 4.57x | measured |
| wall time, whole task | 181.85 s | 707.27 s | 3.89x | measured |
| control cycles to place the apple | 113 | 106 | 0.94x | measured |
| headline cost multiplier | — | — | 444.6x | claimed |
| headline speed multiplier | — | — | 193.6x | claimed |
| LLM latency 193.6x would require (vs Jev at 0.707 s, xArm7) | 0.7071 s | 136.9 s | 193.6x | implied |
| LLM latency 193.6x would require (vs Jev at 178 ms, browser) | 0.178 s | 34.5 s | 193.6x | implied |

This is ONE seed-0 trial per controller, which the repository states plainly and which is the reason it is evidence about magnitudes rather than a success rate. Both sides are remote HTTP endpoints over OpenRouter, so both latencies include a network round trip; that inflates Jev's absolute number and therefore makes the 4.57x ratio a conservative floor for Jev, not a ceiling. GPT-6 Astra ran at low reasoning effort with 4,096 max completion tokens — a long-reasoning-trace configuration would move the latency ratio up and is the only regime in which a three-figure speed multiplier is reachable.

> method: Every figure in the 'measured' rows is read directly from verification/gpt6-comparison.json in openroboto-ai/jev-robot-control, pair 20260919-200239-715974-0, and divided. cost_usd and model_wait_seconds are per-run totals; api_calls is the per-run count of model requests. Per-call figures are total divided by api_calls. The repo's own offline verifier re-executes every recorded motor command in MuJoCo and reports max_qpos_error = 0.0 and cost_sum_verified = true for both arms. The last two rows invert the headline multiplier: they are the per-call LLM latency that would be required to reach 193.6x against Jev's measured latency in each of the two published harnesses.
> source: openroboto-ai/jev-robot-control verification/gpt6-comparison.json and docs/RESULTS.md; browser-use/jev-ultrafast docs/performance.md; TypeSafe homepage figure as itemized in this site's earlier Jev piece.
> captured: 2026-09-19
> data: https://ai.thesatyajit.com/articles/three-tiers/data/ratios.json (11 rows)

The cost multiplier broadly survives: **336x per call** measured, against 444.6x
claimed — same order, and the gap is the kind of thing task mix explains.

The speed multiplier does not. **4.57x per call** measured, against 193.6x
claimed: off by a factor of 42.

Invert it and you can see what the headline would require. At Jev's measured
0.707 s per call in this harness, a 193.6x ratio needs the LLM side to spend
**136.9 seconds** on every call. Take Jev's best published latency instead — the
178 ms median from the browser harness — and it still needs **34.5 seconds** per
call. Neither is a classification call. Both are a model writing a long reasoning
trace.

Which is the quiet joke in the whole comparison: the three-figure speed multiplier
is only reachable against a baseline that is doing the thing the entire argument
says you should stop doing. Against an LLM asked the same bounded question at low
reasoning effort, the honest number is single digits.

Two more things in that table deserve saying out loud. GPT-4.1 mini — a cheap
model, the one a spectrum reading would place just above the decision tier — spent
**15.3x Jev's total cost and failed the task**, exhausting the 160-cycle budget
with one grasp attempt behind it and the apple still off the plate. And GPT-6
Astra placed the apple in **seven fewer
cycles** than Jev. Per decision, the frontier model was better. It cost 336x more
per decision to be better, and the cheaper LLM was not better at all. There is no
single line these three sit on.

<Callout type="note">
One seed, one trial per controller, one task. The repository says so first and
says it twice — "not success-rate estimates" — which is why these numbers are
worth quoting for their magnitudes and worth nothing as a success rate. Both arms
are remote endpoints over OpenRouter, so both latencies carry a network round
trip; that inflates Jev's absolute number, which makes 4.57x a floor.
</Callout>

## What did not move, and one roster entry that does not exist

[jev-recruiter](https://github.com/skeptrunedev/jev-recruiter) is circulating with
the claim that decision models kill all prospecting and sourcing work. The
repository is a genuinely clean instance of the pattern — Jev screens titles,
picks an operation and a target, and chooses a status plus an indexed excerpt per
criterion, while code resolves the quotation and validates that it exists in the
observed text, and "model output never becomes a selector or executable code."

It also publishes **no recruiting numbers at all.** No precision, no recall, no N,
no denominator. And the README is the thing that told me so, in a paragraph its
author had every incentive to leave out:

> The inherited Flights examples and measurements describe the upstream agent, not
> recruiting performance.

It goes further. A `potential_match` "is **not** an independently verified
qualification or a hiring decision"; "a quotation can be real while the model's
interpretation is wrong, especially when adding years across multiple jobs"; the
interface "samples at most seven visible screens per profile"; "a completed run
does not prove every candidate meets the brief." That is a project refusing to let
its own artifact be read as a benchmark. Credit it — and then notice that the
distance between that README and "kills all prospecting work" is the entire
distance between an architecture and a result.

One correction to the roster I was given. [dimensionalOS/dimos](https://github.com/dimensionalOS/dimos)
is cited as evidence for this architecture. At commit `c1c3cdc` the string `jev`
does not appear in it, nor `typesafe`, nor `systemone`. It is a robotics operating
system with agent modules; it has no decision-model integration. It belongs in a
different article.

## So what is the honest picture?

Three tiers, yes. The decomposition is real, three independent codebases and two
independent write-ups converged on it, and
the sorting rule the practitioners actually use — does this operation create text,
pick from a list, or follow an exact rule — is a good rule.

But it is a **type distinction, not a spectrum**, and three of its six defining
properties are not monotonic. The decision tier is the only one that cannot see
across its own options, the only one that cannot explain itself, and the only one
that comes with a trained probability. You do not reach for it because a task got
easy enough to demote. You reach for it when the output is genuinely one of *k*
known things and you genuinely want a number attached — and you accept, in
exchange, that you will write the code that builds every relation it needs and the
code that manufactures every explanation it owes.

Stanley's 64 constants and the executor's millimetres are not scaffolding around
the interesting part. In a three-tier system they *are* the interesting part. The
decision model is the smallest piece of code in every repository I read, and that
is the claim worth making: not that decisions moved to a cheaper model, but that
making a decision cheap enough to call constantly pushes the hard work back down
into deterministic code, where — for once — you can read it.

<ChangeMyMind>

<Falsifier claim="The three-tier split is a type distinction, not a spectrum, because three of its six properties are non-monotonic.">
The whole frame collapses if the relational gap is a training artefact rather than a structural one. Ship a System One model whose options share one context — a single forward pass that scores all k options jointly and still returns a calibrated distribution — and relational reach stops being a V. Bespoke's contrastive-pair approach is the nearest thing to an attempt. If such a model matches Jev on latency and cost while solving Chopra's relational-choice set, the middle tier really is just "cheap reasoning" and the spectrum reading is the right one.
</Falsifier>

<Falsifier claim="Stanley delegates only bounded questions, and deterministic code makes every decision.">
I counted 23 question definitions and 64 policy constants by reading the source, not by running it. Run `stanley` against a real repository with `--json` and diff what the model returned against what the report says. If any finding's severity or text varies with something other than a threshold applied to a returned probability — if a Jev answer reaches the output unmediated — the "code, not a model, decides" claim is weaker than the README and I have over-read it.
</Falsifier>

<Falsifier claim="The measured per-call latency ratio is 4.57x, against a 193.6x headline.">
This is one trial, both arms remote, GPT-6 at low reasoning effort with a 4,096-token cap. Run the same harness against a locally-served LLM and a locally-served decision model, removing the network round trip from both sides, and the ratio will move — possibly a lot, since Jev's 0.707 s is almost certainly dominated by transport. If a like-for-like local comparison on the same bounded question lands anywhere near three figures, my arithmetic is measuring OpenRouter rather than the models.
</Falsifier>

<Falsifier claim="Moving a decision down a tier means writing the explanation yourself.">
Straightforwardly falsified by a decision model that emits an attribution alongside its probability — the evidence span it scored highest, an influence map over the input state, anything faithful and machine-checkable. Nothing in the architecture forbids it; per-option scoring arguably makes it easier than it is for an LLM. If TypeSafe or an open reimplementation ships one, the `provenance` / `coverage` / `notChecked` bookkeeping in Stanley becomes redundant rather than load-bearing, and the cost I'm describing disappears.
</Falsifier>

<Falsifier claim="Jev Engineering's 'questions cannot read one another's answers' describes the same constraint as the 0/100 relational-choice score.">
These could be two different things: one a batching rule about parallel questions within a request, the other a failure within a single question's option set. If a probe shows Jev handling cross-option reference fine inside one question while only failing across questions in a batch, then the guide's rule is about request parallelism and the benchmark result needs a different explanation — and my claim that four sources describe one constraint is an over-reading of three of them.
</Falsifier>

</ChangeMyMind>
