2026-09-19 · 23 min · explainer · architecture · agents · llm · calibration
On September 18, Sydney Runkle of LangChain published Building a Harness with Jev: 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 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?
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 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 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.
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:
// 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:
// 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,
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:
// 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.
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:
// 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:
// 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 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:
# 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.

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 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.
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.
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.
What did not move, and one roster entry that does not exist
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
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.
What would change my mind
5 claims above, and what would falsify each
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.
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
stanleyagainst a real repository with--jsonand 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.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.
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/notCheckedbookkeeping in Stanley becomes redundant rather than load-bearing, and the cost I'm describing disappears.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.