El resumen en una línea: el problema casi nunca fue el modelo ni el código de captura. Fue todo lo que rodea al modelo — un import incompleto que se tragaba un error, y un system prompt distinto según el canal de entrada que hacía que el agente eligiera la herramienta equivocada. Esta es la bitácora real, sanitizada, de un sistema de archivo de grupos sobre el agente Hermes.

1. El objetivo

Capturar automáticamente todos los mensajes de los grupos de WhatsApp donde el bot es miembro — identificados por grupo, con timestamp, autor y contenido — y después poder consultarlos por DM en lenguaje natural: "resume el canal de hoy", "dame los links", "qué dijo fulano". Suena simple. La mitad lo fue.

2. La parte que sí funcionó al primer intento: la arquitectura del archivo

El conector de WhatsApp usa Baileys (cliente no oficial de WhatsApp Web sobre WebSocket). La estrategia fue un tap: interceptar el evento messages.upsert antes del filtro de allowlist y escribir cada mensaje de grupo a disco.

El pipeline

Estructura en disco — un directorio por grupo, un archivo por día:

~/.hermes/archive/
  ia-latam-canal-general/
    2026-06-17.ndjson  ← una línea JSON por mensaje
    2026-06-17.md      ← legible

3. El primer bug: el fallo silencioso (un clásico)

Primer despliegue: la captura no creaba ningún archivo. Cero. Sin errores en los logs. El culpable es uno de los bugs más frustrantes de la programación moderna:

El bridge importaba de fs solo funciones específicas (mkdirSync, readFileSync, writeFileSync) — pero no appendFileSync ni fs como módulo. El código nuevo usaba fs.mkdirSync y fs.appendFileSyncReferenceError → y el catch(e) {} que envolvía la llamada se tragaba el error en silencio. Nada se escribía, nada se quejaba.

El fix fue mecánico: agregar appendFileSync al import, usar las funciones nombradas en vez del namespace fs., eliminar un import os duplicado (ESM en modo estricto) y usar process.env.HOME en vez de os.homedir(). La lección no lo es:

Un catch vacío alrededor de una escritura es una bomba de tiempo. Si vas a tragar errores "para no romper el flujo principal", al menos haz console.error del error tragado. El silencio te cuesta horas.

4. El problema real: lograr que el agente lea el archivo

La captura quedó perfecta — verificada leyendo el .md directo en el servidor. Pero al preguntarle al agente por WhatsApp "resume el grupo de hoy", no ejecutaba cat para leer el archivo. En su lugar:

Lo desconcertante: por CLI funcionaba perfecto. El mismo agente, el mismo modelo, la misma máquina — hermes chat -q "resume el grupo de hoy" ejecutaba el cat correcto. Por WhatsApp, ignoraba el skill, la memoria, la personalidad y el archivo de identidad.

5. La causa raíz: dos caminos, dos cerebros

El path de WhatsApp tenía un system prompt distinto al de la CLI. Mismo modelo, contexto diferente:

La gran lección de arquitectura: cuando un agente se comporta distinto según el canal de entrada (CLI vs. WhatsApp vs. web), casi siempre hay código que arma el system prompt y la lista de tools por separado para cada canal. Tu skill puede ser perfecto y aun así no cargarse nunca en ese path. La descripción de cada tool es prompt: una frase mal puesta ahí redirige todo el comportamiento del modelo.

6. Doce intentos de fix (la crónica honesta)

Lo que siguió fue ingeniería de verdad: hipótesis, prueba, descarte. Resumen de la cacería:

Patrón doloroso pero universal: casi todos los fix funcionaban en CLI y fallaban en WhatsApp. Eso es una flecha enorme apuntando a "hay dos rutas de código y estás parchando solo una". El bug no estaba donde mirábamos.

7. Lecciones de ingeniería de agentes IA

Más allá de este sistema puntual, esto aplica a cualquiera que esté construyendo agentes con tool-calling — que es media comunidad:

8. Cómo se consulta el archivo hoy

Mientras se estabiliza el camino conversacional, el archivo ya es 100% consultable a mano — y honestamente, para muchos casos esto basta:

# Ver el día actual de un grupo
cat ~/.hermes/archive/<grupo>/$(date -u +%Y-%m-%d).md

# Buscar una palabra en todos los grupos
grep -i "ley" ~/.hermes/archive/*/*.md

# Listar todos los links del día
grep -oE 'https?://[^ ]+' ~/.hermes/archive/*/$(date -u +%Y-%m-%d).md

# Conteo de mensajes por autor
cat ~/.hermes/archive/<grupo>/<fecha>.ndjson | jq -r .senderName | sort | uniq -c | sort -rn

El renderer también puede forzarse a mano para cualquier día sin esperar al timer de las 23:59. El estado conversacional por WhatsApp sigue inestable — y eso es parte de la historia: a veces el camino directo (un grep) entrega más valor que el agente, y está bien admitirlo.

9. Pendientes

"En agentes de IA, el código que escribes casi nunca es el problema. El problema es el contexto que el modelo recibe — y ese contexto se arma en lugares que no estás mirando."

¿Estás construyendo agentes con tool-calling? Comparte tus propios "fallos silenciosos" con la comunidad.

Conversa en la comunidad