AI Agents

Cómo darle memoria a un agente de IA: patrones de persistencia de estado para producción

Alejandro Rioja
Alejandro Rioja
9 min de lectura
TL;DR

Los agentes sin estado — los que olvidan todo al salir del Worker — funcionan bien para tareas únicas. En el momento en que un agente necesita recordar qué pasó ayer, reconocer a un cliente recurrente o construir sobre salidas anteriores, necesitas memoria. Hay tres patrones: memoria de trabajo (contexto en vuelo, vive en KV durante la duración de una ejecución), memoria episódica (qué pasó y cuándo, un registro consultable) y memoria semántica (qué sabes, recuperado mediante búsqueda vectorial o datos estructurados). Conecta el patrón correcto al trabajo correcto.

Newsletter gratuita

Cada miércoles. 28.400+ operadores. Sin relleno.

Tabla de contenidos

Actualizado junio 2026.

TL;DR: Los agentes sin estado — los que olvidan todo al salir del Worker — funcionan bien para tareas únicas. En el momento en que un agente necesita recordar qué pasó ayer, reconocer a un cliente recurrente o construir sobre salidas anteriores, necesitas memoria. Hay tres patrones: memoria de trabajo (contexto en vuelo, vive en KV durante la duración de una ejecución), memoria episódica (qué pasó y cuándo, un registro consultable) y memoria semántica (qué sabes, recuperado mediante búsqueda vectorial o datos estructurados). Conecta el patrón correcto al trabajo correcto.

[Perspectiva del operador] Me he chocado contra la pared del agente sin estado más de una vez. El agente de respuesta social que seguía presentándose a clientes con los que había hablado 20 veces. El agente de resumen diario que marcaba el mismo problema cuatro días seguidos porque no recordaba haberlo marcado el día anterior. Agregar el tipo correcto de memoria solucionó ambos problemas. Esto es lo que uso.

Por qué los agentes sin estado siguen fallando

Un agente sin estado comienza cada ejecución solo con lo que le pasas explícitamente: el prompt del sistema, el mensaje del usuario y los datos que obtienes en el momento de la invocación. No tiene conciencia de ejecuciones anteriores, usuarios anteriores ni decisiones anteriores.

Para una tarea de clasificación única — leer un comentario, devolver una categoría — el estado sin estado es correcto. Es rápido, barato y predecible.

La superficie de falla aparece en el momento en que necesitas continuidad:

  • Un agente orientado al cliente que no reconoce el historial del cliente
  • Un agente de contenido que recomienda un artículo que ya recomendó la semana pasada
  • Un agente de moderación que sigue re-escalando un caso resuelto
  • Un resumen diario que muestra la misma alerta obsoleta indefinidamente

Todos estos son síntomas del mismo problema: el agente no tiene forma de llevar contexto entre ejecuciones.

Tres tipos de memoria

El marco que encuentro útil en producción:

  1. Memoria de trabajo — lo que el agente sabe ahora mismo, durante una sola ejecución. Se mantiene en KV o en memoria durante la vida de la invocación.
  2. Memoria episódica — qué pasó y cuándo. Un registro estructurado que el agente lee al inicio de cada ejecución para orientarse.
  3. Memoria semántica — lo que sabe sobre el mundo, los clientes o una base de conocimiento. Recuperado mediante consultas estructuradas o búsqueda vectorial cuando es relevante.

No siempre necesitas las tres. La mayoría de los agentes que ejecuto necesitan trabajar + episódica. La memoria semántica es la más difícil de construir y solo justifica su lugar cuando la base de conocimiento es demasiado grande para caber en la ventana de contexto.

Memoria de trabajo: contexto en vuelo

La memoria de trabajo es estado que vive durante la duración de una ejecución del agente. La forma más simple son variables en el alcance de la función. La forma más interesante es una clave KV compartida que las subtareas dentro de la misma ejecución leen y escriben.

Mi agente de respuesta social usa la memoria de trabajo para acumular contexto mientras procesa un lote de comentarios en un mensaje de cola. Lee el historial de conversación reciente de cada cliente desde KV al inicio, agrega nuevo contexto mientras procesa y escribe de vuelta al final.

typescript
// workers/social-reply.ts

async function processComment(
  comment: SocialCommentEvent,
  env: Env
): Promise<void> {
  // Cargar el historial reciente de este cliente desde KV (memoria de trabajo)
  const historyKey = `customer:${comment.userId}:history`;
  const rawHistory = await env.AGENT_KV.get(historyKey);
  const history: ConversationTurn[] = rawHistory
    ? JSON.parse(rawHistory)
    : [];

  // Construir un prompt del sistema contextual a partir del historial
  const systemPrompt = buildSystemPrompt(history);

  const response = await anthropic.messages.create({
    model: "claude-opus-4-8",
    max_tokens: 512,
    system: systemPrompt,
    messages: [{ role: "user", content: comment.text }],
  });

  const reply =
    response.content[0].type === "text" ? response.content[0].text : "";

  // Actualizar historial — mantener los últimos 10 turnos, TTL 30 días
  const updatedHistory: ConversationTurn[] = [
    ...history.slice(-9),
    { role: "assistant", content: reply, timestamp: comment.timestamp },
  ];
  await env.AGENT_KV.put(historyKey, JSON.stringify(updatedHistory), {
    expirationTtl: 60 * 60 * 24 * 30,
  });

  await postReply(comment, reply, env);
}

Dos cosas a notar. El historial está limitado a 10 turnos — inyecta una ventana deslizante, no lo dejes crecer sin límite. Y el TTL es de 30 días: si un cliente se calla durante un mes, el historial expira y el agente empieza de nuevo. Ambos son intencionales.

Memoria episódica: qué pasó y cuándo

La memoria episódica es el registro del agente. Un registro estructurado de ejecuciones pasadas que el agente lee al inicio de cada nueva ejecución para no repetirse.

Mi agente de resumen diario mostraba las mismas alertas obsoletas cada día porque cada ejecución no tenía conciencia de lo que ya había marcado. La solución: un registro estructurado de alertas pasadas que el agente lee antes de generar el resumen.

typescript
// workers/daily-brief.ts

interface AlertLogEntry {
  id: string;
  surfacedAt: string; // marca de tiempo ISO
  resolvedAt?: string;
  summary: string;
}

async function buildDailyBrief(env: Env): Promise<void> {
  const [emails, calendar, tasks] = await Promise.all([
    fetchOvernightEmails(env),
    fetchTodayCalendar(env),
    fetchTopTasks(env),
  ]);

  // Cargar memoria episódica: qué ya ha sido marcado
  const rawLog = await env.AGENT_KV.get("brief:alert-log");
  const alertLog: AlertLogEntry[] = rawLog ? JSON.parse(rawLog) : [];

  // Filtrar solo alertas recientes y no resueltas
  const sevenDaysAgo = new Date(
    Date.now() - 7 * 24 * 60 * 60 * 1000
  ).toISOString();
  const recentAlerts = alertLog.filter(
    (e) => e.surfacedAt > sevenDaysAgo && !e.resolvedAt
  );

  const brief = await synthesizeBrief(
    { emails, calendar, tasks, recentAlerts },
    env
  );

  // Actualizar el registro con nuevas alertas marcadas en esta ejecución
  const newAlerts: AlertLogEntry[] = brief.newAlerts.map((a) => ({
    id: crypto.randomUUID(),
    surfacedAt: new Date().toISOString(),
    summary: a,
  }));

  const updatedLog = [...alertLog, ...newAlerts].slice(-100); // conservar las últimas 100
  await env.AGENT_KV.put("brief:alert-log", JSON.stringify(updatedLog));

  await writeToWorkspace(brief.content, env);
}

El agente ahora sabe qué ha dicho antes. Las alertas duplicadas no aparecen en el resumen hasta que el problema subyacente cambia. Cuando marco una alerta como resuelta, desaparece de la lista activa.

Este patrón se generaliza: cualquier agente que produce decisiones, marcas o recomendaciones se beneficia de un registro. El registro es barato (unos pocos KB en KV), el beneficio es alto (no más salidas redundantes).

Memoria semántica: lo que sabes

La memoria semántica es la base de conocimiento. Responde “¿qué sabes sobre X?” en el momento de la consulta, en lugar de meter todo en el prompt del sistema de antemano.

La forma más simple es una búsqueda estructurada en KV o una base de datos. Mi agente de reservas de Pickleland consulta perfiles de clientes y preferencias de cancha antes de redactar confirmaciones:

typescript
// workers/booking-agent.ts

interface CustomerProfile {
  userId: string;
  preferredCourts: string[];
  experienceLevel: "beginner" | "intermediate" | "advanced";
  specialNotes: string;
}

async function draftConfirmation(
  booking: BookingEvent,
  env: Env
): Promise<string> {
  // Obtener perfil del cliente desde KV (memoria semántica — conocimiento factual)
  const profileKey = `customer:${booking.userId}:profile`;
  const rawProfile = await env.AGENT_KV.get(profileKey);
  const profile: CustomerProfile | null = rawProfile
    ? JSON.parse(rawProfile)
    : null;

  const systemPrompt = profile
    ? `Redactas confirmaciones de reserva personalizadas. Este cliente prefiere ${profile.preferredCourts.join(", ")}, es un jugador ${profile.experienceLevel}. ${profile.specialNotes}`
    : "Redactas confirmaciones de reserva para una instalación de pickleball.";

  const response = await anthropic.messages.create({
    model: "claude-haiku-4-5-20251001",
    max_tokens: 256,
    system: systemPrompt,
    messages: [
      {
        role: "user",
        content: `Redacta una confirmación para: ${JSON.stringify(booking)}`,
      },
    ],
  });

  return response.content[0].type === "text" ? response.content[0].text : "";
}

Para bases de conocimiento más grandes — documentación de productos, una base de conocimiento de soporte, cualquier cosa demasiado grande para caber en una ventana de contexto — necesitas un almacén de vectores. El flujo de trabajo es: embeber la consulta, recuperar los k fragmentos más relevantes, inyectarlos en el contexto. Cloudflare Vectorize maneja esto de forma nativa si ya estás en Workers. Para índices más grandes he usado Upstash Vector. La elección depende de la escala, no del principio.

La nota honesta sobre la memoria semántica: es la más difícil de las tres de construir y mantener. El índice necesita mantenerse actualizado. La calidad de recuperación varía. Empieza con búsquedas estructuradas — KV, una tabla en D1 — y solo alcanza la búsqueda vectorial cuando el enfoque estructurado no puede cubrir la superficie de conocimiento que necesitas.

El marco de decisión de memoria

Antes de agregar cualquier memoria a un agente, responde tres preguntas:

  1. ¿El agente necesita recordar entre ejecuciones? Si cada invocación es genuinamente independiente — una traducción, una clasificación, una generación única — omite la memoria. Sin estado es más simple y más barato.

  2. ¿El agente se está repitiendo o actuando ciego a su propio historial? Si es así, agrega memoria episódica primero. Es la corrección de menor esfuerzo y cubre la mayoría de las quejas de “el agente sigue haciendo X”.

  3. ¿El agente trata a todos los usuarios o entidades de forma idéntica cuando no debería? Si es así, agrega memoria de trabajo (historial del cliente, perfil de usuario) o memoria semántica (un sistema de búsqueda o recuperación).

El error que más veo en la práctica: alguien agrega una enorme base de conocimiento (memoria semántica) a un agente que en realidad fallaba porque no tenía memoria episódica — ningún registro de lo que ya había hecho. La complejidad no coincide con el problema.

Lo que realmente uso en producción

En más de 30 agentes:

  • Todos ellos tienen al menos memoria de trabajo — alguna forma de estado dentro de una ejecución, aunque sea solo la ventana de contexto en sí.
  • Aproximadamente la mitad tienen memoria episódica — un registro de ejecuciones pasadas, decisiones o marcas. Esto casi siempre vale la pena agregar.
  • Tres o cuatro tienen memoria semántica real respaldada por un almacén de vectores. Estos son los agentes que responden preguntas contra una base de conocimiento grande y dinámica.

Cloudflare KV es mi almacén predeterminado para memoria de trabajo y episódica. Es rápido, barato y está integrado nativamente en Workers — sin cliente adicional, sin credencial separada. La limitación: KV es eventualmente consistente y no es ideal para escrituras de alta frecuencia. Para agentes que escriben estado muchas veces por segundo, uso Durable Objects o una base de datos D1 en su lugar.

Para memoria semántica respaldada por vectores, uso Cloudflare Vectorize para índices pequeños a medianos (menos de ~100K vectores) y Upstash Vector para todo lo más grande. Ambos tienen clientes JavaScript de primera clase.

La conclusión del operador

Agrega memoria a un agente solo cuando el comportamiento sin estado esté causando problemas reales — salidas repetidas, puntos ciegos en el historial del cliente, ignorancia de decisiones pasadas. Luego elige la capa correcta: memoria de trabajo para el contexto en ejecución, episódica para lo que sucedió históricamente, semántica para lo que sabes. Empieza con episódica si no estás seguro — corrige el modo de fallo más común con la menor complejidad. No uses una base de datos vectorial hasta que hayas agotado las búsquedas estructuradas. El mejor sistema de memoria es el más simple que hace que el agente se comporte correctamente.


Relacionado: El stack de agentes que uso para ejecutar más de 30 agentes en producción · Agentes disparados por eventos vs. programados · Cómo mido si un agente de IA realmente funciona

¿Necesitas ayuda para diseñar la memoria de agentes para tu caso de uso? Contáctame — diseño sistemas de agentes en producción para equipos operadores.

Seguir leyendo

Artículos relacionados

Seguir leyendo

Recibe el manual de IA en tu buzón

Cada miércoles. 28.400+ operadores. Sin relleno.

↵ para ver todos los resultados esc esc para cerrar