Saltar a contenido

Traducción automática

Este artículo se tradujo automáticamente a partir de la versión original en inglés.

Arquitectura de memoria para agentes de IA en 2026: checkpoints, almacenes vectoriales y memoria basada en archivos

Parte 2 de la serie Engineering the Agentic Stack

El estado es lo que separa a un chatbot de un agente de IA. Sin memoria de agente, cada interacción empieza desde cero. El agente no puede pausar y reanudar, aprender de sesiones pasadas ni personalizar. La Parte 1 trató los bucles de razonamiento de agentes de IA: cómo piensa un agente. Esta entrada trata de la arquitectura de memoria que determina qué recuerda.

Voy a recorrer la arquitectura de memoria del Market Analyst Agent, mostrando cómo los checkpoints calientes, los almacenes vectoriales fríos y la memoria documental basada en archivos trabajan juntos en agentes de larga ejecución. Después cubriré cuándo tienen sentido PostgreSQL, Redis, Qdrant, los almacenes clave-valor y los archivos Markdown simples.

TL;DR: la memoria de un agente de IA se divide en tres capas. La memoria caliente son checkpoints a nivel de hilo en Redis o PostgreSQL para pausar/reanudar. La memoria fría es conocimiento entre sesiones en un almacén vectorial o backend clave-valor para personalización. La memoria documental son archivos legibles por humanos que el agente lee y escribe para conocimiento persistente del proyecto. El sistema de checkpoints de LangGraph gestiona la capa caliente de forma nativa. Para memoria fría, la búsqueda vectorial con Qdrant te da recuperación semántica; un almacén clave-valor basta para hechos estructurados. Para memoria documental, un almacén basado en archivos te da depurabilidad y cero infraestructura de embeddings.


Por qué importa la arquitectura de memoria de un agente de IA

Un agente sin estado es un autocompletado sofisticado. Recibe una petición, devuelve una respuesta y lo olvida todo. Eso funciona para preguntas y respuestas de un solo turno. Se rompe en cuanto necesitas:

  • Pausar y reanudar: un usuario inicia una tarea de investigación, cierra el portátil y vuelve mañana. Sin estado checkpointado, el agente reinicia desde cero.
  • Coherencia multi-turno: en una conversación larga el agente tiene que recordar qué herramientas llamó, qué datos recopiló y qué pasos del plan terminó.
  • Personalización: un usuario recurrente espera que el agente conozca su tolerancia al riesgo, la profundidad de análisis preferida y sus interacciones pasadas.
  • Human-in-the-loop (HITL): el agente redacta un informe y espera aprobación. El estado de “espera” tiene que sobrevivir a reinicios del proceso.

Tomemos el Market Analyst Agent de la Parte 1. Un usuario pide “Analiza NVDA”. El agente construye un plan, llama a cinco herramientas, recopila datos y redacta un informe. El usuario responde “está bien, pero añade análisis de competidores”. Sin estado checkpointado, el agente no tiene ni idea de a qué se refiere “está bien” y tiene que empezar de nuevo. Con un almacén de checkpoints, carga el estado del último nodo, incluida la investigación ya recopilada, y solo añade un paso de competidores.

Ahora imagina que el mismo usuario vuelve una semana después: “Actualiza mi análisis de NVDA”. Sin memoria a largo plazo, el agente no sabe que este usuario prefiere evaluaciones de riesgo conservadoras o que le interesan las acciones de semiconductores. Con un almacén de memoria respaldado por vectores, recuerda esos hechos y personaliza la respuesta sin volver a preguntarlos.

LangGraph separa esto limpiamente. Cada ejecución del grafo se ejecuta dentro de un thread: una única conversación o tarea. El estado dentro de un thread es memoria de trabajo. El estado que persiste entre threads es memoria a largo plazo. La pregunta de ingeniería es qué backend de almacenamiento usar para cada uno.

Taxonomía de memoria


Una taxonomía de la memoria de agentes de IA

Antes de entrar en la implementación, ayuda clasificar qué necesitan recordar los agentes. El framework CoALA (Sumers, Yao et al., 2023) es la taxonomía estándar y se apoya en ciencia cognitiva. Introduje el alcance de memoria en mi post sobre context engineering; aquí lo amplío a seis categorías:

Tipo de memoria Alcance Duración Ejemplo Patrón de almacenamiento
Working Paso actual Milisegundos Argumentos de llamada a herramientas, respuesta actual del LLM En proceso (Python dict)
Short-term Thread actual Minutos–horas Historial de conversación, progreso del plan, datos recopilados Almacén de checkpoints
Episodic Entre threads Días–meses “La semana pasada el usuario preguntó por resultados de NVDA” Almacén vectorial / KV
Semantic Entre threads Meses–permanente “El usuario prefiere inversiones conservadoras” Almacén vectorial / KV
Document Entre threads Días–permanente Notas del proyecto, resúmenes de investigación, patrones aprendidos Almacén de archivos (Markdown/JSON)
Procedural Todo el sistema Permanente “Al analizar acciones, revisa siempre los SEC filings” Config / system prompt

La working memory es con lo que el LLM está razonando activamente ahora mismo: variables de Python en la función actual, el contenido de la ventana de contexto, argumentos de llamada a herramientas a mitad de ejecución. Es la capa más rápida y más efímera. Nada persiste más allá del paso actual. La memoria de trabajo está limitada por la ventana de contexto del modelo, lo que la convierte en el verdadero cuello de botella. Todo lo que el agente “sabe” en el momento de decidir tiene que caber aquí, venga del almacén de checkpoints, de una consulta vectorial o de la lectura de un archivo. Las demás capas existen para alimentar la información correcta a la memoria de trabajo en el momento adecuado.

La memoria a corto plazo es el almacén de checkpoints que LangGraph escribe después de cada ejecución de nodo. La memoria episódica y la semántica son almacenes a largo plazo que persisten entre threads. La memoria documental es conocimiento estructurado que el agente acumula con el tiempo —notas del proyecto, resúmenes de investigación, convenciones aprendidas— almacenado en archivos legibles por humanos que tanto el agente como el desarrollador pueden inspeccionar y editar. La memoria procedimental está codificada en el system prompt y en las definiciones de herramientas; no cambia por usuario.

En la práctica, esto se reduce a tres capas: memoria caliente (working + short-term) para la sesión actual, memoria fría (episodic + semantic) para recuperación entre sesiones y memoria documental para conocimiento acumulado del proyecto que se beneficia de ser legible por humanos y editable directamente.

La mayoría de frameworks y estudios se centran en la división caliente/fría y omiten la memoria documental, pese a lo extendida que está. El framework CoALA clasifica la memoria como working, episodic, semantic y procedural; no menciona archivos. El survey Memory in the Age of AI Agents cubre almacenes vectoriales y grafos de conocimiento, pero no archivos documentales. La documentación de LangGraph cubre checkpoints y la interfaz Store, pero no tiene un concepto nativo de memoria basada en archivos.

En la práctica, la memoria documental es ya una opción por defecto en asistentes de programación con IA. Claude Code, Cursor, Windsurf y Devin tratan la memoria basada en archivos como una funcionalidad central. Y el patrón se está extendiendo más allá de la programación: agentes para juegos de mundo abierto almacenan habilidades reutilizables como bibliotecas de código (Voyager), agentes empresariales ganadores de competiciones iteran sobre documentos procedimentales de prompts entre ejecuciones (ECR3 winning approaches), y agentes de automatización web sintetizan APIs de flujo de trabajo reutilizables a partir de episodios exitosos (Agent Workflow Memory). Las ventajas —depurabilidad, control de versiones, cero infraestructura— no son específicas de la programación.

Una observación útil del mismo survey: la memoria de agente no es lo mismo que RAG o context engineering. La característica diferencial es que el propio agente hace las operaciones de lectura/escritura. Decide qué recordar y qué olvidar, en lugar de depender de un pipeline de recuperación fijo.

El artículo Generative Agents (Park et al., 2023) mostró hasta dónde puede llegar esto: agentes simulados que almacenaban, reflexionaban sobre y recuperaban sus propios recuerdos produjeron un comportamiento sorprendentemente humano. El patrón arquitectónico —un memory stream con recuperación puntuada por actualidad, importancia y relevancia— es la plantilla sobre la que siguen construyéndose la mayoría de sistemas modernos de memoria para agentes.


Memoria de agente a corto plazo: el almacén de checkpoints

Cada vez que se ejecuta un nodo de LangGraph, el framework serializa el estado completo del grafo y lo escribe en un almacén de checkpoints. Esa es la base para pausar/reanudar, depuración con time-travel y flujos HITL.

Flujo de checkpoints de memoria caliente

Un checkpoint contiene el estado del grafo necesario para reanudar: el AgentState de la Parte 1 (mensajes, identidad, perfil de usuario, pasos del plan, datos de investigación, modo de ejecución), además de metadatos de LangGraph como qué nodo lo produjo y un número de secuencia monótonamente creciente. Cuando el grafo se reanuda —tras una interrupción HITL o un reinicio del proceso— carga el último checkpoint y continúa exactamente donde se detuvo. Un checkpoint no es lo mismo que un log append-only de eventos o un trace; la Parte 5 separa explícitamente esas superficies de observabilidad en runtime.

Cómo funciona el checkpointing en LangGraph

El BaseCheckpointSaver de LangGraph es una interfaz simple: put() escribe un checkpoint, get_tuple() lee el último de un thread, list() devuelve el historial. Cada checkpoint está indexado por (thread_id, checkpoint_ns, checkpoint_id), donde thread_id identifica la conversación, checkpoint_ns gestiona el namespacing del subgrafo y checkpoint_id es una versión única.

La decisión importante es qué backend poner detrás. LangGraph ofrece dos opciones de producción: PostgreSQL y Redis.

PostgreSQL vs Redis

Redis vs PostgreSQL

Dimensión PostgreSQL (langgraph-checkpoint-postgres) Redis (langgraph-checkpoint-redis)
Latencia de lectura ~0.65ms ~0.095ms
Latencia de escritura ~2ms (unlogged) a 10ms (con WAL) ~0.095ms
Throughput ~15K txn/s ~893K req/s
Durabilidad ACID completo, WAL + replicación Configurable (AOF/RDB), riesgo de pérdida de datos
Historial de checkpoints Historial completo (resume, depuración con time-travel) Retención configurable vía maxcount
Coste operativo Moderado (operación estándar de RDBMS) Más alto (limitado por RAM, gestión de memoria)
Patrón de escalado Vertical + réplicas de lectura Horizontal (Redis Cluster)
Mejor para Reanudación y depuración con prioridad en durabilidad Baja latencia, alto throughput, tiempo real

Benchmarks de latencia de comparativas de CyberTec y RisingWave.

PostgreSQL: el valor por defecto duradero

PostgreSQL es la opción por defecto más segura para la mayoría de equipos. Los checkpoints sobreviven a caídas, obtienes semántica transaccional completa y el historial de checkpoints hace sencilla la depuración con time-travel.

De checkpointer_setup.py:

from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver

async def create_postgres_checkpointer(connection_string: str) -> AsyncPostgresSaver:
    """Create a PostgreSQL-backed checkpoint store.

    PostgreSQL gives us ACID guarantees — if a checkpoint write succeeds,
    the state is durable even if the process crashes immediately after.
    """
    checkpointer = AsyncPostgresSaver.from_conn_string(connection_string)

    # Create the checkpoint tables if they don't exist.
    # This is idempotent — safe to call on every startup.
    await checkpointer.setup()

    return checkpointer

# Usage: wire into the graph compilation
checkpointer = await create_postgres_checkpointer(
    "postgresql://user:pass@localhost:5432/agent_memory"
)
graph = create_graph(checkpointer=checkpointer)

# Every invoke/stream call now persists state automatically
config = {"configurable": {"thread_id": "user-123-session-1"}}
result = await graph.ainvoke({"messages": [HumanMessage(content="Analyze NVDA")]}, config)

# Resume later — loads the latest checkpoint for this thread
result = await graph.ainvoke({"messages": [HumanMessage(content="approved")]}, config)

El AsyncPostgresSaver usa el paquete langgraph-checkpoint-postgres, que crea tres tablas: checkpoints (el estado serializado), checkpoint_blobs (datos binarios grandes) y checkpoint_writes (escrituras pendientes para recuperación ante caídas). El esquema soporta acceso concurrente y usa advisory locks para evitar conflictos de escritura.

Redis: cuando la latencia es el cuello de botella

Cuando importa una latencia de checkpoint por debajo del milisegundo (agentes conversacionales en tiempo real, bucles de herramientas de alta frecuencia), Redis es la mejor opción.

De checkpointer_setup.py:

from langgraph.checkpoint.redis.aio import AsyncRedisSaver

async def create_redis_checkpointer(redis_url: str) -> AsyncRedisSaver:
    """Create a Redis-backed checkpoint store.

    Redis stores checkpoints in memory for sub-millisecond access.
    Trade-off: less durable than PostgreSQL unless AOF is enabled.
    """
    checkpointer = AsyncRedisSaver.from_conn_string(redis_url)

    # Initialize Redis data structures
    await checkpointer.setup()

    return checkpointer

# Usage: same graph API, different backend
checkpointer = await create_redis_checkpointer("redis://localhost:6379")
graph = create_graph(checkpointer=checkpointer)

El AsyncRedisSaver de langgraph-checkpoint-redis almacena checkpoints como documentos JSON indexados por thread ID. El rediseño v0.1.0 sustituyó múltiples operaciones de búsqueda por una sola llamada a JSON.GET, reduciendo significativamente la latencia. Redis 8.0+ incluye RedisJSON y RediSearch por defecto; no hay módulos extra que instalar.

Para despliegues con memoria limitada, ShallowRedisSaver almacena solo el último checkpoint por thread: sin historial, pero con uso mínimo de RAM. Úsalo cuando necesites pausar/reanudar pero no depuración con time-travel.

Cuándo usar cada uno

Usa PostgreSQL cuando:

  • Necesitas historial completo de checkpoints para depuración con time-travel o reanudación reproducible
  • La durabilidad no es negociable (servicios financieros, sanidad)
  • Ya ejecutas PostgreSQL en tu stack
  • Tu agente ejecuta tareas largas donde perder el estado implica horas de recomputación
  • Quieres un almacén de datos unificado: PostgreSQL con pgvector puede servir como backend único para checkpoints, memoria a largo plazo y búsqueda vectorial, simplificando tu infraestructura

Usa Redis cuando:

  • La latencia de checkpoints es tu cuello de botella (chat en tiempo real, UX en streaming)
  • Estás construyendo voice bots: los pipelines STT-to-LLM-to-TTS necesitan acceso al estado por debajo del milisegundo
  • Necesitas escalado horizontal entre muchos threads concurrentes
  • Patrones fan-out de alta concurrencia donde varios agentes comparten estado
  • Sesiones de vida corta donde perder un checkpoint es recuperable
  • Quieres caché semántica para reducir llamadas redundantes al LLM (Redis LangCache cachea consultas semánticamente similares para evitar llamadas repetidas al LLM)

Otras opciones: langgraph-checkpoint-sqlite funciona para desarrollo local y despliegues de un solo proceso. Para stacks nativos de AWS, langgraph-checkpoint-aws proporciona un DynamoDBSaver con manejo inteligente del payload: los checkpoints pequeños (<350 KB) se quedan en DynamoDB, los grandes se descargan automáticamente a S3. El pricing serverless y no tener infraestructura que gestionar lo hacen atractivo para despliegues con carga variable.


Memoria a largo plazo: recordar entre sesiones

La memoria caliente gestiona la conversación actual. Pero ¿qué pasa con el usuario que vuelve la semana siguiente? La memoria a largo plazo almacena hechos, preferencias e historial de interacciones que persisten entre threads.

LangGraph proporciona una interfaz Store para memoria entre threads mediante su clase BaseStore. Cada elemento de memoria es un par (namespace, key) con un valor JSON y un embedding vectorial opcional. El namespace suele codificar el usuario o la organización: ("user", "user-123", "preferences").

Flujo de memoria a largo plazo

Almacenamiento vectorial: recuperación semántica con Qdrant

Cuando el agente necesita recordar hechos no estructurados (“¿Qué dijo el usuario sobre su horizonte de inversión?”), la búsqueda vectorial proporciona recuperación semántica. En lugar de búsquedas exactas por clave, el agente consulta por significado.

Qdrant es una base de datos vectorial especializada escrita en Rust que gestiona almacenamiento de embeddings, indexación (HNSW) y búsqueda filtrada. Cubrí HNSW y sus trade-offs en detalle en mi post sobre search ranking. Qdrant también ofrece un servidor MCP que actúa como capa de memoria semántica, útil si tu framework de agentes soporta Model Context Protocol.

De memory_store.py:

from qdrant_client import QdrantClient
from qdrant_client.models import PointStruct, Distance, VectorParams
from langchain_anthropic import ChatAnthropic
import hashlib
import json

class UserMemoryStore:
    """Long-term memory backed by Qdrant vector search.

    Stores user facts as embedded vectors for semantic retrieval.
    Each fact is a short natural-language statement about the user.
    """

    def __init__(self, qdrant_url: str, collection_name: str = "user_memory"):
        self.client = QdrantClient(url=qdrant_url)
        self.collection_name = collection_name
        self._ensure_collection()

    def _ensure_collection(self):
        """Create the collection if it doesn't exist."""
        collections = [c.name for c in self.client.get_collections().collections]
        if self.collection_name not in collections:
            self.client.create_collection(
                collection_name=self.collection_name,
                vectors_config=VectorParams(
                    size=1536,  # text-embedding-3-small dimensions
                    distance=Distance.COSINE,
                ),
            )

    def store_fact(self, user_id: str, fact: str, embedding: list[float]):
        """Store a user fact with its embedding."""
        point_id = hashlib.md5(f"{user_id}:{fact}".encode()).hexdigest()
        self.client.upsert(
            collection_name=self.collection_name,
            points=[PointStruct(
                id=point_id,
                vector=embedding,
                payload={"user_id": user_id, "fact": fact},
            )],
        )

    def recall(self, user_id: str, query_embedding: list[float], top_k: int = 5):
        """Retrieve the most relevant facts for a user given a query."""
        results = self.client.query_points(
            collection_name=self.collection_name,
            query=query_embedding,
            query_filter={"must": [{"key": "user_id", "match": {"value": user_id}}]},
            limit=top_k,
        )
        return [hit.payload["fact"] for hit in results.points]

El flujo es: (1) después de cada conversación, un LLM extrae hechos clave de la interacción (“el usuario tiene alta tolerancia al riesgo”, “el usuario está interesado en acciones de semiconductores”), (2) los hechos se embeben y se almacenan en Qdrant, (3) al inicio de la siguiente conversación, el agente consulta Qdrant con el nuevo mensaje del usuario para recuperar contexto relevante.

Puntuación de recuperación: más allá de la similitud coseno

La similitud coseno bruta es un punto de partida, pero los sistemas de memoria en producción necesitan una recuperación más rica. El artículo Generative Agents (Park et al., 2023) introdujo una función de puntuación que combina tres señales:

  • Recency: decaimiento basado en reglas para que los recuerdos recientes puntúen más alto. Una función de decaimiento exponencial hace que un hecho de ayer supere a un hecho equivalente de hace seis meses.
  • Importance: significación evaluada por el LLM en una escala de 1 a 10. “La cartera del usuario ha caído un 40%” puntúa más que “el usuario dijo hola”.
  • Relevance: similitud coseno del embedding entre la consulta y el hecho almacenado.

La puntuación final de recuperación es una suma ponderada: score = alpha * recency + beta * importance + gamma * relevance. Así evitas que hechos frescos e importantes queden enterrados bajo otros obsoletos pero semánticamente similares. Para el Market Analyst Agent, doy más peso a relevance (0.5), seguida de recency (0.3) e importance (0.2), porque la intención actual de la consulta del usuario importa más. Son pesos iniciales adaptados del artículo Generative Agents (que usaba peso igual para todo); vi que enfatizar relevance funcionaba mejor para consultas de análisis financiero, pero los valores se basan en intuición, no están optimizados empíricamente.

Alternativas a la búsqueda vectorial

La búsqueda vectorial es potente, pero no siempre es la herramienta adecuada. Aquí tienes cuándo usar alternativas:

Enfoque Mejor para Latencia Complejidad
Búsqueda vectorial (Qdrant) Recuperación semántica de hechos no estructurados 5–20ms Media
Almacén clave-valor (Redis) Perfiles de usuario estructurados, preferencias <1ms Baja
Almacén documental (archivos) Conocimiento del proyecto, notas gestionadas por el agente 1–5ms Baja
Búsqueda full-text (PostgreSQL GIN index) Recuperación por palabras clave del historial de conversación 2–10ms Baja
Grafo de conocimiento (Neo4j) Relaciones entre entidades, razonamiento multi-hop 10–50ms Alta
Híbrido (vector + keyword) Mejor recuperación cuando la intención de consulta varía 10–30ms Media

Los almacenes clave-valor funcionan bien para datos estructurados. Si tu memoria a largo plazo es un perfil de usuario —tolerancia al riesgo, horizonte de inversión, sectores preferidos— un hash de Redis o una columna JSONB de PostgreSQL es más simple y rápida que embebiendo y consultando vectores. Usa búsqueda vectorial cuando la memoria sea no estructurada y la formulación de la consulta varíe.

El Store integrado de LangGraph proporciona una interfaz clave-valor basada en namespaces con búsqueda vectorial opcional. La API BaseStore es simple: put(), get(), search() y delete() con alcance jerárquico por namespaces. Hay tres implementaciones disponibles:

  • InMemoryStore — para desarrollo y testing (los datos se pierden al salir del proceso)
  • PostgresStore — almacén persistente de producción con consultas SQL completas
  • AsyncRedisStore — memoria entre threads con búsqueda vectorial, soporte TTL y filtrado por metadatos

La configuración index habilita búsqueda vectorial sobre los elementos almacenados usando un modelo de embeddings configurable. Para muchos casos de uso, este almacén integrado es suficiente sin recurrir a una base de datos vectorial dedicada.

from langgraph.store.memory import InMemoryStore

# Create a store with vector search enabled
store = InMemoryStore(
    index={
        "dims": 1536,
        "embed": my_embedding_function,  # e.g., OpenAI text-embedding-3-small
    }
)

# Store a user preference (namespace scopes to user)
await store.aput(
    namespace=("user", "user-123", "preferences"),
    key="risk-profile",
    value={"risk_tolerance": "high", "horizon": "long-term"},
)

# Semantic search across user's memories
results = await store.asearch(
    namespace=("user", "user-123"),
    query="What is their investment style?",
    limit=5,
)

Elegir una estrategia de memoria a largo plazo

Empieza con clave-valor si tu memoria es estructurada y bien definida (perfiles de usuario, ajustes, entidades nombradas). Añade búsqueda vectorial cuando necesites recuperación semántica sobre hechos no estructurados o cuando la formulación de la consulta varíe de forma impredecible.

Los grafos de conocimiento compensan cuando importan las relaciones entre entidades, por ejemplo: “¿Sobre qué empresas preguntó el usuario que son competidoras de NVDA?”. El proyecto reciente más interesante aquí es Graphiti (de Zep), que construye un grafo de conocimiento sensible al tiempo que rastrea cuándo eran ciertos los hechos, no solo qué era cierto. Cada arista lleva intervalos de validez, así que un cambio en la tolerancia al riesgo del usuario invalida el valor anterior en lugar de sobrescribirlo silenciosamente. Graphiti informa de una precisión del 94.8% en el benchmark DMR, y su modelo bitemporal aborda el problema de la memoria obsoleta en la propia capa de datos.

El problema es operativo. Ejecutar una base de datos de grafos no es trivial y, para la mayoría de aplicaciones de agentes, la búsqueda vectorial con filtrado por metadatos cubre el mismo terreno con menos infraestructura.

Los frameworks de memoria gestionada como Mem0 y Letta (antes MemGPT) gestionan por ti el pipeline de extracción-consolidación-recuperación. El enfoque de Mem0 es destacable: un LLM extrae recuerdos candidatos, un motor de decisión compara cada hecho nuevo con las entradas existentes del almacén vectorial, y un resolvedor decide si añadir, actualizar o borrar, manteniendo el almacén de memoria coherente y sin redundancias. Letta adopta un enfoque inspirado en sistemas operativos: los agentes gestionan su propia ventana de contexto usando herramientas de gestión de memoria, moviendo de forma autónoma datos entre “core memory” (en contexto) y “archival memory” (fuera de contexto). Ambos merecen evaluación si quieres llegar antes a producción y no necesitas control total sobre el pipeline de memoria.


Memoria documental: el archivador del agente

El patrón está muy extendido en la práctica, pero poco explorado en la literatura académica. La documentación de frameworks cubre en profundidad almacenes vectoriales, grafos de conocimiento y gestión de contexto, pero la memoria basada en archivos apenas recibe una nota al pie. Es una laguna que merece atención, porque así es como los asistentes de programación con IA más eficaces persisten realmente conocimiento. El benchmark de Letta encontró que un enfoque simple basado en filesystem obtuvo un 74.0% en el benchmark de memoria conversacional LoCoMo, superando a varias librerías especializadas de memoria. Los modelos frontier ya están entrenados en tareas de programación agentic y gestionan operaciones con archivos de forma nativa, así que el patrón encaja con sus fortalezas.

El cambio llegó con ventanas de contexto más largas. En 2023-2024, con 8k-32k tokens, no tenías más remedio que trocear documentos en chunks y embebirlos. Con ventanas de 1M+ tokens (Gemini 1.5, Claude 4), normalmente es más efectivo dejar que el agente lea el archivo entero que intentar adivinar qué fragmentos son relevantes. La historia operativa también es más simple. Puedes leer, editar y git diff un archivo Markdown. No puedes git diff una base de datos vectorial.

Los almacenes vectoriales y los backends clave-valor gestionan bien la recuperación semántica y las búsquedas estructuradas. Pero hay una tercera categoría de conocimiento del agente que ninguno de los dos cubre limpiamente: contexto acumulado del proyecto, las convenciones, notas de investigación y decisiones que el agente necesita entre sesiones y que se benefician de ser legibles por humanos y versionables.

Esto es memoria documental: el agente lee y escribe archivos estructurados (Markdown, JSON, YAML) en un directorio conocido. Sin embeddings, sin base de datos, sin infraestructura. Solo archivos en disco que tanto el agente como el desarrollador pueden cat, grep, git diff y editar a mano.

¿Por qué archivos?

Para flujos de trabajo de agentes de larga vida, el patrón más eficaz que he visto no es una base de datos vectorial. Es un directorio de notas bien organizadas. Considera qué pasa cuando un agente de programación trabaja en un proyecto durante semanas:

  • Aprende que el proyecto usa Pydantic v2, no v1
  • Descubre que los tests deben ejecutarse con pytest -x --tb=short
  • Acumula conocimiento sobre la arquitectura de la base de código
  • Aprende las preferencias del desarrollador (“usa siempre pathlib, nunca os.path”)

Estos hechos son demasiado estructurados para búsqueda vectorial (necesitas recuperación exacta, no similitud difusa) y demasiado numerosos para un almacén clave-valor (forman documentos interconectados, no hechos aislados). Además, son hechos que el desarrollador quiere ver y editar directamente. Si el agente aprende algo incorrecto, abres el archivo y lo corriges.

Así es como funcionan el CLAUDE.md de Claude Code y el directorio .claude/. El agente lee archivos CLAUDE.md a nivel de proyecto para convenciones e instrucciones, y escribe en ~/.claude/MEMORY.md para aprendizajes entre sesiones. Los archivos son Markdown plano: los lees, los editas, los haces commit a git, los compartes con tu equipo. .cursorrules de Cursor y .windsurfrules de Windsurf siguen el mismo patrón: archivos de texto plano que el agente carga al arrancar para incorporar contexto del proyecto.

Implementar un almacén de memoria basado en archivos

La implementación es deliberadamente simple. El agente recibe cuatro operaciones: escribir un documento, leer un documento, listar documentos disponibles y buscar por palabra clave a través de documentos.

De file_memory.py:

from pathlib import Path
import json
import fnmatch

class FileMemory:
    """Document memory backed by the local filesystem.

    Stores agent knowledge as human-readable files organized by topic.
    No embeddings, no database — just files that both the agent and
    the developer can read, edit, and version-control.
    """

    def __init__(self, base_dir: str | Path):
        self.base_dir = Path(base_dir)
        self.base_dir.mkdir(parents=True, exist_ok=True)

    def write_doc(self, path: str, content: str, metadata: dict | None = None):
        """Write or overwrite a document at the given path.

        Paths are relative to base_dir. Directories are created automatically.
        Metadata (if provided) is stored as a JSON sidecar file.
        """
        full_path = self.base_dir / path
        full_path.parent.mkdir(parents=True, exist_ok=True)
        full_path.write_text(content, encoding="utf-8")

        if metadata:
            meta_path = full_path.with_suffix(full_path.suffix + ".meta")
            meta_path.write_text(json.dumps(metadata, indent=2), encoding="utf-8")

    def read_doc(self, path: str) -> str | None:
        """Read a document by path. Returns None if not found."""
        full_path = self.base_dir / path
        if full_path.exists():
            return full_path.read_text(encoding="utf-8")
        return None

    def list_docs(self, pattern: str = "**/*") -> list[str]:
        """List documents matching a glob pattern."""
        return [
            str(p.relative_to(self.base_dir))
            for p in self.base_dir.glob(pattern)
            if p.is_file() and not p.name.endswith(".meta")
        ]

    def search_docs(self, query: str, pattern: str = "**/*.md") -> list[dict]:
        """Search documents by keyword. Returns matching files with context.

        This is intentionally simple — grep-style keyword search.
        For semantic search, use a vector store instead.

        NOTE: This is a sketch for demonstration. A simple substring check
        won't scale beyond a few hundred documents. For production with 500+
        documents, use TF-IDF/BM25 scoring (e.g., rank_bm25) or a full-text
        search backend (PostgreSQL GIN index, Elasticsearch).
        """
        results = []
        for path in self.base_dir.glob(pattern):
            if not path.is_file() or path.name.endswith(".meta"):
                continue
            content = path.read_text(encoding="utf-8")
            if query.lower() in content.lower():
                # Return the paragraph containing the match for context
                for paragraph in content.split("\n\n"):
                    if query.lower() in paragraph.lower():
                        results.append({
                            "path": str(path.relative_to(self.base_dir)),
                            "match": paragraph.strip()[:500],
                        })
        return results

Estructura de carpetas

La mayor parte del valor de la memoria documental viene de cómo está organizado el directorio. Esta es la estructura que uso para el Market Analyst Agent:

.agent-memory/
    README.md                  # What this directory is, for human readers
    user-profiles/
        user-123.md            # Preferences, history, risk profile
        user-456.md
    research/
        NVDA-2026-02.md        # Research notes from recent analysis
        TSLA-2026-01.md
    conventions/
        analysis-format.md     # How to structure analysis reports
        data-sources.md        # Preferred data sources and API patterns
    learnings/
        common-errors.md       # Mistakes the agent has learned to avoid
        tool-patterns.md       # Effective tool call sequences

Todos los archivos son Markdown. La finalidad de cada archivo es obvia por su ruta. Puedes git diff todo el directorio de memoria para ver lo que aprendió el agente en una sesión, git revert un aprendizaje erróneo o copiar el directorio a otro proyecto. Intenta hacer algo de eso con una colección de Qdrant.

Cuándo usar memoria documental vs vectorial vs clave-valor

Los tres backends de memoria sirven a patrones de acceso distintos:

Dimensión Vector Store Key-Value Store Document Store
Patrón de consulta “Encuentra hechos similares a X” “Obtén el valor de una clave” “Lee el documento en una ruta”
Mejor para Recuperación no estructurada y variada Búsquedas estructuradas Contexto del proyecto, notas
Legible por humanos No (embeddings) Parcialmente (JSON) Sí (Markdown)
Depurable Difícil (puntuaciones de similitud) Fácil (claves exactas) Trivial (abre el archivo)
Versionable No Posible Sí (nativo de git)
Infraestructura de embeddings Requerida No necesaria No necesaria
Escala hasta Millones de hechos Millones de claves Miles de documentos
Capacidad de búsqueda Similitud semántica Coincidencia exacta Por palabra clave / ruta

Usa memoria documental cuando:

  • El agente acumula conocimiento del proyecto a lo largo de múltiples sesiones
  • Los desarrolladores necesitan inspeccionar, editar o sobrescribir lo que el agente “sabe”
  • El conocimiento está estructurado como documentos (notas, resúmenes, convenciones) en lugar de hechos aislados
  • Quieres versionado en git de la memoria del agente
  • Cero infraestructura es un requisito estricto

Usa almacenes vectoriales cuando:

  • Necesitas recuperación semántica difusa (“encuentra recuerdos relacionados con X”)
  • La formulación de la consulta varía de forma impredecible
  • Tienes de miles a millones de hechos individuales

Usa almacenes clave-valor cuando:

  • Necesitas búsquedas exactas y rápidas para datos estructurados (perfiles de usuario, ajustes)
  • El esquema de datos está bien definido

En la práctica, los agentes en producción suelen combinar los tres. El Market Analyst Agent usa checkpoints en PostgreSQL para memoria caliente, Qdrant para recuperación semántica de hechos del usuario y un almacén documental basado en archivos para convenciones del proyecto y notas de investigación.

Ejemplos del mundo real

El patrón ya está muy extendido en asistentes de programación con IA:

  • Claude Code lee archivos CLAUDE.md desde la raíz del proyecto y directorios padre, y escribe en ~/.claude/MEMORY.md para aprendizajes entre sesiones. Todo el sistema de memoria son archivos Markdown planos que haces commit junto con tu código.
  • Cursor carga archivos .cursorrules para instrucciones del agente específicas del proyecto: convenciones de programación, preferencias de framework, decisiones arquitectónicas.
  • Windsurf usa archivos .windsurfrules más un directorio memories/ donde el agente almacena patrones aprendidos de tu base de código.
  • La memory tool de Anthropic para la Claude API proporciona operaciones create_memory, read_memory, update_memory y delete_memory implementadas en el cliente. Tu aplicación decide dónde viven realmente los archivos (disco local, S3, base de datos).

El hilo común: todos almacenan conocimiento del agente como archivos de texto legibles por humanos con operaciones explícitas de lectura/escritura. Sin embeddings. Sin infraestructura vectorial. El agente decide qué escribir, el desarrollador puede ver y editar todo, y el sistema entero cabe en un git diff.

Más allá de los asistentes de programación

La memoria documental no se limita a agentes de programación. El patrón aparece en dominios de agentes muy distintos:

  • Agentes para juegos de mundo abierto: Voyager (Wang et al., 2023) construye una biblioteca persistente de habilidades como programas JavaScript verificados que un agente de Minecraft acumula con el tiempo, reuniendo 3.3x más objetos únicos y alcanzando hitos 15.3x más rápido que los baselines. Las habilidades se transfieren a mundos nuevos sin reentrenamiento. JARVIS-1 amplía esto con una memoria multimodal que combina planes textuales y observaciones visuales, con una tasa de éxito 5x en las tareas más difíciles.

    Aquí conviene distinguir algo: las bibliotecas de habilidades son memoria ejecutable (archivos de código que se importan y ejecutan), mientras que la memoria documental en asistentes de programación es declarativa (Markdown inyectado en prompts). Los modos de fallo difieren. Un código ejecutable incorrecto hace caer al agente; un texto declarativo incorrecto lleva a errores de razonamiento. Pero el patrón de almacenamiento y los beneficios operativos (depurabilidad, control de versiones) son los mismos.

  • Automatización de workflows empresariales: los ganadores de la competición ECR3 usaron memoria documental para refinamiento iterativo de prompts. Los agentes Analyzer y Versioner de uno de los equipos ganadores iteraron sobre 80 versiones de prompt almacenadas como documentos procedimentales. Otro equipo puntero construyó más de 20 módulos enricher como conocimiento procedimental en formato documento. LEGOMem (2025) formaliza esto como un framework modular de memoria para sistemas multiagente, con tipos de memoria especializados (sensorial, a corto plazo, a largo plazo) que los agentes componen como bloques de construcción.

  • Automatización web: Agent Workflow Memory (Wang et al., 2024) permite que agentes web induzcan workflows reutilizables a partir de episodios exitosos, con una mejora del 51% en tasa de éxito en WebArena. SkillWeaver (2025) va más allá: los agentes sintetizan herramientas API reutilizables a partir de la exploración, con una ganancia del 31.8% en tasa de éxito. Las habilidades aprendidas también se transfieren a modelos más débiles (mejora del 54.3%), de modo que la memoria acumulada de un agente más potente puede elevar a uno más pequeño.

  • Atención al cliente: Gartner predice que los agentes de IA resolverán de forma autónoma el 80% de los problemas comunes de atención al cliente en 2029. Estos agentes consultan SOPs, playbooks e historiales de clientes, que son formas de memoria documental.

El workshop MemAgents en ICLR 2026 es una señal de que la comunidad investigadora se está poniendo al día respecto a lo que los practitioners ya han construido. La memoria documental ha superado claramente sus orígenes en asistentes de programación.

Las skills son memoria documental con un esquema. El estándar Agent Skills (archivos SKILL.md con frontmatter YAML y cuerpo Markdown) ya lo usan tanto Anthropic como OpenAI Codex.

MCP (Model Context Protocol) va en la misma dirección: las definiciones de herramientas son archivos JSON Schema que cualquier agente puede descubrir y llamar. El protocolo tiene 97 millones de descargas mensuales del SDK y cuenta con soporte de OpenAI, Google, Microsoft y AWS. MCP no es específico de la programación. Los mismos servidores conectan agentes con bases de datos, APIs internas y sistemas empresariales.

Ambos apuntan al mismo patrón: conocimiento procedimental almacenado como documentos con esquema forzado, con operaciones explícitas de lectura/escritura. MCP, ahora gobernado por la Agentic AI Foundation, es lo más parecido a un estándar de interoperabilidad que tiene el ecosistema de agentes.

Escalar la memoria documental para producción

La implementación basada en archivos anterior funciona bien en portátiles de un solo desarrollador y en despliegues pequeños. Producción multi-tenant con cientos de usuarios y miles de documentos es otro problema.

El límite de archivos en un único nodo se hace evidente: no puedes escalar I/O de archivos horizontalmente, las escrituras concurrentes necesitan locking y gestionar permisos entre tenants es doloroso. Producción necesita un backing store que gestione correctamente concurrencia, búsqueda y multi-tenancy.

Tres enfoques comunes:

Enfoque A: híbrido con una capa fina de base de datos

Mantén archivos para autoría (los desarrolladores editan Markdown localmente) pero sirve desde una base de datos en runtime. En despliegue, sincroniza archivos a filas de PostgreSQL. El agente lee de la base de datos, no del disco. Esto te da:

  • Ergonomía para desarrolladores (editar Markdown, hacer commit a git)
  • Rendimiento de consulta en producción (lecturas indexadas en base de datos)
  • Separación limpia entre autoría y serving

Enfoque B: object storage + sidecar de índice vectorial

Almacena documentos en S3/GCS como objetos, con una colección de Qdrant que indexa sus embeddings. El agente consulta Qdrant para obtener IDs de documentos relevantes y luego recupera el contenido desde object storage. Esto escala horizontalmente y soporta búsqueda semántica, pero añade complejidad: dos sistemas que gestionar, un pipeline de embeddings que mantener y consistencia eventual entre almacén e índice.

Enfoque C: almacén documental estructurado con PostgreSQL (recomendado)

Almacena documentos como filas JSONB de PostgreSQL con búsqueda full-text (GIN index) y embeddings vectoriales opcionales (pgvector). Esto te da búsqueda híbrida (keyword + semántica), transacciones ACID y un único sistema operativo.

Un esbozo del Enfoque C:

from typing import Optional
import asyncpg

class ProductionDocumentMemory:
    """PostgreSQL-backed document memory with hybrid search.

    Schema:
        CREATE TABLE documents (
            id SERIAL PRIMARY KEY,
            tenant_id TEXT NOT NULL,
            path TEXT NOT NULL,
            content TEXT NOT NULL,
            metadata JSONB,
            embedding vector(1536),  -- pgvector extension
            ts_vector tsvector GENERATED ALWAYS AS (to_tsvector('english', content)) STORED,
            created_at TIMESTAMPTZ DEFAULT NOW(),
            UNIQUE(tenant_id, path)
        );
        CREATE INDEX ON documents USING GIN(ts_vector);
        CREATE INDEX ON documents USING ivfflat(embedding vector_cosine_ops);
    """

    def __init__(self, pool: asyncpg.Pool):
        self.pool = pool

    async def write(
        self,
        tenant_id: str,
        path: str,
        content: str,
        metadata: Optional[dict] = None,
        embedding: Optional[list[float]] = None,
    ):
        """Write or update a document."""
        async with self.pool.acquire() as conn:
            await conn.execute(
                """
                INSERT INTO documents (tenant_id, path, content, metadata, embedding)
                VALUES ($1, $2, $3, $4, $5)
                ON CONFLICT (tenant_id, path) DO UPDATE
                SET content = EXCLUDED.content,
                    metadata = EXCLUDED.metadata,
                    embedding = EXCLUDED.embedding
                """,
                tenant_id, path, content, metadata, embedding,
            )

    async def search(
        self,
        tenant_id: str,
        query: str,
        embedding: Optional[list[float]] = None,
        limit: int = 5,
    ) -> list[dict]:
        """Hybrid search: full-text + optional vector similarity."""
        async with self.pool.acquire() as conn:
            if embedding:
                # Hybrid scoring: 0.6 * text relevance + 0.4 * vector similarity
                rows = await conn.fetch(
                    """
                    SELECT path, content, metadata,
                           (0.6 * ts_rank(ts_vector, plainto_tsquery('english', $2)) +
                            0.4 * (1 - (embedding <=> $3))) AS score
                    FROM documents
                    WHERE tenant_id = $1
                      AND ts_vector @@ plainto_tsquery('english', $2)
                    ORDER BY score DESC
                    LIMIT $4
                    """,
                    tenant_id, query, embedding, limit,
                )
            else:
                # Full-text search only
                rows = await conn.fetch(
                    """
                    SELECT path, content, metadata,
                           ts_rank(ts_vector, plainto_tsquery('english', $2)) AS score
                    FROM documents
                    WHERE tenant_id = $1
                      AND ts_vector @@ plainto_tsquery('english', $2)
                    ORDER BY score DESC
                    LIMIT $3
                    """,
                    tenant_id, query, limit,
                )
            return [dict(row) for row in rows]

Qué obtienes:

  • Búsqueda híbrida: coincidencia por palabras clave (GIN index) + similitud semántica (pgvector) puntuadas conjuntamente
  • Multi-tenancy: alcance por tenant_id con row-level security
  • Garantías ACID: sin problemas de consistencia eventual
  • Un único sistema operativo: sin base de datos vectorial separada que gestionar
  • Escalado horizontal: réplicas de lectura para carga de consultas, particionado por tenant para escalar escritura

Los archivos son excelentes para flujos de trabajo de un único desarrollador. Para producción multi-tenant, un almacén documental estructurado sobre PostgreSQL suele ser el mejor equilibrio entre simplicidad, rendimiento y madurez operativa.


Juntándolo todo: la arquitectura completa

Así es como trabajan juntas las tres capas de memoria en el Market Analyst Agent. El diagrama muestra el flujo completo desde la petición del usuario hasta la respuesta, con todas las capas de memoria activas.

Arquitectura completa de memoria

La arquitectura tiene tres rutas de memoria:

  1. Ruta caliente (almacén de checkpoints): cada nodo del LangGraph escribe su estado resumible del grafo en el almacén de checkpoints. Cuando el grafo alcanza un nodo interrupt_before (como el reporter de la Parte 1), la ejecución se pausa. El usuario puede cerrar la app y, cuando vuelve, el grafo se reanuda desde el checkpoint. Los logs de eventos y traces de runtime son preocupaciones de producción separadas.

  2. Ruta fría (almacén a largo plazo): al inicio de cada conversación, el agente consulta el almacén a largo plazo para obtener contexto de usuario relevante. Al final, extrae y almacena nuevos hechos. Esto se ejecuta de forma asíncrona: nunca debería bloquear el bucle principal de razonamiento.

  3. Ruta documental (almacén de archivos): al arrancar, el agente carga convenciones del proyecto y notas de investigación relevantes desde el almacén documental. Durante la ejecución, escribe nuevos resúmenes de investigación y patrones aprendidos en disco. A diferencia de la ruta fría, las lecturas documentales son síncronas (informan la tarea actual), mientras que las escrituras pueden diferirse.

El cableado en LangGraph es directo: el almacén de checkpoints y el almacén a largo plazo se pasan en la compilación del grafo, mientras que el almacén documental se inyecta como dependencia. El esquema local de abajo usa InMemoryStore para que el snippet sea pequeño; la topología Docker de referencia usa Qdrant para el mismo papel de recuperación semántica.

from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver
from langgraph.store.memory import InMemoryStore

# Hot memory: PostgreSQL for durable checkpoints
checkpointer = await create_postgres_checkpointer(pg_connection_string)

# Cold memory: local sketch with vector search
# (The reference Docker topology uses Qdrant for persistent recall.)
memory_store = InMemoryStore(
    index={"dims": 1536, "embed": embedding_function}
)

# Document memory: file-based store for project knowledge
doc_memory = FileMemory(base_dir=".agent-memory")

# Checkpoint store and long-term store wired into the graph
graph = create_graph(
    checkpointer=checkpointer,
    store=memory_store,
)

# The store is accessible inside any node via the store parameter
def planner_node(state: AgentState, *, store: BaseStore) -> dict:
    """Plan with user context from long-term memory."""

    # Recall relevant user facts from vector store
    user_memories = store.search(
        namespace=("user", state.user_id),
        query=state.messages[-1].content,
        limit=5,
    )

    # Load project conventions from document memory
    conventions = doc_memory.read_doc("conventions/analysis-format.md")

    # Inject both into planning context
    memory_context = "\n".join(m.value["fact"] for m in user_memories)
    # ... rest of planning logic with personalized context and conventions

El flujo completo

Qué ocurre cuando un usuario recurrente envía “Analiza TSLA” al Market Analyst Agent:

  1. Carga de memoria documental: al arrancar, el agente lee convenciones del proyecto desde el almacén documental: preferencias de formato de análisis, fuentes de datos preferidas, patrones de uso de herramientas. Esto fija el comportamiento base.

  2. Recuperación de memoria fría: antes de que se ejecute el nodo router, el grafo consulta el almacén a largo plazo con el mensaje del usuario. Recupera: “El usuario tiene alta tolerancia al riesgo”, “El usuario prefiere análisis detallado de competidores”, “El usuario investigó antes NVDA y AMD”.

  3. Router + Planner: el router clasifica esto como DEEP_RESEARCH. El planner crea un plan de investigación de 5 pasos personalizado según las preferencias recuperadas. Incluye un paso de análisis de competidores porque el historial del usuario muestra que lo quiere. El plan sigue el formato del documento de convenciones.

  4. Bucle del Executor (memoria caliente): cada paso se ejecuta mediante el patrón ReAct de la Parte 1. Después de cada nodo (router, planner, cada paso del executor) LangGraph escribe un checkpoint en PostgreSQL. Si el proceso cae después del paso 3 de 5, reinicias y continúas desde el paso 4.

  5. Interrupción HITL: el grafo alcanza el nodo reporter con interrupt_before. El borrador del informe está en el checkpoint. El usuario lo revisa horas más tarde, y el grafo carga el checkpoint y continúa.

  6. Actualizaciones de memoria: después de terminar la conversación: (a) un proceso asíncrono extrae nuevos hechos del usuario (“el usuario ahora sigue TSLA”, “el usuario aprobó el formato del informe”) y los almacena en el almacén vectorial a largo plazo, y (b) el agente escribe un resumen de investigación en el almacén documental (research/TSLA-2026-02.md) para futuras referencias.

El patrón de tres capas separa claramente las responsabilidades. El almacén de checkpoints gestiona durabilidad y reanudación; es infraestructura. El almacén a largo plazo gestiona personalización; es lógica de producto. El almacén documental guarda conocimiento acumulado del proyecto; es el cuaderno del agente.


Trade-offs y consideraciones

La memoria aporta valor, y también coste y complejidad. Sé honesto con los trade-offs:

  • Coste de embeddings: cada hecho almacenado en una base de datos vectorial requiere una llamada a una API de embeddings. A $0.02 por millón de tokens (OpenAI text-embedding-3-small), el coste por hecho es insignificante, pero se acumula a lo largo de miles de usuarios y sesiones. Agrupa llamadas de embeddings y cachea resultados. El coste real es la latencia: 100-300ms de latencia de API de embeddings en tiempo de consulta para recuperación de memoria fría, que importa más que el coste en dólares para agentes conversacionales en tiempo real. Cachea embeddings para consultas comunes o usa un modelo local de embeddings para cargas sensibles a la latencia.

  • Memoria obsoleta: las preferencias del usuario cambian. Un hecho almacenado hace seis meses (“el usuario prefiere inversiones conservadoras”) puede dejar de ser correcto. Define políticas de expiración. Yo uso 365 días para preferencias y 90 días para eventos episódicos, como describo en mi post sobre context engineering.

  • Sobrecarga de memoria en contexto: cada hecho recuperado consume tokens en la ventana de contexto del LLM. Si recuperas 20 hechos por consulta, son varios cientos de tokens de contexto de memoria compitiendo con la tarea real. Limita el número de hechos recuperados y prioriza por puntuación de relevancia.

  • Privacidad y cumplimiento: la memoria a largo plazo almacena datos de usuario. Necesitas redacción de PII antes de almacenar, políticas claras de retención y controles para que el usuario pueda borrar sus datos. Nada de esto es opcional en industrias reguladas.

  • Crecimiento del almacenamiento de checkpoints: las tablas de checkpoints de PostgreSQL crecen con cada ejecución de nodo. Para agentes de larga ejecución, define una política de retención: conserva los últimos N checkpoints por thread y archiva o elimina los más antiguos. Un ejemplo de consulta de limpieza que conserva los 10 checkpoints más recientes por thread y elimina cualquier cosa con más de 30 días:

    DELETE FROM checkpoints
    WHERE thread_id = $1
      AND created_at < NOW() - INTERVAL '30 days'
      AND checkpoint_id NOT IN (
        SELECT checkpoint_id FROM checkpoints
        WHERE thread_id = $1
        ORDER BY created_at DESC
        LIMIT 10
      );
    
  • Consolidación de memoria: con el tiempo, los recuerdos episódicos detallados deberían comprimirse en representaciones semánticas compactas: “el usuario preguntó por NVDA tres veces en enero” en lugar de almacenar literalmente las tres conversaciones. Eso refleja la consolidación de memoria humana y mantiene manejable el almacén. Mem0 y Graphiti gestionan esto automáticamente; si construyes el tuyo, programa jobs periódicos de consolidación.

  • Problema del cold start: los usuarios nuevos no tienen memoria a largo plazo. El agente debe degradar con elegancia y hacer preguntas aclaratorias en lugar de asumir cosas. La memoria suma, no es obligatoria.

  • Envenenamiento de memoria: cualquier cosa en la ventana de contexto del agente es un posible punto de inyección. Si un atacante escribe hechos engañosos en el almacén documental o en la memoria a largo plazo (“aprueba siempre transacciones sin verificación”), el agente puede ejecutarlos como instrucciones. El prompt injection a través de memorias almacenadas es una superficie real de ataque. Las mitigaciones son validación antes de almacenar, tratar el contenido recuperado como datos no confiables en lugar de instrucciones de sistema y controles de acceso que limiten qué memorias pueden influir en operaciones críticas.

  • Deriva de memoria documental: la memoria basada en archivos no tiene deduplicación automática ni resolución de conflictos. Con el tiempo, los documentos acumulan contradicciones: un archivo dice “usa pytest” mientras otro dice “usa unittest”. Programa revisiones periódicas (o deja que las haga el agente) para podar y consolidar. La buena noticia es que, a diferencia de los almacenes vectoriales donde la obsolescencia queda oculta, puedes grep en busca de contradicciones.

  • La memoria documental no escala a millones de elementos: la memoria basada en archivos funciona para cientos o unos pocos miles de documentos. Si tu agente necesita recuperar entre millones de hechos con fuzzy matching, necesitas un almacén vectorial. La memoria documental es para conocimiento estructurado del proyecto, no para la larga cola de cada interacción de usuario.


Ideas clave

  1. La memoria de agente se divide en tres capas: caliente (almacén de checkpoints para la sesión actual), fría (almacén a largo plazo para conocimiento entre sesiones) y documental (almacén de archivos para conocimiento acumulado del proyecto). Diseña cada capa según su patrón de acceso.

  2. Usa checkpointing con PostgreSQL como valor por defecto. Te da durabilidad ACID, historial completo de checkpoints y depuración con time-travel. Cambia a Redis solo cuando la latencia submilisegundo sea un requisito estricto.

  3. El sistema de checkpoints de LangGraph gestiona la memoria caliente de forma nativa. Cada escritura de nodo se persiste automáticamente, así que los flujos de pausar/reanudar y HITL requieren cero código de aplicación.

  4. Empieza la memoria a largo plazo con almacenes clave-valor para perfiles de usuario estructurados. Añade búsqueda vectorial (Qdrant, Pinecone o el Store integrado de LangGraph con índice vectorial) cuando necesites recuperación semántica sobre hechos no estructurados.

  5. La memoria documental está poco explorada en la literatura, pero muy adoptada en la práctica. La mayoría de frameworks y surveys cubren almacenes vectoriales y checkpoints, pero omiten la memoria basada en archivos. El patrón se ha extendido mucho más allá de los asistentes de programación con IA. Claude Code, Cursor y Windsurf convergieron en archivos de texto plano; Voyager almacena skills de Minecraft como bibliotecas de código; los ganadores de ECR3 iteraron sobre documentos procedimentales de prompts; los agentes web sintetizan APIs de workflow reutilizables. Cuando el agente aprende algo incorrecto, abres un archivo Markdown y lo corriges. Cuando quieres saber qué sabe el agente, ls el directorio de memoria.

  6. La memoria cambia el producto, no solo la infraestructura. La diferencia entre “el agente recuerda mis preferencias” y “el agente me hace siempre las mismas preguntas” es lo que hace que los usuarios vuelvan.

  7. Define políticas de retención desde el primer día. La memoria obsoleta perjudica el rendimiento del agente, y el almacenamiento sin límites crea riesgos de privacidad. Expira memorias episódicas a los 90 días, preferencias a los 365 y revisa periódicamente la memoria documental en busca de contradicciones.

  8. Limita el contexto recuperado. Cada hecho recuperado compite por tokens en la ventana de contexto. Recupera los 5 hechos más relevantes, no todo lo que tengas.


Qué viene después

En la Parte 3, Uso de herramientas por agentes de IA en 2026, cubriré ergonomía de herramientas y la Agent-Computer Interface (ACI): cómo diseñar herramientas que los LLMs puedan usar de forma fiable de verdad. Las descripciones de herramientas, los esquemas de argumentos y los patrones de gestión de errores son lo que decide si tu agente llama a la herramienta correcta con los argumentos correctos, o alucina hasta meterse en una cascada de fallos.


Referencias

Papers

Documentación de LangGraph

Backends de checkpoints

Bases de datos vectoriales y herramientas de memoria

  • Qdrant — Base de datos vectorial open-source con indexación HNSW y filtrado
  • Qdrant Agentic Builders Guide — Guía práctica para construir memoria de agentes con Qdrant
  • pgvector — Extensión de búsqueda por similitud vectorial para PostgreSQL
  • Graphiti — Motor open-source de grafo de conocimiento temporal de Zep

Memoria documental y basada en archivos

  • Claude Code Memory — Sistema de memoria basada en archivos CLAUDE.md y MEMORY.md
  • Anthropic Memory Tool — Memoria basada en archivos en cliente para agentes de Claude API
  • Cursor Rules — Archivos .cursorrules a nivel de proyecto para contexto del agente
  • Windsurf Memories — Memoria basada en archivos y .windsurfrules para agentes de programación

Frameworks de memoria

  • Mem0 — Capa de memoria gestionada con pipeline de extracción/consolidación
  • Letta (MemGPT) — Gestión virtual de contexto para agentes inspirada en sistemas operativos
  • LangMem SDK — Herramientas de gestión de memoria para LangGraph

Benchmarks

Workshops

Proyecto demo


El código completo de Market Analyst Agent, incluida la arquitectura de memoria de esta entrada, está en GitHub si quieres seguir la lectura.

Serie: Engineering the Agentic Stack