Score first, words second
A chat model is a bad
router. You ask “is this a
refund?” and it writes a
paragraph. Then your code
parses the paragraph, or a
human skims it, or both
disagree with the tool call
that already happened. The
justification can say no
while the action said yes.
That is not a decision. It
is two outputs that were
never required to match.
A decision model — or a
schema that is only allowed
to return a score — makes
the branch a float you can
threshold, log, and eval.
Jev is one commercial shape
of that. You do not need
their API to steal the
split. The router returns a
typed number. The writer,
or the tool caller, runs
only if the number clears a
threshold that lives in
config, not in a prompt.
Log the score, the
threshold, and a hash of
the input. Do not log a
paragraph and call it the
reason.
The missing explanation is
the cost, not a bug you
paper over with “think step
by step.” Simon’s city
scores are the warning: a
fluent model would have
invented a civic theory.
Jev just returned the
ranking. Evals are how you
see the bias, because there
is nothing else to read.
Hold out labeled cases.
If you cannot say what 0.9
means on that set, you do
not have a gate. You have a
vibe. Cheap classification
makes “we will eval it
later” indefensible. A few
cents buys the experiment
that used to be a project.
Two bans follow. Do not use
an unexplained float to rank
a person — hiring, trust,
moderation of a named human
— unless a person owns a
written policy and can
override the score. And do
not deploy the loop into a
runtime that cannot do what
the library assumes. Python
Workers will run
langchain and
mcp. They will
not run your thread pool.
Concurrency belongs in the
platform: a queue, a
workflow, another isolate.
If the design says
threading, it
is the wrong sandbox.
The same split explains why
MCP still matters on the
agents that are not shells.
A terminal agent with the
network open does not need
a protocol to reach an API.
A constrained agent needs a
named tool, an auth path
that never puts the key in
the transcript, and a log.
That is a score gate’s
cousin: the allowlist is
the branch. Everything else
is prose.
Checklist
-
Name the branch. If it
is yes/no, a choice, or a
score, do not ask for
prose.
-
Put the threshold in
config. A prompt that
says “be strict” is not a
threshold.
-
Eval the classifier on
held-out labels before it
can block a tool or a
refund.
-
If the model cannot
explain itself, the eval
set is the explanation.
Keep the set.
-
Ban person-ranking until
a human owns the policy
and can override.
-
If the runtime is Wasm or
Pyodide, delete thread
and process-pool
assumptions before you
port the loop.
This morning: find one
prompt that asks “is this
X? explain.” Split it.
Score first. Words only if
a human is going to read
them, and those words do
not get to override the
gate.