ChanlChanl
Knowledge & Memory

Memoria de Agentes de IA: Del contexto de sesion al conocimiento a largo plazo

Construye sistemas de memoria para agentes de IA desde cero en TypeScript. Cubre tipos de memoria (sesion, episodica, semantica, procedural), arquitecturas (buffer, resumen, recuperacion vectorial), interseccion con RAG y diseno con privacidad.

DGDean GroverCo-founderFollow
March 10, 2026
25 min read read
Ilustracion en acuarela de nodos de memoria interconectados formando una red de conocimiento en tonos verde salvia y oliva

Tu agente de soporte maneja una queja de un cliente sobre un envio retrasado. El cliente menciona que esta preparando la fiesta de cumpleanos de su hija este sabado. El agente resuelve el problema, acelera el paquete, confirma la nueva fecha de entrega. Gran interaccion.

Dos dias despues, el mismo cliente vuelve a llamar. Pregunta completamente diferente: quiere agregar un articulo a su pedido. Tu agente no tiene idea de quien es. No recuerda el cumpleanos. No sabe que hay una entrega con tiempo limitado en progreso. El cliente repite todo. La magia se fue.

Esta es la brecha que separa a un chatbot de un agente. La memoria, la capacidad de retener, organizar y recuperar informacion entre interacciones, es lo que transforma un modelo de lenguaje sin estado en algo que genuinamente aprende sobre las personas a las que sirve. Y construirla bien es mas dificil de lo que parece.

Construiremos un sistema de memoria funcional desde cero en TypeScript, cubriendo cada capa: desde buffers de conversacion simples hasta recuperacion semantica impulsada por vectores. En el camino, exploraremos la ciencia cognitiva detras de los tipos de memoria, examinaremos arquitecturas de produccion desde MemGPT hasta Zep, entenderemos donde convergen la memoria y RAG, y abordaremos las restricciones de privacidad que determinan lo que tu agente deberia, y no deberia, recordar.

Prerrequisitos y configuracion

Necesitaras Node.js 20+, TypeScript y familiaridad con patrones async/await. Algunas secciones hacen referencia a embeddings vectoriales y busqueda por similitud. Si estos conceptos son nuevos, comienza con RAG desde cero para los fundamentos.

bash
npm install openai uuid
npm install -D typescript @types/node

Usaremos la API de OpenAI para embeddings y generacion de texto. Los patrones arquitectonicos funcionan con cualquier proveedor de LLM: cambia a Anthropic, Ollama o lo que prefieras.

Los ejemplos de codigo se construyen progresivamente uno sobre otro. Cada uno es lo suficientemente autocontenido para ejecutarse independientemente, pero estan disenados para mostrar como la memoria simple evoluciona hacia sistemas de grado de produccion.

Por que importa la memoria: El problema sin estado

Cada llamada a un LLM es sin estado por defecto: el modelo recibe un prompt, genera una respuesta y olvida todo. Sin memoria externa, los agentes no pueden aprender de interacciones pasadas, reconocer usuarios recurrentes o construir contexto con el tiempo. Esta limitacion fundamental significa que incluso el modelo mas capaz comienza cada conversacion desde cero.

Los numeros lo hacen concreto. La ventana de contexto de Claude tiene 200,000 tokens. GPT-4o soporta 128,000. Gemini 2.0 Pro alcanza 2 millones. Suenan enormes hasta que calculas lo que realmente contienen. Una interaccion tipica de servicio al cliente tiene entre 2,000-4,000 tokens. Un usuario con 100 conversaciones pasadas ha generado 200,000-400,000 tokens de historial, ya excediendo la mayoria de las ventanas de contexto, y eso es solo un usuario.

Incluso si pudieras meter todo, no querrias. La investigacion muestra consistentemente que el rendimiento de los LLM se degrada en el medio de contextos largos, un fenomeno que los investigadores llaman el problema de "perdido en el medio". Un modelo que anuncia 200K tokens tipicamente se vuelve poco confiable alrededor de 130K, con caidas de precision repentinas en lugar de degradacion gradual. Meter todo el historial en cada prompt no es solo costoso. Afecta activamente la calidad.

Los sistemas de memoria resuelven esto actuando como un filtro inteligente entre el historial de conversacion crudo y la ventana de contexto del modelo. En lugar de "aqui esta todo lo que ha pasado", la memoria dice "aqui esta lo que es relevante ahora mismo".

Mensaje del usuario(Sesion 1) LLM(Sin estado) Respuesta Mensaje del usuario(Sesion 2) LLM(Sin estado) Respuesta(Sin contexto de Sesion 1) Mensaje del usuario(Sesion 1) Sistema deMemoria LLM +Contexto Relevante Respuesta Mensaje del usuario(Sesion 2)
El problema sin estado: sin memoria, cada interaccion comienza desde cero

Los cuatro tipos de memoria de agentes

La ciencia cognitiva nos da un marco sorprendentemente util para pensar sobre la memoria de agentes de IA. El sistema de memoria humana, estudiado por mas de un siglo, se mapea claramente a los desafios que enfrentan los agentes. Cuatro tipos importan mas: memoria de trabajo para la conversacion actual, memoria episodica para eventos pasados especificos, memoria semantica para conocimiento destilado y memoria procedural para comportamientos aprendidos.

Esto no es solo una analogia. La encuesta de diciembre 2025 "Memory in the Age of AI Agents" de la Universidad Tsinghua y CMU explicitamente argumenta que las taxonomias tradicionales de corto/largo plazo son insuficientes, proponiendo una taxonomia basada en funciones que refleja las categorias de ciencia cognitiva. Desglosemos cada una con implementaciones en TypeScript.

Memoria de trabajo (Contexto de sesion)

La memoria de trabajo contiene la informacion necesaria para la tarea actual: la conversacion activa, llamadas recientes a herramientas y contexto inmediato. Es rapida, limitada y desechable. Cuando la sesion termina, la memoria de trabajo puede descartarse o consolidarse en almacenamiento a mas largo plazo.

Cada aplicacion de chat que has usado implementa memoria de trabajo, aunque no la llame asi. Es el historial de mensajes que se antepone a cada llamada al LLM.

Aqui esta la implementacion mas simple posible: un buffer limitado que mantiene los ultimos N mensajes:

typescript
interface Message {
  role: 'user' | 'assistant' | 'system';
  content: string;
  timestamp: Date;
}
 
class WorkingMemory {
  private messages: Message[] = [];
  private maxMessages: number;
 
  constructor(maxMessages: number = 20) {
    this.maxMessages = maxMessages;
  }
 
  add(message: Message): void {
    this.messages.push(message);
    // Evict oldest messages when buffer is full
    if (this.messages.length > this.maxMessages) {
      this.messages = this.messages.slice(-this.maxMessages);
    }
  }
 
  getContext(): Message[] {
    return [...this.messages];
  }
 
  getTokenEstimate(): number {
    // Rough estimate: 1 token ≈ 4 characters
    return this.messages.reduce(
      (sum, m) => sum + Math.ceil(m.content.length / 4), 0
    );
  }
 
  clear(): void {
    this.messages = [];
  }
}

Esto funciona para conversaciones cortas, pero tiene un defecto obvio: una vez que un mensaje sale de la ventana, desaparece. La mencion del cumpleanos del cliente en el mensaje #3 desaparece despues del mensaje #23. Ahi es donde entran los otros tipos de memoria.

Memoria episodica (Lo que paso)

La memoria episodica registra eventos especificos con su contexto: cuando ocurrieron, quien estuvo involucrado, cual fue el resultado. Piensa en ella como la autobiografia del agente. El paper de Stanford de 2023 "Generative Agents" demostro esto poderosamente: agentes que mantuvieron memoria episodica pudieron organizar autonomamente una fiesta de San Valentin recordando a quien habian invitado, que conversaciones habian tenido y cuando estaba programado el evento.

La idea clave es que las memorias episodicas llevan metadatos temporales y contextuales. No es solo "al cliente le gusta el correo electronico", es "el 5 de marzo de 2026, durante una disputa de facturacion sobre la factura #4821, el cliente dijo explicitamente que prefiere la comunicacion por correo electronico sobre las llamadas telefonicas."

Esta implementacion almacena episodios con metadatos ricos y los recupera por recencia y relevancia:

typescript
import { randomUUID } from 'crypto';
 
interface Episode {
  id: string;
  userId: string;
  sessionId: string;
  event: string;           // What happened
  context: string;         // Surrounding circumstances
  outcome?: string;        // How it resolved
  importance: number;      // 1-10 scale
  timestamp: Date;
  tags: string[];
  embedding?: number[];    // For semantic search (added later)
}
 
class EpisodicMemory {
  private episodes: Map<string, Episode[]> = new Map();
 
  async store(episode: Omit<Episode, 'id'>): Promise<string> {
    const id = randomUUID();
    const stored: Episode = { ...episode, id };
 
    const userEpisodes = this.episodes.get(episode.userId) || [];
    userEpisodes.push(stored);
    this.episodes.set(episode.userId, userEpisodes);
 
    return id;
  }
 
  // Retrieve by recency — most recent episodes first
  getRecent(userId: string, limit: number = 10): Episode[] {
    const episodes = this.episodes.get(userId) || [];
    return episodes
      .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())
      .slice(0, limit);
  }
 
  // Retrieve by importance — highest importance first
  getMostImportant(userId: string, limit: number = 5): Episode[] {
    const episodes = this.episodes.get(userId) || [];
    return episodes
      .sort((a, b) => b.importance - a.importance)
      .slice(0, limit);
  }
 
  // Combined retrieval: weighted score of recency + importance
  getRelevant(
    userId: string,
    limit: number = 5,
    recencyWeight: number = 0.4,
    importanceWeight: number = 0.6
  ): Episode[] {
    const episodes = this.episodes.get(userId) || [];
    if (episodes.length === 0) return [];
 
    const now = Date.now();
    const maxAge = Math.max(
      ...episodes.map(e => now - e.timestamp.getTime())
    );
 
    return episodes
      .map(episode => {
        const age = now - episode.timestamp.getTime();
        const recencyScore = 1 - (age / (maxAge || 1));
        const importanceScore = episode.importance / 10;
        const score =
          recencyWeight * recencyScore +
          importanceWeight * importanceScore;
        return { episode, score };
      })
      .sort((a, b) => b.score - a.score)
      .slice(0, limit)
      .map(({ episode }) => episode);
  }
}

El metodo getRelevant implementa el mismo enfoque de puntuacion usado en el paper de agentes generativos de Stanford: combina recencia, importancia y (cuando los embeddings estan disponibles) relevancia para determinar que memorias se muestran. Los sistemas en produccion agregan una tercera senal: relevancia a la consulta actual, calculada via similitud de embeddings.

Memoria semantica (Lo que el agente sabe)

La memoria semantica almacena hechos y conocimiento destilados, no eventos especificos, sino los patrones y preferencias extraidos de ellos. Mientras la memoria episodica dice "el cliente llamo sobre facturacion el 5 de marzo", la memoria semantica dice "este cliente frecuentemente tiene preguntas de facturacion y prefiere resolucion por correo electronico."

La distincion importa porque las memorias semanticas son mas compactas, mas generalizables y mas utiles para moldear el comportamiento del agente. Son el resultado de la consolidacion: el proceso de convertir experiencias crudas en conocimiento reutilizable.

Asi es como se extraen memorias semanticas de conversaciones usando un LLM:

typescript
import OpenAI from 'openai';
 
interface SemanticMemory {
  id: string;
  userId: string;
  fact: string;              // The distilled knowledge
  confidence: number;        // 0-1, how certain we are
  source: string;            // Which episode(s) this came from
  category: string;          // preference, fact, behavior, relationship
  lastAccessed: Date;
  accessCount: number;
  createdAt: Date;
  updatedAt: Date;
}
 
class SemanticMemoryExtractor {
  private openai: OpenAI;
  private memories: Map<string, SemanticMemory[]> = new Map();
 
  constructor(apiKey: string) {
    this.openai = new OpenAI({ apiKey });
  }
 
  async extractFromConversation(
    userId: string,
    conversation: string,
    sessionId: string
  ): Promise<SemanticMemory[]> {
    const existing = this.memories.get(userId) || [];
    const existingFacts = existing.map(m => m.fact).join('\n');
 
    const response = await this.openai.chat.completions.create({
      model: 'gpt-4o',
      temperature: 0.1,
      response_format: { type: 'json_object' },
      messages: [
        {
          role: 'system',
          content: `Extract factual knowledge about the user from this conversation.
Return JSON: { "memories": [{ "fact": "...", "confidence": 0.0-1.0, "category": "preference|fact|behavior|relationship" }] }
 
Rules:
- Only extract information explicitly stated or strongly implied
- Confidence 0.9+ for direct statements, 0.5-0.8 for inferences
- Skip transient information (current mood, one-time requests)
- If a fact contradicts existing knowledge, include it with the updated information
 
Existing knowledge about this user:
${existingFacts || 'None yet'}`
        },
        { role: 'user', content: conversation }
      ]
    });
 
    const parsed = JSON.parse(
      response.choices[0].message.content || '{"memories":[]}'
    );
 
    const newMemories: SemanticMemory[] = parsed.memories.map(
      (m: { fact: string; confidence: number; category: string }) => ({
        id: randomUUID(),
        userId,
        fact: m.fact,
        confidence: m.confidence,
        source: sessionId,
        category: m.category,
        lastAccessed: new Date(),
        accessCount: 0,
        createdAt: new Date(),
        updatedAt: new Date(),
      })
    );
 
    // Merge with existing — update if contradicting, add if new
    this.mergeMemories(userId, newMemories);
 
    return newMemories;
  }
 
  private mergeMemories(
    userId: string,
    newMemories: SemanticMemory[]
  ): void {
    const existing = this.memories.get(userId) || [];
 
    for (const newMem of newMemories) {
      const conflict = existing.findIndex(
        e => e.category === newMem.category &&
             this.isContradiction(e.fact, newMem.fact)
      );
 
      if (conflict >= 0 && newMem.confidence > existing[conflict].confidence) {
        // Replace lower-confidence memory with higher-confidence one
        existing[conflict] = { ...newMem, updatedAt: new Date() };
      } else if (conflict < 0) {
        existing.push(newMem);
      }
    }
 
    this.memories.set(userId, existing);
  }
 
  private isContradiction(a: string, b: string): boolean {
    // Simplified — production systems use embedding similarity
    // to detect semantic overlap, then LLM to judge contradiction
    const normalize = (s: string) => s.toLowerCase().trim();
    return normalize(a).includes(normalize(b).split(' ')[0]);
  }
 
  getMemories(
    userId: string,
    category?: string
  ): SemanticMemory[] {
    const all = this.memories.get(userId) || [];
    if (category) {
      return all.filter(m => m.category === category);
    }
    return all;
  }
}

Observa la logica de fusion: cuando nueva informacion contradice memorias existentes, la version de mayor confianza gana. Esto previene el problema clasico donde una preferencia desactualizada sobreescribe una correccion reciente ("En realidad me mude a Portland el mes pasado, por favor deja de enviar cosas a Seattle").

Memoria procedural (Como hacer las cosas)

La memoria procedural captura procesos y estrategias aprendidos, no lo que paso o lo que es verdad, sino como lograr tareas. En ciencia cognitiva, este es el tipo de memoria que te permite andar en bicicleta sin pensarlo. Para agentes de IA, es la memoria que captura patrones exitosos de resolucion de problemas.

Investigacion reciente como "Remember Me, Refine Me" (2025) demuestra agentes que evolucionan sus procedimientos basados en experiencia. Un agente que ha resuelto exitosamente 50 disputas de facturacion desarrolla una memoria procedural para el flujo optimo de resolucion: verificar estado de cuenta, confirmar cargo, ofrecer resolucion apropiada segun el nivel del cliente.

Aqui hay una implementacion practica que registra y recupera secuencias de acciones exitosas:

typescript
interface Procedure {
  id: string;
  name: string;
  description: string;
  steps: ProcedureStep[];
  successRate: number;
  timesUsed: number;
  context: string;         // When to apply this procedure
  lastUsed: Date;
  createdAt: Date;
}
 
interface ProcedureStep {
  action: string;
  parameters?: Record<string, unknown>;
  expectedOutcome: string;
  fallback?: string;       // What to do if this step fails
}
 
class ProceduralMemory {
  private procedures: Procedure[] = [];
 
  // Record a successful action sequence as a procedure
  recordProcedure(
    name: string,
    description: string,
    steps: ProcedureStep[],
    context: string
  ): Procedure {
    const existing = this.procedures.find(p => p.name === name);
 
    if (existing) {
      // Reinforce existing procedure
      existing.timesUsed++;
      existing.successRate =
        (existing.successRate * (existing.timesUsed - 1) + 1) /
        existing.timesUsed;
      existing.lastUsed = new Date();
      return existing;
    }
 
    const procedure: Procedure = {
      id: randomUUID(),
      name,
      description,
      steps,
      successRate: 1.0,
      timesUsed: 1,
      context,
      lastUsed: new Date(),
      createdAt: new Date(),
    };
 
    this.procedures.push(procedure);
    return procedure;
  }
 
  // Record a failure to adjust success rate
  recordFailure(procedureId: string): void {
    const proc = this.procedures.find(p => p.id === procedureId);
    if (proc) {
      proc.timesUsed++;
      proc.successRate =
        (proc.successRate * (proc.timesUsed - 1)) / proc.timesUsed;
    }
  }
 
  // Find the best procedure for a given context
  findProcedure(context: string): Procedure | null {
    // Simple keyword matching — production uses embedding similarity
    const candidates = this.procedures.filter(p =>
      context.toLowerCase().includes(p.context.toLowerCase()) ||
      p.context.toLowerCase().includes(context.toLowerCase())
    );
 
    if (candidates.length === 0) return null;
 
    // Prefer high success rate, then recency
    return candidates.sort((a, b) => {
      const scoreA = a.successRate * 0.7 +
        (a.lastUsed.getTime() / Date.now()) * 0.3;
      const scoreB = b.successRate * 0.7 +
        (b.lastUsed.getTime() / Date.now()) * 0.3;
      return scoreB - scoreA;
    })[0];
  }
 
  // Format procedure as instructions for the LLM
  toPromptInstructions(procedure: Procedure): string {
    const steps = procedure.steps
      .map((s, i) => `${i + 1}. ${s.action}${
        s.fallback ? ` (if this fails: ${s.fallback})` : ''
      }`)
      .join('\n');
 
    return `Recommended approach (${Math.round(procedure.successRate * 100)}% success rate, used ${procedure.timesUsed} times):
${procedure.description}
 
Steps:
${steps}`;
  }
}

La memoria procedural es la menos comunmente implementada de los cuatro tipos, pero es posiblemente la mas poderosa para agentes que manejan flujos de trabajo repetidos. En lugar de descifrar el proceso de resolucion de disputas de facturacion desde cero cada vez, el agente recuerda: "Las ultimas 47 veces que esto paso, esto es lo que funciono."

Arquitecturas de memoria: De lo simple a produccion

Ahora que entendemos los tipos de memoria, como se estructura realmente un sistema de memoria? Tres arquitecturas dominan los sistemas en produccion, cada una con diferentes compromisos entre complejidad, costo y calidad de recuperacion. La mayoria de los despliegues en produccion combinan multiples enfoques.

Memoria buffer

La memoria buffer es la arquitectura mas simple: una ventana deslizante de mensajes recientes pasados directamente como contexto al LLM. Sin recuperacion, sin embeddings, sin almacenamiento externo. Ya viste esto en la implementacion de memoria de trabajo arriba.

Funciona bien para interacciones cortas y enfocadas. El problema aparece cuando las conversaciones se alargan o abarcan multiples sesiones: el contexto mas antiguo desaparece silenciosamente a medida que nuevos mensajes lo empujan fuera del buffer.

Un refinamiento comun es el buffer con ventana y conciencia de tokens:

typescript
class TokenAwareBuffer {
  private messages: Message[] = [];
  private maxTokens: number;
 
  constructor(maxTokens: number = 4000) {
    this.maxTokens = maxTokens;
  }
 
  add(message: Message): void {
    this.messages.push(message);
    this.trim();
  }
 
  private trim(): void {
    let totalTokens = this.estimateTokens(this.messages);
    while (totalTokens > this.maxTokens && this.messages.length > 1) {
      this.messages.shift();
      totalTokens = this.estimateTokens(this.messages);
    }
  }
 
  private estimateTokens(msgs: Message[]): number {
    return msgs.reduce(
      (sum, m) => sum + Math.ceil(m.content.length / 4) + 4, // +4 for role tokens
      0
    );
  }
 
  getMessages(): Message[] {
    return [...this.messages];
  }
}

Cuando usar memoria buffer: Prototipos, interacciones de una sola sesion, y como la capa de memoria de trabajo dentro de un sistema mas grande. No la uses sola si tu agente necesita recordar algo entre sesiones.

Memoria de resumen

La memoria de resumen aborda la debilidad principal del buffer comprimiendo mensajes antiguos en resumenes antes de descartarlos. En lugar de perder informacion por completo, el sistema la condensa en una representacion mas corta que captura los puntos esenciales.

La idea es directa: cuando el buffer se llena, resume los mensajes mas antiguos, reemplazalos con el resumen y continua. El LLM ve una version comprimida del historial mas los mensajes recientes completos.

Asi es como construir una que resuma progresivamente a medida que las conversaciones crecen:

typescript
class SummaryMemory {
  private recentMessages: Message[] = [];
  private summary: string = '';
  private maxRecentMessages: number;
  private openai: OpenAI;
 
  constructor(apiKey: string, maxRecentMessages: number = 10) {
    this.openai = new OpenAI({ apiKey });
    this.maxRecentMessages = maxRecentMessages;
  }
 
  async add(message: Message): Promise<void> {
    this.recentMessages.push(message);
 
    if (this.recentMessages.length > this.maxRecentMessages) {
      // Take the oldest messages and summarize them
      const toSummarize = this.recentMessages.splice(
        0,
        Math.floor(this.maxRecentMessages / 2)
      );
      await this.updateSummary(toSummarize);
    }
  }
 
  private async updateSummary(messages: Message[]): Promise<void> {
    const conversation = messages
      .map(m => `${m.role}: ${m.content}`)
      .join('\n');
 
    const response = await this.openai.chat.completions.create({
      model: 'gpt-4o-mini',  // Cheaper model for summarization
      temperature: 0,
      messages: [
        {
          role: 'system',
          content: `Progressively summarize the conversation, adding to the existing summary.
Include: key facts, user preferences, unresolved issues, action items, and any commitments made.
Be concise but don't drop important details.`
        },
        {
          role: 'user',
          content: `Existing summary:\n${this.summary || '(none yet)'}\n\nNew messages:\n${conversation}`
        }
      ]
    });
 
    this.summary = response.choices[0].message.content || this.summary;
  }
 
  getContext(): { summary: string; recentMessages: Message[] } {
    return {
      summary: this.summary,
      recentMessages: [...this.recentMessages],
    };
  }
 
  // Format for injection into LLM prompt
  toPromptContext(): string {
    const parts: string[] = [];
 
    if (this.summary) {
      parts.push(`Previous conversation summary:\n${this.summary}`);
    }
 
    if (this.recentMessages.length > 0) {
      parts.push('Recent messages:');
      for (const msg of this.recentMessages) {
        parts.push(`${msg.role}: ${msg.content}`);
      }
    }
 
    return parts.join('\n\n');
  }
}

La memoria de resumen hace un compromiso: preservas la esencia de conversaciones antiguas a costa de detalles especificos. Las palabras exactas del cliente sobre el cumpleanos de su hija podrian resumirse como "el cliente tiene una entrega con tiempo limitado", lo cual captura la urgencia pero pierde el contexto personal. Para muchas aplicaciones, ese es un compromiso aceptable. Para otras, necesitas la siguiente arquitectura.

Memoria de recuperacion basada en vectores

La memoria de recuperacion vectorial almacena cada memoria como un embedding vectorial y recupera entradas por similitud semantica con la consulta actual. En lugar de mantener todo en un buffer o resumir a un tamano fijo, buscas a traves del almacen de memoria completo lo que realmente es relevante.

Aqui es donde la memoria se intersecta con RAG: las mismas tecnicas de embedding y recuperacion cubiertas en RAG desde cero aplican directamente. La diferencia es la fuente de datos: RAG recupera de documentos; la memoria recupera de las propias experiencias del agente.

Aqui hay una implementacion completa con busqueda por similitud coseno:

typescript
class VectorMemoryStore {
  private entries: Array<{
    id: string;
    text: string;
    embedding: number[];
    metadata: Record<string, unknown>;
    timestamp: Date;
  }> = [];
  private openai: OpenAI;
 
  constructor(apiKey: string) {
    this.openai = new OpenAI({ apiKey });
  }
 
  async store(
    text: string,
    metadata: Record<string, unknown> = {}
  ): Promise<string> {
    const embedding = await this.embed(text);
    const id = randomUUID();
 
    this.entries.push({
      id,
      text,
      embedding,
      metadata,
      timestamp: new Date(),
    });
 
    return id;
  }
 
  async search(
    query: string,
    topK: number = 5,
    filter?: (meta: Record<string, unknown>) => boolean
  ): Promise<Array<{ text: string; score: number; metadata: Record<string, unknown> }>> {
    const queryEmbedding = await this.embed(query);
 
    let candidates = this.entries;
    if (filter) {
      candidates = candidates.filter(e => filter(e.metadata));
    }
 
    const scored = candidates.map(entry => ({
      text: entry.text,
      score: this.cosineSimilarity(queryEmbedding, entry.embedding),
      metadata: entry.metadata,
    }));
 
    return scored
      .sort((a, b) => b.score - a.score)
      .slice(0, topK);
  }
 
  private async embed(text: string): Promise<number[]> {
    const response = await this.openai.embeddings.create({
      model: 'text-embedding-3-small',
      input: text,
    });
    return response.data[0].embedding;
  }
 
  private cosineSimilarity(a: number[], b: number[]): number {
    let dot = 0, normA = 0, normB = 0;
    for (let i = 0; i < a.length; i++) {
      dot += a[i] * b[i];
      normA += a[i] * a[i];
      normB += b[i] * b[i];
    }
    return dot / (Math.sqrt(normA) * Math.sqrt(normB));
  }
 
  // Decay old memories — reduce their retrieval priority over time
  applyDecay(halfLifeDays: number = 30): void {
    const now = Date.now();
    for (const entry of this.entries) {
      const ageDays =
        (now - entry.timestamp.getTime()) / (1000 * 60 * 60 * 24);
      const decayFactor = Math.pow(0.5, ageDays / halfLifeDays);
      // Store decay factor in metadata for retrieval scoring
      entry.metadata._decayFactor = decayFactor;
    }
  }
}

En produccion, reemplazarias el almacen en memoria con una base de datos vectorial: Pinecone, Qdrant, pgvector o Weaviate. La superficie de la API es esencialmente la misma: embeber, almacenar, buscar. La base de datos vectorial maneja la busqueda eficiente de vecinos mas cercanos aproximados a escala, lo cual importa una vez que tienes miles o millones de entradas de memoria.

El mecanismo de decaimiento merece atencion. Sin el, memorias antiguas compiten equitativamente con las recientes durante la recuperacion. El metodo applyDecay implementa decaimiento exponencial con una vida media configurable: una memoria de hace 30 dias puntua al 50% de su relevancia original. Mem0 hace algo similar, llamandolo "olvido dinamico", lo que ayuda a mantener el contexto recuperado actual y relevante.

Uniendo todo: Un sistema de memoria unificado

Un sistema de memoria en produccion no usa una sola arquitectura: combina los cuatro tipos de memoria en una capa unificada que el agente consulta antes de cada respuesta. La clave es hacer esto transparente para el codigo de la aplicacion: el agente pregunta "que se sobre este usuario y esta situacion?" y obtiene de vuelta un bloque de contexto curado.

Aqui hay un gestor de memoria unificado que orquesta las piezas:

typescript
interface MemoryContext {
  workingMemory: Message[];
  relevantEpisodes: Episode[];
  semanticFacts: SemanticMemory[];
  suggestedProcedure: Procedure | null;
  summary: string;
}
 
class UnifiedMemoryManager {
  private working: WorkingMemory;
  private episodic: EpisodicMemory;
  private semantic: SemanticMemoryExtractor;
  private procedural: ProceduralMemory;
  private vectorStore: VectorMemoryStore;
  private summaryMemory: SummaryMemory;
 
  constructor(apiKey: string) {
    this.working = new WorkingMemory(20);
    this.episodic = new EpisodicMemory();
    this.semantic = new SemanticMemoryExtractor(apiKey);
    this.procedural = new ProceduralMemory();
    this.vectorStore = new VectorMemoryStore(apiKey);
    this.summaryMemory = new SummaryMemory(apiKey);
  }
 
  // Called on every user message
  async processMessage(
    userId: string,
    sessionId: string,
    message: Message
  ): Promise<void> {
    // Update working memory
    this.working.add(message);
 
    // Update summary
    await this.summaryMemory.add(message);
 
    // Store in vector memory for future retrieval
    await this.vectorStore.store(message.content, {
      userId,
      sessionId,
      role: message.role,
      timestamp: message.timestamp.toISOString(),
    });
  }
 
  // Build full context for LLM prompt
  async buildContext(
    userId: string,
    currentQuery: string
  ): Promise<MemoryContext> {
    // Parallel retrieval for speed
    const [vectorResults, episodes, facts] = await Promise.all([
      this.vectorStore.search(currentQuery, 5, (meta) =>
        meta.userId === userId
      ),
      Promise.resolve(this.episodic.getRelevant(userId, 3)),
      Promise.resolve(this.semantic.getMemories(userId)),
    ]);
 
    // Find applicable procedure
    const procedure = this.procedural.findProcedure(currentQuery);
 
    const { summary, recentMessages } = this.summaryMemory.getContext();
 
    return {
      workingMemory: recentMessages,
      relevantEpisodes: episodes,
      semanticFacts: facts,
      suggestedProcedure: procedure,
      summary,
    };
  }
 
  // Format context for injection into system prompt
  formatForPrompt(context: MemoryContext): string {
    const sections: string[] = [];
 
    if (context.summary) {
      sections.push(
        `## Conversation History\n${context.summary}`
      );
    }
 
    if (context.semanticFacts.length > 0) {
      const facts = context.semanticFacts
        .map(f => `- ${f.fact} (confidence: ${f.confidence})`)
        .join('\n');
      sections.push(`## What You Know About This User\n${facts}`);
    }
 
    if (context.relevantEpisodes.length > 0) {
      const episodes = context.relevantEpisodes
        .map(e => `- [${e.timestamp.toLocaleDateString()}] ${e.event}${
          e.outcome ? `${e.outcome}` : ''
        }`)
        .join('\n');
      sections.push(`## Relevant Past Interactions\n${episodes}`);
    }
 
    if (context.suggestedProcedure) {
      sections.push(
        `## Suggested Approach\n${this.procedural.toPromptInstructions(
          context.suggestedProcedure
        )}`
      );
    }
 
    return sections.join('\n\n');
  }
 
  // End-of-session consolidation
  async consolidateSession(
    userId: string,
    sessionId: string,
    conversation: string
  ): Promise<void> {
    // Extract semantic memories from the full conversation
    await this.semantic.extractFromConversation(
      userId,
      conversation,
      sessionId
    );
 
    // Store key events as episodes
    // (In production, use LLM to identify notable events)
    await this.episodic.store({
      userId,
      sessionId,
      event: `Conversation session ${sessionId}`,
      context: conversation.slice(0, 500),
      importance: 5,
      timestamp: new Date(),
      tags: ['conversation'],
    });
 
    // Clear working memory
    this.working.clear();
  }
}

El metodo buildContext ejecuta la recuperacion en paralelo: busqueda vectorial, consulta episodica y recuperacion de hechos semanticos suceden simultaneamente. Esto mantiene la latencia manejable incluso con multiples fuentes de memoria. En produccion, la salida de formatForPrompt va en el mensaje del sistema, dandole al LLM todo lo que necesita para responder con contexto completo.

El metodo consolidateSession se ejecuta cuando una conversacion termina. Es el puente entre la memoria de trabajo efimera y el almacenamiento persistente a largo plazo, extrayendo la senal valiosa de la conversacion cruda y almacenandola donde sesiones futuras puedan encontrarla.

Customer service representative

Customer Memory

4 memories recalled

Sarah Chen
Premium
Last call
2 days ago
Prefers
Email follow-up
Session Memory

“Discussed upgrading to Business plan. Budget approved at $50k. Follow up next Tuesday.”

85% relevance

Donde se encuentran la memoria y RAG

La memoria y RAG comparten infraestructura, embeddings, almacenes vectoriales, busqueda por similitud, pero sirven propositos fundamentalmente diferentes. Entender el limite previene confusiones arquitectonicas y te ayuda a construir sistemas donde ambos trabajen juntos efectivamente.

RAG recupera de conocimiento organizacional: documentacion de productos, preguntas frecuentes, documentos de politicas, articulos de base de conocimiento. Esta informacion existe independientemente de cualquier usuario o conversacion particular. Es la misma para todos.

La memoria recupera de conocimiento experiencial: lo que paso en conversaciones pasadas, que prefiere este usuario especifico, como se resolvieron situaciones similares antes. Esta informacion se genera a traves de la interaccion y es unica para cada usuario o agente.

La superposicion practica se ve asi:

Consulta del usuario Capa de recuperacion Base de datos vectorial Resultados RAG(Docs de producto, FAQs,politicas) Resultados de memoria(Conversaciones pasadas,preferencias, episodios) Contexto combinado Generacion LLM
Memoria y RAG: infraestructura compartida, fuentes de datos diferentes

En produccion, una sola base de datos vectorial frecuentemente aloja ambos. El namespace o coleccion los separa: documentos RAG viven en una coleccion, entradas de memoria en otra. La capa de recuperacion consulta ambos, y los resultados se fusionan en un solo bloque de contexto.

Asi funciona esa fusion en la practica:

typescript
interface RetrievalResult {
  text: string;
  score: number;
  source: 'rag' | 'memory';
  metadata: Record<string, unknown>;
}
 
async function hybridRetrieval(
  query: string,
  userId: string,
  ragStore: VectorMemoryStore,
  memoryStore: VectorMemoryStore,
  options: {
    ragTopK?: number;
    memoryTopK?: number;
    ragWeight?: number;
    memoryWeight?: number;
  } = {}
): Promise<RetrievalResult[]> {
  const {
    ragTopK = 3,
    memoryTopK = 3,
    ragWeight = 0.5,
    memoryWeight = 0.5,
  } = options;
 
  const [ragResults, memResults] = await Promise.all([
    ragStore.search(query, ragTopK),
    memoryStore.search(query, memoryTopK, (meta) =>
      meta.userId === userId
    ),
  ]);
 
  const combined: RetrievalResult[] = [
    ...ragResults.map(r => ({
      ...r,
      score: r.score * ragWeight,
      source: 'rag' as const,
    })),
    ...memResults.map(r => ({
      ...r,
      score: r.score * memoryWeight,
      source: 'memory' as const,
    })),
  ];
 
  // Sort by weighted score, interleave sources
  return combined.sort((a, b) => b.score - a.score);
}

La ponderacion entre resultados de RAG y memoria depende del caso de uso. Agentes de soporte al cliente podrian ponderar mas la memoria (el historial del cliente importa mas que documentos genericos). Un agente de soporte tecnico podria ponderar mas RAG (la respuesta esta en la documentacion, la memoria proporciona contexto). Si trabajas con bases de conocimiento, las funciones de memoria y base de conocimiento de Chanl manejan esta orquestacion de recuperacion, permitiendote configurar el balance por agente.

Arquitecturas de memoria en produccion

Varios sistemas de codigo abierto y comerciales han surgido especificamente para memoria de agentes. Entender sus enfoques te ayuda a decidir si construir o integrar, y que patrones adoptar si construyes el tuyo.

MemGPT / Letta: Niveles inspirados en sistemas operativos

El paper de MemGPT (arXiv:2310.08560) introdujo la idea de tratar la ventana de contexto del LLM como un sistema operativo trata la RAM. Asi como un SO pagina datos entre memoria rapida y disco, MemGPT pagina informacion entre el contexto del LLM (memoria central) y almacenamiento externo (memoria de archivo).

Letta, el sistema de produccion construido a partir de la investigacion de MemGPT, implementa tres niveles:

  • Memoria central: siempre en la ventana de contexto. Contiene la persona del agente, hechos clave sobre el usuario actual y estado de conversacion activo. Limitada en tamano, como la RAM.
  • Memoria de evocacion: historial de conversacion buscable. El agente puede consultar sesiones pasadas emitiendo llamadas a funciones de recuperacion de memoria. Analogo a una cache de disco.
  • Memoria de archivo: almacenamiento a largo plazo para cualquier informacion que el agente quiera preservar. Respaldada por una base de datos vectorial. Analogo a disco.

La idea revolucionaria es que el agente gestiona su propia memoria a traves de llamadas a herramientas. En lugar de un sistema separado decidiendo que almacenar, el agente mismo llama a core_memory_append, archival_memory_insert o conversation_search como herramientas. Esto le da al agente agencia sobre lo que recuerda, una forma de metacognicion.

Zep: Grafos de conocimiento temporales

Zep toma un enfoque completamente diferente. En lugar de recuperacion vectorial plana, construye un grafo de conocimiento temporal a partir de conversaciones: los nodos representan entidades (personas, productos, eventos), las aristas representan relaciones, y cada elemento lleva metadatos temporales mostrando cuando era verdadero.

Su paper de enero 2025 (arXiv:2501.13956) reporta mejoras de precision de hasta 18.5% sobre implementaciones base y 90% menos latencia. El componente temporal es el diferenciador clave: Zep puede responder "cual era la direccion del cliente antes de que se mudara?" porque el grafo preserva el estado historico, no solo el estado actual.

Esto importa para agentes de experiencia del cliente donde el contexto evoluciona con el tiempo. Las preferencias, direcciones, detalles de cuenta y relaciones de un cliente cambian. Un almacen de memoria plano sobreescribe el historial, mientras que un grafo temporal preserva la linea de tiempo completa.

Mem0: Extraccion y consolidacion inteligente

Mem0 toma el enfoque mas cercano a la consolidacion de memoria humana. En lugar de almacenar fragmentos crudos de conversacion, usa un LLM para extraer memorias significativas de las interacciones, consolidar informacion superpuesta y degradar entradas irrelevantes con el tiempo.

Su paper de abril 2025 reporta una mejora del 26% en metricas de calidad LLM-como-juez sobre recuperacion cruda, 91% menos latencia P95 y mas del 90% de ahorro en costos de tokens. La clave es la selectividad: no cada oracion en una conversacion merece convertirse en una memoria. El pipeline de extraccion de Mem0 filtra por informacion reutilizable en futuras interacciones.

La arquitectura soporta tres ambitos de memoria: memoria de usuario (persiste entre todas las sesiones con una persona), memoria de sesion (dentro de una sola conversacion) y memoria de agente (especifica de una instancia particular de agente). Este mapeo se alinea naturalmente con los cuatro tipos de memoria que construimos antes.

Eligiendo una arquitectura

EnfoqueMejor paraCompromiso
BufferPrototipos, sesion unicaPierde contexto entre sesiones
ResumenConversaciones largas, sensible al costoPierde detalles especificos
Recuperacion vectorialEvocacion entre sesiones, historial grandeRequiere infraestructura de embeddings
Niveles de SO (Letta)Agentes que necesitan metacognicionComplejidad, overhead de llamadas a herramientas
Grafo de conocimiento (Zep)Relaciones temporales, seguimiento de entidadesComplejidad de infraestructura de grafos
Extraccion inteligente (Mem0)Personalizacion a escalaLa calidad de extraccion varia

La mayoria de los equipos deberian comenzar con buffer + resumen para memoria de trabajo, agregar recuperacion vectorial para evocacion entre sesiones, y solo adoptar grafos de conocimiento o frameworks de memoria completos cuando los enfoques mas simples alcancen limites medibles.

Consolidacion de memoria: De crudo a refinado

La consolidacion de memoria, el proceso de convertir experiencias crudas en conocimiento duradero y recuperable, es donde ocurre la magia. Sin ella, tu almacen de memoria se convierte en una pila en constante crecimiento de fragmentos de conversacion. Con ella, el agente genuinamente aprende y mejora con el tiempo.

El pipeline de consolidacion se ejecuta asincronamente despues de cada sesion, extrayendo conocimiento estructurado de conversacion no estructurada:

typescript
interface ConsolidationResult {
  newFacts: SemanticMemory[];
  updatedFacts: SemanticMemory[];
  episodes: Episode[];
  procedures: Procedure[];
}
 
class MemoryConsolidator {
  private openai: OpenAI;
 
  constructor(apiKey: string) {
    this.openai = new OpenAI({ apiKey });
  }
 
  async consolidate(
    userId: string,
    sessionId: string,
    transcript: string,
    existingFacts: SemanticMemory[]
  ): Promise<ConsolidationResult> {
    const response = await this.openai.chat.completions.create({
      model: 'gpt-4o',
      temperature: 0.1,
      response_format: { type: 'json_object' },
      messages: [
        {
          role: 'system',
          content: `Analyze this conversation and extract structured memory.
 
Return JSON with:
{
  "facts": [{ "text": "...", "confidence": 0-1, "category": "preference|fact|behavior", "updates": "id-of-existing-fact-if-updating|null" }],
  "episodes": [{ "event": "...", "importance": 1-10, "outcome": "..." }],
  "procedures": [{ "name": "...", "description": "...", "steps": ["step1", "step2"], "context": "when-to-use" }]
}
 
Existing facts about this user:
${existingFacts.map(f => `[${f.id}] ${f.fact}`).join('\n') || 'None'}
 
Rules:
- Update existing facts when new information supersedes them
- Only extract episodes that are noteworthy (importance >= 5)
- Only extract procedures from successful resolution patterns
- Confidence 0.9+ for explicit statements, 0.5-0.8 for inferences`
        },
        { role: 'user', content: transcript }
      ]
    });
 
    const parsed = JSON.parse(
      response.choices[0].message.content || '{}'
    );
 
    // Transform into typed results
    const result: ConsolidationResult = {
      newFacts: [],
      updatedFacts: [],
      episodes: [],
      procedures: [],
    };
 
    for (const fact of parsed.facts || []) {
      const memory: SemanticMemory = {
        id: fact.updates || randomUUID(),
        userId,
        fact: fact.text,
        confidence: fact.confidence,
        source: sessionId,
        category: fact.category,
        lastAccessed: new Date(),
        accessCount: 0,
        createdAt: new Date(),
        updatedAt: new Date(),
      };
 
      if (fact.updates) {
        result.updatedFacts.push(memory);
      } else {
        result.newFacts.push(memory);
      }
    }
 
    for (const ep of parsed.episodes || []) {
      result.episodes.push({
        id: randomUUID(),
        userId,
        sessionId,
        event: ep.event,
        context: transcript.slice(0, 200),
        outcome: ep.outcome,
        importance: ep.importance,
        timestamp: new Date(),
        tags: [],
      });
    }
 
    return result;
  }
}

Este pipeline de consolidacion refleja lo que los cientificos cognitivos llaman "consolidacion de memoria durante el sueno": el cerebro reproduce experiencias y extrae patrones. La version del agente se ejecuta despues de cada sesion, destilando conversacion cruda en los tres tipos de memoria a largo plazo: hechos semanticos, eventos episodicos y conocimiento procedural.

Puntuacion y clasificacion de memorias recuperadas

Recuperar memorias es solo la mitad del problema. La otra mitad es clasificarlas, decidir que memorias merecen ocupar el espacio limitado en la ventana de contexto del LLM. El paper de agentes generativos de Stanford establecio el enfoque estandar: una combinacion ponderada de recencia, importancia y relevancia.

Aqui hay un calificador de recuperacion de grado de produccion:

typescript
interface ScoredMemory {
  content: string;
  recencyScore: number;
  importanceScore: number;
  relevanceScore: number;
  finalScore: number;
  source: 'episodic' | 'semantic' | 'vector';
}
 
function scoreMemories(
  candidates: Array<{
    content: string;
    timestamp: Date;
    importance: number;       // 1-10
    similarityScore: number;  // 0-1 (from vector search)
    source: 'episodic' | 'semantic' | 'vector';
  }>,
  weights: {
    recency: number;
    importance: number;
    relevance: number;
  } = { recency: 0.3, importance: 0.3, relevance: 0.4 }
): ScoredMemory[] {
  const now = Date.now();
 
  // Find the range of timestamps for normalization
  const timestamps = candidates.map(c => c.timestamp.getTime());
  const oldest = Math.min(...timestamps);
  const newest = Math.max(...timestamps);
  const timeRange = newest - oldest || 1;
 
  return candidates
    .map(candidate => {
      // Recency: exponential decay, most recent = 1.0
      const age = now - candidate.timestamp.getTime();
      const recencyScore = Math.exp(-age / (7 * 24 * 60 * 60 * 1000)); // 7-day half-life
 
      // Importance: normalize to 0-1
      const importanceScore = candidate.importance / 10;
 
      // Relevance: already 0-1 from vector similarity
      const relevanceScore = candidate.similarityScore;
 
      const finalScore =
        weights.recency * recencyScore +
        weights.importance * importanceScore +
        weights.relevance * relevanceScore;
 
      return {
        content: candidate.content,
        recencyScore,
        importanceScore,
        relevanceScore,
        finalScore,
        source: candidate.source,
      };
    })
    .sort((a, b) => b.finalScore - a.finalScore);
}

La distribucion de pesos importa. Para agentes de soporte al cliente, la relevancia deberia dominar (0.5+) porque el agente necesita mostrar memorias contextualmente apropiadas. Para agentes de asistente personal, la recencia importa mas: las solicitudes recientes del usuario anulan patrones mas antiguos. Para aplicaciones sensibles al cumplimiento, la importancia deberia tener mayor peso para asegurar que hechos criticos (como el estado de consentimiento o restricciones de cuenta) siempre aparezcan.

Si estas construyendo sistemas de prompts que integren contexto de memoria, los pesos de puntuacion se convierten en parte de tu ingenieria de prompts: determinan lo que el modelo ve y por lo tanto como responde.

Diseno de memoria con privacidad primero

La memoria crea una tension: mientras mas recuerda tu agente, mas util se vuelve, y mas riesgo de privacidad conlleva. Construir memoria sin controles de privacidad no es solo un problema regulatorio, es un problema de confianza. Cuando el 82% de los consumidores ven el manejo de datos por IA como una amenaza seria, hacerlo bien es una ventaja competitiva.

La autoridad de proteccion de datos de Espana (AEPD) publico una guia de 71 paginas en febrero de 2026 abordando especificamente los riesgos de memoria de agentes de IA. Identifican cuatro dimensiones criticas: relevancia (lo que se almacena debe ser controlado), consistencia (los datos almacenados deben ser precisos), retencion (los datos no deben persistir mas alla de lo necesario) e integridad (la informacion almacenada debe resistir manipulacion).

Que almacenar y que no

No todo el contenido de una conversacion merece convertirse en una memoria. El principio de minimizacion de datos, requerido por GDPR, HIPAA y CCPA, significa almacenar solo lo que es adecuado, relevante y necesario para el proposito declarado.

Un marco practico de clasificacion:

typescript
type MemoryTier = 'transient' | 'short-term' | 'long-term' | 'never-store';
 
interface MemoryClassification {
  tier: MemoryTier;
  retentionDays: number | null;  // null = until explicit deletion
  requiresConsent: boolean;
  piiCategory?: string;
}
 
function classifyForStorage(content: string): MemoryClassification {
  // Tier 1: Never store — sensitive PII, health, financial details
  const neverStorePatterns = [
    /\b\d{3}-?\d{2}-?\d{4}\b/,              // SSN
    /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/, // Credit card
    /\b(?:password|ssn|social security)\b/i,
  ];
 
  if (neverStorePatterns.some(p => p.test(content))) {
    return {
      tier: 'never-store',
      retentionDays: 0,
      requiresConsent: false,
      piiCategory: 'sensitive',
    };
  }
 
  // Tier 2: Transient — current session only
  const transientPatterns = [
    /\b(?:hold on|one moment|let me check)\b/i,
    /\b(?:yes|no|okay|sure|thanks)\b/i,
  ];
 
  if (transientPatterns.some(p => p.test(content)) && content.length < 50) {
    return {
      tier: 'transient',
      retentionDays: 1,
      requiresConsent: false,
    };
  }
 
  // Tier 3: Short-term — operational retention (30-90 days)
  // Default for conversation content not matching other categories
  return {
    tier: 'short-term',
    retentionDays: 90,
    requiresConsent: false,
  };
}

Este es un punto de partida: los sistemas en produccion usan clasificacion basada en LLM para matices que las regex no pueden capturar. El principio se mantiene: clasifica antes de almacenar, aplica politicas de retencion automaticamente y por defecto usa retencion mas corta cuando hay incertidumbre.

Eliminacion de memoria y el derecho al olvido

El Articulo 17 del GDPR otorga a las personas el derecho a solicitar la eliminacion de datos. Para sistemas de memoria de IA, esto significa que necesitas poder eliminar todas las memorias asociadas con un usuario especifico sin destruir los datos de otros usuarios ni romper el sistema.

La decision arquitectonica que hace esto posible: mantiene la memoria en bases de datos de recuperacion (almacenes vectoriales, almacenes clave-valor) en lugar de integrar datos de usuarios en modelos afinados. Puedes eliminar una entrada vectorial. No puedes des-entrenar un modelo.

typescript
class PrivacyAwareMemoryStore {
  private store: VectorMemoryStore;
 
  constructor(apiKey: string) {
    this.store = new VectorMemoryStore(apiKey);
  }
 
  async storeWithConsent(
    text: string,
    userId: string,
    consentBasis: 'explicit' | 'legitimate-interest' | 'contract',
    retentionDays: number
  ): Promise<string> {
    const classification = classifyForStorage(text);
 
    if (classification.tier === 'never-store') {
      // Log the rejection for audit, don't store the content
      console.log(`Rejected storage: sensitive content detected for user ${userId}`);
      return 'rejected';
    }
 
    const expiresAt = new Date();
    expiresAt.setDate(
      expiresAt.getDate() +
      Math.min(retentionDays, classification.retentionDays || retentionDays)
    );
 
    return this.store.store(text, {
      userId,
      consentBasis,
      retentionDays,
      expiresAt: expiresAt.toISOString(),
      storedAt: new Date().toISOString(),
    });
  }
 
  // Honor right to erasure — delete ALL memories for a user
  async deleteUserMemories(userId: string): Promise<number> {
    // In production, this queries the vector DB with a metadata filter
    // and deletes all matching entries
    let deleted = 0;
    // ... deletion logic against your vector database
    return deleted;
  }
 
  // Automated retention enforcement — run daily
  async enforceRetention(): Promise<number> {
    const now = new Date().toISOString();
    // Delete all entries where expiresAt < now
    let expired = 0;
    // ... expiration logic against your vector database
    return expired;
  }
}

El informe de la Fundacion New America de 2025 sobre agentes de IA y memoria destaca un punto critico: cuando los agentes interactuan con servicios externos a traves de protocolos como MCP, datos personales pueden fluir a terceros sin el conocimiento del usuario. La gobernanza de memoria no se trata solo de lo que tu agente almacena, se trata de lo que se transmite durante la ejecucion de herramientas y llamadas a APIs externas.

Registros de auditoria

Cada operacion de memoria, creacion, acceso, modificacion, eliminacion, deberia producir un registro de auditoria. Esto no es solo overhead de cumplimiento. Cuando un cliente pregunta "por que tu agente dijo eso?", el registro de auditoria te dice que memorias informaron la respuesta.

typescript
interface MemoryAuditEntry {
  action: 'create' | 'read' | 'update' | 'delete';
  memoryId: string;
  userId: string;
  agentId: string;
  sessionId: string;
  timestamp: Date;
  reason: string;
  metadata?: Record<string, unknown>;
}

Los sistemas de monitoreo y analitica en produccion deberian rastrear patrones de acceso a memoria junto con metricas de calidad de conversacion. Si el puntaje de calidad de un agente baja, verificar que memorias recupero (o fallo en recuperar) es frecuentemente el camino mas rapido al diagnostico, un patron cubierto en profundidad en Como evaluar agentes de IA.

Errores comunes y como evitarlos

Construir sistemas de memoria ensena lecciones dificiles. Aqui estan las que mas tiempo cuestan.

Almacenar todo

La tentacion es fuerte: el disco es barato, los embeddings son baratos, entonces por que no almacenar cada mensaje? Porque la calidad de recuperacion se degrada cuando el almacen de memoria se llena de ruido. "Hola" y "puedes esperar un segundo" no necesitan ser memorias buscables. Filtra antes de almacenar: un simple umbral de relevancia (similitud de embedding con el tema de conversacion > 0.3) elimina la mayor parte del ruido.

Ignorar conflictos de memoria

Cuando un cliente dice "me mude a Portland" pero su cuenta aun muestra Seattle, tienes un conflicto. Los sistemas de memoria ingenuos almacenan ambos, y el agente se confunde: a veces usa uno, a veces el otro. Siempre implementa resolucion de conflictos: memorias mas nuevas de alta confianza deben actualizar o sobreescribir las antiguas que las contradigan.

Falta de contexto temporal

"El cliente prefiere correo electronico" es menos util que "el cliente dijo que prefiere correo electronico el 5 de marzo de 2026, durante una disputa de facturacion." Los metadatos temporales hacen que las memorias sean auditables, depurables y eliminables. Almacena marcas de tiempo en todo.

Recuperacion unica para todo

Diferentes preguntas necesitan diferentes tipos de memoria. "Cual es el metodo de contacto preferido de este cliente?" necesita memoria semantica. "Que paso la ultima vez que llamo?" necesita memoria episodica. "Como debo manejar una solicitud de reembolso?" necesita memoria procedural. Dirige las consultas al tipo de memoria apropiado en lugar de buscar todo uniformemente.

Pasar por alto la memoria en la evaluacion

Si estas evaluando tu agente (y deberias, ve como construir un marco de evaluacion), la memoria necesita ser parte de la evaluacion. Los casos de prueba deben cubrir: El agente recupera correctamente informacion de una sesion anterior? Maneja preferencias actualizadas? Evita mostrar informacion que el usuario pidio olvidar? La memoria es una funcionalidad, y las funcionalidades necesitan pruebas.

Que sigue para la memoria de agentes

El campo se mueve rapido. Tres direcciones se destacan.

Memoria como servicio. Capas de memoria dedicadas, Mem0, Zep, Letta, se estan convirtiendo en infraestructura a la que los agentes se conectan en lugar de construir desde cero. De la misma manera que no construyes tu propia base de datos, podrias no necesitar construir tu propio sistema de memoria. Pero entender los internos te ayuda a evaluar que enfoque se ajusta a tu caso de uso y depurar cuando las cosas salen mal.

Memoria basada en grafos. Los almacenes vectoriales planos tratan cada memoria como independiente. Los grafos de conocimiento capturan relaciones entre memorias: este cliente esta conectado a esa cuenta, que usa este producto, que tuvo ese problema. El enfoque de grafo de conocimiento temporal de Zep reporta mejoras significativas de precision para tareas que requieren sintesis entre sesiones. Espera que la memoria basada en grafos se convierta en el estandar para dominios complejos con muchas relaciones.

Estandares de gobernanza de memoria. La Ley de IA de la UE se aplica completamente en agosto de 2026. La AEPD de Espana ya publico orientacion detallada sobre memoria de agentes. ICLR 2026 acepto un taller especificamente sobre "Memory for LLM-Based Agentic Systems." Las comunidades regulatorias y de investigacion estan convergiendo en la posicion de que la memoria del agente no es solo una funcionalidad, es una preocupacion de confianza y seguridad que necesita marcos de gobernanza.

La memoria es lo que separa a los agentes que responden preguntas de los agentes que construyen relaciones. Los fundamentos tecnicos son directos: buffers para la conversacion actual, resumenes para compresion, almacenes vectoriales para recuperacion y pipelines de extraccion para consolidacion. Lo dificil son las decisiones de diseno: que recordar, que olvidar y como mostrar el contexto correcto en el momento correcto. Comienza simple, mide lo que importa y deja que las necesidades de tus usuarios impulsen la complejidad.

Construye agentes de IA con memoria persistente

Chanl maneja la gestion de memoria, recuperacion y controles de privacidad, para que tus agentes recuerden lo que importa y olviden lo que deben.

Comienza a construir gratis
DG

Co-founder

Building the platform for AI agents at Chanl — tools, testing, and observability for customer experience.

Aprende IA Agéntica

Una lección por semana: técnicas prácticas para construir, probar y lanzar agentes IA. Desde ingeniería de prompts hasta monitoreo en producción. Aprende haciendo.

500+ ingenieros suscritos

Frequently Asked Questions