Agentes IA stateful: por qué tu automatización falla
Tu agente IA funciona perfecto en el demo. Lo presentas en la reunión, el cliente lo ve claro, todo parece resuelto.
Una semana después recibes el primer aviso: el agente mandó tres recordatorios de pago al mismo cliente en el mismo día. El cliente está enfadado. El equipo no sabe por qué ha pasado. Y el agente sigue ejecutando, sin enterarse de nada.
El problema casi nunca está en el prompt. Está en que tu agente no sabe dónde está.
Los agentes sin estado no son agentes: son scripts de un solo uso
Cuando implementas un agente de automatización — para facturación, seguimiento de clientes, generación de informes, lo que sea — la primera trampa es tratarlo como un programa normal.
Un programa normal ejecuta, termina, y si lo vuelves a llamar, empieza desde cero. Sin problema. Los programas normales no necesitan recordar lo que hicieron la última vez porque operan sobre datos que persisten fuera de ellos.
Un agente IA en producción tiene que recordar en qué punto dejó cada proceso y qué ya ejecutó. Si no lo hace, comete errores que en papel suenan imposibles:
- Enviar el mismo email dos veces porque el proceso se reinició tras un crash
- Perder el hilo de una conversación de cliente al reiniciar el servidor
- Ejecutar un cobro que ya procesó porque no sincronizó el estado externo
Esto no es un fallo de la IA. Es un fallo de arquitectura.
Modela el proceso como una máquina de estados
Una máquina de estados define explícitamente en qué situaciones puede estar un proceso y qué condiciones permiten avanzar entre ellas. No hay ambigüedad. No hay forma de saltarse pasos.
Toma el flujo de una factura como ejemplo concreto:
BORRADOR → ENVIADA → RECORDATORIO_1 → RECORDATORIO_2 → PAGADA | VENCIDA
Cada estado representa una situación real y verificable. Cada transición tiene una condición: "pasar de ENVIADA a RECORDATORIO_1 solo si han pasado 7 días sin pago confirmado".
El LLM interviene donde su inteligencia añade valor: redactar el texto del recordatorio, adaptar el tono según el historial del cliente, decidir si escalar. El control del flujo — cuándo avanzar, cuándo esperar, cuándo escalar a una persona — lo gestiona la máquina, no el modelo.
type InvoiceState =
| 'DRAFT'
| 'SENT'
| 'REMINDED_1'
| 'REMINDED_2'
| 'PAID'
| 'OVERDUE';
interface InvoiceProcess {
invoiceId: string;
state: InvoiceState;
lastTransitionAt: Date;
attempts: number;
}
async function advanceState(process: InvoiceProcess): Promise<InvoiceProcess> {
const now = new Date();
const days =
(now.getTime() - process.lastTransitionAt.getTime()) / 86_400_000;
if (process.state === 'SENT' && days >= 7) {
const text = await generateReminder(process.invoiceId, 1);
await sendEmail(process.invoiceId, text);
return { ...process, state: 'REMINDED_1', lastTransitionAt: now, attempts: 1 };
}
if (process.state === 'REMINDED_1' && days >= 5) {
const text = await generateReminder(process.invoiceId, 2);
await sendEmail(process.invoiceId, text);
return { ...process, state: 'REMINDED_2', lastTransitionAt: now, attempts: 2 };
}
if (process.state === 'REMINDED_2' && days >= 7) {
return { ...process, state: 'OVERDUE', lastTransitionAt: now };
}
return process;
}
El diferencial de este enfoque: el estado se lee y se escribe en base de datos antes y después de cada transición. La próxima ejecución del scheduler sabe exactamente en qué punto está el proceso, sin asumir nada.
El estado vive en la base de datos, no en memoria
El error más frecuente en agentes de producción es guardar el estado en variables de memoria del proceso. Funciona hasta que el servidor se reinicia, el proceso crashea o se hace un despliegue en caliente.
Hay tres reglas que todo agente stateful debe seguir:
1. Leer el estado antes de actuar. Nunca asumir. Siempre consultar la base de datos para saber en qué punto está el proceso.
2. Comprobar idempotencia antes de ejecutar. Usar una idempotency_key por acción para detectar si ya se ejecutó: reminder-{invoiceId}-REMINDED_1. Si existe en los logs, no repetir.
3. Guardar el nuevo estado en la misma transacción que la acción. Si el email se envía pero falla el guardado del estado, la próxima ejecución lo detectará y reintentará. Con una transacción atómica, o ambas cosas ocurren o ninguna.
async function processInvoice(invoiceId: string) {
const process = await db.invoiceProcess.findUnique({ where: { invoiceId } });
const key = `reminder-${invoiceId}-${process.state}`;
const done = await db.emailLog.findUnique({ where: { idempotencyKey: key } });
if (done) return;
const text = await generateReminder(invoiceId, process.attempts + 1);
await sendEmail(invoiceId, text);
await db.$transaction([
db.emailLog.create({ data: { idempotencyKey: key, sentAt: new Date() } }),
db.invoiceProcess.update({
where: { invoiceId },
data: { state: 'REMINDED_1', lastTransitionAt: new Date(), attempts: 1 },
}),
]);
}
Este patrón elimina los duplicados. No importa si el proceso muere a mitad, si hay un fallo de red o si el scheduler se ejecuta dos veces seguidas — el agente comprueba antes de actuar y solo avanza cuando hay que avanzar.
La pregunta que debes hacer antes de desplegar cualquier agente
Si estás construyendo o evaluando una automatización con IA — ya sea para integración con tus sistemas de negocio, seguimiento de clientes o cualquier flujo que toque dinero o reputación — hay una pregunta técnica que separa los prototipos de los sistemas reales:
¿Cómo persiste su estado entre ejecuciones? ¿Qué ocurre si el proceso muere a mitad?
Si la respuesta es vaga, el problema llegará. Y cuando llega en producción, el coste no es solo técnico: son clientes enfadados, datos inconsistentes y horas de depuración para rastrear qué pasó y cuándo.
En proyectos de AI Driven Development implementamos este patrón desde el diseño inicial, no como un parche posterior. Es parte de lo que hace que los agentes que construimos sobrevivan al primer mes en producción.
Si quieres revisar la arquitectura de un agente que ya tienes, o empezar uno nuevo con estas bases, cuéntame tu caso: