Memória de AI Agents: estado orientado por schema e proveniência
Tradução automática Este artigo foi traduzido automaticamente a partir da versão original em inglês.
Os agents de longa duração recuperam frequentemente factos obsoletos porque a memória semântica comum não tem uma regra para decidir qual é o valor actual.
Quando um utilizador altera o prazo do passaporte de 15 de Julho para 30 de Junho, a pesquisa vectorial pode recuperar ambas as afirmações. Uma camada de memória com estado tem de registar que o valor de 30 de Junho substitui o anterior.
Para os engenheiros que desenvolvem agents de longa duração ou multi-tenant, esta falha com factos obsoletos explica por que razão a memória tem de sobreviver a execuções separadas, impondo simultaneamente o estado actual e os limites entre tenants. O design abaixo mostra como escrever registos tipados e testar leituras do estado actual e de um momento específico, sem tratar a recuperação vectorial como fonte de verdade.
A armadilha da context window
A context window é a entrada disponível para uma chamada ao modelo. Uma aplicação pode transportar mensagens para chamadas posteriores, mas a política da aplicação tem de decidir quais dos factos antigos continuam verdadeiros e quem pode vê-los.
Os agents de longa duração precisam de recordar preferências, estado de tarefas, factos sobre clientes, decisões de ferramentas, notas de conformidade e erros anteriores. A versão fácil consiste em acrescentar resumos ou despejar notas antigas num vector store. Isso funciona até que um dos factos memorizados seja alterado.
Agora o agent tem dois prazos de passaporte, dois formatos preferidos ou duas decisões de projecto. A pesquisa semântica pode recuperar ambos. Um resumo pode substituir um deles. Um contexto longo pode incluir o facto obsoleto ao lado do activo. Estes designs recuperam texto, mas deixam o valor actual sem imposição.
O contrato da memória tem de responder a perguntas concretas:
- O que é verdade agora?
- O que era verdade a 2 de Junho?
- Quem o afirmou?
- A que tenant pertence?
- Que facto anterior foi substituído por este?
- Posso eliminá-lo ou fazê-lo expirar?
A Schema-Guided Agent Memory (SGAM) armazena estas respostas como campos e relações, em vez de as deixar implícitas em prosa.
O que significa SGAM
Três conceitos com nomes semelhantes definem o âmbito de SGAM.
Schema-Guided Dialogue (SGD) é o dataset de diálogo orientado para tarefas da Google de 2019. O seu schema descreve APIs de serviços, intents e slots, permitindo a um modelo de diálogo acompanhar o estado de serviços que ainda não viu. É um precedente útil para o acompanhamento baseado em schema, com um âmbito limitado a serviços de diálogo.
Schema-Guided Memory (SGM) é o termo de investigação utilizado por Mei et al. em According to Me: Long-Term Personalized Referential Memory QA. O artigo compara Descriptive Memory (DM) em texto livre com itens de memória key-value de schema fixo. Ambas as representações contêm a mesma informação de origem, mas em estruturas diferentes.
Neste artigo, uso Schema-Guided Agent Memory (SGAM) para designar um padrão de engenharia em que os schemas governam escritas, actualizações, recuperação e eliminação. O schema define o estado da aplicação e o seu ciclo de vida.
O ATM-Bench mostra por que razão a representação é importante. Utiliza aproximadamente quatro anos de dados pessoais provenientes de emails, imagens e vídeos. As perguntas exigem referências pessoais, localização, múltiplas evidências e actualizações ao longo do tempo. No seu hard split, o artigo relata que SGM supera DM na recuperação e no question answering, sob a configuração testada. SGM expõe campos como tempo, fonte, localização, entidades e tags numa representação fixa. Considero essa representação e os resultados reportados a conclusão sustentada; o artigo não estabelece um mecanismo directo de endereçamento por campo.
SGM versus DM responde a uma questão de armazenamento: a memória deve permanecer em texto livre ou usar campos nomeados? Um agent em produção tem outro problema antes do armazenamento. Tem de transformar uma conversa não estruturada numa actualização de memória proposta. Schema-Guided Reasoning (SGR) torna inspectável esse caminho de decisão pretendido: a resposta pode incluir evidência, o sujeito e o atributo, uma comparação com o estado actual e uma escrita candidata. A resposta do modelo não impõe dependências nem ordem entre esses campos. Chamadas separadas, validadores e a política da aplicação impõem essas regras; SGAM aplica as regras de armazenamento e do ciclo de vida depois da chamada ao modelo.
Separar a extracção pelo modelo da propriedade da memória
A escrita na memória atravessa três camadas. Structured Output (SO) impõe a forma do objecto candidato. Schema-Guided Reasoning (SGR) torna inspectáveis os campos pretendidos pelo modelo e a intenção da decisão numa resposta estruturada. Schema-Guided Agent Memory (SGAM) gere o candidato como estado persistente depois da chamada ao modelo. Chamadas separadas, validadores e a política da aplicação impõem dependências, ordem e regras do ciclo de vida.
Um veredicto, encaminhamento ou plano normalmente expira com o pedido actual. Outra execução pode ler um candidato a memória dias mais tarde ou utilizá-lo para escolher um tool call. Esse tempo de vida mais longo exige regras de armazenamento que SGR não fornece.
SGR estrutura uma chamada ao modelo em torno de uma topologia de reasoning pretendida. Para uma escrita na memória, a resposta pode incluir evidência de origem, um sujeito e atributo normalizados, uma comparação com o estado actual e uma actualização proposta. Pydantic ou JSON Schema descrevem esses campos. Structured Output nativo do provider ou um runtime de guided decoding, como XGrammar, mantém a resposta nessa forma.
Essa forma torna inspectáveis os campos e a intenção declarados pelo modelo. Não garante uma conclusão correcta nem prova que o modelo utilizou esses campos pela ordem indicada. Chamadas separadas, validadores e a política da aplicação são a fronteira de imposição para dependências, ordem e ciclo de vida.
SGAM decide o que acontece depois de esse objecto existir. Deve ser armazenado? Substitui um facto anterior? Que tenant pode vê-lo? É actual ou histórico? Que source episode o sustenta?
A tabela apresenta a responsabilidade e o modo de falha de cada camada:
| Dimensão | SO | SGR | SGAM |
|---|---|---|---|
| Objectivo | Devolver um objecto conforme a um schema | Tornar inspectáveis os campos pretendidos e a intenção da decisão | Gerir memória persistente depois da chamada ao modelo |
| Âmbito | Uma resposta gerada | Uma resposta do modelo; a política entre passos pode abranger várias chamadas | Registos utilizados entre chamadas, sessões e execuções |
| Papel do schema | Define campos de saída, tipos e valores permitidos | Descreve evidência, campos intermédios e a decisão candidata | Define registos armazenados, relações e ciclo de vida |
| Imposição | constrained decoding impede uma saída inválida segundo o schema | Apenas a forma; chamadas separadas, validadores e política impõem dependências e ordem | Validação da aplicação, restrições da base de dados e regras de conflito impõem o ciclo de vida |
| Tempo de vida | A chamada actual, salvo se a aplicação armazenar o objecto | O reasoning trace é normalmente descartado depois da decisão | Persiste até ser actualizado, expirar ou ser eliminado |
| Modo de falha | Forma válida com significado errado | Os campos estão presentes, mas o reasoning ou o tratamento de dependências podem continuar errados | Estado obsoleto, poluído, sem âmbito ou não auditável |
No caminho de escrita, a sequência é:
SGR reasoning schema + Structured Output -> candidate write -> SGAM policy and persistence
O excerto ilustrativo seguinte de memory_models.py define o objecto transmitido da extracção para o serviço de escrita SGAM. Este bloco precisa de Pydantic, pelo que é marcado como no-run nas verificações do exemplo, para ambientes que não instalem essa dependência 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 regista aquilo que o modelo extraiu. O serviço de escrita SGAM continua a decidir se deve rejeitá-lo, combiná-lo ou armazená-lo.
Os caminhos de escrita e de leitura têm funções diferentes
Só o caminho de escrita altera o estado armazenado. O caminho de leitura selecciona registos para o pedido actual.
O fluxo de ingestão é o caminho de escrita:
- Capturar um episódio bruto a partir de mensagens, tool results ou eventos de negócio.
- Extrair candidatos tipados através de structured output.
- Validar o schema e rejeitar escritas malformadas.
- Resolver conflitos, fechar factos obsoletos e preservar a proveniência.
- Fazer commit do registo no SGAM store.
O fluxo do pedido é o caminho de leitura:
- Começar pela pergunta do utilizador.
- Decidir se a pergunta necessita do estado actual ou do estado num momento específico.
- Filtrar por tenant, tipo de memória, sujeito, atributo e janela de validade.
- Acrescentar expansão vectorial ou em grafo apenas se a pesquisa exacta do estado não for suficiente.
- Montar o contexto mínimo com citações para o modelo.
Leia o diagrama da esquerda para a direita, em duas linhas. A linha superior escreve memória e a inferior lê-a. Ambas utilizam o mesmo store.
O que deve pertencer a um schema de memória
Um registo SGAM mínimo precisa de mais do 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
Com estes campos, um novo prazo de passaporte pode fechar o prazo anterior sem apagar o histórico. A mesma tabela pode responder a consultas sobre o estado actual e sobre um momento específico, e depois seguir o resultado até ao seu source episode. schema_version permite migrações, enquanto retention_policy informa os jobs de eliminação sobre o que mais têm de remover.
Use RAG para recuperar documentos e SGAM para manter estado mutável. A pesquisa vectorial continua a ter lugar no sistema para recuperação difusa, clustering e expansão. O valor actual de mira.passport_deadline deve provir de um registo de memória delimitado pelo âmbito, e não do chunk que por acaso ficou em primeiro lugar.
Um exemplo de facto obsoleto
Considere um trace sintético com dois episódios, representado como baseline DM e ledger SGAM:
e1: Mira prefers concise answers. Her passport deadline is 2026-07-15.
e2: Mira corrected the deadline. It is now 2026-06-30.
Um baseline DM mantém ambos os episódios como texto livre, pelo que a pesquisa textual pode devolver e1 por conter as palavras certas. SGAM extrai um facto tipado de cada episódio, indexado por tenant, sujeito e atributo. Deve devolver e2 como estado actual e manter e1 para uma consulta histórica.
O seguinte é um caminho de escrita SQLite autónomo. Utiliza strings UTC ISO-8601 para timestamps, configura sqlite3.Row antes de ler pelo nome das colunas e representa factos como intervalos semiabertos: [valid_from, valid_to). A tabela rejeita intervalos inválidos e tem um índice unique parcial para um facto aberto por tenant, sujeito e atributo. replace_fact controla uma transacção; SQLite serializa writers concorrentes, pelo que os callers devem repetir a transacção completa depois de um erro de lock ou de unicidade.
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 rejeita primeiro um valid_from igual: esse conflito precisa de uma política da aplicação, como precedência da fonte ou uma revisão confirmada pelo utilizador, em vez de uma substituição silenciosa. Caso contrário, encontra o intervalo que contém o timestamp recebido. Se encontrar um, fecha esse predecessor no limite recebido e atribui à linha inserida o end anterior do predecessor. Se o timestamp for anterior a todos os intervalos armazenados, utiliza o valid_from do intervalo seguinte como end da nova linha. Assim, um facto atrasado anterior a e1 torna-se [2026-05-30, 2026-06-01) e mantém e1 e o valor actual e2 intactos. A row factory torna row["fact_id"] e os campos de resultado nomeados válidos na ligação predefinida criada aqui. O índice unique parcial é a salvaguarda da base de dados para uma única linha aberta; uma implementação com múltiplos writers deve escolher uma base de dados e uma política de retry adequadas à sua carga de trabalho. DM não tem um passo de actualização equivalente, pelo que o texto antigo pode continuar a superar a correcção.
As asserções verificam a linha actual e2, a linha histórica e1, a inserção atrasada anterior a e1, o invariante dos intervalos e o primeiro episódio textual correspondente obtido de forma ingénua:
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
Em produção, combine esta transacção com extracção estruturada no caminho de escrita. A transacção da base de dados actualiza a validade temporal. O modelo extrai um facto candidato, mas não decide qual das linhas armazenadas permanece actual.
As escolhas de armazenamento seguem o padrão de recuperação
Os projectos utilizam vários nomes para partes deste padrão: memory stores, context graphs, profiles, long-term stores, graph RAG e stateful agents.
| Ferramenta ou framework | Camada principal de armazenamento | Mecanismo de estado temporal | Mecanismo de schema | Nicho prático |
|---|---|---|---|---|
| Graphiti | Neo4j, FalkorDB e Amazon Neptune; Kuzu está deprecated | Intervalos de validade dos factos e proveniência do source episode | Tipos de entidades e edges em Pydantic, edges temporais e proveniência | Memória temporal em grafo, self-hosted |
| Zep | Context Graph Engine proprietário | Context Graphs temporais geridos que preservam factos mutáveis | Tipos predefinidos e custom de entidades e edges | Memória de agents e governance geridas |
| LangGraph / LangMem | Stores do LangGraph, stores suportados por Postgres | Timestamps e campos geridos pela aplicação nos registos do store | Stores JSON e extracção de profiles ou collections com Pydantic | Aplicações de agents já construídas sobre LangGraph |
| Mem0 | Stack gerida, Valkey / Redis / backends vectoriais em configurações OSS | Actualizações de memória; a política temporal continua sob responsabilidade da aplicação | Tipos de memória, categorias custom e prompts de extracção | Memória de utilizador, agent e sessão como serviço |
| Letta / MemGPT | Estado e memory blocks do agent suportados por base de dados | Blocos editáveis sem intervalos de validade ao nível dos campos | Blocos de memória editáveis e com labels | Agents com estado e gestão de contexto ao estilo de um sistema operativo |
| Cognee | Backends de grafo, vectoriais e relacionais | O histórico depende da ontologia e do backend seleccionado | Extracção e validação orientadas por ontologia | Memória de knowledge graphs empresariais |
| LlamaIndex property graph | Stores de property graph e vector stores | Os campos temporais dependem do schema do grafo e do store | SchemaLLMPathExtractor com entidades e relações permitidas | Extracção em grafo sobre documentos e traces |
Graphiti é uma implementação open-source concreta de memória relacional e temporal. Acompanha a evolução dos factos, mantém ponteiros para source episodes e suporta recuperação híbrida. LangGraph separa checkpoints de threads de stores entre threads. Mem0 empacota operações de memória como um serviço gerido. Letta utiliza context blocks editáveis em vez de SGAM ao nível dos campos, mas continua a tratar o estado do agent como dados persistentes.
Comece pelo modelo de dados. Se a pesquisa exacta de factos for a operação principal, uma tabela relacional com payloads JSON, colunas de validade, índices por tenant e um vector sidecar é normalmente suficiente. Acrescente um grafo quando a travessia de relações fizer parte do produto, não porque a demonstração do grafo pareça impressionante.
Construa primeiro o caminho de escrita, antes do grafo
Primeiro, decida o que o produto está autorizado a recordar. A escolha entre grafo e vector vem depois.
Um support agent pode recordar o nível da conta, casos em aberto e preferências de contacto persistentes. Não deve promover todos os comentários de frustração para estado de profile. Um coding agent pode recordar convenções do repo e tarefas por resolver. Não deve guardar indefinidamente uma nota privada só porque essa nota foi recuperada uma vez.
Comece pelo caminho de escrita e trate a memória como uma pequena mutação de estado:
- Dê um nome ao tipo de memória, ao sujeito, ao âmbito do tenant e à classe de retenção.
- Extraia registos candidatos com structured output.
- Valide o payload com Pydantic ou com a camada de schema que a sua stack já utiliza.
- Resolva os conflitos antes do insert, incluindo se o novo registo substitui um antigo.
- Mantenha um ponteiro para o episódio bruto, tool result, ficheiro, ticket ou confirmação do utilizador que originou o registo.
- Escreva a versão do schema com cada registo, em vez de a deixar apenas no código da aplicação.
O primeiro SGAM store pode ser uma tabela relacional com uma coluna JSON e alguns índices. Um grafo torna-se útil quando o produto precisa de percorrer relações como cliente-conta, conta-política, tarefa-artefacto ou projecto-decisão.
Hot path e escritas em background
A extracção imediata vale a pena quando o turno seguinte depende da nova memória. Se o utilizador disser «lembra-te de que prefiro respostas curtas», o sistema não deve ter de esperar por um job nocturno para passar a comportar-se de forma diferente.
A maioria dos turnos não precisa de uma escrita imediata. Guarde o episódio bruto com metadados de tenant, sessão e ferramentas, e deixe que um worker em background extraia candidatos mais tarde. Com consolidação baseada em recorrência, o worker armazena sinais fracos e promove um facto apenas depois de se repetirem evidências semelhantes ou de o utilizador o confirmar. Isto acrescenta latência de actualização. É aceitável para «o utilizador pede frequentemente exportações CSV» e arriscado para «o cliente alterou a morada de entrega».
Mantenha o caminho de leitura determinístico. Imponha primeiro o âmbito do tenant e a validade; só depois use recuperação difusa quando esta puder acrescentar contexto útil.
- Filtre por tenant, tipo de memória e janela de validade.
- Recupere primeiro o estado estruturado exacto, antes dos vizinhos semânticos.
- Utilize expansão vectorial ou em grafo para evidência de suporte, entidades relacionadas e exemplos, não como autoridade para factos actuais.
- Monte o menor contexto com citações capaz de responder à pergunta.
Trate a migração do schema como uma alteração do produto, porque altera aquilo que o agent consegue recordar, citar ou eliminar. Também pode alterar quais os factos históricos que contam como actuais. Planeie scripts de migração, backfills, janelas de dual-read e comportamento de eliminação na mesma release.
Quando SGAM compensa a complexidade
Utilize SGAM quando os factos podem mudar ao longo do tempo:
- preferências do utilizador que podem ser actualizadas ou revogadas
- factos sobre clientes ou contas com requisitos de auditoria
- estado de tarefas para assistants de longa duração
- memória de projectos de coding agents
- estado partilhado por multi-agent systems
- notas de conformidade em que a proveniência é importante
- perguntas temporais como «em que acreditávamos antes da migração?»
SGAM é excessivo quando a memória é efémera, exploratória ou barata de recalcular. Se o agent só precisar de continuidade durante alguns turnos, um checkpoint e um histórico de mensagens reduzido são suficientes. QA de documentos estáticos pode precisar apenas de RAG. E, se o domínio for tão instável que o schema mude todos os dias, a memória tipada vai atrasar a equipa.
Checklist de avaliação
Avalie o ciclo de vida da memória, além da resposta final. Um sistema pode produzir uma resposta plausível depois de ter escrito o facto errado, recuperado um facto obsoleto ou atravessado um limite de tenant.
Utilizo a mesma separação por etapas que no meu artigo sobre avaliação de RAG. Meça a etapa em que uma falha pode ocorrer, em vez de limitar a avaliação ao texto gerado. A disciplina de traces do artigo sobre avaliação de agents também se aplica, porque um bug de memória aparece frequentemente no histórico da execução antes de chegar à resposta.
Eu testaria SGAM com replay. Alimente o memory writer com uma sequência fixa de episódios e inspeccione o ledger depois de cada turno relevante. Em seguida, faça perguntas sobre o estado actual e sobre momentos específicos contra o store resultante.
| Camada | Falha procurada | Métricas |
|---|---|---|
| Extracção de escrita | O agent não detectou um facto, inventou um ou produziu uma forma inválida | Taxa de escritas válidas segundo o schema, precisão/recall da extracção, cobertura dos source episodes |
| Gestão de conflitos | Um facto obsoleto permaneceu actual ou um facto antigo válido foi substituído | Correcção da substituição, taxa de duplicados, correcção da invalidação de factos obsoletos |
| Isolamento e política | A memória foi partilhada entre utilizadores ou sobreviveu para além da sua janela de política | Falhas de isolamento entre tenants, correcção da eliminação, conformidade da retenção |
| Recuperação de leitura | O registo certo existe, mas o leitor não o foi buscar | Precisão do estado actual, precisão num momento específico, recall@k sobre registos de memória |
| Fundamentação da resposta | A resposta usou memória sem suporte ou citou a fonte errada | Suporte das claims face aos source episodes, precisão das citações, correcção da resolução de conflitos |
| Operações | O caminho de memória é demasiado lento, obsoleto ou dispendioso | p95 da latência de escrita, latência de actualização, latência de leitura, custo por consulta |
Benchmarks como LoCoMo, LongMemEval e ATM-Bench disponibilizam casos de teste públicos. Não substituem um conjunto de testes específico do domínio. Um coding assistant, um bot de apoio ao cliente e um copiloto de conformidade precisam de schemas, filtros, regras de retenção e testes de falhas diferentes.
Limitações
SGAM é o meu nome para um padrão, não um standard. Os projectos existentes dividem o problema de formas diferentes. A memória do LangGraph e LangMem descrevem stores de curto e longo prazo, profiles, collections, escritas no hot path e gestores de memória em background. Zep Graphiti utiliza o termo temporal Context Graph. Letta persiste memory blocks editáveis, enquanto Mem0 disponibiliza uma camada de memória gerida. Microsoft GraphRAG, os property graphs do LlamaIndex e o Cognee enquadram partes relacionadas do problema como knowledge graphs.
Um user profile, um episode log, um document graph e um memory block editável pelo agent resolvem problemas diferentes de recuperação e actualização. Reservo SGAM para memória persistente que representa o estado actual da aplicação e que, por isso, precisa de schema, validade, proveniência, gestão de conflitos, retenção e migração.
A memória tipada também pode estar errada. Um schema torna as escritas incorrectas mais fáceis de inspeccionar; não as torna fiáveis. Continua a precisar de confiança na fonte, confirmação do utilizador para factos sensíveis, política de conflitos, eliminação e monitorização.
A migração do schema dá trabalho. Quando a memória passa a ser estado, fica responsável pelo versionamento, backfills, registos antigos e comportamento de eliminação. Se ignorar esse trabalho, os registos antigos sobreviverão às semânticas ou à política de retenção que lhes deram origem.
Referências
- According to Me: Long-Term Personalized Referential Memory QA - Artigo de Mei et al. que introduz ATM-Bench e Schema-Guided Memory.
- Towards Scalable Multi-Domain Conversational Agents: The Schema-Guided Dialogue Dataset - Artigo de Rastogi et al. sobre o dataset SGD.
- LongMemEval: Benchmarking Chat Assistants on Long-Term Interactive Memory - Benchmark de Wu et al. para capacidades de memória de longo prazo.
- 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