rubric: CI para prompts
Trata a los prompts y agentes como código: golden sets, scorers deterministas, un juez LLM calibrado contra labels humanos (κ de Cohen 0.81), y un gate que frena el PR cuando un cambio de prompt empeora la salida sin avisar.
TL;DR
- Una suite de tests para prompts y agentes: golden sets, puntuados en cada corrida, diffeados contra la última corrida verde, frenando el PR ante regresiones.
- Un juez LLM calibrado contra labels humanos (κ de Cohen 0.81, una matriz de confusión juez-vs-humano, chequeos de sesgo por posición/longitud), así el juez es un instrumento medido, no una caja negra.
- Nació de auditar dos productos ya enviados y encontrar el mismo agujero: prompts cambiados a ojo, calidad medida con fe.

Problema
No podés enviar lo que no podés medir.
Una suite de tests te frena un regreso en la lógica. Nada te frena un regreso en la calidad: un system prompt reescrito que baja la precisión sin que te enteres, un cambio de modelo que hunde la fidelidad, un agente que empieza a elegir la tool equivocada. Lo había visto exacto en dos productos enviados: un número de confianza hardcodeado tapando una medición que nunca existió. rubric cierra ese agujero.
Arquitectura
CLI ───── escribe ─────► store libSQL ◄───── lee ───── dashboard Next.js
(bin/rubric.ts) (SQLite / Turso) (server components)
├─ spec golden-set YAML → zod
├─ scorers exact · json-schema · field-accuracy · judge
├─ runner fixture (offline) · exec (cualquier lenguaje, JSON por stdout)
└─ calibration κ de Cohen · matriz de confusión · regresión de sesgo
La CLI es el producto; el dashboard es una lente de solo lectura sobre lo que escribe. Las dos superficies se tocan únicamente en el store.

Decisiones clave
Scorers deterministas primero, el juez solo cuando hace falta
Elegí exact-match, json-schema y field-accuracy con un piso de aprobación como default (sin llamada a modelo, sin flake, sin costo) y dejé al juez LLM solo para lo genuinamente subjetivo. Trade-off: menos cobertura de salidas abiertas por defecto, pero el gate es rápido, gratis y nunca flakea.
Un juez calibrado en lugar de uno en el que confiás
Elegí medir al juez contra labels humanos en vez de asumir que coincide conmigo. Trade-off: un paso de etiquetado (rubric label), pero saca a la luz el sesgo peligroso (la leniencia, el falso-positivo) y convierte "el juez dijo que está bien" en un número que podés defender.

Gatear sobre un diff, no sobre un score
Elegí persistir cada corrida y diffearla contra la última verde para el mismo suite + versión de prompt, saliendo con código distinto de cero al pasar el piso de una métrica. Trade-off: necesitás un baseline antes de que el gate signifique algo, pero muestra causa y efecto: el diff del prompt al lado de los casos que pasaron de pass→fail.
Un juez que no calibraste es apenas una segunda opinión que decidiste creer. El κ y la matriz de confusión son lo que lo vuelven evidencia: el conteo de falsos-positivos es el número que de verdad importa.
por qué la calibración es innegociable
Más difícil de lo esperado
Hacer al juez confiable como para gatear un PR. Un juez LLM sin calibrar es indulgente con confianza: aprueba cosas que un humano reprobaría, y esos falsos-positivos son justo las regresiones que querés cazar. Casi todo el laburo fue la matemática de calibración y el flujo de etiquetado, no el juzgar en sí.
Resultados
- κ 0.81: acuerdo juez-vs-humano, calibrado
- 126/142: pasando en el suite demo, una regresión cazada
- PR en rojo: el gate bloquea el merge, no solo lo loguea

Live + código
El dashboard corre sobre un dataset demo pre-reconciliado, así cada pantalla se explora sin API key. Los scorers deterministas corren totalmente offline; el juez es intercambiable (Groq, u Ollama para algo 100% local).