# 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.

Published 2026-09-20 · https://synsema.org/blog/how-to-use-jev-the-judge-block


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

```synsema
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

```synsema
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](https://synsema.dev/en/0.6.x/54-judge), the model is
[Jev](https://typesafe.ai/), and the companion post here is
[System One vs System Two](/blog/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`.

