# Cómo usar Jev desde Synsema — decisiones calibradas en un bloque judge

> Jev es el modelo System One de TypeSafe: responde con probabilidades calibradas y tipadas en vez de texto. En Synsema es una primitiva del lenguaje — `require judge`, un bloque, una llamada, tres verbos — con degradación honesta cuando no está.

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


Un modelo System One no escribe. Le pasás un estado y preguntas tipadas, y responde con
probabilidades: *¿es esto cierto?* (0,93), *¿cuál de estas?* (`billing`, y la distribución), *¿dónde
en esta escala?* (nivel 2 de 3, y qué tan concentrada está la respuesta). **Jev**, de TypeSafe, es el
primero, y desde **v0.6.25** Synsema le habla con un bloque del lenguaje en vez de un cliente HTTP y
un parser.

El motivo de que sea sintaxis y no una biblioteca: un juicio es un valor con forma, y esa forma es
sobre la que ramifica el resto de tu código.

## El bloque

```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) + "?"
```

Un estado, tres preguntas, **una sola llamada**. Ese es todo el sentido de que el bloque sea la única
forma: el estado se ingiere una vez y las preguntas se evalúan contra él en paralelo. Medido contra
`jev-1.13.0`, un bloque de ocho preguntas fue **7,6× más rápido y usó 4,9× menos tokens de entrada**
que ocho llamadas sueltas, y la latencia se mantuvo plana — unos 0,8 s con una pregunta o con
cuarenta. No hay atajo de una sola pregunta a propósito: los agentes de programación tienden a
hacer una llamada por pregunta, y el lenguaje no los deja.

## Tres verbos, tres distribuciones

| Verbo | Pregunta | Distribución | Qué leés |
|---|---|---|---|
| `whether "…"` | ¿es cierta esta afirmación? | Bernoulli | `probability` |
| `choose "…" between {…} [or nothing]` | ¿cuál de estas? | categórica | `choice`, `probabilities`, `confidence` |
| `rate "…" across […]` | ¿dónde en esta escala ordenada? | ordinal | `score`, `level`, `levels`, `probabilities`, `confidence` |

Las preposiciones son distintas a propósito: `between` significa opciones sin orden, `across`
significa niveles ordenados. Escribir `rate … between` es un error de carga que nombra el arreglo.

El resultado es un mapa plano, id → respuesta, así que una pregunta llamada `usage` no choca con
nada:

```
v.refund.probability    -- 0..1 (un `whether` no tiene confianza aparte)
v.team.choice           -- uno de TUS ids, byte por byte, o nothing
v.team.probabilities    -- {"billing": 0.93, "technical": 0.07, "none": 0.0}
v.team.confidence       -- qué tan concentrada está la distribución
v.anger.score           -- 0..n-1, ponderado por probabilidad: 1,4 es reparto entre el 2º y el 3º
v.anger.level           -- "Frustrated"
```

Las opciones aceptan una lista (el ítem es el id y la descripción) o un mapa (`id: descripción`) —
usá el mapa cuando las descripciones son largas, porque `v.anger.probabilities["Visiblemente
molesto, amenaza con cancelar"]` no es forma de vivir. En `choose`, el modelo lee tus ids de opción,
así que poneles nombres con sentido; los ids de las preguntas no se envían.

## `or nothing`, y la falla que lo hizo existir

Un `choose` **tiene** que elegir. Un mensaje que preguntaba por los horarios de atención, con solo
`billing` y `technical` como opciones, volvió `technical` con **0,69** — una respuesta equivocada que
pasa derecho por una compuerta de 0,5. `or nothing` agrega una opción de escape al cable; cuando
gana, `choice` es `nothing` y la masa queda en `probabilities.none`. En esos mismos casos respondió
`none` con 1,00 y 0,98, y no costó nada en los casos claros (billing se quedó en 0,97).

Escribilo siempre que el estado pueda no encajar en ninguna opción. `rate` no tiene escape — una
escala ordenada no tiene niveles fuera de sí misma —, así que protegé un `rate` con un `whether` que
pregunte si el estado aplica.

## Cuando no está, lo dice

Esta es la parte que decide si podés ponerlo en producción. Sin clave, pasado el presupuesto o tras
una falla de red, cada respuesta vuelve con `available: false`, `confidence: 0` y su valor principal
en `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
```

Una oración inventada se ve en tu salida. Una probabilidad inventada no, y se multiplica hasta
volverse plata — así que este bloque nunca devuelve una. Y fijate qué hace la degradación con el
código que ya escribiste: la confianza 0 está por debajo de cualquier compuerta, así que el ticket se
va solo por la rama humana. Un programa que se salteó la compuerta y compara directo falla fuerte
(`Unsupported operation: nothing > number`) en vez de tomar la rama equivocada en silencio.

## Su propia capacidad

```synsema
require judge      -- clasificar
require llm        -- generar
```

`llm` no otorga `judge` y `judge` no otorga `llm`. Clasificar y generar son derechos distintos, y el
techo que los separa es real: `--cap-set judge` es un programa que puede medir y no puede escribir —
no puede exfiltrar por texto libre y no se lo puede convencer de generar. En lo demás se comporta
como `llm`: se otorga solo en un `run` común, es obligatoria bajo `serve`, se vacía en `sandbox`, se
deniega bajo `--deterministic` (es E/S de red) y está offline dentro de un guest wasm. La clave nunca
entra al programa y el host lo fija el runtime, así que un `.syn` no puede redirigir la llamada.

## El slot paralelo, y un preflight

El juez se configura al lado del LLM, no adentro: `TYPESAFE_API_KEY`, `SYNSEMA_JUDGE_PROVIDER`,
`SYNSEMA_JUDGE_MODEL`, `SYNSEMA_JUDGE_BASE_URL`, `SYNSEMA_JUDGE_TIMEOUT`, `SYNSEMA_JUDGE_BUDGET` —
todas escritas en `.env.example` por `synsema init`. El juez decide, el LLM escribe, y tener los dos
cableados es la configuración normal.

Desde **v0.6.26** el CLI responde qué quedó resuelto y por qué, sin tocar la red:

```
$ synsema judge status
Key         TYPESAFE_API_KEY             ✗ FALTA
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      (sin techo)                  (SYNSEMA_JUDGE_BUDGET, default)
decide      LLM (default)                (SYNSEMA_JUDGE_DECIDE)
```

Sale 0 si está vivo y 1 si está offline, así que `synsema judge status && synsema serve app.syn` es
una compuerta de despliegue. La clave se informa por presencia, nunca por valor.

## El verificador no gasta tokens

`synsema check` conoce los límites de la API y rechaza un bloque que sería un 400 en producción:

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

Y advierte — nunca falla — sobre lo que corre pero engaña:

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

Las rutas entre comillas invertidas — `` `ticket.messages[0].text` ``, el modismo del proveedor para
apuntar a un elemento — se resuelven contra el estado antes de la llamada, así que una ruta que no
existe es una advertencia con el arreglo en vez de una respuesta de 0,31 salida de la nada.

## Tests sin clave, y `decide` de regalo

`SYNSEMA_JUDGE_PROVIDER=mock` cablea un proveedor determinista — `whether` → 0,5, `choose` → la
primera opción, `rate` → el nivel del medio — así que el mismo bloque corre en CI sin red y sin
cuenta. Con una clave real, afirmá el ganador y rangos (`v.team.choice == "billing"`,
`v.refund.probability > 0.9`), nunca números exactos: el mismo pedido dos veces se mueve unas
centésimas (0,72 → 0,69).

Y si tu programa ya usa `decide between […] given x`, la v0.6.26 agrega `SYNSEMA_JUDGE_DECIDE=1`:
cada `decide` del proceso lo responde el juez como un `choose` calibrado — una de tus opciones byte
por byte, sin normalización ni reintento — **sin cambiar una línea del programa**. Es opt-in porque
cambia qué modelo responde, necesita `require judge`, y cae al camino del LLM cuando el juez no está
disponible.

## Dónde seguir

La página del manual es [Judge](https://synsema.dev/es/0.6.x/54-judge), el modelo es
[Jev](https://typesafe.ai/), y la entrada compañera acá es
[System One vs System Two](/es/blog/system-one-vs-system-two) — cuándo una probabilidad es la
respuesta correcta y cuándo seguís queriendo una oración.

Instalalo y probalo en cinco minutos: `curl -fsSL https://synsema.org/install.sh | sh`, y después
`SYNSEMA_JUDGE_PROVIDER=mock synsema run triage.syn`.

