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ón | SO | SGR | SGAM |
|---|---|---|---|
| Propósito | Devolver un objeto que cumpla un esquema | Hacer inspeccionables los campos previstos y la intención de decisión | Gestionar la memoria persistente después de la llamada al modelo |
| Alcance | Una respuesta generada | Una respuesta del modelo; la política entre pasos puede abarcar varias llamadas | Registros utilizados entre llamadas, sesiones y ejecuciones |
| Papel del esquema | Define campos de salida, tipos y valores permitidos | Describe evidencia, campos intermedios y decisión candidata | Define registros almacenados, relaciones y ciclo de vida |
| Enforcement | El constrained decoding bloquea salidas no válidas según el esquema | Solo la forma; las llamadas independientes, los validadores y la política imponen dependencias y orden | La validación de la aplicación, las restricciones de la base de datos y las reglas de conflicto imponen el ciclo de vida |
| Vida útil | La llamada actual, salvo que la aplicación almacene el objeto | El reasoning trace normalmente se descarta después de la decisión | Persiste hasta que se actualiza, expira o elimina |
| Modo de fallo | Forma válida con significado incorrecto | Los campos están presentes, pero el reasoning o la gestión de dependencias aún pueden ser incorrectos | Estado 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:
- Captura un episodio sin procesar procedente de mensajes, tool results o eventos de negocio.
- Extrae candidatos tipados mediante structured output.
- Valida el esquema y rechaza las escrituras malformadas.
- Reconcilia conflictos, cierra datos obsoletos y conserva la procedencia.
- Confirma el registro en el almacén de SGAM.
El flujo de solicitud es la read path:
- Empieza con la pregunta del usuario.
- Decide si la pregunta necesita el estado actual o el estado en un momento concreto.
- Filtra por tenant, tipo de memoria, subject, attribute y ventana de validez.
- Añade expansión vectorial o de grafo solo si la consulta exacta del estado no es suficiente.
- Ensambla el contexto citado más pequeño posible para el modelo.
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 framework | Capa principal de almacenamiento | Mecanismo de estado temporal | Mecanismo de esquema | Nicho práctico |
|---|---|---|---|---|
| Graphiti | Neo4j, FalkorDB y Amazon Neptune; Kuzu está deprecated | Intervalos de validez de datos y procedencia del episodio de origen | Tipos de entidades y aristas en Pydantic, aristas temporales y procedencia | Memoria de grafo temporal self-hosted |
| Zep | Context Graph Engine propietario | Context Graphs temporales gestionados que conservan datos cambiantes | Tipos de entidades y aristas predeterminados y personalizados | Memoria de agentes y governance gestionadas |
| LangGraph / LangMem | Almacenes de LangGraph respaldados por Postgres | Marcas de tiempo y campos propiedad de la aplicación en los registros | Almacenes JSON más extracción de profiles o collections con Pydantic | Aplicaciones de agentes ya construidas sobre LangGraph |
| Mem0 | Stack gestionado, Valkey / Redis / backends vectoriales en configuraciones OSS | Actualizaciones de memoria; la política temporal sigue siendo propiedad de la aplicación | Tipos de memoria, categorías personalizadas y prompts de extracción | Memoria de usuario, agente y sesión como servicio |
| Letta / MemGPT | Estado y bloques de memoria del agente respaldados por base de datos | Bloques editables sin intervalos de validez a nivel de campo | Bloques de memoria editables y etiquetados | Agentes con estado y gestión de contexto al estilo de un sistema operativo |
| Cognee | Backends de grafo, vectoriales y relacionales | El historial depende de la ontología y del backend seleccionado | Extracción y validación orientadas a ontologías | Memoria de knowledge graph empresarial |
| LlamaIndex property graph | Almacenes de property graph y almacenes vectoriales | Los campos temporales dependen del esquema del grafo y del almacén | SchemaLLMPathExtractor con entidades y relaciones permitidas | Extracció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:
- Define el tipo de memoria, el subject, el ámbito del tenant y la clase de retención.
- Extrae registros candidatos con structured output.
- Valida el payload con Pydantic o con la capa de esquemas que ya utilice tu stack.
- Resuelve los conflictos antes del insert, incluido si el nuevo registro supersede a uno antiguo.
- 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.
- 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.
- Filtra por tenant, tipo de memoria y ventana de validez.
- Recupera primero el estado estructurado exacto, antes que los vecinos semánticos.
- Utiliza expansión vectorial o de grafo para obtener evidencia de apoyo, entidades relacionadas y ejemplos, no como autoridad sobre los datos actuales.
- 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.
| Capa | Fallo que buscas | Medidas |
|---|---|---|
| Extracción de escritura | El agente omitió un dato, inventó uno o produjo una forma no válida | Tasa de escrituras válidas según el esquema, precision/recall de extracción, cobertura de episodios de origen |
| Gestión de conflictos | Un dato obsoleto siguió siendo actual o se sobrescribió un dato antiguo válido | Corrección de supersession, tasa de duplicados, corrección de la invalidación de datos obsoletos |
| Aislamiento y política | La memoria se filtró entre usuarios o sobrevivió a su ventana de política | Fallos de aislamiento de tenant, corrección del borrado, cumplimiento de la retención |
| Recuperación de lectura | El registro correcto existe, pero el lector no lo obtuvo | Exactitud del estado actual, exactitud en un momento concreto, recall@k sobre registros de memoria |
| Grounding de la respuesta | La respuesta utilizó memoria sin respaldo o citó la fuente incorrecta | Soporte de claims frente a episodios de origen, exactitud de citas, corrección de la resolución de conflictos |
| Operaciones | La ruta de memoria es demasiado lenta, obsoleta o cara | Latencia 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
- According to Me: Long-Term Personalized Referential Memory QA - Artículo de Mei et al. que introduce ATM-Bench y Schema-Guided Memory.
- Towards Scalable Multi-Domain Conversational Agents: The Schema-Guided Dialogue Dataset - Artículo de Rastogi et al. sobre el dataset SGD.
- LongMemEval: Benchmarking Chat Assistants on Long-Term Interactive Memory - Benchmark de Wu et al. sobre capacidades de memoria a largo plazo.
- Graphiti: Build Temporal Context Graphs for AI Agents
- Zep: A Temporal Knowledge Graph Architecture for Agent Memory
- Mem0 Platform Overview
- Mem0: Building Production-Ready AI Agents with Scalable Long-Term Memory
- LangGraph Memory Overview
- LangGraph Persistence
- LangMem documentation
- Letta: Introduction to Stateful Agents
- Microsoft GraphRAG documentation
- LlamaIndex: Using a Property Graph Index
- Cognee Documentation
- Pydantic model validation docs
- Python sqlite3 documentation