Sync Cascade: ediciones puntuales en lugar de body completo (P5 · s83)
Tercera palanca del plan de optimización de coste del Bibliotecario (sesión s83, PR #84, 2026-05-25). Cambia la estrategia de wiki_propose_mirror_update: en vez de pedirle a Haiku que devuelva el documento espejo completo, ahora devuelve ediciones puntuales {old_str, new_str} que se aplican mediante search/replace seguro.
Problema que resuelve
wikiProposeMirrorUpdate usaba max_tokens: 24000 y pedía a Haiku el body completo del espejo. Dos consecuencias graves:
- Coste desproporcionado: el output de tokens es el componente más caro de Haiku. El pico del 24-05-2026 ($4,03/día, de los cuales $2,96 solo en output) fue causado por el refactor de onboardings, que disparó cascadas a 3 documentos de ~42 KB cada uno, regenerados enteros.
- Riesgo destructivo (footgun s80): el flujo de PR #66/#67 (+20 / -600 LOC) vivía aquí. Se parcheó subiendo límites, pero la raíz estructural permanecía: “devuélveme el documento entero”. Un modelo truncado o confundido podía borrar secciones enteras sin aviso.
Solución implementada
Nuevo protocolo con Haiku
El system prompt cambia de “devuelve proposed_body completo” a “devuelve edits: [{old_str, new_str}]”:
"edits": [
{ "old_str": "<fragmento EXACTO del espejo>", "new_str": "<texto de reemplazo>" }
]
Reglas impuestas a Haiku en el prompt:
old_strdebe ser copia exacta y literal del espejo (mismos espacios, acentos, mayúsculas).- Para añadir contenido nuevo: usar una línea ancla existente como
old_stry repetirla al inicio denew_str. - Solo devolver fragmentos que cambian, nunca el documento completo.
max_tokensbajado de 24 000 → 4 000 (solo fragmentos, no documentos enteros).
Función applyMirrorEdits
Nueva función de aplicación segura (ver entity--biblioteca--function--apply-mirror-edits):
applyMirrorEdits(body: string, edits: MirrorEdit[])
→ { result: string; applied: number; failed: Array<{excerpt, reason}> }
Comportamiento ante casos problemáticos:
| Caso | Acción | Razón |
|---|---|---|
old_str vacío | Omitir | empty_old_str |
old_str === new_str | Omitir | noop |
old_str no encontrado | Omitir | not_found |
old_str aparece >1 vez | Omitir | ambiguous |
| Aplicación correcta | Aplicar | — |
Lógica de seguridad post-apply
Tras aplicar ediciones, wikiProposeMirrorUpdate aplica salvaguardas adicionales:
- 0 ediciones aplicadas →
should_propose=falsecon mensaje[safety]. Mejor revisión manual que aplicar a ciegas. should_propose=trueconedits=[]→should_propose=falsecon mensaje[safety].- Red heredada de s80 (aún activa): si el resultado encoge >50% del mirror_body →
should_propose=false.
Respuesta enriquecida
El caller (sync_cascade.mjs) sigue recibiendo proposed_body construido. Se añaden dos campos de observabilidad:
edits_applied: número de ediciones aplicadas con éxito.edits_failed: lista de{excerpt, reason}para trazabilidad.
Validación
- Smoke test de
applyMirrorEdits: 5/5 casos- Reemplazo normal ✅
- Ancla inexistente (no-destructiva) ✅
- Ancla ambigua omitida ✅
- Inserción vía ancla ✅
- Preserva el resto del documento ✅
tsc --noEmitsin errores enwiki.ts✅
Impacto esperado en coste
| Métrica | Antes | Después |
|---|---|---|
max_tokens Haiku | 24 000 | 4 000 |
| Output típico | Body completo (~14K tokens en onboardings grandes) | Solo fragmentos (~500–1500 tokens) |
| Riesgo PR destructivo | Presente (body completo regenerado) | Eliminado por construcción |
Véase también
- [[entity—biblioteca—function—apply-mirror-edits]]