# Correr el juez local con Laya

> El slot del juez es un protocolo, no un proveedor. Desde la v0.6.27 un checkpoint de Laya en disco — ModernBERT, Apache 2.0 — responde whether, choose y rate sin red, sin secreto y sin costo por token, y el mismo bloque corre sin cambiar una línea.

Published 2026-09-22 · https://synsema.org/es/blog/run-the-judge-locally-with-laya


Un bloque `judge` hace preguntas tipadas sobre un estado y recibe probabilidades calibradas: *¿es
esto cierto?* (0,86), *¿cuál de estas?* (`billing`, y la distribución), *¿dónde en esta escala?*
(entre `upset` y `furious`). Hasta la **v0.6.27** eso quería decir una cuenta en TypeSafe y una
llamada a [Jev](/es/blog/how-to-use-jev-the-judge-block), o el provider `mock`, que te da una forma
sin significado.

Los binarios oficiales traen ahora un tercer backend. Un checkpoint de **Laya** en disco responde
los mismos tres verbos sin red, sin secreto y sin costo por token:

```
SYNSEMA_JUDGE_PROVIDER=laya
SYNSEMA_JUDGE_MODEL=/models/laya      # el directorio del checkpoint, o un org/repo ya en la caché de HF
```

Nada más cambia. El programa no sabe qué backend respondió, porque el slot del juez siempre fue un
**protocolo, no un proveedor**.

## Qué es Laya

[Laya](https://huggingface.co/convaiinnovations/laya) es un modelo de decisión no autorregresivo de
Convai Innovations, **Apache 2.0**: un backbone ModernBERT-large (421M de parámetros) con una cabeza
de decisión, que toma un estado y preguntas tipadas y las responde en una sola pasada. Es lo mismo
que es Jev — un modelo System One — desde otro lado, con pesos que podés poner en un disco tuyo.

Sus tres tipos de pregunta mapean a los tres verbos del bloque:

| Synsema | Laya | Qué leés |
|---|---|---|
| `whether "…"` | `noul` | `probability` |
| `choose "…" between {…} [or nothing]` | `choice` | `choice`, `probabilities`, `confidence` |
| `rate "…" across […]` | `score` | `score`, `level`, `levels`, `probabilities` |

`noul` es el nombre que le da el upstream a una pregunta de sí o no; el lenguaje se queda con
`whether`, porque adoptar el glosario de un proveedor es la forma en que un protocolo se convierte
en una dependencia.

No se descarga nada por vos: el checkpoint pesa ~843 MB y bajarlo es una decisión, como un `.gguf`.
Si no está, el error dice cómo conseguirlo.

## El mismo bloque, sin clave

Éste es el programa de la entrada de Jev — sin cambiar un byte — con dos variables de entorno
distintas:

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

En una laptop sin 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
```

Leé dos veces la cuarta línea desde abajo. El juez eligió `billing` y tiene razón, pero la masa está
repartida — 0,36 de confianza — así que la compuerta que ya habías escrito manda el ticket a una
persona. Ése es todo el motivo para querer una probabilidad en vez de un string, y funciona igual
contra un checkpoint en un directorio tuyo.

Cinco preguntas en dos bloques tardaron **7,9 s** de reloj, carga del modelo incluida, y una sola
pregunta tarda **2,5 s** de punta a punta en un proceso nuevo. Tres corridas dieron números
idénticos. `usage` sigue contando tokens de entrada — sólo que no hay factura detrás.

## `decide` también, y sin nada de red

Si tu programa ya usa `decide between […] given x`, `SYNSEMA_JUDGE_DECIDE=1` hace que cada `decide`
del proceso lo responda el juez como un `choose` calibrado — una de tus opciones byte por byte, sin
normalización y sin reintento. Con `laya` detrás, eso es una decisión que nunca sale de la máquina:

```
$ synsema run --cap-set judge,llm,stdout triage.syn
decide: refund
model: laya:/models/laya
available: true
```

Ningún `net` en el conjunto de capacidades. Nada que denegar, porque no hay nada a quien llamar.

## Qué está verificado y qué no

La secuencia que ve un modelo System One es la capa donde un error **no falla**: los marcadores
corren igual, el modelo simplemente puntúa otra cosa, y la respuesta se ve tan verosímil como una
correcta. Así que es la parte que tiene un test permanente.

```
[CLS] <tipo> question: <instrucciones> [SEP] [MASK] opción0 [MASK] opción1 … [SEP] <estado> [SEP]
```

Los `[MASK]` son **marcadores** — el modelo puntúa cada posición, y ese puntaje es la preferencia por
esa opción — así que el orden y la posición exacta importan. Contra la implementación de referencia
con el tokenizador real: **96 tokens idénticos y marcadores en [12, 22, 32, 39]**. Eso descarta todo
lo que escribimos alrededor del modelo: el tokenizador, el render de las opciones, la serialización
del estado, los recortes de presupuesto.

Lo que **no** está cerrado, y hay que decirlo en voz alta: sobre el caso publicado en el README del
upstream obtenemos el mismo argmax (`billing`) con **confianza 0,864** donde el README reporta 0,94.
Nuestra aritmética es correcta dada la distribución que observamos, y los tokens de entrada son
idénticos, así que la diferencia está en los números que salen del encoder, o el valor publicado no
es comparable (otra versión del checkpoint, u otro checkpoint servido por su Router). Cerrarlo exige
correr la implementación del upstream, que pide PyTorch.

Lo que lleva a lo que hay que hacer antes de mover un umbral de un backend al otro: **medirlo de
nuevo**. Una compuerta en 0,9 es un número ajustado contra un modelo concreto. Jev y Laya son
modelos distintos; la forma de la respuesta es la misma, la calibración no necesariamente.

## Los otros límites, todos juntos

- **512 tokens de contexto** (leídos de la propia config del checkpoint). El prefijo — tipo de
  pregunta, instrucciones, marcadores de opción — se lleva hasta 192, y el estado se trunca a lo que
  queda. Un ticket largo pierde la cola, así que poné primero lo que importa, o juzgá un campo en
  vez del objeto entero.
- **CPU, en segundos, no en milisegundos.** Los ~33 ms de la tarjeta del modelo no son lo que hace
  una laptop. La carga se paga una vez por proceso; bajo `serve` los requests después del primero la
  reusan.
- **Corre en `f32` aunque el checkpoint sea `f16`**, porque en CPU la media precisión se emula y sale
  más lenta. Cuesta ~1,7 GB de RAM con el modelo cargado.
- **La cabeza de acción/escalamiento no está implementada a propósito.** Es una rama paralela que no
  toca los logits — la respuesta es idéntica con o sin ella — y el contrato de `judge` no expone una
  probabilidad de acción, así que sería aritmética que nadie puede leer.

## Qué cambia sobre estar offline

Cada respuesta de `judge` trae `available`, y sin provider vuelve con `available: false`,
`confidence: 0` y su valor principal en `nothing` — nunca un número inventado. Esa degradación es
deliberada y se queda. Lo que cambia en la v0.6.27 es que **estar offline pasa a ser una elección y
no un destino**: un programa con `require judge`, sin secreto y sin red obtiene probabilidades
calibradas reales de un archivo en disco.

`synsema judge status` muestra sólo lo que le aplica al backend elegido — con `laya`, el checkpoint y
nada más:

```
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 vivo, 1 offline, sin tocar la red — así que `synsema judge status && synsema serve app.syn`
sigue siendo una compuerta de despliegue. Y `judge_model()` devuelve `laya:<checkpoint>`, así que dos
corridas con pesos distintos nunca se parecen en un log.

## Cuál usar

| | `typesafe` (Jev) | `laya` | `mock` |
|---|---|---|---|
| Dónde | la API del proveedor | un checkpoint en tu disco | en ningún lado |
| Necesita | una clave | ~843 MB y RAM | nada |
| Responde | juicios reales | juicios reales | una forma sin significado |
| Sirve para | producción a escala, estados largos | aislado de la red, on-prem, CI con números reales, sin proveedor | tests |

`mock` y `laya` responden preguntas distintas. `mock` va en CI, donde un checkpoint no tiene nada que
hacer. `laya` es para cuando querés un juicio real y no podés — o no querés — mandar el estado a
ningún lado.

La página del manual es [Judge](https://synsema.dev/es/0.6.x/54-judge); el bloque en sí, verbo por
verbo, está en [Cómo usar Jev desde Synsema](/es/blog/how-to-use-jev-the-judge-block); y la mitad
generativa del mismo release — un modelo en tu proceso, arquitecturas como archivos — está en
[Un modelo que ya tenés](/es/blog/local-inference-architectures-as-files).

