Memoria de agentes AI: estado guiado por esquema y procedencia

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

Los agentes de larga duración suelen recuperar datos obsoletos porque la memoria semántica convencional no tiene ninguna regla para decidir qué valor es el actual.

Cuando un usuario cambia la fecha límite del pasaporte del 15 de julio al 30 de junio, la búsqueda vectorial puede recuperar ambas afirmaciones. Una capa de memoria con estado debe registrar que el valor del 30 de junio sustituye al anterior.

Para los ingenieros que desarrollan agentes de larga duración o multi-tenant, este fallo por datos obsoletos explica por qué la memoria debe sobrevivir a ejecuciones independientes y, al mismo tiempo, imponer el estado actual y los límites entre tenants. El diseño siguiente muestra cómo escribir registros tipados y probar lecturas del estado actual frente a lecturas en un momento concreto, sin tratar la recuperación vectorial como fuente de verdad.


La trampa de la ventana de contexto

La ventana de contexto es la entrada disponible para una única llamada al modelo. Una aplicación puede trasladar mensajes a llamadas posteriores, pero la política de la aplicación debe decidir qué datos antiguos siguen siendo ciertos y quién puede verlos.

Los agentes de larga duración necesitan recordar preferencias, estado de tareas, datos de clientes, decisiones de herramientas, notas de cumplimiento y errores anteriores. La opción sencilla consiste en añadir resúmenes o volcar notas antiguas en un almacén vectorial. Funciona hasta que uno de los datos recordados cambia.

Ahora el agente tiene dos fechas límite de pasaporte, dos formatos preferidos o dos decisiones de proyecto. La búsqueda semántica puede recuperar ambas. Un resumen puede sobrescribir una. Un contexto largo puede incluir la obsoleta junto a la activa. Estos diseños recuperan texto, pero no imponen el valor actual.

El contrato de memoria debe responder a preguntas concretas:

  • ¿Qué es cierto ahora?
  • ¿Qué era cierto el 2 de junio?
  • ¿Quién lo dijo?
  • ¿A qué tenant pertenece?
  • ¿Qué dato anterior sustituyó este dato?
  • ¿Puedo eliminarlo o hacerlo expirar?

Schema-Guided Agent Memory (SGAM) almacena esas respuestas como campos y relaciones, en lugar de dejarlas implícitas en la prosa.


Qué significa SGAM

Tres conceptos con nombres similares delimitan el alcance de SGAM.

Schema-Guided Dialogue (SGD) es el dataset de diálogo orientado a tareas de Google de 2019. Su esquema describe APIs de servicios, intents y slots para que un modelo de diálogo pueda realizar el seguimiento del estado de servicios que no ha visto antes. Es un precedente útil para el seguimiento basado en esquemas, con un alcance limitado a los servicios de diálogo.

Schema-Guided Memory (SGM) es el término de investigación utilizado por Mei et al. en According to Me: Long-Term Personalized Referential Memory QA. El artículo compara la Descriptive Memory (DM) en texto libre con elementos de memoria clave-valor de esquema fijo. Ambas representaciones contienen la misma información de origen en estructuras distintas.

En este artículo, utilizo Schema-Guided Agent Memory (SGAM) para referirme a un patrón de ingeniería en el que los esquemas gobiernan las escrituras, las actualizaciones, la recuperación y el borrado. El esquema define el estado de la aplicación y su ciclo de vida.

ATM-Bench muestra por qué importa la representación. Utiliza aproximadamente cuatro años de datos personales procedentes de emails, imágenes y vídeos. Las preguntas requieren referencias personales, ubicación, múltiples piezas de evidencia y actualizaciones a lo largo del tiempo. En su hard split, el artículo informa de que SGM supera a DM en recuperación y question answering bajo la configuración evaluada. SGM expone campos como tiempo, fuente, ubicación, entidades y etiquetas en una representación fija. Considero que esa representación y los resultados comunicados son la conclusión respaldada; el artículo no establece un mecanismo directo de acceso a campos.

La comparación entre SGM y DM responde a una pregunta de almacenamiento: ¿debe la memoria seguir siendo texto libre o debe utilizar campos con nombre? Un agente en producción tiene otro problema antes del almacenamiento. Debe convertir una conversación no estructurada en una actualización de memoria propuesta. Schema-Guided Reasoning (SGR) hace inspeccionable esa ruta de decisión prevista: la respuesta puede incluir evidencia, el subject y el attribute, una comparación con el estado actual y una escritura candidata. La respuesta del modelo no impone dependencias ni orden entre esos campos. Las llamadas independientes, los validadores y la política de la aplicación imponen esas reglas; SGAM aplica las reglas de almacenamiento y ciclo de vida después de la llamada al modelo.


Separa la extracción del modelo de la propiedad de la memoria

La escritura en memoria atraviesa tres capas. Structured Output (SO) impone la forma del objeto candidato. Schema-Guided Reasoning (SGR) hace inspeccionables los campos previstos y la intención de decisión del modelo en una respuesta estructurada. Schema-Guided Agent Memory (SGAM) gestiona el candidato como estado persistente después de la llamada al modelo. Las llamadas independientes, los validadores y la política de la aplicación imponen las dependencias, el orden y las reglas de ciclo de vida.

Un veredicto, una ruta o un plan suelen expirar con la solicitud actual. Otra ejecución puede leer un candidato de memoria días después o utilizarlo para elegir un tool call. Esa vida útil más larga exige reglas de almacenamiento que SGR no proporciona.

SGR estructura una llamada al modelo en torno a una topología de reasoning prevista. Para una escritura de memoria, la respuesta podría incluir evidencia de origen, un subject y attribute normalizados, una comparación con el estado actual y una actualización propuesta. Pydantic o JSON Schema describen esos campos. Structured Output nativo del proveedor o un runtime de guided decoding como XGrammar mantiene la respuesta con esa forma.

Esa forma hace inspeccionables los campos e intención declarados por el modelo. No puede garantizar una conclusión correcta ni demostrar que el modelo haya utilizado esos campos en orden. Las llamadas independientes, los validadores y la política de la aplicación son la frontera de enforcement para las dependencias, el orden y el ciclo de vida.

SGAM decide qué ocurre una vez existe ese objeto. ¿Debe almacenarse? ¿Sustituye a un dato anterior? ¿Qué tenant puede verlo? ¿Es actual o histórico? ¿Qué episodio de origen lo respalda?

La tabla expone la responsabilidad y el modo de fallo de cada capa:

DimensiónSOSGRSGAM
PropósitoDevolver un objeto que cumpla un esquemaHacer inspeccionables los campos previstos y la intención de decisiónGestionar la memoria persistente después de la llamada al modelo
AlcanceUna respuesta generadaUna respuesta del modelo; la política entre pasos puede abarcar varias llamadasRegistros utilizados entre llamadas, sesiones y ejecuciones
Papel del esquemaDefine campos de salida, tipos y valores permitidosDescribe evidencia, campos intermedios y decisión candidataDefine registros almacenados, relaciones y ciclo de vida
EnforcementEl constrained decoding bloquea salidas no válidas según el esquemaSolo la forma; las llamadas independientes, los validadores y la política imponen dependencias y ordenLa validación de la aplicación, las restricciones de la base de datos y las reglas de conflicto imponen el ciclo de vida
Vida útilLa llamada actual, salvo que la aplicación almacene el objetoEl reasoning trace normalmente se descarta después de la decisiónPersiste hasta que se actualiza, expira o elimina
Modo de falloForma válida con significado incorrectoLos campos están presentes, pero el reasoning o la gestión de dependencias aún pueden ser incorrectosEstado obsoleto, contaminado, sin ámbito o no auditable

En la write path, la secuencia es:

SGR reasoning schema + Structured Output -> candidate write -> SGAM policy and persistence

El siguiente fragmento ilustrativo de memory_models.py define el objeto que se pasa desde la extracción al servicio de escritura de SGAM. Este bloque necesita Pydantic, por lo que aparece marcado como no-run en las comprobaciones de ejemplo para entornos que no instalan esa dependencia opcional:

from datetime import datetime
from pydantic import BaseModel, Field

class MemoryDelta(BaseModel):
    tenant_id: str = Field(description="Isolation boundary, e.g. acme")
    subject: str = Field(description="Normalized entity ID, e.g. mira")
    attribute: str = Field(description="Property being updated")
    value: str = Field(description="New value")
    valid_from: datetime
    source_episode_id: str

MemoryDelta recoge lo que ha extraído el modelo. El servicio de escritura de SGAM sigue decidiendo si lo rechaza, lo fusiona o lo almacena.


Las write paths y read paths tienen funciones distintas

Solo la write path muta el estado almacenado. La read path selecciona registros para la solicitud actual.

El flujo de ingestión es la write path:

  1. Captura un episodio sin procesar procedente de mensajes, tool results o eventos de negocio.
  2. Extrae candidatos tipados mediante structured output.
  3. Valida el esquema y rechaza las escrituras malformadas.
  4. Reconcilia conflictos, cierra datos obsoletos y conserva la procedencia.
  5. Confirma el registro en el almacén de SGAM.

El flujo de solicitud es la read path:

  1. Empieza con la pregunta del usuario.
  2. Decide si la pregunta necesita el estado actual o el estado en un momento concreto.
  3. Filtra por tenant, tipo de memoria, subject, attribute y ventana de validez.
  4. Añade expansión vectorial o de grafo solo si la consulta exacta del estado no es suficiente.
  5. Ensambla el contexto citado más pequeño posible para el modelo.

Arquitectura de Schema-Guided Agent MemoryArquitectura de Schema-Guided Agent Memory

Lee el diagrama de izquierda a derecha en dos carriles. El carril superior escribe memoria y el inferior la lee. Ambos utilizan el mismo almacén.


Qué debe incluir un esquema de memoria

Un registro SGAM mínimo necesita más que text.

tenant_id
memory_id
subject
attribute
value
memory_type
schema_version
valid_from
valid_to
supersedes_memory_id
source_episode_id
confidence
retention_policy

Con estos campos, una nueva fecha límite del pasaporte puede cerrar la anterior sin borrar el historial. La misma tabla puede responder a consultas sobre el estado actual y sobre un momento concreto, y después rastrear el resultado hasta su episodio de origen. schema_version permite realizar migraciones, mientras que retention_policy indica a los trabajos de borrado qué más deben eliminar.

Utiliza RAG para recuperar documentos y SGAM para mantener el estado mutable. La búsqueda vectorial sigue teniendo su lugar en el sistema para recuperación difusa, clustering y expansión. El valor actual de mira.passport_deadline debe proceder de un registro de memoria con el ámbito adecuado, no del fragmento que haya quedado primero en el ranking.


Un ejemplo de dato obsoleto

Considera un trace sintético de dos episodios representado como una baseline de DM y un ledger de SGAM:

e1: Mira prefers concise answers. Her passport deadline is 2026-07-15.
e2: Mira corrected the deadline. It is now 2026-06-30.

Una baseline de DM conserva ambos episodios como texto libre, por lo que la búsqueda textual puede devolver e1 porque contiene las palabras correctas. SGAM extrae un dato tipado de cada episodio, identificado por tenant, subject y attribute. Debe devolver e2 como estado actual y conservar e1 para una consulta histórica.

Lo siguiente es una write path autocontenida en SQLite. Utiliza cadenas UTC ISO-8601 para las marcas de tiempo, configura sqlite3.Row antes de leer por nombre de columna y representa los datos como intervalos semiabiertos: [valid_from, valid_to). La tabla rechaza intervalos no válidos y tiene un índice unique parcial para permitir un único dato abierto por tenant, subject y attribute. replace_fact gestiona una transacción; SQLite serializa las escrituras concurrentes, por lo que los clientes deben reintentar la transacción completa después de un error de bloqueo o de unicidad.

import sqlite3
from dataclasses import dataclass

@dataclass(frozen=True)
class MemoryFact:
    tenant_id: str
    subject: str
    attribute: str
    value: str
    valid_from: str
    source_episode_id: str

def open_db() -> sqlite3.Connection:
    db = sqlite3.connect(":memory:")
    db.row_factory = sqlite3.Row
    db.executescript(
        """
        create table memory_facts (
            fact_id integer primary key,
            tenant_id text not null,
            subject text not null,
            attribute text not null,
            value text not null,
            valid_from text not null,
            valid_to text,
            source_episode_id text not null,
            check (valid_to is null or valid_from < valid_to)
        );
        create unique index one_open_fact
        on memory_facts (tenant_id, subject, attribute)
        where valid_to is null;
        """
    )
    return db

def replace_fact(db: sqlite3.Connection, fact: MemoryFact) -> None:
    with db:
        exact = db.execute(
            """
            select fact_id
            from memory_facts
            where tenant_id = ? and subject = ? and attribute = ? and valid_from = ?
            """,
            (fact.tenant_id, fact.subject, fact.attribute, fact.valid_from),
        ).fetchone()
        if exact:
            raise ValueError("equal valid_from requires an application conflict policy")

        containing = db.execute(
            """
            select fact_id, valid_to
            from memory_facts
            where tenant_id = ?
              and subject = ?
              and attribute = ?
              and valid_from < ?
              and (valid_to is null or valid_to > ?)
            limit 1
            """,
            (
                fact.tenant_id,
                fact.subject,
                fact.attribute,
                fact.valid_from,
                fact.valid_from,
            ),
        ).fetchone()
        if containing:
            db.execute(
                "update memory_facts set valid_to = ? where fact_id = ?",
                (fact.valid_from, containing["fact_id"]),
            )
            successor_boundary = containing["valid_to"]
        else:
            successor = db.execute(
                """
                select valid_from
                from memory_facts
                where tenant_id = ? and subject = ? and attribute = ? and valid_from > ?
                order by valid_from
                limit 1
                """,
                (fact.tenant_id, fact.subject, fact.attribute, fact.valid_from),
            ).fetchone()
            successor_boundary = successor["valid_from"] if successor else None

        db.execute(
            """
            insert into memory_facts
                (tenant_id, subject, attribute, value, valid_from, valid_to, source_episode_id)
            values (?, ?, ?, ?, ?, ?, ?)
            """,
            (
                fact.tenant_id,
                fact.subject,
                fact.attribute,
                fact.value,
                fact.valid_from,
                successor_boundary,
                fact.source_episode_id,
            ),
        )

def fact_at(db: sqlite3.Connection, timestamp: str) -> sqlite3.Row:
    return db.execute(
        """
        select value, source_episode_id
        from memory_facts
        where tenant_id = ?
          and subject = ?
          and attribute = ?
          and valid_from <= ?
          and (valid_to is null or valid_to > ?)
        limit 1
        """,
        ("acme", "mira", "passport_deadline", timestamp, timestamp),
    ).fetchone()

db = open_db()
replace_fact(
    db,
    MemoryFact("acme", "mira", "passport_deadline", "2026-07-15", "2026-06-01T09:00:00Z", "e1"),
)
replace_fact(
    db,
    MemoryFact("acme", "mira", "passport_deadline", "2026-06-30", "2026-06-03T10:00:00Z", "e2"),
)
current = fact_at(db, "2026-06-04T00:00:00Z")
assert (current["value"], current["source_episode_id"]) == ("2026-06-30", "e2")

historical = fact_at(db, "2026-06-02T00:00:00Z")
assert (historical["value"], historical["source_episode_id"]) == ("2026-07-15", "e1")

# A delayed extraction predates e1. It ends at e1's existing boundary,
# rather than closing e2, which remains current.
replace_fact(
    db,
    MemoryFact("acme", "mira", "passport_deadline", "2026-08-01", "2026-05-30T08:00:00Z", "e0"),
)
delayed = fact_at(db, "2026-05-31T00:00:00Z")
assert (delayed["value"], delayed["source_episode_id"]) == ("2026-08-01", "e0")
current = fact_at(db, "2026-06-04T00:00:00Z")
assert (current["value"], current["source_episode_id"]) == ("2026-06-30", "e2")
intervals = db.execute("select valid_from, valid_to from memory_facts").fetchall()
assert all(row["valid_to"] is None or row["valid_from"] < row["valid_to"] for row in intervals)

episodes = (
    "e1: Mira prefers concise answers. Her passport deadline is 2026-07-15.",
    "e2: Mira corrected the deadline. It is now 2026-06-30.",
)
naive_match = next(episode for episode in episodes if "passport deadline" in episode)
assert naive_match.startswith("e1:")

replace_fact rechaza primero un valid_from igual: ese conflicto necesita una política de aplicación, como precedencia de fuentes o una revisión confirmada por el usuario, en lugar de una sobrescritura silenciosa. En caso contrario, busca el intervalo que contiene la marca de tiempo entrante. Si encuentra uno, cierra ese predecesor en el límite entrante y asigna a la fila insertada el final anterior del predecesor. Si la marca de tiempo es anterior a todos los intervalos almacenados, utiliza el valid_from del intervalo siguiente como final de la nueva fila. Así, un dato retrasado anterior a e1 se convierte en [2026-05-30, 2026-06-01) y deja intactos e1 y el dato actual e2. El row factory hace válidos row["fact_id"] y los campos de resultado con nombre en la conexión predeterminada creada aquí. El índice unique parcial es la protección de la base de datos para una única fila abierta; una implementación con múltiples writers debe elegir una base de datos y una política de reintentos adecuadas para su carga de trabajo. DM no tiene un paso de actualización equivalente, por lo que el texto antiguo aún puede superar a la corrección en el ranking.

Las assertions verifican la fila actual de e2, la fila histórica de e1, la inserción retrasada anterior a e1, el invariante de intervalos y el primer episodio textual coincidente de forma ingenua:

Naive text memory:
  returned episode: e1 -> passport deadline is 2026-07-15

SGAM current state:
  mira.passport_deadline = 2026-06-30
  valid_from=2026-06-03T10:00:00Z, source=e2

SGAM point-in-time state:
  on 2026-06-02, mira.passport_deadline = 2026-07-15

En producción, combina esta transacción con extracción estructurada en la write path. La transacción de la base de datos actualiza la validez temporal. El modelo extrae un dato candidato, pero no decide qué fila almacenada sigue siendo actual.


Las opciones de almacenamiento dependen del patrón de recuperación

Los proyectos utilizan varios nombres para las partes de este patrón: almacenes de memoria, context graphs, profiles, long-term stores, graph RAG y stateful agents.

Herramienta o frameworkCapa principal de almacenamientoMecanismo de estado temporalMecanismo de esquemaNicho práctico
GraphitiNeo4j, FalkorDB y Amazon Neptune; Kuzu está deprecatedIntervalos de validez de datos y procedencia del episodio de origenTipos de entidades y aristas en Pydantic, aristas temporales y procedenciaMemoria de grafo temporal self-hosted
ZepContext Graph Engine propietarioContext Graphs temporales gestionados que conservan datos cambiantesTipos de entidades y aristas predeterminados y personalizadosMemoria de agentes y governance gestionadas
LangGraph / LangMemAlmacenes de LangGraph respaldados por PostgresMarcas de tiempo y campos propiedad de la aplicación en los registrosAlmacenes JSON más extracción de profiles o collections con PydanticAplicaciones de agentes ya construidas sobre LangGraph
Mem0Stack gestionado, Valkey / Redis / backends vectoriales en configuraciones OSSActualizaciones de memoria; la política temporal sigue siendo propiedad de la aplicaciónTipos de memoria, categorías personalizadas y prompts de extracciónMemoria de usuario, agente y sesión como servicio
Letta / MemGPTEstado y bloques de memoria del agente respaldados por base de datosBloques editables sin intervalos de validez a nivel de campoBloques de memoria editables y etiquetadosAgentes con estado y gestión de contexto al estilo de un sistema operativo
CogneeBackends de grafo, vectoriales y relacionalesEl historial depende de la ontología y del backend seleccionadoExtracción y validación orientadas a ontologíasMemoria de knowledge graph empresarial
LlamaIndex property graphAlmacenes de property graph y almacenes vectorialesLos campos temporales dependen del esquema del grafo y del almacénSchemaLLMPathExtractor con entidades y relaciones permitidasExtracción de grafos sobre documentos y traces

Graphiti es una implementación open source concreta de memoria relacional y temporal. Registra los datos a medida que cambian, conserva punteros a los episodios de origen y permite recuperación híbrida. LangGraph separa los checkpoints de thread de los almacenes entre threads. Mem0 empaqueta las operaciones de memoria como un servicio gestionado. Letta utiliza bloques de contexto editables en lugar de SGAM a nivel de campo, pero sigue tratando el estado del agente como datos persistentes.

Empieza por el modelo de datos. Si la operación principal es la consulta exacta de datos, una tabla relacional con payloads JSON, columnas de validez, índices por tenant y un vector sidecar suele ser suficiente. Añade un grafo cuando recorrer relaciones forme parte del producto, no porque la demo del grafo resulte impresionante.


Construye la write path antes que el grafo

Primero decide qué puede recordar el producto. La elección entre grafo y vector viene después.

Un agente de soporte podría recordar el nivel de cuenta, los casos abiertos y las preferencias de contacto duraderas. No debería convertir cada comentario de frustración en estado del perfil. Un agente de coding podría recordar las convenciones del repositorio y las tareas sin resolver. No debería conservar indefinidamente una nota privada solo porque se recuperó una vez.

Empieza por la write path y trata la memoria como una pequeña mutación de estado:

  1. Define el tipo de memoria, el subject, el ámbito del tenant y la clase de retención.
  2. Extrae registros candidatos con structured output.
  3. Valida el payload con Pydantic o con la capa de esquemas que ya utilice tu stack.
  4. Resuelve los conflictos antes del insert, incluido si el nuevo registro supersede a uno antiguo.
  5. Conserva un puntero a la fuente: el episodio sin procesar, el tool result, el archivo, el ticket o la confirmación del usuario que produjo el registro.
  6. Escribe la versión del esquema con cada registro, en lugar de dejarla únicamente en el código de la aplicación.

El primer almacén SGAM puede ser una tabla relacional con una columna JSON y algunos índices. Un grafo resulta útil cuando el producto necesita recorrer relaciones como cliente-cuenta, cuenta-política, tarea-artefacto o proyecto-decisión.

Hot path y escrituras en background

La extracción inmediata merece la pena cuando el siguiente turno depende de la nueva memoria. Si el usuario dice «recuerda que prefiero respuestas breves», el sistema no debería tener que esperar a un job nocturno para comportarse de otra forma.

La mayoría de los turnos no necesitan una escritura inmediata. Guarda el episodio sin procesar con los metadatos del tenant, la sesión y las herramientas, y deja que un worker en background extraiga los candidatos más adelante. Con consolidación basada en recurrencia, el worker almacena señales débiles y promociona un dato solo después de que se repita evidencia similar o el usuario lo confirme. Esto añade un freshness lag. Es aceptable para «el usuario suele pedir exportaciones CSV» y arriesgado para «el cliente ha cambiado la dirección de entrega».

Mantén la read path determinista. Impón primero el ámbito del tenant y la validez; utiliza después recuperación difusa solo cuando pueda aportar contexto útil.

  1. Filtra por tenant, tipo de memoria y ventana de validez.
  2. Recupera primero el estado estructurado exacto, antes que los vecinos semánticos.
  3. Utiliza expansión vectorial o de grafo para obtener evidencia de apoyo, entidades relacionadas y ejemplos, no como autoridad sobre los datos actuales.
  4. Ensambla el contexto citado más pequeño que pueda responder a la pregunta.

Trata la migración del esquema como un cambio de producto, porque altera lo que el agente puede recordar, citar o eliminar. También puede cambiar qué datos históricos cuentan como actuales. Planifica los scripts de migración, los backfills, las ventanas de dual-read y el comportamiento del borrado en la misma release.


Cuándo merece la pena SGAM

Utiliza SGAM cuando los datos puedan cambiar con el tiempo:

  • preferencias de usuario que puedan actualizarse o revocarse
  • datos de clientes o cuentas con requisitos de auditoría
  • estado de tareas para asistentes de larga duración
  • memoria de proyectos de coding agents
  • estado compartido entre multi-agent
  • notas de cumplimiento en las que importe la procedencia
  • preguntas temporales como «¿qué creíamos antes de la migración?»

SGAM es excesivo cuando la memoria es efímera, exploratoria o barata de recalcular. Si el agente solo necesita continuidad durante unos pocos turnos, basta con un checkpoint y un historial de mensajes recortado. El QA sobre documentos estáticos puede necesitar únicamente RAG. Y si el dominio cambia tanto que el esquema se modifica a diario, la memoria tipada ralentizará al equipo.


Checklist de evaluación

Evalúa el ciclo de vida de la memoria además de la respuesta final. Un sistema puede producir una respuesta plausible después de haber escrito el dato equivocado, recuperado uno obsoleto o cruzado un límite de tenant.

Utilizo la misma separación por etapas que en mi artículo sobre evaluación de RAG. Mide la etapa en la que puede producirse el fallo, en lugar de limitar la evaluación al texto generado. La disciplina de trazas de el artículo sobre evaluación de agentes también se aplica, porque un bug de memoria suele aparecer en el historial de la ejecución antes de llegar a la respuesta.

Yo probaría SGAM mediante replay. Introduce una secuencia fija de episodios en el memory writer e inspecciona el ledger después de cada turno relevante. Después, formula preguntas sobre el estado actual y sobre momentos concretos contra el almacén resultante.

CapaFallo que buscasMedidas
Extracción de escrituraEl agente omitió un dato, inventó uno o produjo una forma no válidaTasa de escrituras válidas según el esquema, precision/recall de extracción, cobertura de episodios de origen
Gestión de conflictosUn dato obsoleto siguió siendo actual o se sobrescribió un dato antiguo válidoCorrección de supersession, tasa de duplicados, corrección de la invalidación de datos obsoletos
Aislamiento y políticaLa memoria se filtró entre usuarios o sobrevivió a su ventana de políticaFallos de aislamiento de tenant, corrección del borrado, cumplimiento de la retención
Recuperación de lecturaEl registro correcto existe, pero el lector no lo obtuvoExactitud del estado actual, exactitud en un momento concreto, recall@k sobre registros de memoria
Grounding de la respuestaLa respuesta utilizó memoria sin respaldo o citó la fuente incorrectaSoporte de claims frente a episodios de origen, exactitud de citas, corrección de la resolución de conflictos
OperacionesLa ruta de memoria es demasiado lenta, obsoleta o caraLatencia de escritura p95, freshness lag, latencia de lectura, coste por consulta

Benchmarks como LoCoMo, LongMemEval y ATM-Bench proporcionan casos de prueba públicos. No sustituyen a un conjunto de tests de dominio. Un asistente de coding, un bot de soporte al cliente y un copiloto de cumplimiento necesitan esquemas, filtros, reglas de retención y tests de fallos diferentes.


Advertencias

SGAM es mi etiqueta para un patrón, no un estándar. Los proyectos existentes dividen el problema de formas distintas. La memoria de LangGraph y LangMem describen almacenes a corto y largo plazo, perfiles, colecciones, escrituras en la hot path y gestores de memoria en background. Zep Graphiti utiliza el término Context Graph temporal. Letta conserva bloques de memoria editables, mientras que Mem0 ofrece una capa de memoria gestionada. Microsoft GraphRAG, los property graphs de LlamaIndex y Cognee plantean partes relacionadas del problema como knowledge graphs.

Un perfil de usuario, un log de episodios, un grafo documental y un bloque de memoria editable por el agente resuelven problemas distintos de recuperación y actualización. Reservo SGAM para la memoria persistente que representa el estado actual de la aplicación y que, por tanto, necesita esquema, validez, procedencia, gestión de conflictos, retención y migración.

La memoria tipada también puede ser incorrecta. Un esquema facilita la inspección de las escrituras erróneas; no las convierte en fiables. Sigues necesitando confianza en las fuentes, confirmación del usuario para datos sensibles, una política de conflictos, borrado y monitorización.

La migración del esquema requiere trabajo. Una vez que la memoria se convierte en estado, eres responsable del versionado, los backfills, los registros antiguos y el comportamiento del borrado. Si omites ese trabajo, los registros antiguos sobrevivirán a la semántica o a la política de retención que los creó.


Referencias