How to use Jev from Synsema — calibrated decisions in a judge block
Jev is TypeSafe's System One model: it answers with typed, calibrated probabilities instead of text. In Synsema it is a language primitive — `require judge`, one block, one call, three verbs — with honest degradation when it is not there.
A System One model does not write. You hand it a state and typed questions, and it answers with probabilities: is this true (0.93), which of these (billing, and the distribution), where on this scale (level 2 of 3, and how concentrated the answer is). TypeSafe's Jev is the first one, and since v0.6.25 Synsema speaks to it with a block of the language rather than an HTTP client and a parser.
The reason it is syntax and not a library: a judgment is a value with a shape, and the shape is what the rest of your code branches on.
The block§
require judge
let ticket be {"subject": "Payouts failing", "text": "I want my money back NOW or I'm cancelling"}
let v be judge ticket
refund: whether "The customer is asking for money back"
team: choose "Which team should handle this?" between {
"billing": "Payments, invoicing, refunds",
"technical": "Bugs, outages, integrations"
} or nothing
anger: rate "How frustrated is the customer?" across ["Calm", "Frustrated", "Very angry"]
when v.team.available and confidence of v.team >= 0.8
print("route to " + v.team.choice)
otherwise
approve "Route this ticket to " + text(v.team.choice) + "?"
One state, three questions, one call. That is the whole point of the block being the only form: the state is ingested once and the questions are evaluated against it in parallel. Measured against jev-1.13.0, a block of eight questions was 7.6× faster and used 4.9× fewer input tokens than eight separate calls, and latency stayed flat — about 0.8 s from one question to forty. There is no one-question shortcut on purpose: coding agents reach for one call per question, and the language does not let them.
Three verbs, three distributions§
| Verb | Asks | Distribution | You read |
|---|---|---|---|
whether "…" | is this statement true? | Bernoulli | probability |
choose "…" between {…} [or nothing] | which one of these? | categorical | choice, probabilities, confidence |
rate "…" across […] | where on this ordered scale? | ordinal | score, level, levels, probabilities, confidence |
The prepositions differ on purpose: between means unordered options, across means ordered levels. Writing rate … between is a load error that names the fix.
The result is a flat map, id → answer, so a question called usage collides with nothing:
v.refund.probability -- 0..1 (a `whether` has no separate confidence)
v.team.choice -- one of YOUR ids, byte for byte, or nothing
v.team.probabilities -- {"billing": 0.93, "technical": 0.07, "none": 0.0}
v.team.confidence -- how concentrated the distribution is
v.anger.score -- 0..n-1, probability-weighted: 1.4 means split between the 2nd and 3rd
v.anger.level -- "Frustrated"
Options take a list (the item is the id and the description) or a map (id: description) — use the map when the descriptions are long, because v.anger.probabilities["Visibly upset, threatens to cancel"] is no way to live. For choose, the model reads your option ids, so name them meaningfully; the question ids are not sent.
or nothing, and the failure that made it exist§
A choose must pick. A message asking about opening hours, offered only billing and technical, came back technical at 0.69 — a wrong answer that walks straight through a 0.5 gate. or nothing adds an escape option to the wire; when it wins, choice is nothing and the mass lands in probabilities.none. On the same cases it answered none at 1.00 and 0.98, and it cost nothing on the clear ones (billing stayed at 0.97).
Write it whenever the state might not fit any option. rate has no escape — an ordered scale has no level outside itself — so guard a rate with a whether that asks whether the state applies at all.
When it is not there, it says so§
This is the part that decides whether you can put it in production. Without a key, over the budget, or after a network failure, every answer comes back available: false, confidence: 0, and its main value nothing:
[synsema] notice: judge is OFFLINE — every `judge` answer is returning available: false with
confidence 0 and its main value (probability/choice/score) as nothing, not a real judgment.
The program keeps running; a confidence gate sends these to the human path by itself.
offline: available=false probability=nothing team.confidence=0.0
An invented sentence is visible in your output. An invented probability is not, and it multiplies into money — so this block never returns one. And notice what degradation does to the code you already wrote: confidence 0 is below every gate, so the ticket routes itself to the human branch. A program that skipped the gate and compared directly fails loud (Unsupported operation: nothing > number) instead of taking the wrong branch in silence.
Its own capability§
require judge -- classifying
require llm -- generating
llm does not grant judge and judge does not grant llm. Classifying and generating are different rights, and the ceiling that separates them is real: --cap-set judge is a program that can measure and cannot write — it cannot exfiltrate through free text, and it cannot be talked into generating. Otherwise it behaves like llm: auto-granted in a plain run, required under serve, emptied in sandbox, denied under --deterministic (it is network I/O), offline inside a wasm guest. The key never enters the program and the host is fixed by the runtime, so a .syn cannot redirect the call.
The parallel slot, and a preflight§
The judge is configured next to the LLM, not inside it: TYPESAFE_API_KEY, SYNSEMA_JUDGE_PROVIDER, SYNSEMA_JUDGE_MODEL, SYNSEMA_JUDGE_BASE_URL, SYNSEMA_JUDGE_TIMEOUT, SYNSEMA_JUDGE_BUDGET — all written into .env.example by synsema init. The judge decides, the LLM writes, and having both wired is the ordinary setup.
Since v0.6.26 the CLI answers what is resolved and why, with no network:
$ synsema judge status
Key TYPESAFE_API_KEY ✗ MISSING
Model jev-latest (SYNSEMA_JUDGE_MODEL, default)
Base URL https://api.typesafe.ai (SYNSEMA_JUDGE_BASE_URL, default)
Timeout 60s (SYNSEMA_JUDGE_TIMEOUT, default)
Budget (no ceiling) (SYNSEMA_JUDGE_BUDGET, default)
decide LLM (default) (SYNSEMA_JUDGE_DECIDE)
Exit 0 live, 1 offline — so synsema judge status && synsema serve app.syn is a deploy gate. The key is reported by presence, never by value.
The checker spends no tokens§
synsema check knows the API's limits and refuses a block that would be a 400 in production:
$ synsema check bad.syn
Error: bad.syn:4: judge 'team': choose needs at least 2 options (got 1);
with one option the model can only agree
And it warns — never fails — about the things that run but mislead:
warning: judge 'quiet' asks in the negative — the model reads negations literally and
P(not A) is not 1 − P(A) (measured 0.37 + 0.78); ask in the positive and negate in code
warning: judge 'total' asks for arithmetic or counting over the state — the model recognises
the shape of an answer, it does not calculate (a six-line total came out wrong at 0.32);
compute in Synsema and judge the result
Backticked paths — ` ticket.messages[0].text `, the vendor's idiom for pointing at one element — are resolved against the state before the call, so a path that does not exist is a warning with the fix instead of an answer of 0.31 out of nowhere.
Tests without a key, and decide for free§
SYNSEMA_JUDGE_PROVIDER=mock wires a deterministic provider — whether → 0.5, choose → the first option, rate → the middle level — so the same block runs in CI with no network and no account. With a real key, assert the winner and ranges (v.team.choice == "billing", v.refund.probability > 0.9), never exact numbers: the same request twice moves by a few hundredths (0.72 → 0.69).
And if your program already uses decide between […] given x, v0.6.26 adds SYNSEMA_JUDGE_DECIDE=1: every decide in the process is answered by the judge as a calibrated choose — one of your options byte for byte, no normalisation, no retry — with no change to the program. It is opt-in because it changes which model answers, it needs require judge, and it falls back to the LLM path when the judge is unavailable.
Where to read more§
The manual page is Judge, the model is Jev, and the companion post here is System One vs System Two — when a probability is the right answer and when you still want a sentence.
Install and try it in five minutes: curl -fsSL https://synsema.org/install.sh | sh, then SYNSEMA_JUDGE_PROVIDER=mock synsema run triage.syn.