Cuando un agente LLM manda un correo para pedir aprobacion, el fallo serio rara vez esta en el texto del mensaje. Casi siempre aparece un paso despues: el equipo aprueba una version del plan, pero el worker ejecuta otra. Ese desajuste parece pequeno al principio, aunque termina rompiendo auditoria, soporte y confianza operativa.
En equipos que ya automatizan contenido, reportes o acciones internas, yo prefiero tratar el email como una frontera de control, no como un canal donde el agente sigue pensando. El punto es congelar un artefacto antes de pedir respuesta humana. Si luego el job se reanuda, debe leer ese artefacto y no rearmar el mundo desde cero. Suena obvio, pero no siempre se hace, y ahi empiesa el ruido.
El problema no es el email, es el cambio de artefacto
Lo dibujo asi, en palabras:
- El agente propone una accion.
- Genera un plan estable.
- El sistema manda un resumen por email.
- Una persona responde.
- Un ejecutor retoma el trabajo.
La grieta aparece entre el paso 2 y el 5. Si el plan no esta congelado, la respuesta humana termina pegada a un resumen narrativo, no a una pieza verificable. En ese punto el inbox se vuelve una pseudo base de datos. Funciona un rato, luego deja rastros confusos y cuesta explicar que se aprobo de verda.
En flujos de QA lo vi incluso con pruebas rapidas sobre fake e mail com o temp org mail: el email "llego bien", pero nadie podia probar que el ejecutor uso el mismo payload aprobado. El correo fue correcto; el sistema alrededor, no tanto.
El plan congelado como frontera entre pensar y ejecutar
Mi preferencia es separar tres capas con limites bastate estrictos:
- Planeacion: el agente decide el que y produce
plan.json. - Aprobacion: el humano revisa un resumen corto con ids claros.
- Ejecucion: un script retoma usando artefactos congelados.
Este patron se parece a lo que me gusta en contratos de inbox para agentes LLM: menos interpretacion durante el polling, mas evidencia estable entre pasos. Y aunque el caso de uso sea distinto, tambien conecta con una idea muy util de auditar emails de upgrade en SaaS: si no puedes unir evento, contenido y resultado final, la revision humana se queda corta.
El tradeoff es claro. Guardar un plan congelado mete un paso extra y algunos campos mas. A cambio, reduces reintentos ambiguos, corridas que mezclan contexto nuevo y respuestas humanas que parecen validas pero llegaron tarde.
Campos minimos que yo guardaria
No hace falta una tonelada de metadatos. Yo empezaria con esto:
{
"run_id": "20260809T142220Z-lucasg88",
"plan_hash": "b1c4e920",
"decision_scope": "publish_post",
"reply_token": "approve_71aa",
"expires_at": "2026-08-09T15:00:00Z",
"executor": "publish_devto.sh"
}
Cada campo responde una duda operativa concreta:
-
run_id: une logs, archivos y respuesta humana. -
plan_hash: evita que la aprobacion se aplique a una version distinta. -
decision_scope: dice exactamente que permiso se esta dando. -
reply_token: bloquea respuestas cruzadas o reenvios viejos. -
expires_at: cierra la ventana de aprobacion cuando ya no tiene sentido. -
executor: deja claro que script hara el paso final.
Con eso basta para mantener la arquitectura legible. Si luego quieres adjuntar resumenes, diffs o checklist, mejor hacerlo como lectura secundaria. El correo principal debe seguir siendo compacto, porque un operador cansado no va a leer cinco parrafos para aprobar una accion simple.
Donde poner la logica de reanudacion
Yo no meteria la logica de reanudacion en el agente escritor. La dejaria en el ejecutor o en un worker muy pequeño. Esa separacion importa por dos razones:
- El agente puede variar su razonamiento entre corridas.
- El ejecutor debe ser aburrido, estable y facil de auditar.
Un pseudocodigo simple seria este:
type ApprovalEnvelope = {
runId: string;
planHash: string;
replyToken: string;
decisionScope: "publish_post";
};
async function resumeAfterApproval(env: ApprovalEnvelope, reply: string) {
const verdict = parseReply(reply, env.replyToken);
if (verdict !== "approve") return;
const plan = await loadFrozenPlan(env.runId, env.planHash);
await executePlan(plan);
}
Fijate en la idea central: el ejecutor no "entiende todo el email". Solo valida un sobre corto y luego carga el artefacto congelado. Esa decision baja bastante la superficie de errores, sobre todo si el prompt del agente cambia entre una corrida y la siguiente.
Tambien ayuda cuando hay incidencias. Si alguien pregunta por que una publicacion salio con cierto titulo o cierto tag, la respuesta no depende de reconstruir el contexto del LLM a posteriori. Solo necesitas el run folder, el hash y el resultado final. Eso hace la depuracion mucho mas tranquila, la verdad.
Checklist para una implementacion tranquila
Si tuviera una semana para endurecer este flujo, haria esto en este orden:
- Escribir
plan.jsonantes de mandar el email. - Calcular y mostrar
plan_hashen el asunto o cuerpo. - Limitar la aprobacion a un
decision_scopeexacto. - Reanudar solo con
reply_tokenvalido y no expirado. - Guardar un
publish-result.jsono veredicto equivalente. - Evitar que el ejecutor regenere contenido durante publish.
Ese ultimo punto me parece clave. Cuando el ejecutor tambien "mejora" el texto, reordena tags o toca el cuerpo, se pierde la frontera entre planeacion y entrega. El sistema sigue funcionando, pero ya no sabes bien cual parte tomar en serio cuando algo sale raro.
Preguntas frecuentes
Debo incluir todo el prompt del agente en el correo?
No. Pondria el objetivo, los riesgos visibles y los ids de control. El prompt completo cambia mucho, ocupa espacio y casi nunca mejora la decision humana.
Y si la persona responde varias horas despues?
Yo invalidaria la respuesta si expires_at ya paso. Es mejor pedir una aprobacion nueva que ejecutar una intencion vieja en un contexto distinto.
Esto sirve solo para publicar posts?
Para nada. El mismo patron vale para borrados, despliegues internos, cambios de configuracion o cualquier accion donde un LLM prepara algo y otro componente lo ejecuta despues.
En resumen: si el email aprueba una accion, el sistema necesita congelar el plan antes de pedir permiso. No es la parte mas llamativa del stack, pero si una de las que mas reduce confucion cuando el flujo crece.
Top comments (0)