SPEC — contrato de la ruta crítica
Este documento describe el contrato observable de Batuta. La implementación de referencia vive en crates/batuta/ y la batería ejecutable en crates/batuta/tests/conformance.rs. Un port es conforme cuando produce los mismos resultados para los mismos casos; “parecido” no es suficiente.
Versión del contrato: 1, congelada el 24 de agosto de 2026.
1. Frontera del sistema
La ruta crítica contiene únicamente trabajo determinista, local y acotado. El binario de referencia no abre conexiones de red y no llama a un LLM.
| Ruta crítica | Ruta fría |
|---|---|
route, log, index | report, summary, find, conflicts |
| sin red | la integración externa pertenece a un cliente separado |
| sin LLM | un juez puede usar un modelo fuera de la ruta crítica |
| objetivo de 100 ms; techo de 300 ms | sin presupuesto por turno |
Descomponer tareas, reescribir prompts y formatear entregas son capacidades candidatas que deben medirse como cualquier otra skill; no se incorporan silenciosamente al enrutador.
2. Tokenización
Indexación y consulta utilizan el mismo camino:
- convertir a minúsculas y eliminar diacríticos;
- separar por caracteres no alfanuméricos;
- descartar tokens de menos de dos caracteres;
- descartar una lista fija de palabras vacías en portugués e inglés;
- descartar tokens formados solo por dígitos;
- para tokens con más de cinco caracteres, emitir también el prefijo de cinco.
El prefijo sustituye a un stemmer pesado. Una palabra de cinco caracteres o menos no obtiene prefijo, para evitar que términos cortos distintos se vuelvan indistinguibles.
3. Índice
Cada SKILL.md es un documento. La bolsa de términos pondera nombre y carpeta por tres, descripción por dos y hasta 400 términos del cuerpo por uno. El índice local se guarda en ~/.batuta/index.txt como líneas simples; la consulta abre únicamente las listas de postings necesarias.
El formato empieza con una cabecera versionada y contiene metadatos de skills (S) y postings por término (P). Cambiar el formato exige cambiar su versión y los casos de conformidad.
4. BM25 y silencio
La puntuación de referencia usa:
K1 = 1.5
B = 0.75
NOISE_CUTOFF = 2.0
MAX_SUGGESTIONS = 3
TOP_FRACTION = 0.55
Un término repetido en la consulta cuenta una vez. El orden es puntuación descendente y, en empate, índice ascendente. Una coincidencia por debajo del corte no genera sugerencia: el silencio forma parte del contrato, porque un falso positivo añade contexto y coste sin evidencia de utilidad.
5. Eventos locales
Los eventos se escriben como JSON Lines en ~/.batuta/events.jsonl. Hay tres señales que comparten un identificador de turno:
route: qué propuso el enrutador y cuánto tardó;activation: qué skill se utilizó realmente y quién la activó;outcome: éxito, reprompts, errores, reintentos, turnos, tokens y coste.
El evento local puede contener un hash salado del prompt, longitud y número de términos. No contiene un destino de red y nunca es el cuerpo aceptado por la ingestión pública.
6. Privacidad y agregado
El texto del prompt no se persiste. El hash es sha256(sal_local || prompt) truncado; la sal se crea en la máquina, se guarda con permisos restringidos y no se sube.
El único formato de subida es batuta.daily_summary.v1, un agregado por skill y día. Excluye prompt, hash de prompt, identificador de turno, ruta de archivo, nombre de usuario, IP y geolocalización. La API de ingestión rechaza las claves prompt, prompt_hash, turn y text en cualquier profundidad antes de validar el resto del schema.
El envío viene desactivado. Estos comandos permiten inspeccionar la frontera antes de habilitarla:
batuta privacy
batuta summary
batuta config upload yes
7. Grupo de control
La referencia selecciona un holdout determinista a partir de la sal local y el prompt. La configuración inicial es del 5%. La misma pregunta en la misma instalación cae siempre en el mismo brazo, para impedir reintentos hasta obtener una sugerencia.
El holdout se declara en la primera ejecución, viaja en el agregado y puede modificarse o desactivarse con batuta config holdout_pct N. Sin brazo de control, el resultado describe correlación, no lift causal.
8. Métricas
El informe local deriva, como mínimo:
- tasa de sugerencia y silencio;
- activaciones divididas por rutas;
- skills fantasma: cinco rutas o más y ninguna activación;
- resultados exitosos con su denominador;
- coste total dividido por tareas completadas;
- mediana de turnos hasta el final;
- comparación entre brazo con sugerencia y holdout.
Una tasa sin n no es publicable. El ranking público añade un mínimo de instalaciones distintas para evitar presentar una sola máquina como resultado del ecosistema.
9. Cadena de publicación
El cuerpo de cada registro se serializa como JSON canónico: claves en orden de punto de código y ningún espacio añadido. El siguiente hash se calcula sobre {body, previous_hash}. Tres implementaciones —Rust, el script de Node y el portal— deben producir el mismo byte.
El primer enlace usa 64 ceros como génesis. El historial Git y un eventual sello externo añaden observabilidad, pero ninguno corrige una medición mal diseñada.
10. Arena
Una tarea pública entra con estado de cribado y sin enunciado canónico. Nunca se ejecuta tal como fue enviada. Antes de la batería debe tener enunciado, criterio de aceptación, categoría y complejidad escritos por quien mantiene la regla.
La frontera rechaza cuerpos excesivos, código ejecutable, esquemas de URL activos, descargas de binarios y comandos destructivos. El voto solo ordena la cola.
11. Conformidad y medidas de referencia
En la línea base auditada 9fa7471 del 24 de agosto de 2026, la batería contenía 15 pruebas y las 15 pasaban. El mismo registro midió 91 ms para indexar 506 skills y 136 ms para 50 rutas incluyendo el arranque del proceso. Estas cifras describen esa máquina y ese commit; no son una garantía universal.
La verificación del crate usa un único hilo porque los fixtures actuales comparten un directorio temporal:
cd crates/batuta
cargo test -- --test-threads=1
cargo clippy --all-targets -- -D warnings
cargo fmt --check
12. Cambios de contrato
Una modificación de tokenización, corte, formato de índice, evento o agregado puede romper comparabilidad histórica. Debe añadir primero un caso de conformidad que falle, documentar la versión afectada y publicar la migración. El contrato cambia de forma explícita o no cambia.