Un agente LLM puede pedir un email de verificación, abrir un buzón y seguir un enlace. Lo difícil empieza cuando el mensaje tarda, llega dos veces o aparece en una corrida anterior. Si el agente solo devuelve “no encontré el correo”, tenemos una automatización que parece inteligente pero no es operable.

En flujos de modelos LLM para QA prefiero tratar cada verificación como un contrato de evidencia. El agente no debe prometer que el producto funciona; debe producir un recibo que explique qué intentó, qué observó y por qué terminó. Ese límite hace que la automatización sea menos mágica, pero mucho más útil para un equipo.

El problema: un agente que solo espera

El patrón inicial suele ser sencillo: crear una cuenta, pulsar “enviar código” y hacer polling hasta que llegue un email. El agente puede decidir cuánto esperar, pero esa decisión no siempre es visible. Tambien puede abrir el mensaje equivocado si el buzón conserva datos de otra prueba.

Hay cuatro causas que conviene distinguir:

  1. El producto todavía no ha enviado el mensaje.
  2. El proveedor aceptó el mensaje, pero el buzón aún no lo muestra.
  3. El mensaje llegó, pero no pertenece a esta corrida.
  4. El mensaje sí pertenece a la corrida, pero el enlace o código es inválido.

Un único timeout mezcla todos esos casos. La consecuencia es costosa: el agente reintenta, consume más llamadas y termina ocultando la señal original. En una búsqueda rápida aparecen términos como temp gamil com o tamp mail com; no son estados del sistema, pero muestran por qué conviene separar la intención de búsqueda de la evidencia técnica.

El contrato de evidencia

Antes de escribir el prompt del agente, definiría un objeto pequeño y estable. No hace falta guardar el cuerpo completo del email ni datos sensibles. Basta con la información que permite reconstruir la decisión:

{ "run_id": "signup-7f31", "inbox_id": "inbox-204", "triggered_at": "2026-11-04T02:00:00Z", "deadline_at": "2026-11-04T02:01:00Z", "observations": [ {"id": "msg-81", "subject": "Verifica tu cuenta", "received_at": "2026-11-04T02:00:21Z"} ], "decision": "accepted", "reason": "message matched run token and contained one verification link" } 

El run_id conecta navegador, backend y buzón. triggered_at evita aceptar un mensaje viejo. deadline_at convierte el polling en un presupuesto finito. La razón debe ser legible por una persona y también útil para una métrica. Si el agente falla, el contrato debe conservar una decisión como timeout, wrong_message, invalid_link o provider_error.

Un coordinador con límites explícitos

La arquitectura puede describirse como una cadena corta:

acción del agente -> evento de aplicación -> buzón aislado -> filtro -> recibo 

El agente inicia la acción y entrega un token de correlación. Un worker observa el buzón durante un intervalo limitado. El filtro valida fecha, asunto, destinatario y token; después extrae solo el dato necesario, como una URL de verificación. El recibo queda fuera de la conversación del modelo y se adjunta al resultado de la prueba.

Un pseudocódigo razonable sería:

deadline = now() + 60s while now() < deadline: for message in inbox.messages(since=triggered_at): if owns_run(message, run_id) and valid_verification(message): return receipt("accepted", message.id) sleep(backoff()) return receipt("timeout", reason="no matching message before deadline") 

El backoff reduce presión sobre el servicio, pero no debe convertir un minuto en una espera indefinida. El modelo puede resumir el recibo y sugerir el siguiente paso; no deberia cambiar el deadline para fabricar un resultado positivo.

Aislar el buzón y la corrida

Un buzón temporal sirve para pruebas cuando tiene un propietario claro y una vida útil corta. Un generador de cuentas de correo temporal puede ser parte de esa preparación, siempre que el equipo documente su retención, sus límites y el tipo de datos permitido.

La regla importante es una cuenta por corrida, o al menos un alias con un token único. Compartir un inbox parece barato, pero crea carreras entre agentes. Un mensaje válido para la prueba A puede ser leído por la prueba B. Ese bug es especialmente difícil de explicar cuando un LLM resume solamente el último estado.

También conviene registrar un hash del destinatario en vez de la dirección completa, eliminar el contenido después de la aserción y conservar únicamente metadatos. Para ver cómo encaja este pensamiento con la operación, los runbooks de SRE para caídas parciales ofrecen una buena referencia de síntomas, decisiones y escalado.

Puntos de comprobación y tradeoffs

Hay una tensión real entre más evidencia y más coste. Guardar todos los mensajes simplifica el diagnóstico, pero aumenta la exposición de datos. Guardar solo accepted protege mejor la privacidad, pero deja al equipo sin contexto cuando aparece un timeout. Mi punto medio es conservar identificadores, marcas de tiempo, motivo de rechazo y una huella del enlace, no el contenido completo.

Para un flujo con varios agentes, añadiría tres métricas: porcentaje de recibos aceptados, tiempo hasta el primer mensaje y distribución de causas de fallo. Si el tiempo sube pero la entrega sigue igual, el problema puede estar en el polling. Si crecen los wrong_message, hay una fuga de aislamiento. Estas señales son más accionables que una métrica única de “agente completó”.

El contrato tambien ayuda a revisar prompts. Un agente que no puede devolver run_id, decisión y motivo no tiene suficiente contexto para operar con seguridad. En cambio, obligarlo a narrar cada consulta completa solo aumenta ruido y coste.

Para evitar que una corrida contamine otra, conviene evitar mezclar corridas en un flujo de email y hacer que el coordinador rechace mensajes sin prueba de pertenencia.

Checklist final

  • ¿Cada acción tiene un run_id y un triggered_at?
  • ¿El buzón está aislado o usa un token único?
  • ¿Existe un deadline que el agente no pueda ampliar?
  • ¿El recibo distingue timeout, mensaje incorrecto y enlace inválido?
  • ¿Se guarda la evidencia mínima necesaria, sin cuerpos completos?
  • ¿Las métricas muestran dónde se atasca el flujo?

Preguntas frecuentes

¿Debe el LLM leer todo el email?

No. Un servicio determinista puede filtrar el mensaje y extraer una URL. El modelo solo debería interpretar el resultado estructurado cuando haya ambigüedad real.

¿Qué hago si el proveedor tarda más que el deadline?

Devuelve timeout, conserva el recibo y permite una investigación separada. Extender automáticamente la espera convierte un fallo observable en una corrida lenta y cara.

¿Por qué no basta con usar el mensaje más reciente?

Porque “más reciente” no significa “de esta corrida”. La pertenencia debe probarse con tiempo, destinatario y correlación.

La idea central es modesta: un agente LLM no necesita controlar todo el sistema de email. Necesita límites claros y evidencia suficiente para que otra persona pueda repetir la decisión. Con ese contrato, la automatización deja de ser una secuencia de esperas y se convierte en una pieza que el equipo puede depurar, medir y mejorar.