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ãoSOSGRSGAM
ObjectivoDevolver um objecto conforme a um schemaTornar inspectáveis os campos pretendidos e a intenção da decisãoGerir memória persistente depois da chamada ao modelo
ÂmbitoUma resposta geradaUma resposta do modelo; a política entre passos pode abranger várias chamadasRegistos utilizados entre chamadas, sessões e execuções
Papel do schemaDefine campos de saída, tipos e valores permitidosDescreve evidência, campos intermédios e a decisão candidataDefine registos armazenados, relações e ciclo de vida
Imposiçãoconstrained decoding impede uma saída inválida segundo o schemaApenas a forma; chamadas separadas, validadores e política impõem dependências e ordemValidação da aplicação, restrições da base de dados e regras de conflito impõem o ciclo de vida
Tempo de vidaA chamada actual, salvo se a aplicação armazenar o objectoO reasoning trace é normalmente descartado depois da decisãoPersiste até ser actualizado, expirar ou ser eliminado
Modo de falhaForma válida com significado erradoOs campos estão presentes, mas o reasoning ou o tratamento de dependências podem continuar erradosEstado 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:

  1. Capturar um episódio bruto a partir de mensagens, tool results ou eventos de negócio.
  2. Extrair candidatos tipados através de structured output.
  3. Validar o schema e rejeitar escritas malformadas.
  4. Resolver conflitos, fechar factos obsoletos e preservar a proveniência.
  5. Fazer commit do registo no SGAM store.

O fluxo do pedido é o caminho de leitura:

  1. Começar pela pergunta do utilizador.
  2. Decidir se a pergunta necessita do estado actual ou do estado num momento específico.
  3. Filtrar por tenant, tipo de memória, sujeito, atributo e janela de validade.
  4. Acrescentar expansão vectorial ou em grafo apenas se a pesquisa exacta do estado não for suficiente.
  5. Montar o contexto mínimo com citações para o modelo.

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

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 frameworkCamada principal de armazenamentoMecanismo de estado temporalMecanismo de schemaNicho prático
GraphitiNeo4j, FalkorDB e Amazon Neptune; Kuzu está deprecatedIntervalos de validade dos factos e proveniência do source episodeTipos de entidades e edges em Pydantic, edges temporais e proveniênciaMemória temporal em grafo, self-hosted
ZepContext Graph Engine proprietárioContext Graphs temporais geridos que preservam factos mutáveisTipos predefinidos e custom de entidades e edgesMemória de agents e governance geridas
LangGraph / LangMemStores do LangGraph, stores suportados por PostgresTimestamps e campos geridos pela aplicação nos registos do storeStores JSON e extracção de profiles ou collections com PydanticAplicações de agents já construídas sobre LangGraph
Mem0Stack gerida, Valkey / Redis / backends vectoriais em configurações OSSActualizações de memória; a política temporal continua sob responsabilidade da aplicaçãoTipos de memória, categorias custom e prompts de extracçãoMemória de utilizador, agent e sessão como serviço
Letta / MemGPTEstado e memory blocks do agent suportados por base de dadosBlocos editáveis sem intervalos de validade ao nível dos camposBlocos de memória editáveis e com labelsAgents com estado e gestão de contexto ao estilo de um sistema operativo
CogneeBackends de grafo, vectoriais e relacionaisO histórico depende da ontologia e do backend seleccionadoExtracção e validação orientadas por ontologiaMemória de knowledge graphs empresariais
LlamaIndex property graphStores de property graph e vector storesOs campos temporais dependem do schema do grafo e do storeSchemaLLMPathExtractor com entidades e relações permitidasExtracçã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:

  1. Dê um nome ao tipo de memória, ao sujeito, ao âmbito do tenant e à classe de retenção.
  2. Extraia registos candidatos com structured output.
  3. Valide o payload com Pydantic ou com a camada de schema que a sua stack já utiliza.
  4. Resolva os conflitos antes do insert, incluindo se o novo registo substitui um antigo.
  5. Mantenha um ponteiro para o episódio bruto, tool result, ficheiro, ticket ou confirmação do utilizador que originou o registo.
  6. 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.

  1. Filtre por tenant, tipo de memória e janela de validade.
  2. Recupere primeiro o estado estruturado exacto, antes dos vizinhos semânticos.
  3. Utilize expansão vectorial ou em grafo para evidência de suporte, entidades relacionadas e exemplos, não como autoridade para factos actuais.
  4. 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.

CamadaFalha procuradaMétricas
Extracção de escritaO agent não detectou um facto, inventou um ou produziu uma forma inválidaTaxa de escritas válidas segundo o schema, precisão/recall da extracção, cobertura dos source episodes
Gestão de conflitosUm facto obsoleto permaneceu actual ou um facto antigo válido foi substituídoCorrecção da substituição, taxa de duplicados, correcção da invalidação de factos obsoletos
Isolamento e políticaA memória foi partilhada entre utilizadores ou sobreviveu para além da sua janela de políticaFalhas de isolamento entre tenants, correcção da eliminação, conformidade da retenção
Recuperação de leituraO registo certo existe, mas o leitor não o foi buscarPrecisão do estado actual, precisão num momento específico, recall@k sobre registos de memória
Fundamentação da respostaA resposta usou memória sem suporte ou citou a fonte erradaSuporte das claims face aos source episodes, precisão das citações, correcção da resolução de conflitos
OperaçõesO caminho de memória é demasiado lento, obsoleto ou dispendiosop95 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