Inicio / Artículos

Explicación de la memoria del agente: ventana de contexto, RAG y estado persistente

La memoria del agente es tres cosas diferentes. Observa cómo difieren la ventana de contexto, la recuperación y el estado persistente, cuál es el costo de cada uno, y cómo elegir primero el adecuado.

Editor: REACTAPP.TOP10 min de lectura2195 palabras

Fuentes: Agent Memory Explained

Pocas palabras generan tanto confusión en el desarrollo de agentes como “memoria”. Cuando alguien dice que su agente la tiene, puede referirse a que la conversación es larga, a que está conectada una base de datos vectorial, o a que los datos se guardan en un archivo que sobrevive a un reinicio. Esos son tres sistemas separados con costos diferentes, formas distintas de fallar y razones distintas para existir.

Una sola oración resuelve la mayor parte de la confusión. El modelo no tiene memoria, solo una entrada, y lo que la gente llama memoria es simplemente una política para decidir qué contenido se incluye en esa entrada.

El modelo es sin estado. Cada llamada a la API comienza desde cero, sin recordar su último mensaje, su última sesión ni el nombre que se le haya dado. Lo único que sabe durante una llamada es lo que contiene la solicitud, y nada más.

Por lo tanto, la pregunta relevante no es si el agente tiene memoria, sino qué es lo que merece un lugar en esta solicitud y de dónde proviene. Hay tres respuestas, y la tarea consiste en distinguirlas.

Modelo sin estado, aplicación con estado

Los principiantes asumen que el modelo mantiene una conversación, pero no es así. La aplicación es la que mantiene la conversación y la reproduce. Cada vez que envías un cuarto mensaje de chat, tu código transmite los mensajes 1, 2, 3 y 4, todos ellos. La sensación de continuidad proviene del reenvío de la historia, y el modelo simplemente recibe una entrada más larga que antes.

Teniendo esto en cuenta, las tres capas dejan de ser abstractas:

  • La ventana de contexto es lo que se encuentra actualmente en la solicitud.
  • Retrieval (RAG) es la forma en que se encuentra material para incluir en la solicitud.
  • Estate persistente es la forma en que los datos sobreviven a una solicitud para poder añadirse nuevamente más tarde.
  • Un contenedor, una búsqueda y un almacén: tres cosas diferentes. Si se confunden, terminas conectando una base de datos vectorial a un problema que un archivo preferences.json habría resuelto.

    Capa 1: la ventana, o el escritorio del modelo

    Imagina la ventana como todo lo que el modelo puede ver actualmente. El prompt del sistema, el historial, las definiciones de herramientas, los resultados de las herramientas, los documentos recuperados y la respuesta que se está escribiendo todos utilizan un mismo presupuesto de tokens compartido.

    Imagina un escritorio. Todo sobre lo que el modelo debe razonar tiene que estar físicamente sobre él; un archivador en otra habitación no cuenta.

    Donde brilla: la inmediatez. Todo lo que se encuentra en la ventana es completamente visible sin necesidad de buscar, y el modelo puede procesarlo todo al mismo tiempo. Para una tarea con un conjunto limitado y conocido de información, llenar la ventana es el método más sencillo y, con frecuencia, el adecuado.

    Costos:

    1. El dinero y la latencia dependen de lo que se reenvíe. Reenviar 51 turnos de una conversación larga implica reenviar también 50 turnos de la historia anterior. Un contexto largo no es gratuito solo porque quepa.
    2. La atención es desigual. Los modelos tienden a utilizar de manera más fiable el inicio y el final de una entrada larga, mientras que el medio se utiliza menos, lo que da lugar al efecto comúnmente observado de “perderse en el medio”. Una información ubicada al 60% del largo texto de entrada está presente, pero podría no utilizarse efectivamente. Que algo quepa en el contexto no significa necesariamente que sea leído.
  • Se termina. Una vez que finaliza la sesión, la ventana desaparece. Una ventana de contexto nunca conserva nada. Este es el error más común entre los principiantes: una conversación larga parece ser memoria, pero en realidad es un búfer, no un almacenamiento.
  • Toda esta mecánica se resume en unas pocas líneas:

    messages = []
    
    def turn(user_text):
        messages.append(
            {"role": "user",
             "content": user_text}
        )
        # every call resends
        # the ENTIRE history
        reply = client.messages.create(
            model="claude-opus-5",
            max_tokens=1024,
            messages=messages,
        )
        messages.append(
            {"role": "assistant",
             "content": reply.content[0].text}
        )
        return reply
    
    # messages grows forever.
    # so does your bill.
    

    Esa lista es la “memoria” en este caso. Solo existe dentro de su proceso, por lo que una vez que el proceso termina, la lista también desaparece.

    La ventana por sí sola es suficiente cuando el trabajo es breve, el material necesario es moderado y se conoce de antemano, y nada tiene que sobrevivir a la sesión. Un resumidor, un revisor de un archivo o una extracción puntual no necesitan nada más. No añada complejidades innecesarias.

    Nivel 2: recuperación, la biblioteca de referencia

    Tarde o temprano el material deja de ser útil o ya no merece la pena adquirirlo. Puede tratarse de 10,000 tickets de soporte, una wiki interna o un repositorio de código. No puede incluir todo eso en cada solicitud, ni debería hacerlo.

    La generación reforzada por recuperación divide el trabajo: se prepara un índice con antelación y luego se busca la información cuando surge una pregunta.

    Antes, se cortan los documentos en fragmentos, cada fragmento se convierte en un embedding (un vector numérico que captura el significado) y se almacena. Cuando llega una pregunta, se le aplica un embedding, se buscan los fragmentos cuyos vectores están más cerca y esos pocos se insertan en la ventana de contexto junto a la pregunta.

    # 1. AHEAD OF TIME (once, offline)
    for chunk in split(documents):
        store.add(
            text=chunk,
            vector=embed(chunk),
        )
    
    # 2. AT QUESTION TIME (every request)
    def answer(question):
        hits = store.search(
            embed(question), top_k=5
        )
        context = "\n\n".join(h.text for h in hits)
        return client.messages.create(
            model="claude-opus-5",
            max_tokens=1024,
            # retrieved chunks land in the same request
            messages=[{
                "role": "user",
                "content": f"{context}\n\nQ: {question}",
            }],
        )
    

    Fíjese en lo que realmente sucedió. RAG no proporcionó memoria al modelo; le dio una estrategia de selección para el rango de datos. Los fragmentos recuperados se incluyen en la misma solicitud que todo lo demás, por lo que RAG funciona como un conducto hacia la Capa 1 y no existe alternativa a él.

    Puntos fuertes: escalabilidad y actualidad. Millones de documentos pueden cambiar independientemente de su código, mientras que el modelo solo ve aquellos pocos que son relevantes para esa pregunta.

    Costos:

    1. El resultado se decide desde el principio. El tamaño de los fragmentos, el modelo de incrustación y el diseño del índice se fijan al crearlo. Un hecho que está distribuido en dos fragmentos o que está redactado de manera diferente a como lo preguntan los usuarios simplemente no se encuentra, y el modelo responde con confianza basándose en lo que recibió. Para un observador, una recuperación deficiente y las alucinaciones se ven idénticas.
  • En su mayor parte es de solo lectura. El RAG clásico funciona como una tarjeta de biblioteca y no como un cuaderno. El agente busca información pero no anota nada, y lo que aprende en una sesión no llega al índice a menos que se cree ese camino.
  • Hay un paso adicional. La incrustación de la consulta y la búsqueda en el índice añaden latencia a cada solicitud.
  • La recuperación es adecuada cuando la base de conocimientos es grande, común para todos los usuarios y rara vez cambia, como por ejemplo la documentación, las políticas, los catálogos de productos y el código. La característica distintiva es que un único corpus sirve a todos los usuarios. Si lo que deseas recordar pertenece a un solo usuario y cambia a medida que hablas, la recuperación no es una opción adecuada y lo que necesitas es el Nivel 3.

    Nivel 3: estado persistente, el cuaderno

    El estado persistente es lo que sobrevive cuando el proceso termina. No se trata de lo que el agente sabe sobre el mundo, sino de lo que ha aprendido y que debe perdurar más allá de esta sesión: preferencias del usuario, puntos de control de tareas, decisiones ya tomadas, cosas probadas y abandonadas, resúmenes condensados de ejecuciones anteriores. Se trata de hechos pequeños, específicos y valiosos que el propio agente anotó.

    El patrón consiste en un ciclo de lectura, modificación y escritura que abarca toda la sesión:

    # session start: load
    with open("state.json") as f:
        state = json.load(f)
    
    system = (
        "Known about this user:\n"
        f"{json.dumps(state)}"
    )
    
    messages = []
    
    def turn(user_text):
        messages.append(
            {"role": "user",
             "content": user_text}
        )
        reply = client.messages.create(
            model="claude-opus-5",
            max_tokens=1024,
            system=system,   # state in
            messages=messages,
        )
        return reply
    
    # ... along the way ...
    state["prefers"] = "metric units"
    state["last_checkpoint"] = "step_4_done"
    
    # session end: save
    with open("state.json", "w") as f:
        json.dump(state, f)
    

    Conceptualmente, no se necesita nada más. Un archivo JSON cuenta como una implementación real; lo mismo ocurre con una fila de base de datos, un almacén clave-valor o una carpeta de notas en formato Markdown. El medio es la parte aburrida; lo interesante es quién decide qué guardar y qué volver a cargar.

    Tomar esa decisión adecuadamente es lo difícil, y ahí es donde difieren los frameworks de agentes. La opción más directa actual deja esa tarea al modelo: con la herramienta de memoria de Anthropic, Claude dispone de un conjunto reducido de comandos para archivos (ver, crear, str_replace, insertar, eliminar y renombrar) que funcionan en un directorio aislado /memories, de modo que el agente elige qué merece ser guardado y lee esas notas al inicio de la siguiente sesión. Tu propia aplicación ejecuta las operaciones en el almacenamiento que elijas. La herramienta se ejecuta en el lado del cliente, lo que mantiene los archivos bajo tu control.

    Puntos fuertes: continuidad, personalización y trabajo a largo plazo. Un agente que retoma una tarea de varios días desde donde se detuvo lo necesita, al igual que aquel que no debería preguntar por tu zona horaria dos veces.

    Costos:

    1. Alguien debe encargarse de la organización. Si anotas todo, creas un registro imposible de buscar. Si escribes demasiado poco, se rompe la continuidad. Lograr un estado persistente adecuado es muy complicado.
    2. Los datos obsoletos son peores que no tenerlos. Una preferencia guardada hace ocho meses y nunca revisada se aplica con total confianza. Los fallos en la recuperación generan ambigüedad, mientras que un estado obsoleto provoca comportamientos incorrectos de forma segura.
    3. Cargar datos no es gratis. Lo que sea que cargues sigue ocupando espacio en la ventana, por lo que la Capa 3 también afecta a la Capa 1.

    Se debe utilizar un estado persistente si el mismo usuario o tarea activa repetidamente al agente, y lo que aprende la ejecución n debería modificar la ejecución n+1.

    Los tres en paralelo

    Ventana de contexto Recuperación Estado persistente
    Rol El escritorio La biblioteca El cuaderno de notas
    Almacena Todo lo incluido en esta solicitud Un corpus grande y compartido Hechos pequeños escritos por el agente
    Creado por Su aplicación, en cada llamada Tarea de indexación El agente o su código
    Sobrevive a un reinicio No Sí Sí
    Riesgo principal Costo, información perdida en el proceso Fragmentos omitidos Hechos desactualizados

    La fila más importante es la relacionada con los reinicios. Es el límite que separa una conversación larga de un agente con memoria.

    Ellos componen

    Un agente real utiliza los tres elementos, y resulta útil observar cómo trabajan en una sola solicitud. Al agente de soporte se le pregunta si un pedido cumple con los requisitos para el plazo extendido de devolución.

    1. Primero se carga el estado persistente: este cliente tiene un plan empresarial y prefiere respuestas breves. Dos hechos, aproximadamente cuarenta tokens.
    2. Luego se realiza la recuperación de información a partir de la pregunta, extrayendo la sección sobre política de devoluciones para planes empresariales junto con las dos excepciones más recientes. Tres fragmentos, unos mil doscientos tokens.
    3. Finalmente, la ventana de contexto incluye todo lo anterior, además del chat en tiempo real y las definiciones de herramientas, permitiendo que el modelo razone sobre todo ello de forma conjunta.
  • Después de responder, el agente escribe una línea en el estado persistente: se preguntó por la política de devoluciones a mediados de agosto, por lo que la siguiente sesión no los trata como clientes nuevos.
  • Una solicitud, tres capas. La ventana es donde termina todo, mientras que la recuperación y el estado persistente son dos rutas separadas hacia ella.

    Por esta razón también se recomienda generalmente a los agentes de larga duración combinar la memoria con la edición de contexto: los resultados antiguos de las herramientas se eliminan de la ventana para mantenerla ligera, y los datos importantes ya fueron guardados, así que la ventana se mantiene pequeña sin perder conocimiento.

    ¿Cuál necesitas realmente?

    Los principiantes casi siempre empiezan con una base de datos vectorial. Es la parte más visible del conjunto de herramientas, pero rara vez es la solución adecuada. Mejor sube por la escalera gradualmente.

    Comience con la ventana de memoria. ¿Encaja todo lo relevante en una sola solicitud al precio que desea? Entonces deténgase. Sin índice, sin almacenamiento.

    Introduzca el estado persistente una vez que el agente se ejecute por segunda vez. Cuando la segunda sesión debe conocer lo aprendido por la primera, se necesita una capa 3, y un archivo JSON es la solución adecuada. Hágalo antes de realizar la recuperación; es más económico, sencillo y lo que realmente quieren la mayoría de las personas cuando dicen que desean que el agente les recuerde.

    Introduzca la recuperación cuando el corpus sea más grande que la ventana de memoria. Eso significa muchos documentos compartidos entre usuarios, editados por separado de su código. Es la capa más compleja de desarrollar y mantener en funcionamiento, así que úsela solo cuando sea realmente necesario.

    Una trampa que evitar: crear una pipeline RAG para lo que en realidad era un archivo de preferencias, y luego preguntarse por qué el agente sigue olvidando el nombre de alguien. La recuperación busca en un corpus, y nunca podría contener la información de que una persona prefiere unidades métricas.

    La versión breve

    • Ventana de contexto: el escritorio. Visible al instante, costosa por token, y se vacía en cuanto se cierra la sesión.
    • Recuperación: la biblioteca. Enorme, buscable, se actualiza según un horario establecido, y es algo que el agente solo lee.
    • Estatuto persistente: el cuaderno de notas. Pequeño, escrito por el agente, y es la única capa que transfiere una lección del día a otro.

    El modelo en sí no guarda nada. La aparente memoria es una decisión de ingeniería que se toma nuevamente con cada solicitud: ¿qué merece un lugar en esta ventana y de dónde debería provenir?

    Para leer más, los artículos técnicos de Anthropic sobre la ingeniería efectiva del contexto y sobre cómo crear agentes eficaces, además de su documentación relativa a la herramienta de memoria y la edición de contexto, abordan este tema con mayor profundidad.