No escribo esto desde la teoría. Es el registro literal de lo que pasó hoy, 4 de agosto, en la Raspberry Pi 5 donde corren mis 58 agentes. Cinco fallos distintos, todos reales, todos en el log de incidentes con hora y causa raíz. Cuatro los arregló el sistema solo. El quinto me hizo abrir la terminal.
Lo publico porque cuando leo artículos sobre agentes IA casi siempre son escenarios hipotéticos: "imagina que tu agente falla". No hace falta imaginar. Esto es lo que falla de verdad.
Fallo 1: la sesión de Telegram llegó a 388.000 tokens y se quedó congelada
A media mañana la compresión de contexto dejó de funcionar. El mecanismo que resume conversaciones largas devolvía un error 400 con mensaje "Unknown Model", y la sesión de Telegram seguía creciendo sin que nadie la tocara. Cuando lo miré, estaba en 388.000 tokens. Cada mensaje nuevo costaba más y tardaba más.
La causa no estaba en la compresión. El fallback, cuando el compresor fallaba, caía al modelo principal. Ese modelo tiene un provider con etiqueta "custom" y sin base_url correcto. Es decir: el plan B apuntaba a algo que no podía funcionar. Nadie lo notó hasta que el plan A falló.
El arreglo fue una línea de configuración: el fallback de compresión ahora apunta a deepseek-v4-flash con su provider completo. Verificado con una llamada real a la API antes de cerrar el incidente.
Fallo 2: un separador mal escrito bloqueó toda la memoria
Este me gustó por lo absurdo. El archivo MEMORY.md usa separadores para partir entradas. En algún momento aparecieron separadores con doble salto de línea (\n\n§) en lugar de uno solo (\n§). La validación de formato rechazaba cada escritura. Resultado: el tool de memoria no podía guardar nada. Ningún agente aprendía nada nuevo.
El sistema normalizó los separadores, guardó backup del archivo corrupto, y después parcheó el script que genera las entradas para que no vuelva a emitir el formato malo. La memoria pasó de 2.238 a 2.124 caracteres y el check de drift quedó en falso.
Detalle que me hizo pensar: el fallo no estaba en el dato, estaba en el formato del dato. El sistema podía leer la memoria perfectamente. Lo que no podía era escribirla.
Fallo 3: el digest se moría a los 10 minutos porque el modelo colgaba 15
Mi digest del mediodía, el resumen automático que junta lo que pasó en el sistema, fallaba con TimeoutError a los 600 segundos. Todos los días, misma hora.
La investigación mostró el choque de dos timers: deepseek-v4-flash se quedaba colgado unos 15 minutos en llamadas non-streaming dentro de crons, y el watchdog mataba el job a los 10. El modelo iba a terminar, pero el supervisor llegaba primero.
La solución no fue subir el timeout. Fue mover los tres digest jobs (mañana, mediodía y noche) a deepseek-v4-pro, que no cuelga en llamadas de cron. Probado funcionando a las 14:31 del mismo día.
Fallo 4: USER.md se pasó del límite y rompió el contrato de salud
Tengo un contrato de memoria que falla si los archivos de identidad superan su tamaño. USER.md llegó a 1.390 caracteres contra un límite de 1.306. Un 104%. Poco, pero suficiente para marcar FAIL y bloquear verificaciones.
El arreglo fue consolidar. Los detalles técnicos de X ya vivían en otro lado (el skill loop-engine), así que la entrada de X quedó con lo esencial. Las entradas duplicadas de suscripciones se unificaron. De 1.390 a 1.193 caracteres, 91% del límite, contrato verde de nuevo. Backup guardado por si había que volver.
Lección: los límites de tamaño no son decorativos. Si los ponés, hacelos cumplir, porque el día que no se cumplen todo lo que depende de ese check queda en rojo.
Fallo 5: la clave de configuración que nadie leía
vision_analyze estuvo roto 14 minutos. Entre las 16:23 y las 16:37, cualquier análisis de imagen fallaba. El modelo primario (glm-4.6v-flash) devolvía 429 por rate limit, y el fallback caía a un modelo que rechaza imágenes con error 400.
Ahora la parte buena. La configuración tenía una clave llamada fallback_models. Sonaba exactamente como lo que hacía falta. El problema: nadie la lee. La clave real es fallback_chain. Alguien (yo, probablemente) configuró la que no existe y el sistema usaba una lista vacía.
El arreglo: cambiar la clave a fallback_chain con dos modelos gratuitos de OpenRouter, y verificar con una llamada real que devuelve 200.
Si una opción de configuración no está documentada ni la lee el código, no existe. No importa cuántas veces la escribas.
El patrón que se repite en los cinco
Miro el log del día y los cinco fallos tienen algo en común: ninguno estaba en el camino principal. Todos estaban en los fallbacks, los límites, los formatos, las claves secundarias. El sistema funciona bien cuando funciona. Rompe cuando tiene que usar el plan B, y el plan B estaba roto sin que nadie lo supiera.
- El fallback de compresión apuntaba a un modelo sin base_url.
- El fallback de visión apuntaba a nada, por una clave mal escrita.
- El watchdog y el modelo tenían expectativas distintas de tiempo.
- Dos validadores de formato peleaban por un salto de línea.
Si estás armando agentes, mi consejo concreto: testeá los fallbacks con la misma seriedad que el camino principal. Un fallback que nunca se ejecutó no es un fallback, es una promesa.
Lo que el sistema hace con cada fallo
Cada incidente queda registrado en un ledger JSONL con timestamp, job, tipo y detalle. Los de tipo fix llevan la causa raíz y la verificación. Esto no es cosmético: el digest de la noche lee ese ledger y lo convierte en conocimiento, y el cron de auto-reparación lo usa para no repetir arreglos que ya hizo.
Hoy fueron cinco entradas. Algunas semanas son veinte. El punto no es que no falle. El punto es que cada fallo deja una línea de texto que la próxima vez alguien (humano o agente) puede leer antes de romperse la cabeza contra el mismo problema.