Un agente LLM puede completar una tarea de navegador, pedir una verificación por email y continuar cuando llega el mensaje. En una demo, basta con esperar. En un workflow real, esa espera tiene un coste, una probabilidad de fallo y un efecto directo en la capacidad del sistema.
El error habitual es tratar el email como una llamada sin límite: el agente pregunta cada pocos segundos, interpreta cualquier mensaje reciente como propio y sigue reintentando cuando la bandeja tarda. El resultado es caro de depurar y facil de duplicar.
La solución que prefiero es diseñar un presupuesto de latencia antes de escribir el prompt del agente. El presupuesto dice cuánto puede esperar, qué señales puede aceptar y qué debe devolver cuando la evidencia no aparece. Es una frontera de arquitectura, no solo un timeout.
El problema: un agente no espera gratis
Imagina este flujo:
- El agente crea un registro de prueba con un
run_id. - Solicita una acción que dispara un email.
- Consulta una bandeja aislada y busca un mensaje asociado al
run_id. - Extrae un enlace o código y completa la tarea.
En palabras: aplicación → evento de email → bandeja de prueba → evidencia → siguiente acción. Cada flecha puede introducir retraso. Si el agente solo recibe “todavía no”, no sabe si debe esperar, repetir la solicitud o declarar un fallo.
Una bandeja temporal o un temp mail so puede ser útil para aislar cuentas de prueba, pero no convierte la entrega en una señal instantánea ni prueba que el mensaje pertenece al workflow actual. Esa propiedad debe venir del contrato de la prueba.
Diseñar el presupuesto de latencia
Primero separo tres tiempos:
- Tiempo de propagación: cuánto tarda el proveedor en aceptar y entregar el mensaje.
- Tiempo de observación: cuánto tarda el worker en consultar o recibir el evento.
- Tiempo de acción: cuánto necesita el agente para procesar la evidencia y continuar.
El presupuesto total es la suma de los tres, más un margen pequeño. Por ejemplo, un flujo de staging puede reservar 90 segundos: 45 para propagación, 30 para observación y 15 para acción. No es una cifra universal; es una hipótesis que se revisa con datos del propio sistema.
Con ese límite, el agente tiene estados concretos: waiting, received, invalid, expired y failed. No conviene devolver una respuesta ambigua como “intenta de nuevo” cuando el deadline ya venció. El modelo puede elegir mal y reiniciar una acción que ya funcionó.
Para una consulta con polling, el intervalo tambien forma parte del diseño. Un intervalo de dos segundos durante 90 segundos son hasta 45 lecturas por ejecución. Con cien workers, la cola puede crecer rapido. Un backoff acotado reduce presión, pero no debe hacer que la última lectura ocurra después del deadline.
deadline = monotonic() + 90
delay = 2
while monotonic() < deadline:
message = inbox.find(run_id=run_id)
if message is not None:
return validate_message(message)
sleep(min(delay, max(0, deadline - monotonic())))
delay = min(delay * 2, 10)
raise EmailEvidenceExpired(run_id)
El código no decide si el mensaje es correcto. Solo limita la espera. La validación debe comprobar remitente esperado, destinatario del run, asunto o token de correlación y que el mensaje no haya sido consumido por otra ejecución.
Separar polling, eventos y evidencia
El polling es fácil de introducir y dificil de observar. Para cada lectura conviene registrar run_id, número de intento, instante, latencia acumulada y razón de descarte. No hace falta guardar el contenido completo del email; una huella del mensaje y los campos mínimos suelen bastar.
Cuando existe un webhook o una cola, el agente puede esperar un evento en vez de golpear la bandeja. Aun así, mantengo un deadline de seguridad. Un evento tardío no debe reabrir una tarea marcada como expirada.
También separo la evidencia del razonamiento del modelo. El servicio determina “mensaje válido para run X”; el LLM recibe un resultado pequeño y estructurado. Esto reduce alucinaciones y hace más simple aislar emails de agentes LLM.
Un contrato mínimo puede verse así:
{
"run_id": "run_482",
"status": "received",
"evidence_id": "msg_91",
"action": "continue",
"observed_at": "2026-11-04T14:20:00Z"
}
El campo action es deliberadamente limitado. El agente no debería inventar un siguiente paso cuando el estado es expired.
Implementar reintentos sin duplicar acciones
Hay dos operaciones distintas: pedir que la aplicación envíe el email y buscar la evidencia. Repetir la segunda suele ser seguro; repetir la primera puede crear usuarios, tokens o mensajes duplicados.
Por eso cada solicitud de envío necesita una clave idempotente derivada del run_id y del paso, no del número de polling. Si el worker se reinicia, recupera la misma operación. Si el negocio necesita un nuevo intento, debe crear un nuevo paso explícito y conservar la relación con el anterior.
Antes de aceptar un mensaje, la prueba debe verificar que la dirección, el run_id y el tipo de evento coinciden. Si la búsqueda encuentra un mensaje de una ejecución vieja, se descarta y se registra. Este detalle permite validar correos después de un rollback sin confundir una evidencia antigua con una recuperación correcta.
Puntos de control antes de ponerlo en CI
Antes de ejecutar el agente en paralelo, reviso cinco preguntas:
- ¿Cada worker tiene una bandeja o un identificador de correlación propio?
- ¿El deadline aparece en logs y en la respuesta estructurada?
- ¿El fallo de entrega es distinto del fallo de validación?
- ¿Un reintento de red puede repetir una acción de negocio?
- ¿La evidencia tiene retención suficiente para investigar, sin guardar datos innecesarios?
Tambien pruebo el caso menos agradable: el email llega justo después del deadline. La ejecución debe quedar como expirada, y el evento tardío debe ser descartable. Si la señal es util para diagnostico, se guarda su metadata, no se revive automáticamente el workflow.
Las búsquedas humanas introducen ruido adicional. Alguien puede escribir tamp mail com o tempail mientras intenta encontrar una herramienta, pero esas variantes no deben convertirse en lógica de correlación ni en una decisión del agente. El sistema necesita identificadores estables, no aproximaciones de texto.
Preguntas frecuentes
¿Es mejor aumentar el timeout cuando hay fallos?
No siempre. Primero hay que separar entrega lenta, polling ineficiente y mensajes inválidos. Aumentar el límite puede ocultar una regresión y retener workers durante demasiado tiempo. El timeout debe basarse en una distribución observada y un objetivo de servicio.
¿Puede el LLM decidir cuándo dejar de esperar?
Puede ayudar a explicar el estado, pero el deadline debe ser determinista y estar fuera del modelo. Un controlador o una cola aplica el límite; el LLM interpreta el resultado y elige entre las acciones permitidas.
¿Qué métrica seguiría primero?
Empezaría por latencia de evidencia por etapa: solicitud aceptada, mensaje recibido, mensaje validado y acción completada. Así se ve si el problema está en el email, en la observación o en el razonamiento posterior.
Un agente fiable no es el que espera más. Es el que sabe qué evidencia necesita, cuánto puede esperar y cómo terminar de forma explicable cuando esa evidencia no llega.
Top comments (0)