El 9 de agosto de 2026, mi sistema de agentes se corrompió a sí mismo. Dos veces. En el mismo día.
No fue un fallo de disco. No fue un bug en SQLite. No fue un corte de luz en la Raspberry Pi. Fue mi propio agente de auto-reparación — el que yo mismo diseñé para detectar y arreglar errores — reparando la base de datos mientras otro proceso escribía en ella. El resultado: una corrupción peor que la original.
Esto es lo que pasó, por qué ningún tutorial te avisa, y cómo lo arreglé de verdad.
El síntoma: errores fantasma en FTS
Todo empezó con esto en los logs:
FTS-corruption detected in messages_fts
database disk image is malformed
session storage could not be written
Hermes usa SQLite con FTS5 para la búsqueda de texto completo en el historial de sesiones. El índice FTS es frágil por diseño: una escritura interrumpida, un apagado inesperado, o simplemente el desgaste de una SD card en una Raspberry Pi, y el índice se desalinea. Los datos siguen ahí. La búsqueda deja de funcionar.
No es grave. Se repara con un INSERT INTO messages_fts(messages_fts) VALUES('rebuild') y listo. El problema no fue la corrupción. El problema fue quién la reparó.
El mecanismo asesino: reparación en caliente
Mi cron de Self-Healing (el que corre cada 30 minutos con un LLM analizando logs) detectó los errores FTS. Su instrucción: "si ves corrupción en la base de datos, repárala".
Y la reparó. Literalmente:
- Hizo
cp state.db state.db.bak - Ejecutó
INSERT INTO messages_fts(messages_fts) VALUES('rebuild') - Verificó que el error desapareció del log
- Reportó "✅ repaired"
Lo que el LLM no sabía — porque nadie se lo dijo — es que el gateway de Hermes estaba escribiendo sesiones en state.db en ese mismo instante. La Raspberry Pi estaba procesando una conversación mientras el agente tocaba la base de datos. El cp no es atómico en SQLite. El INSERT compite con las escrituras del gateway. El resultado: una base de datos medio copiada, medio reparada, con el índice FTS apuntando a filas que ya no existen.
La corrupción original era leve. La reparación la dejó inservible.
Y pasó dos veces
Lo peor: el Self-Healing volvió a detectar errores FTS 4 horas después. Y aplicó exactamente la misma "reparación". Mismo procedimiento. Misma corrupción nueva. Mismo log diciendo "✅ repaired".
Esto es un bucle de auto-destrucción perfecto:
FTS corrupto → LLM repara en caliente → gateway escribe → FTS más corrupto
→ LLM lo detecta otra vez → repara otra vez → más corrupción
→ repite hasta que la BD es irrecuperable
Menos mal que los backups diarios existen. Si este bucle hubiera durado 24 horas sin intervención, habría perdido todas las sesiones del día.
La causa raíz: tres fallos encadenados
1. Reparación sin locking. SQLite soporta concurrencia, pero un cp del archivo entero + INSERT no es seguro si otro proceso tiene una transacción abierta. El Self-Healing no chequeaba si el gateway estaba activo antes de tocar la base de datos.
2. LLM con permisos de cirujano. Darle a un modelo de lenguaje acceso de escritura directo sobre state.db es como darle un bisturí a un interno y decirle "si ves algo raro, opera". El prompt del Self-Healing decía "repara errores". El LLM obedeció. El prompt no decía "no toques la base de datos si otro proceso la está usando".
3. Sin guardián externo. El que repara no puede ser el que vigila. Si el mismo agente que rompe la base de datos es el que decide si está rota, el bucle está garantizado. Necesitas un watchdog externo, determinista, que no use un LLM para decidir si intervenir.
El arreglo definitivo (tres capas)
Capa 1: Watchdog externo por crontab del sistema
Fuera de Hermes. Fuera del alcance del LLM. Un script bash que corre cada hora desde el crontab de Linux:
# /etc/crontab o crontab -e
0 * * * * /home/luigy/.hermes/scripts/state-db-watchdog.sh
Este script es determinista. No usa IA. Si detecta corrupción FTS, repara con locking explícito y vuelca un log. Si detecta corrupción estructural (malformed), para todo y notifica. Es el único proceso autorizado para tocar state.db.
Capa 2: Prompt blindado para el Self-Healing
El cron del LLM sigue existiendo — es útil para otros errores. Pero su prompt ahora incluye esto:
PROHIBIDO tocar state.db, sqlite3, o messages_fts.
Si ves errores de base de datos, verifica que el watchdog
externo (state-db-watchdog.sh) está corriendo.
No hagas NADA sobre la base de datos. Repórtalo y punto.
El LLM ya no tiene permisos de escritura sobre la base de datos. Puede leer logs. Puede alertar. No puede reparar. Esa separación de responsabilidades es lo que faltaba.
Capa 3: Recuperación para casos graves
Si la corrupción es estructural (no solo FTS), el watchdog intenta:
hermes sessions recover --allow-partial
Y si eso falla, hace swap con el backup más reciente. La regla: si la reparación in-place no funciona al primer intento, no reintentar. Swap.
Lo que aprendí (y deberías aplicar hoy)
Un LLM no debería tener acceso de escritura a tu base de datos de producción. Nunca. Suena obvio. Pero cuando tienes 61 crons y uno de ellos es "arregla lo que veas roto", la línea entre leer logs y escribir en la base de datos se vuelve peligrosamente fina. Si tu agente de auto-reparación puede ejecutar comandos SQL, asume que algún día ejecutará el comando equivocado en el momento equivocado.
Separa el que diagnostica del que repara. El LLM puede leer logs, detectar patrones y sugerir acciones. Pero la ejecución de reparaciones sobre infraestructura crítica (base de datos, sistema de archivos, configuración de red) debe ser determinista y externa. Un script que sabes exactamente lo que hace. Sin ambigüedad. Sin "probablemente".
Un fix automático sin verificación de concurrencia no es un fix. Es una apuesta. Cualquier script que modifique un recurso compartido (archivo, base de datos, socket) debe verificar antes que ningún otro proceso lo está usando. lsof, fuser, un lockfile, lo que sea. Pero verifícalo.
El outer loop del outer loop. La semana pasada escribí sobre bugs que se reparan solos y no dejan traz a. Esta semana aprendí que hay un nivel más arriba: el reparador automático puede ser el causante del daño que repara. Si no tienes a alguien — o algo — vigilando al vigilante, tu sistema puede estar cavando su propia tumba mientras los health checks marcan verde.
El estado actual
El watchdog externo está corriendo. El prompt del Self-Healing está blindado. La base de datos está limpia. Los backups pasaron de ser "por si acaso" a ser "gracias a Dios que existen".
Pero no me engaño: este bug no fue un accidente. Fue una consecuencia inevitable de un diseño donde el reparador y el reparado comparten el mismo espacio sin barreras. Si estás construyendo agentes autónomos, pregúntate: ¿quién vigila a tu vigilante? Si la respuesta es "nadie", tienes una bomba de tiempo.