Ir para o conteúdo

Tradução automática

Este artigo foi traduzido automaticamente a partir da versão original em inglês.

Arquitetura de Memória para Agentes de IA em 2026: Checkpoints, Vector Stores e Memória Baseada em Ficheiros

Parte 2 da série Engineering the Agentic Stack

O estado é o que separa um chatbot de um agente de IA. Sem memória do agente, cada interação começa do zero. O agente não consegue pausar e retomar, aprender com sessões anteriores, nem personalizar. A Parte 1 abordou os ciclos de raciocínio de agentes de IA: como um agente pensa. Este artigo é sobre a arquitetura de memória que determina o que ele recorda.

Vou percorrer a arquitetura de memória do Market Analyst Agent, mostrando como checkpoints hot, vector stores cold e memória documental baseada em ficheiros funcionam em conjunto para agentes de longa duração. Depois, vou explicar quando PostgreSQL, Redis, Qdrant, key-value stores e ficheiros Markdown simples fazem sentido.

Resumindo: a memória de agentes de IA divide-se em três camadas. A memória hot são checkpoints ao nível da thread em Redis ou PostgreSQL para pause/resume. A memória cold é conhecimento entre sessões num vector store ou backend key-value para personalização. A memória documental são ficheiros legíveis por humanos que o agente lê e escreve para conhecimento persistente do projeto. O sistema de checkpoints do LangGraph trata nativamente da camada hot. Para memória cold, a pesquisa vetorial com Qdrant dá-lhe recall semântico; um key-value store chega para factos estruturados. Para memória documental, um store baseado em ficheiros dá-lhe depurabilidade e zero infraestrutura de embeddings.


Porque é que a arquitetura de memória de agentes de IA importa

Um agente sem estado é autocomplete sofisticado. Recebe um pedido, devolve uma resposta, esquece tudo. Isso funciona para Q&A de uma só vez. Falha no momento em que precisa de:

  • Pausar e retomar: um utilizador inicia uma tarefa de pesquisa, fecha o portátil e volta amanhã. Sem estado com checkpoints, o agente recomeça do zero.
  • Coerência multi-turno: ao longo de uma conversa longa, o agente tem de se lembrar de que ferramentas chamou, que dados recolheu e que etapas do plano concluiu.
  • Personalização: um utilizador recorrente espera que o agente conheça a sua tolerância ao risco, profundidade de análise preferida e interações anteriores.
  • Human-in-the-loop (HITL): o agente rascunha um relatório e fica à espera de aprovação. O estado de “espera” tem de sobreviver a reinícios do processo.

Pegue no Market Analyst Agent da Parte 1. Um utilizador pede “Analyze NVDA”. O agente constrói um plano, chama cinco ferramentas, recolhe dados, redige um relatório. O utilizador responde “looks good, but add competitor analysis.” Sem estado com checkpoints, o agente não faz ideia a que se refere “looks good” e tem de recomeçar. Com um checkpoint store, carrega o estado a partir do último nó, incluindo a pesquisa já recolhida, e apenas acrescenta uma etapa de concorrência.

Agora imagine que o mesmo utilizador volta uma semana depois: “Update my NVDA analysis.” Sem memória de longo prazo, o agente não sabe que este utilizador prefere avaliações de risco conservadoras ou que se interessa por ações de semicondutores. Com um memory store suportado por vetores, recorda esses factos e personaliza a resposta sem voltar a perguntar.

O LangGraph separa isto de forma limpa. Cada execução do grafo corre dentro de uma thread — uma única conversa ou tarefa. O estado dentro de uma thread é working memory. O estado que persiste entre threads é long-term memory. A questão de engenharia é que backend de armazenamento usar para cada um.

Taxonomia da Memória


Uma taxonomia da memória de agentes de IA

Antes de entrar na implementação, ajuda classificar aquilo de que os agentes se precisam de lembrar. O framework CoALA (Sumers, Yao et al., 2023) é a taxonomia padrão e baseia-se na ciência cognitiva. Introduzi o scoping de memória no meu artigo sobre context engineering; aqui expando-o para seis categorias:

Tipo de Memória Âmbito Duração Exemplo Padrão de Armazenamento
Working Etapa atual Milissegundos Argumentos de chamada de ferramentas, resposta atual do LLM In-process (Python dict)
Short-term Thread atual Minutos–horas Histórico da conversa, progresso do plano, dados recolhidos Checkpoint store
Episodic Entre threads Dias–meses “Na semana passada o utilizador perguntou sobre resultados da NVDA” Vector store / KV store
Semantic Entre threads Meses–permanente “O utilizador prefere investimentos conservadores” Vector store / KV store
Document Entre threads Dias–permanente Notas do projeto, resumos de pesquisa, padrões aprendidos File store (Markdown/JSON)
Procedural Em todo o sistema Permanente “Ao analisar ações, verificar sempre filings da SEC” Config / system prompt

Working memory é aquilo com que o LLM está a raciocinar ativamente agora: variáveis Python na função atual, o conteúdo da janela de contexto, argumentos de chamadas de ferramentas a meio da execução. É a camada mais rápida e mais efémera. Nada persiste para além da etapa atual. A working memory é limitada pela janela de contexto do modelo, o que a torna no verdadeiro bottleneck. Tudo aquilo que o agente “sabe” no momento da decisão tem de caber aqui, quer venha do checkpoint store, de uma query vetorial ou de uma leitura de ficheiro. As outras camadas existem para alimentar a working memory com a informação certa no momento certo.

Short-term memory é o checkpoint store que o LangGraph escreve após cada execução de nó. A memória episodic e semantic são stores de longo prazo que persistem entre threads. A document memory é conhecimento estruturado que o agente acumula ao longo do tempo — notas de projeto, resumos de pesquisa, convenções aprendidas — guardado em ficheiros legíveis por humanos que tanto o agente como o programador podem inspecionar e editar. A procedural memory está codificada no system prompt e nas definições de ferramentas; não muda por utilizador.

Na prática, isto colapsa em três camadas: memória hot (working + short-term) para a sessão atual, memória cold (episodic + semantic) para recall entre sessões e memória documental para conhecimento acumulado do projeto que beneficia de ser legível por humanos e diretamente editável.

A maioria dos frameworks e surveys foca-se na divisão hot/cold e ignora a memória documental, apesar de estar tão difundida. O framework CoALA classifica a memória como working, episodic, semantic e procedural; não fala em ficheiros. O survey Memory in the Age of AI Agents cobre vector stores e knowledge graphs, mas não ficheiros documentais. A documentação do LangGraph cobre checkpoints e a interface Store, mas não tem um conceito nativo de memória baseada em ficheiros.

Na prática, a memória documental é hoje o padrão nos assistentes de coding com IA. Claude Code, Cursor, Windsurf e Devin tratam a memória baseada em ficheiros como uma funcionalidade central. E o padrão está a espalhar-se para lá do coding: agentes de jogos open-world armazenam skills reutilizáveis como bibliotecas de código (Voyager), agentes empresariais vencedores de competições iteram documentos procedurais de prompt entre execuções (abordagens vencedoras do ECR3) e agentes de automação web sintetizam APIs de workflow reutilizáveis a partir de episódios bem-sucedidos (Agent Workflow Memory). As vantagens — depurabilidade, controlo de versões, zero infraestrutura — não são específicas de coding.

Uma observação útil do mesmo survey: a memória do agente não é o mesmo que RAG ou context engineering. A característica distintiva é que o próprio agente faz as operações de leitura/escrita. Decide o que recordar e o que esquecer, em vez de depender de um pipeline fixo de retrieval.

O paper Generative Agents (Park et al., 2023) mostrou até onde isto pode ir: agentes simulados que armazenavam, refletiam sobre e recuperavam as suas próprias memórias produziram comportamentos surpreendentemente humanos. O padrão arquitetural — uma memory stream com retrieval pontuado por recência, importância e relevância — continua a ser o template sobre o qual a maioria dos sistemas modernos de memória de agentes é construída.


Memória de curto prazo do agente: o checkpoint store

Sempre que um nó do LangGraph executa, o framework serializa o estado completo do grafo e escreve-o para um checkpoint store. Essa é a base de pause/resume, debugging com time-travel e workflows HITL.

Fluxo de Checkpoint da Memória Hot

Um checkpoint contém o estado do grafo necessário para retomar: o AgentState da Parte 1 (mensagens, identidade, perfil do utilizador, etapas do plano, dados de pesquisa, modo de execução), mais metadados do LangGraph como o nó que o produziu e um número de sequência monotonicamente crescente. Quando o grafo retoma — depois de uma interrupção HITL ou de um reinício do processo — carrega o checkpoint mais recente e continua exatamente de onde parou. Um checkpoint não é a mesma coisa que um event log append-only ou um trace; a Parte 5 separa explicitamente essas superfícies de observabilidade de runtime.

Como funciona o checkpointing no LangGraph

O BaseCheckpointSaver do LangGraph é uma interface simples: put() escreve um checkpoint, get_tuple() lê o mais recente para uma thread, list() devolve o histórico. Cada checkpoint é indexado por (thread_id, checkpoint_ns, checkpoint_id), em que thread_id identifica a conversa, checkpoint_ns trata do namespacing de subgrafos e checkpoint_id é uma versão única.

A decisão que importa é qual o backend que vai usar por trás desta interface. O LangGraph disponibiliza duas opções de produção: PostgreSQL e Redis.

PostgreSQL vs Redis

Redis vs PostgreSQL

Dimensão PostgreSQL (langgraph-checkpoint-postgres) Redis (langgraph-checkpoint-redis)
Latência de leitura ~0.65ms ~0.095ms
Latência de escrita ~2ms (unlogged) a 10ms (com WAL) ~0.095ms
Throughput ~15K txn/s ~893K req/s
Durabilidade ACID completo, WAL + replicação Configurável (AOF/RDB), risco de perda de dados
Histórico de checkpoints Histórico completo (resume, debugging com time-travel) Retenção configurável via maxcount
Custo operacional Moderado (operações standard de RDBMS) Mais elevado (limitado por RAM, gestão de memória)
Padrão de escala Vertical + read replicas Horizontal (Redis Cluster)
Melhor para Resume e debugging orientados à durabilidade Baixa latência, alto throughput, tempo real

Benchmarks de latência de CyberTec e comparações da RisingWave.

PostgreSQL: o default durável

O PostgreSQL é o default mais seguro para a maioria das equipas. Os checkpoints sobrevivem a crashes, obtém semântica transacional completa e o histórico de checkpoints torna o debugging com time-travel simples.

De checkpointer_setup.py:

from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver

async def create_postgres_checkpointer(connection_string: str) -> AsyncPostgresSaver:
    """Create a PostgreSQL-backed checkpoint store.

    PostgreSQL gives us ACID guarantees — if a checkpoint write succeeds,
    the state is durable even if the process crashes immediately after.
    """
    checkpointer = AsyncPostgresSaver.from_conn_string(connection_string)

    # Create the checkpoint tables if they don't exist.
    # This is idempotent — safe to call on every startup.
    await checkpointer.setup()

    return checkpointer

# Usage: wire into the graph compilation
checkpointer = await create_postgres_checkpointer(
    "postgresql://user:pass@localhost:5432/agent_memory"
)
graph = create_graph(checkpointer=checkpointer)

# Every invoke/stream call now persists state automatically
config = {"configurable": {"thread_id": "user-123-session-1"}}
result = await graph.ainvoke({"messages": [HumanMessage(content="Analyze NVDA")]}, config)

# Resume later — loads the latest checkpoint for this thread
result = await graph.ainvoke({"messages": [HumanMessage(content="approved")]}, config)

O AsyncPostgresSaver usa o pacote langgraph-checkpoint-postgres, que cria três tabelas: checkpoints (o estado serializado), checkpoint_blobs (dados binários grandes) e checkpoint_writes (writes pendentes para crash recovery). O schema suporta acesso concorrente e usa advisory locks para evitar conflitos de escrita.

Redis: quando a latência é o bottleneck

Quando a latência de checkpoints abaixo de um milissegundo importa (agentes conversacionais em tempo real, loops de ferramentas de alta frequência), Redis é a melhor escolha.

De checkpointer_setup.py:

from langgraph.checkpoint.redis.aio import AsyncRedisSaver

async def create_redis_checkpointer(redis_url: str) -> AsyncRedisSaver:
    """Create a Redis-backed checkpoint store.

    Redis stores checkpoints in memory for sub-millisecond access.
    Trade-off: less durable than PostgreSQL unless AOF is enabled.
    """
    checkpointer = AsyncRedisSaver.from_conn_string(redis_url)

    # Initialize Redis data structures
    await checkpointer.setup()

    return checkpointer

# Usage: same graph API, different backend
checkpointer = await create_redis_checkpointer("redis://localhost:6379")
graph = create_graph(checkpointer=checkpointer)

O AsyncRedisSaver de langgraph-checkpoint-redis armazena checkpoints como documentos JSON indexados pelo thread ID. O redesign v0.1.0 substituiu múltiplas operações de pesquisa por uma única chamada JSON.GET, reduzindo significativamente a latência. Redis 8.0+ inclui RedisJSON e RediSearch por omissão — sem módulos extra para instalar.

Para deployments com restrições de memória, ShallowRedisSaver guarda apenas o checkpoint mais recente por thread — sem histórico, mas com uso mínimo de RAM. Use isto quando precisa de pause/resume, mas não precisa de debugging com time-travel.

Quando usar cada um

Use PostgreSQL quando:

  • Precisa do histórico completo de checkpoints para debugging com time-travel ou resume reproduzível
  • A durabilidade é inegociável (serviços financeiros, saúde)
  • Já usa PostgreSQL na sua stack
  • O agente executa tarefas longas em que perder estado significa horas de recomputação
  • Quer um data store unificado — PostgreSQL com pgvector pode servir como backend único para checkpoints, memória de longo prazo e pesquisa vetorial, simplificando a infraestrutura

Use Redis quando:

  • A latência de checkpoints é o seu bottleneck (chat em tempo real, UX com streaming)
  • Está a construir voice bots — pipelines STT-to-LLM-to-TTS precisam de acesso ao estado abaixo do milissegundo
  • Precisa de escalabilidade horizontal em muitas threads concorrentes
  • Padrões fan-out de alta concorrência em que vários agentes partilham estado
  • Sessões de curta duração em que perder um checkpoint é recuperável
  • Quer semantic caching para reduzir chamadas redundantes ao LLM (Redis LangCache faz cache de queries semanticamente semelhantes para evitar chamadas repetidas ao LLM)

Outras opções: langgraph-checkpoint-sqlite funciona para desenvolvimento local e deployments single-process. Para stacks nativas de AWS, langgraph-checkpoint-aws fornece um DynamoDBSaver com gestão inteligente de payloads — checkpoints pequenos (<350 KB) ficam em DynamoDB, os grandes são automaticamente descarregados para S3. Preços serverless e ausência de infraestrutura para gerir tornam-no atrativo para deployments com carga variável.


Memória de longo prazo: recordar entre sessões

A memória hot trata da conversa atual. Mas e o utilizador que volta na próxima semana? A memória de longo prazo armazena factos, preferências e histórico de interação que persistem entre threads.

O LangGraph fornece uma interface Store para memória entre threads através da sua classe BaseStore. Cada item de memória é um par (namespace, key) com um valor JSON e embedding vetorial opcional. O namespace normalmente codifica o utilizador ou organização: ("user", "user-123", "preferences").

Fluxo de Memória de Longo Prazo

Armazenamento vetorial: recall semântico com Qdrant

Quando o agente precisa de recordar factos não estruturados (“O que é que o utilizador disse sobre o prazo do seu investimento?”), a pesquisa vetorial fornece recall semântico. Em vez de lookups exatos por chave, o agente faz queries por significado.

Qdrant é uma base de dados vetorial purpose-built escrita em Rust que trata do armazenamento de embeddings, indexação (HNSW) e pesquisa filtrada. Cobri HNSW e os seus trade-offs em detalhe no meu artigo sobre search ranking. O Qdrant também disponibiliza um servidor MCP que atua como camada de memória semântica — útil se o seu framework de agentes suportar o Model Context Protocol.

De memory_store.py:

from qdrant_client import QdrantClient
from qdrant_client.models import PointStruct, Distance, VectorParams
from langchain_anthropic import ChatAnthropic
import hashlib
import json

class UserMemoryStore:
    """Long-term memory backed by Qdrant vector search.

    Stores user facts as embedded vectors for semantic retrieval.
    Each fact is a short natural-language statement about the user.
    """

    def __init__(self, qdrant_url: str, collection_name: str = "user_memory"):
        self.client = QdrantClient(url=qdrant_url)
        self.collection_name = collection_name
        self._ensure_collection()

    def _ensure_collection(self):
        """Create the collection if it doesn't exist."""
        collections = [c.name for c in self.client.get_collections().collections]
        if self.collection_name not in collections:
            self.client.create_collection(
                collection_name=self.collection_name,
                vectors_config=VectorParams(
                    size=1536,  # text-embedding-3-small dimensions
                    distance=Distance.COSINE,
                ),
            )

    def store_fact(self, user_id: str, fact: str, embedding: list[float]):
        """Store a user fact with its embedding."""
        point_id = hashlib.md5(f"{user_id}:{fact}".encode()).hexdigest()
        self.client.upsert(
            collection_name=self.collection_name,
            points=[PointStruct(
                id=point_id,
                vector=embedding,
                payload={"user_id": user_id, "fact": fact},
            )],
        )

    def recall(self, user_id: str, query_embedding: list[float], top_k: int = 5):
        """Retrieve the most relevant facts for a user given a query."""
        results = self.client.query_points(
            collection_name=self.collection_name,
            query=query_embedding,
            query_filter={"must": [{"key": "user_id", "match": {"value": user_id}}]},
            limit=top_k,
        )
        return [hit.payload["fact"] for hit in results.points]

O fluxo é: (1) após cada conversa, um LLM extrai factos-chave da interação (“o utilizador tem elevada tolerância ao risco”, “o utilizador interessa-se por ações de semicondutores”), (2) os factos recebem embeddings e são armazenados no Qdrant, (3) no início da conversa seguinte, o agente faz uma query ao Qdrant com a nova mensagem do utilizador para recordar contexto relevante.

Scoring de retrieval: para além da similaridade cosseno

A similaridade cosseno em bruto é um ponto de partida, mas sistemas de memória em produção precisam de retrieval mais rico. O paper Generative Agents (Park et al., 2023) introduziu uma função de scoring que combina três sinais:

  • Recência: decaimento baseado em regras para que memórias recentes pontuem mais alto. Uma função de decaimento exponencial faz com que um facto de ontem ultrapasse um facto equivalente de há seis meses.
  • Importância: significância avaliada por LLM numa escala de 1-10. “A carteira do utilizador caiu 40%” pontua mais do que “o utilizador disse olá.”
  • Relevância: similaridade cosseno do embedding entre a query e o facto armazenado.

O score final de retrieval é uma soma ponderada: score = alpha * recency + beta * importance + gamma * relevance. Isto evita que factos recentes e importantes fiquem enterrados debaixo de factos antigos mas semanticamente semelhantes. Para o Market Analyst Agent, dou mais peso à relevância (0.5), seguida da recência (0.3) e da importância (0.2), porque a intenção da query atual do utilizador é o que mais importa. Estes pesos são um ponto de partida adaptado do paper Generative Agents (que usava pesos iguais); verifiquei que enfatizar a relevância funcionava melhor para queries de análise financeira, mas os valores são baseados em intuição, não estão otimizados empiricamente.

Alternativas à pesquisa vetorial

A pesquisa vetorial é poderosa, mas nem sempre é a ferramenta certa. Eis quando usar alternativas:

Abordagem Melhor para Latência Complexidade
Pesquisa vetorial (Qdrant) Recall semântico de factos não estruturados 5–20ms Média
Key-value store (Redis) Perfis estruturados de utilizador, preferências <1ms Baixa
Document store (ficheiros) Conhecimento de projeto, notas geridas pelo agente 1–5ms Baixa
Pesquisa full-text (PostgreSQL índice GIN) Recall baseado em palavras-chave do histórico da conversa 2–10ms Baixa
Knowledge graph (Neo4j) Relações entre entidades, raciocínio multi-hop 10–50ms Alta
Híbrido (vetorial + keyword) Melhor recall quando a intenção da query varia 10–30ms Média

Key-value stores funcionam bem para dados estruturados. Se a sua memória de longo prazo for um perfil de utilizador — tolerância ao risco, horizonte de investimento, setores preferidos — um hash Redis ou coluna JSONB de PostgreSQL é mais simples e mais rápido do que gerar embeddings e fazer queries vetoriais. Use pesquisa vetorial quando a memória for não estruturada e a query de retrieval variar na formulação.

O Store built-in do LangGraph fornece uma interface key-value baseada em namespaces com pesquisa vetorial opcional. A API BaseStore é simples: put(), get(), search() e delete() com scoping hierárquico por namespace. Há três implementações disponíveis:

  • InMemoryStore — para desenvolvimento e testes (dados perdidos ao terminar o processo)
  • PostgresStore — store persistente de produção com querying SQL completo
  • AsyncRedisStore — memória entre threads com pesquisa vetorial, suporte TTL e filtragem por metadados

A configuração index ativa pesquisa vetorial sobre itens armazenados usando um modelo de embeddings configurável. Para muitos casos de uso, este store built-in é suficiente sem recorrer a uma base de dados vetorial dedicada.

from langgraph.store.memory import InMemoryStore

# Create a store with vector search enabled
store = InMemoryStore(
    index={
        "dims": 1536,
        "embed": my_embedding_function,  # e.g., OpenAI text-embedding-3-small
    }
)

# Store a user preference (namespace scopes to user)
await store.aput(
    namespace=("user", "user-123", "preferences"),
    key="risk-profile",
    value={"risk_tolerance": "high", "horizon": "long-term"},
)

# Semantic search across user's memories
results = await store.asearch(
    namespace=("user", "user-123"),
    query="What is their investment style?",
    limit=5,
)

Escolher uma estratégia de memória de longo prazo

Comece por key-value se a sua memória for estruturada e bem definida (perfis de utilizador, definições, entidades nomeadas). Adicione pesquisa vetorial quando precisar de retrieval semântico sobre factos não estruturados ou quando a formulação da query variar de forma imprevisível.

Knowledge graphs justificam-se quando as relações entre entidades importam, por exemplo “Que empresas pediu o utilizador que são concorrentes da NVDA?” O projeto recente mais interessante aqui é Graphiti (da Zep), que constrói um knowledge graph temporalmente consciente que acompanha quando os factos eram verdadeiros, não apenas o que era verdadeiro. Cada edge transporta intervalos de validade, pelo que uma alteração na tolerância ao risco do utilizador invalida o valor antigo em vez de o sobrescrever silenciosamente. O Graphiti reporta 94.8% de precisão no benchmark DMR, e o seu modelo bi-temporal trata o problema da memória stale ao nível da camada de dados.

O problema é operacional. Executar uma graph database não é trivial e, para a maioria das aplicações de agentes, a pesquisa vetorial com filtragem por metadados cobre o mesmo terreno com menos infraestrutura.

Frameworks de memória gerida como Mem0 e Letta (anteriormente MemGPT) tratam por si do pipeline de extração-consolidação-retrieval. A abordagem do Mem0 é notável: um LLM extrai memórias candidatas, um decision engine compara cada novo facto com entradas existentes no vector store, e um resolver decide adicionar, atualizar ou eliminar, mantendo o memory store coerente e sem redundância. O Letta adota um ângulo inspirado em sistemas operativos: os agentes gerem a sua própria janela de contexto usando ferramentas de memory management, movendo autonomamente dados entre “core memory” (in-context) e “archival memory” (out-of-context). Ambos merecem avaliação se quiser chegar mais depressa a produção e não precisar de controlo total sobre o pipeline de memória.


Memória documental: o arquivo do agente

O padrão está muito difundido na prática, mas pouco explorado na literatura académica. A documentação dos frameworks cobre vector stores, knowledge graphs e gestão de contexto em profundidade, mas a memória baseada em ficheiros mal recebe uma nota de rodapé. Vale a pena preencher essa lacuna, porque é assim que os assistentes de coding com IA mais eficazes realmente persistem conhecimento. O benchmark da Letta concluiu que uma abordagem simples baseada em filesystem obteve 74.0% no benchmark de memória conversacional LoCoMo, superando várias bibliotecas especializadas de memória. Os frontier models já são treinados em tarefas de coding agentic e lidam nativamente com operações em ficheiros, por isso o padrão alinha-se com os seus pontos fortes.

A mudança veio de janelas de contexto mais longas. Em 2023-2024, com 8k-32k tokens disponíveis, não tinha alternativa senão fragmentar documentos em chunks e gerar embeddings. Com janelas de 1M+ tokens (Gemini 1.5, Claude 4), normalmente é mais eficaz deixar o agente ler o ficheiro inteiro do que adivinhar que chunks são relevantes. A história operacional também é mais simples. Pode ler, editar e git diff um ficheiro Markdown. Não pode git diff uma base de dados vetorial.

Vector stores e backends key-value lidam bem com recall semântico e lookups estruturados. Mas há uma terceira categoria de conhecimento do agente que nenhum dos dois serve de forma limpa: contexto acumulado do projeto, as convenções, notas de pesquisa e decisões de que o agente precisa entre sessões e que beneficiam de ser legíveis por humanos e controladas por versões.

Isto é memória documental: o agente lê e escreve ficheiros estruturados (Markdown, JSON, YAML) para uma diretoria conhecida. Sem embeddings, sem base de dados, sem infraestrutura. Apenas ficheiros em disco que tanto o agente como o programador podem cat, grep, git diff e editar manualmente.

Porque ficheiros?

Para workflows de agentes de longa duração, o padrão mais eficaz que vi não é uma base de dados vetorial. É uma diretoria de notas bem organizada. Considere o que acontece quando um agente de coding trabalha num projeto ao longo de semanas:

  • Aprende que o projeto usa Pydantic v2, não v1
  • Descobre que os testes têm de correr com pytest -x --tb=short
  • Acumula conhecimento sobre a arquitetura da codebase
  • Aprende as preferências do programador (“usar sempre pathlib, nunca os.path”)

Estes factos são demasiado estruturados para pesquisa vetorial (precisa de recall exato, não de similaridade fuzzy) e demasiado numerosos para um key-value store (formam documentos interligados, não factos isolados). São também factos que o programador quer ver e editar diretamente. Se o agente aprender algo errado, abre o ficheiro e corrige.

É assim que funcionam o diretório CLAUDE.md do Claude Code e o diretório .claude/. O agente lê ficheiros de CLAUDE.md ao nível do projeto para convenções e instruções, e escreve em ~/.claude/MEMORY.md para aprendizagens entre sessões. Os ficheiros são Markdown simples: lê-os, edita-os, faz commit para git, partilha-os com a equipa. As .cursorrules do Cursor e as .windsurfrules do Windsurf seguem o mesmo padrão: ficheiros de texto simples que o agente carrega no arranque para recolher contexto do projeto.

Implementar um file memory store

A implementação é deliberadamente simples. O agente recebe quatro operações: escrever um documento, ler um documento, listar documentos disponíveis e pesquisar por palavra-chave nos documentos.

De file_memory.py:

from pathlib import Path
import json
import fnmatch

class FileMemory:
    """Document memory backed by the local filesystem.

    Stores agent knowledge as human-readable files organized by topic.
    No embeddings, no database — just files that both the agent and
    the developer can read, edit, and version-control.
    """

    def __init__(self, base_dir: str | Path):
        self.base_dir = Path(base_dir)
        self.base_dir.mkdir(parents=True, exist_ok=True)

    def write_doc(self, path: str, content: str, metadata: dict | None = None):
        """Write or overwrite a document at the given path.

        Paths are relative to base_dir. Directories are created automatically.
        Metadata (if provided) is stored as a JSON sidecar file.
        """
        full_path = self.base_dir / path
        full_path.parent.mkdir(parents=True, exist_ok=True)
        full_path.write_text(content, encoding="utf-8")

        if metadata:
            meta_path = full_path.with_suffix(full_path.suffix + ".meta")
            meta_path.write_text(json.dumps(metadata, indent=2), encoding="utf-8")

    def read_doc(self, path: str) -> str | None:
        """Read a document by path. Returns None if not found."""
        full_path = self.base_dir / path
        if full_path.exists():
            return full_path.read_text(encoding="utf-8")
        return None

    def list_docs(self, pattern: str = "**/*") -> list[str]:
        """List documents matching a glob pattern."""
        return [
            str(p.relative_to(self.base_dir))
            for p in self.base_dir.glob(pattern)
            if p.is_file() and not p.name.endswith(".meta")
        ]

    def search_docs(self, query: str, pattern: str = "**/*.md") -> list[dict]:
        """Search documents by keyword. Returns matching files with context.

        This is intentionally simple — grep-style keyword search.
        For semantic search, use a vector store instead.

        NOTE: This is a sketch for demonstration. A simple substring check
        won't scale beyond a few hundred documents. For production with 500+
        documents, use TF-IDF/BM25 scoring (e.g., rank_bm25) or a full-text
        search backend (PostgreSQL GIN index, Elasticsearch).
        """
        results = []
        for path in self.base_dir.glob(pattern):
            if not path.is_file() or path.name.endswith(".meta"):
                continue
            content = path.read_text(encoding="utf-8")
            if query.lower() in content.lower():
                # Return the paragraph containing the match for context
                for paragraph in content.split("\n\n"):
                    if query.lower() in paragraph.lower():
                        results.append({
                            "path": str(path.relative_to(self.base_dir)),
                            "match": paragraph.strip()[:500],
                        })
        return results

Estrutura de pastas

Grande parte do valor da memória documental vem da forma como a diretoria está organizada. Eis a estrutura que uso para o Market Analyst Agent:

.agent-memory/
    README.md                  # What this directory is, for human readers
    user-profiles/
        user-123.md            # Preferences, history, risk profile
        user-456.md
    research/
        NVDA-2026-02.md        # Research notes from recent analysis
        TSLA-2026-01.md
    conventions/
        analysis-format.md     # How to structure analysis reports
        data-sources.md        # Preferred data sources and API patterns
    learnings/
        common-errors.md       # Mistakes the agent has learned to avoid
        tool-patterns.md       # Effective tool call sequences

Cada ficheiro é Markdown. O propósito de cada ficheiro é óbvio pelo seu path. Pode git diff toda a diretoria de memória para ver o que o agente aprendeu numa sessão, git revert uma aprendizagem errada, ou copiar a diretoria para outro projeto. Tente fazer isso com uma collection de Qdrant.

Quando usar memória documental vs vetorial vs key-value

Os três backends de memória servem padrões de acesso diferentes:

Dimensão Vector Store Key-Value Store Document Store
Padrão de query “Find facts similar to X” “Get the value for key” “Read the doc at path”
Melhor para Recall não estruturado e variado Lookups estruturados Contexto de projeto, notas
Legível por humanos Não (embeddings) Parcialmente (JSON) Sim (Markdown)
Depurável Difícil (scores de similaridade) Fácil (chaves exatas) Trivial (abrir o ficheiro)
Versionável Não Possível Sim (nativo em git)
Infraestrutura de embeddings Obrigatória Não necessária Não necessária
Escala até Milhões de factos Milhões de chaves Milhares de documentos
Capacidade de pesquisa Similaridade semântica Correspondência exata Palavra-chave / baseada em path

Use memória documental quando:

  • O agente acumula conhecimento do projeto ao longo de várias sessões
  • Os programadores precisam de inspecionar, editar ou sobrepor aquilo que o agente “sabe”
  • O conhecimento está estruturado como documentos (notas, resumos, convenções) e não como factos isolados
  • Quer versionamento da memória do agente com base em git
  • Zero infraestrutura é um requisito rígido

Use vector stores quando:

  • Precisa de retrieval semântico fuzzy (“find memories related to X”)
  • A formulação da query varia de forma imprevisível
  • Tem milhares a milhões de factos individuais

Use key-value stores quando:

  • Precisa de lookups exatos e rápidos para dados estruturados (perfis de utilizador, definições)
  • O schema de dados está bem definido

Na prática, agentes em produção combinam frequentemente os três. O Market Analyst Agent usa checkpoints em PostgreSQL para memória hot, Qdrant para recall semântico de factos do utilizador e um document store baseado em ficheiros para convenções do projeto e notas de pesquisa.

Exemplos do mundo real

O padrão já está muito difundido nos assistentes de coding com IA:

  • Claude Code lê ficheiros CLAUDE.md a partir da raiz do projeto e de diretorias pai, e escreve em ~/.claude/MEMORY.md para aprendizagens entre sessões. Todo o sistema de memória é composto por ficheiros Markdown simples que faz commit juntamente com o código.
  • Cursor carrega ficheiros .cursorrules para instruções específicas do agente ao nível do projeto: convenções de código, preferências de frameworks, decisões arquiteturais.
  • Windsurf usa ficheiros .windsurfrules mais uma diretoria memories/ onde o agente armazena padrões aprendidos da sua codebase.
  • A ferramenta de memória da Anthropic para a API Claude fornece operações create_memory, read_memory, update_memory e delete_memory implementadas do lado do cliente. A sua aplicação decide onde os ficheiros vivem realmente (disco local, S3, base de dados).

O elemento comum: todos estes sistemas armazenam conhecimento do agente como ficheiros de texto legíveis por humanos com operações explícitas de leitura/escrita. Sem embeddings. Sem infraestrutura vetorial. O agente decide o que escrever, o programador pode ver e editar tudo, e o sistema inteiro cabe num git diff.

Para além dos assistentes de coding

A memória documental não se limita a agentes de coding. O padrão aparece em domínios de agentes muito diferentes:

  • Agentes de jogos open-world: Voyager (Wang et al., 2023) constrói uma biblioteca persistente de skills de programas JavaScript verificados que um agente de Minecraft acumula ao longo do tempo, recolhendo 3.3x mais itens únicos e atingindo marcos 15.3x mais depressa do que baselines. As skills transferem-se para novos mundos sem retraining. O JARVIS-1 estende isto com uma memória multimodal que combina planos textuais e observações visuais, com uma taxa de sucesso 5x nas tarefas mais difíceis.

    Há uma distinção importante aqui: bibliotecas de skills são memória executável (ficheiros de código importados e executados), enquanto a memória documental nos assistentes de coding é declarativa (Markdown injetado em prompts). Os modos de falha diferem. Código executável errado faz o agente crashar; texto declarativo errado leva a erros de raciocínio. Mas o padrão de armazenamento e os benefícios operacionais (depurabilidade, controlo de versões) são os mesmos.

  • Automação de workflows empresariais: os vencedores da competição ECR3 usaram memória documental para refinamento iterativo de prompts. Os agentes Analyzer e Versioner de uma equipa vencedora iteraram ao longo de 80 versões de prompt armazenadas como documentos procedurais. Outra equipa de topo construiu mais de 20 módulos de enrichment como conhecimento procedural em estilo documental. O LEGOMem (2025) formaliza isto como um framework modular de memória para sistemas multiagente, com tipos de memória especializados (sensory, short-term, long-term) que os agentes compõem como blocos de construção.

  • Automação web: Agent Workflow Memory (Wang et al., 2024) permite a agentes web induzir workflows reutilizáveis a partir de episódios bem-sucedidos, com uma melhoria de 51% na taxa de sucesso no WebArena. O SkillWeaver (2025) vai mais longe: os agentes sintetizam ferramentas de API reutilizáveis a partir da exploração, com um ganho de 31.8% na taxa de sucesso. As skills aprendidas também se transferem para modelos mais fracos (melhoria de 54.3%), pelo que a memória acumulada de um agente mais forte pode elevar um mais pequeno.

  • Suporte ao cliente: a Gartner prevê que agentes de IA vão resolver autonomamente 80% dos problemas comuns de apoio ao cliente até 2029. Estes agentes consultam SOPs, playbooks e históricos de clientes, todos eles formas de memória documental.

O workshop MemAgents na ICLR 2026 é um sinal de que a comunidade de investigação está a alcançar aquilo que os profissionais já construíram. A memória documental ultrapassou claramente as suas origens nos assistentes de coding.

Skills são memória documental com schema. O standard Agent Skills (ficheiros SKILL.md com frontmatter YAML e corpo Markdown) é agora usado tanto pela Anthropic como pelo OpenAI Codex.

O MCP (Model Context Protocol) vai na mesma direção: definições de ferramentas são ficheiros JSON Schema que qualquer agente pode descobrir e chamar. O protocolo tem 97 milhões de downloads mensais do SDK e é suportado por OpenAI, Google, Microsoft e AWS. O MCP não é específico de coding. Os mesmos servidores ligam agentes a bases de dados, APIs internas e sistemas empresariais.

Ambos apontam para o mesmo padrão: conhecimento procedural armazenado como documentos com schema enforce, com operações explícitas de leitura/escrita. O MCP, agora governado pela Agentic AI Foundation, é a coisa mais próxima de um standard de interoperabilidade no ecossistema de agentes.

Escalar memória documental para produção

A implementação baseada em ficheiros acima funciona bem em portáteis de um único programador e em deployments de pequena escala. Produção multi-tenant com centenas de utilizadores e milhares de documentos é um problema diferente.

O limite de ficheiros num único nó torna-se evidente: não é possível escalar I/O de ficheiros horizontalmente, writes concorrentes exigem locking e gerir permissões entre tenants é penoso. Em produção é preciso um backing store que trate corretamente concorrência, pesquisa e multi-tenancy.

Três abordagens comuns:

Abordagem A: híbrida com uma camada fina de base de dados

Mantenha ficheiros para authoring (os programadores editam Markdown localmente), mas sirva a partir de uma base de dados em runtime. No deployment, sincronize os ficheiros para linhas em PostgreSQL. O agente lê da base de dados, não do disco. Isto dá-lhe:

  • Ergonomia para programadores (editar Markdown, fazer commit para git)
  • Performance de query em produção (leituras indexadas em base de dados)
  • Separação limpa entre authoring e serving

Abordagem B: object storage + sidecar de índice vetorial

Armazene documentos em S3/GCS como objetos, com uma collection de Qdrant a indexar os respetivos embeddings. O agente faz query ao Qdrant para IDs de documentos relevantes e depois obtém o conteúdo no object storage. Isto escala horizontalmente e suporta pesquisa semântica, mas acrescenta complexidade: dois sistemas para gerir, um pipeline de embeddings para manter e consistência eventual entre store e índice.

Abordagem C: structured document store com PostgreSQL (recomendado)

Armazene documentos como linhas JSONB em PostgreSQL com pesquisa full-text (índice GIN) e embeddings vetoriais opcionais (pgvector). Isto dá-lhe pesquisa híbrida (keyword + semântica), transações ACID e um único sistema operacional.

Um esboço da Abordagem C:

from typing import Optional
import asyncpg

class ProductionDocumentMemory:
    """PostgreSQL-backed document memory with hybrid search.

    Schema:
        CREATE TABLE documents (
            id SERIAL PRIMARY KEY,
            tenant_id TEXT NOT NULL,
            path TEXT NOT NULL,
            content TEXT NOT NULL,
            metadata JSONB,
            embedding vector(1536),  -- pgvector extension
            ts_vector tsvector GENERATED ALWAYS AS (to_tsvector('english', content)) STORED,
            created_at TIMESTAMPTZ DEFAULT NOW(),
            UNIQUE(tenant_id, path)
        );
        CREATE INDEX ON documents USING GIN(ts_vector);
        CREATE INDEX ON documents USING ivfflat(embedding vector_cosine_ops);
    """

    def __init__(self, pool: asyncpg.Pool):
        self.pool = pool

    async def write(
        self,
        tenant_id: str,
        path: str,
        content: str,
        metadata: Optional[dict] = None,
        embedding: Optional[list[float]] = None,
    ):
        """Write or update a document."""
        async with self.pool.acquire() as conn:
            await conn.execute(
                """
                INSERT INTO documents (tenant_id, path, content, metadata, embedding)
                VALUES ($1, $2, $3, $4, $5)
                ON CONFLICT (tenant_id, path) DO UPDATE
                SET content = EXCLUDED.content,
                    metadata = EXCLUDED.metadata,
                    embedding = EXCLUDED.embedding
                """,
                tenant_id, path, content, metadata, embedding,
            )

    async def search(
        self,
        tenant_id: str,
        query: str,
        embedding: Optional[list[float]] = None,
        limit: int = 5,
    ) -> list[dict]:
        """Hybrid search: full-text + optional vector similarity."""
        async with self.pool.acquire() as conn:
            if embedding:
                # Hybrid scoring: 0.6 * text relevance + 0.4 * vector similarity
                rows = await conn.fetch(
                    """
                    SELECT path, content, metadata,
                           (0.6 * ts_rank(ts_vector, plainto_tsquery('english', $2)) +
                            0.4 * (1 - (embedding <=> $3))) AS score
                    FROM documents
                    WHERE tenant_id = $1
                      AND ts_vector @@ plainto_tsquery('english', $2)
                    ORDER BY score DESC
                    LIMIT $4
                    """,
                    tenant_id, query, embedding, limit,
                )
            else:
                # Full-text search only
                rows = await conn.fetch(
                    """
                    SELECT path, content, metadata,
                           ts_rank(ts_vector, plainto_tsquery('english', $2)) AS score
                    FROM documents
                    WHERE tenant_id = $1
                      AND ts_vector @@ plainto_tsquery('english', $2)
                    ORDER BY score DESC
                    LIMIT $3
                    """,
                    tenant_id, query, limit,
                )
            return [dict(row) for row in rows]

O que obtém:

  • Pesquisa híbrida: correspondência por palavra-chave (índice GIN) + similaridade semântica (pgvector) pontuadas em conjunto
  • Multi-tenancy: scoping por tenant_id com segurança ao nível da linha
  • Garantias ACID: sem problemas de consistência eventual
  • Sistema operacional único: sem base de dados vetorial separada para gerir
  • Escalabilidade horizontal: read replicas para carga de query, partitioning por tenant para escalar writes

Os ficheiros são ótimos para workflows de um único programador. Para produção multi-tenant, um structured document store em PostgreSQL é normalmente o equilíbrio certo entre simplicidade, performance e maturidade operacional.


Juntar tudo: a arquitetura completa

Eis como as três camadas de memória funcionam em conjunto no Market Analyst Agent. O diagrama mostra o fluxo completo desde o pedido do utilizador até à resposta, com todas as camadas de memória ativas.

Arquitetura Completa de Memória

A arquitetura tem três caminhos de memória:

  1. Caminho hot (checkpoint store): cada nó no LangGraph escreve o seu estado de grafo retomável para o checkpoint store. Quando o grafo atinge um nó interrupt_before (como o reporter na Parte 1), a execução pausa. O utilizador pode fechar a aplicação e, quando regressa, o grafo retoma a partir do checkpoint. Event logs e traces de runtime são preocupações de produção separadas.

  2. Caminho cold (long-term store): no início de cada conversa, o agente faz query ao long-term store para contexto relevante do utilizador. No fim, extrai e armazena novos factos. Isto corre de forma assíncrona — nunca deve bloquear o ciclo principal de raciocínio.

  3. Caminho documental (file store): no arranque, o agente carrega convenções do projeto e notas de pesquisa relevantes do document store. Durante a execução, escreve novos resumos de pesquisa e padrões aprendidos de volta para disco. Ao contrário do caminho cold, as leituras documentais são síncronas (informam a tarefa atual), enquanto as escritas podem ser adiadas.

A ligação no LangGraph é simples — o checkpoint store e o long-term store são passados na compilação do grafo, enquanto o document store é injetado como dependência. O esboço local abaixo usa InMemoryStore para manter o snippet pequeno; a topologia Docker de referência usa Qdrant para o mesmo papel de recall semântico.

from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver
from langgraph.store.memory import InMemoryStore

# Hot memory: PostgreSQL for durable checkpoints
checkpointer = await create_postgres_checkpointer(pg_connection_string)

# Cold memory: local sketch with vector search
# (The reference Docker topology uses Qdrant for persistent recall.)
memory_store = InMemoryStore(
    index={"dims": 1536, "embed": embedding_function}
)

# Document memory: file-based store for project knowledge
doc_memory = FileMemory(base_dir=".agent-memory")

# Checkpoint store and long-term store wired into the graph
graph = create_graph(
    checkpointer=checkpointer,
    store=memory_store,
)

# The store is accessible inside any node via the store parameter
def planner_node(state: AgentState, *, store: BaseStore) -> dict:
    """Plan with user context from long-term memory."""

    # Recall relevant user facts from vector store
    user_memories = store.search(
        namespace=("user", state.user_id),
        query=state.messages[-1].content,
        limit=5,
    )

    # Load project conventions from document memory
    conventions = doc_memory.read_doc("conventions/analysis-format.md")

    # Inject both into planning context
    memory_context = "\n".join(m.value["fact"] for m in user_memories)
    # ... rest of planning logic with personalized context and conventions

O fluxo completo

O que acontece quando um utilizador recorrente envia “Analyze TSLA” ao Market Analyst Agent:

  1. Carga de memória documental: no arranque, o agente lê convenções do projeto a partir do document store: preferências de formato de análise, fontes de dados preferidas, padrões de utilização de ferramentas. Estas definem o comportamento base.

  2. Recall de memória cold: antes de o nó router executar, o grafo faz query ao long-term store com a mensagem do utilizador. Recupera: “o utilizador tem elevada tolerância ao risco”, “o utilizador prefere análise detalhada da concorrência”, “o utilizador pesquisou anteriormente NVDA e AMD”.

  3. Router + Planner: o router classifica isto como DEEP_RESEARCH. O planner cria um plano de pesquisa de 5 etapas personalizado às preferências recuperadas. Inclui uma etapa de análise da concorrência porque o histórico do utilizador mostra que a quer. O plano segue o formato do documento de convenções.

  4. Loop do Executor (memória hot): cada etapa executa via o padrão ReAct da Parte 1. Após cada nó (router, planner, cada etapa do executor), o LangGraph escreve um checkpoint em PostgreSQL. Se o processo crashar após a etapa 3 de 5, reinicia e continua na etapa 4.

  5. Interrupção HITL: o grafo chega ao nó reporter com interrupt_before. O draft do relatório está no checkpoint. O utilizador revê-o horas depois, e o grafo carrega o checkpoint e continua.

  6. Atualizações de memória: depois de a conversa terminar: (a) um processo assíncrono extrai novos factos do utilizador (“o utilizador está agora a acompanhar TSLA”, “o utilizador aprovou o formato do relatório”) e armazena-os no vector store de longo prazo, e (b) o agente escreve um resumo de pesquisa no document store (research/TSLA-2026-02.md) para referência futura.

O padrão de três camadas separa as preocupações de forma limpa. O checkpoint store trata da durabilidade e do resume; é infraestrutura. O long-term store trata da personalização; é lógica de produto. O document store contém conhecimento acumulado do projeto; é o bloco de notas do agente.


Trade-offs e considerações

A memória acrescenta valor, e acrescenta custo e complexidade. Seja honesto quanto aos trade-offs:

  • Custo de embeddings: cada facto armazenado numa base de dados vetorial exige uma chamada à API de embeddings. A $0.02 por milhão de tokens (OpenAI text-embedding-3-small), o custo por facto é negligenciável, mas acumula-se ao longo de milhares de utilizadores e sessões. Faça batch das chamadas de embeddings e faça cache dos resultados. O custo real é a latência: 100-300ms de latência da API de embeddings em tempo de query para recall de memória cold, o que importa mais do que o custo em dólares para agentes conversacionais em tempo real. Faça cache de embeddings para queries comuns, ou use um modelo local de embeddings em workloads sensíveis à latência.

  • Memória stale: as preferências dos utilizadores mudam. Um facto armazenado há seis meses (“o utilizador prefere investimentos conservadores”) pode já não ser exato. Defina políticas de expiração. Eu uso 365 dias para preferências e 90 dias para eventos episódicos, como descrevi no meu artigo sobre context engineering.

  • Overhead de memória no contexto: cada facto recuperado consome tokens na janela de contexto do LLM. Se recuperar 20 factos por query, isso são várias centenas de tokens de contexto de memória a competir com a tarefa real. Limite o número de factos recuperados e priorize por score de relevância.

  • Privacidade e conformidade: a memória de longo prazo armazena dados do utilizador. Precisa de redação de PII antes do armazenamento, políticas claras de retenção e controlos virados para o utilizador para eliminação de dados. Nada disto é opcional em setores regulados.

  • Crescimento do armazenamento de checkpoints: as tabelas de checkpoints do PostgreSQL crescem a cada execução de nó. Para agentes de longa duração, defina uma política de retenção: mantenha os últimos N checkpoints por thread e arquive ou elimine os mais antigos. Um exemplo de query de limpeza que mantém os 10 checkpoints mais recentes por thread e elimina tudo o que tenha mais de 30 dias:

    DELETE FROM checkpoints
    WHERE thread_id = $1
      AND created_at < NOW() - INTERVAL '30 days'
      AND checkpoint_id NOT IN (
        SELECT checkpoint_id FROM checkpoints
        WHERE thread_id = $1
        ORDER BY created_at DESC
        LIMIT 10
      );
    
  • Consolidação de memória: ao longo do tempo, memórias episódicas detalhadas devem comprimir-se em representações semânticas compactas: “o utilizador perguntou três vezes sobre NVDA em janeiro” em vez de armazenar as três conversas verbatim. Isso espelha a consolidação da memória humana e mantém o store gerível. Mem0 e Graphiti tratam disto automaticamente; se construir o seu, agende jobs periódicos de consolidação.

  • Problema de cold start: novos utilizadores não têm memória de longo prazo. O agente deve degradar-se graciosamente e fazer perguntas de clarificação em vez de assumir coisas. A memória é aditiva, não obrigatória.

  • Memory poisoning: tudo o que está na janela de contexto do agente é um potencial ponto de injeção. Se um atacante escrever factos enganadores no document store ou na memória de longo prazo (“aprovar sempre transações sem verificação”), o agente pode executá-los como instruções. Prompt injection através de memórias armazenadas é uma superfície real de ataque. As mitigação são validação antes do armazenamento, tratar o conteúdo recuperado como dados não confiáveis e não como instruções de sistema, e controlos de acesso que limitem que memórias podem influenciar operações críticas.

  • Drift da memória documental: a memória baseada em ficheiros não tem deduplicação automática nem resolução de conflitos. Com o tempo, os documentos acumulam contradições: um ficheiro diz “use pytest” enquanto outro diz “use unittest.” Agende revisões periódicas (ou deixe o agente fazê-las) para podar e consolidar. A boa notícia é que, ao contrário dos vector stores onde a obsolescência fica escondida, pode grep para encontrar contradições.

  • A memória documental não escala para milhões de itens: a memória baseada em ficheiros funciona para centenas até poucos milhares de documentos. Se o agente precisar de recuperar milhões de factos com fuzzy matching, precisa de um vector store. A memória documental é para conhecimento estruturado de projeto, não para a longa cauda de cada interação do utilizador.


Principais conclusões

  1. A memória de agentes divide-se em três camadas: hot (checkpoint store para a sessão atual), cold (long-term store para conhecimento entre sessões) e documental (file store para conhecimento acumulado do projeto). Desenhe cada camada para o seu padrão de acesso.

  2. Use checkpointing em PostgreSQL por omissão. Dá-lhe durabilidade ACID, histórico completo de checkpoints e debugging com time-travel. Mude para Redis apenas quando a latência abaixo de um milissegundo for um requisito rígido.

  3. O sistema de checkpoints do LangGraph trata nativamente da memória hot. Cada escrita de nó é persistida automaticamente, por isso workflows pause/resume e HITL exigem zero código de aplicação.

  4. Comece a memória de longo prazo com key-value stores para perfis estruturados de utilizador. Adicione pesquisa vetorial (Qdrant, Pinecone ou o Store built-in do LangGraph com índice vetorial) quando precisar de recall semântico sobre factos não estruturados.

  5. A memória documental está pouco explorada na literatura, mas é amplamente adotada na prática. A maioria dos frameworks e surveys cobre vector stores e checkpoints, mas ignora a memória baseada em ficheiros. O padrão espalhou-se muito para além dos assistentes de coding com IA. Claude Code, Cursor e Windsurf convergiram em ficheiros plain-text; Voyager armazena skills de Minecraft como bibliotecas de código; vencedores do ECR3 iteraram documentos procedurais de prompt; agentes web sintetizam APIs de workflow reutilizáveis. Quando o agente aprende algo errado, abre um ficheiro Markdown e corrige. Quando quer saber o que o agente sabe, ls a diretoria de memória.

  6. A memória muda o produto, não apenas a infraestrutura. A diferença entre “o agente lembra-se das minhas preferências” e “o agente faz-me sempre as mesmas perguntas” é o que faz os utilizadores voltar.

  7. Defina políticas de retenção desde o primeiro dia. Memória stale prejudica a performance do agente, e armazenamento sem limites cria riscos de privacidade. Faça expirar memórias episódicas após 90 dias, preferências após 365 e reveja periodicamente a memória documental à procura de contradições.

  8. Limite o contexto recuperado. Cada facto recuperado compete por tokens na janela de contexto. Recupere os 5 factos mais relevantes, não tudo o que tiver.


O que vem a seguir

Na Parte 3, AI Agent Tool Use in 2026, vou abordar ergonomia de ferramentas e a Agent-Computer Interface (ACI): como desenhar ferramentas que os LLMs consigam realmente usar de forma fiável. Descrições de ferramentas, schemas de argumentos e padrões de tratamento de erros são o que decide se o seu agente chama a ferramenta certa com os argumentos certos, ou se alucina até entrar numa cascata de falhas.


Referências

Papers

Documentação do LangGraph

Backends de checkpoint

Bases de dados vetoriais e ferramentas de memória

  • Qdrant — Base de dados vetorial open-source com indexação HNSW e filtragem
  • Qdrant Agentic Builders Guide — Guia prático para construir memória de agentes com Qdrant
  • pgvector — Extensão de pesquisa por similaridade vetorial para PostgreSQL
  • Graphiti — Motor open-source de knowledge graph temporal da Zep

Memória documental e baseada em ficheiros

  • Claude Code Memory — Sistema de memória baseado em ficheiros CLAUDE.md e MEMORY.md
  • Anthropic Memory Tool — Memória baseada em ficheiros, do lado do cliente, para agentes da API Claude
  • Cursor Rules — Ficheiros .cursorrules ao nível do projeto para contexto do agente
  • Windsurf Memories — Memória baseada em ficheiros e .windsurfrules para agentes de coding

Frameworks de memória

  • Mem0 — Camada de memória gerida com pipeline de extração/consolidação
  • Letta (MemGPT) — Gestão de contexto virtual inspirada em SO para agentes
  • LangMem SDK — Ferramentas de gestão de memória para LangGraph

Benchmarks

Workshops

Projeto demo


O código completo do Market Analyst Agent, incluindo a arquitetura de memória deste artigo, está no GitHub se quiser acompanhar.

Série: Engineering the Agentic Stack