Run the judge locally with Laya
The judge slot is a protocol, not a vendor. Since v0.6.27 a Laya checkpoint on disk — ModernBERT, Apache 2.0 — answers whether, choose and rate with no network, no secret and no cost per token, and the same block runs unchanged.
A judge block asks typed questions about a state and gets calibrated probabilities back: is this true (0.86), which of these (billing, and the distribution), where on this scale (between upset and furious). Until v0.6.27 that meant an account with TypeSafe and a call to Jev, or the mock provider, which gives you a shape with no meaning.
The official binaries now ship a third backend. A Laya checkpoint on disk answers the same three verbs with no network, no secret and no cost per token:
SYNSEMA_JUDGE_PROVIDER=laya
SYNSEMA_JUDGE_MODEL=/models/laya # the checkpoint directory, or an org/repo already in the HF cache
Nothing else changes. The program does not know which backend answered, because the judge slot was always a protocol, not a vendor.
What Laya is§
Laya is a non-autoregressive decision model from Convai Innovations, Apache 2.0: a ModernBERT-large backbone (421M parameters) with a decision head, which takes a state and typed questions and answers them in a single forward pass. It is the same shape of thing as Jev — a System One model — from a different place, with weights you can put on a disk you own.
Its three question types map onto the three verbs of the block:
| Synsema | Laya | You read |
|---|---|---|
whether "…" | noul | probability |
choose "…" between {…} [or nothing] | choice | choice, probabilities, confidence |
rate "…" across […] | score | score, level, levels, probabilities |
noul is the upstream's name for a yes/no question; the language keeps whether, because adopting a vendor's glossary is how a protocol turns into a dependency.
Nothing is downloaded for you: the checkpoint is ~843 MB and fetching it is a decision, like a .gguf. If it is not there, the error says how to get it.
The same block, with no key§
This is the program from the Jev post — unchanged, byte for byte — with two environment variables set differently:
require judge
let ticket be {"subject": "Payouts failing", "messages": [
{"from": "customer", "text": "Help! My payouts have been failing for 3 days and nobody
answers. 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": "Polite, no complaint",
"upset": "Repeat contact, asks for a fix soon",
"furious": "Caps, threats to cancel, demands immediate action"
}
when confidence of v.team < 0.8
print("gate: human")
otherwise
print("gate: auto")
On a laptop with no GPU:
available: true
refund.probability: 0.8596
team.choice: billing
team.probabilities: {billing: 0.7621, technical: 0.0850, none: 0.1529}
team.confidence: 0.3595
anger.level: furious
anger.score: 1.7154
model: laya:/models/laya
usage: 288
gate: human
Read the fourth line from the bottom twice. The judge picked billing and it is right, but the mass is spread — 0.36 of confidence — so the gate you already wrote sends the ticket to a person. That is the whole reason to want a probability instead of a string, and it works identically against a checkpoint in your own directory.
Five questions in two blocks took 7.9 s of wall clock, model load included, and a single question takes 2.5 s end to end in a fresh process. Three runs gave identical numbers. usage still counts input tokens — there is simply no bill behind them.
decide too, with no network at all§
If your program already uses decide between […] given x, SYNSEMA_JUDGE_DECIDE=1 routes every decide in the process through the judge as a calibrated choose — one of your options byte for byte, no normalisation and no retry. With laya behind it, that means a decision that never leaves the machine:
$ synsema run --cap-set judge,llm,stdout triage.syn
decide: refund
model: laya:/models/laya
available: true
No net in the capability set. Nothing to deny, because there is nothing to call.
What is verified, and what is not§
The sequence a System One model sees is where an error does not fail loudly: the markers still run, the model simply scores something else, and the answer looks exactly as plausible as a right one. So it is the part that gets a permanent test.
[CLS] <type> question: <instruction> [SEP] [MASK] option0 [MASK] option1 … [SEP] <state> [SEP]
The [MASK]s are markers — the model scores each position, and that score is the preference for that option — so the order and the exact position matter. Against the reference implementation with the real tokenizer: 96 identical tokens, markers at [12, 22, 32, 39]. That rules out everything we wrote around the model: the tokenizer, the rendering of options, the serialisation of the state, the budget trimming.
What is not closed, and it should be said out loud: on the case published in the upstream README we get the same argmax (billing) with confidence 0.864 where the README reports 0.94. Our arithmetic is correct given the distribution we observe, and the input tokens are identical, so the difference is in the numbers coming out of the encoder, or the published value is not comparable (another version of the checkpoint, or another checkpoint served through their Router). Settling it requires running the upstream implementation, which needs PyTorch.
Which brings up the thing to do before you move a threshold from one backend to the other: re-measure it. A 0.9 gate is a number tuned against a specific model. Jev and Laya are different models; the shape of the answer is the same, the calibration is not necessarily.
The other limits, in one place§
- 512 tokens of context (read from the checkpoint's own config). The prefix — question type,
instruction, option markers — gets up to 192, and the state is truncated to what is left. A long ticket gets its tail cut, so put the part that matters first, or judge a field instead of the whole object.
- CPU, in seconds, not milliseconds. The model card's ~33 ms is not what a laptop does. Load is
paid once per process; under serve the requests after the first reuse it.
- It runs in
f32although the checkpoint isf16, because on CPU half precision is emulated
and comes out slower. That costs ~1.7 GB of RAM with the model loaded.
- The act/escalate head is deliberately not implemented. It is a parallel branch that does not
touch the logits — the answer is identical with or without it — and the judge contract does not expose an action probability, so it would be arithmetic nobody can read.
What it changes about being offline§
Every judge answer carries available, and without a provider it comes back available: false, confidence: 0 and its main value nothing — never an invented number. That degradation is deliberate and it stays. What changes in v0.6.27 is that being offline is now a choice rather than a fate: a program with require judge, no secret and no network gets real calibrated probabilities from a file on disk.
synsema judge status shows only what applies to the backend that is selected — with laya, the checkpoint and nothing else:
Provider laya (SYNSEMA_JUDGE_PROVIDER, environ)
Checkpoint /models/laya (SYNSEMA_JUDGE_MODEL, environ)
Budget (sin techo) (SYNSEMA_JUDGE_BUDGET, default)
decide LLM (default) (SYNSEMA_JUDGE_DECIDE)
Estado: ✅ VIVO — los bloques `judge` corren LOCAL contra el checkpoint: sin red, sin clave y sin
costo por token.
Exit 0 live, 1 offline, no network touched — so synsema judge status && synsema serve app.syn is still a deploy gate. And judge_model() returns laya:<checkpoint>, so two runs with different weights never look alike in a log.
Which one to use§
typesafe (Jev) | laya | mock | |
|---|---|---|---|
| Where | the vendor's API | a checkpoint on your disk | nowhere |
| Needs | a key | ~843 MB and RAM | nothing |
| Answers | real judgments | real judgments | a shape with no meaning |
| Good for | production at scale, long states | air-gapped, on-prem, CI with real numbers, no vendor | tests |
mock and laya answer different questions. mock belongs in CI, where a checkpoint has no business being. laya is for when you want a real judgment and cannot — or will not — send the state anywhere.
The manual page is Judge; the block itself, verb by verb, is in How to use Jev from Synsema; and the generative half of the same release — a model in your process, architectures as files — is in A model you already have.