Sistemas de Memória para Agentes: Contexto Persistente para sua IA
Todo agente de IA tem um problema de memória. Cada chamada ao modelo é sem estado. O modelo não se lembra da última solicitação. Não se lembra do nome do usuário, suas preferências ou decisões anteriores. Sem memória externa, seu agente começa do zero a cada turno.
Este guia cobre quatro estratégias de memória — da mais simples à mais poderosa — e quando usar cada uma. Todos os exemplos usam TypeScript e o SDK da Anthropic.
Por que Agentes Precisam de Memória
Considere um agente de revisão de código. Na primeira execução, o usuário explica as convenções da equipe: sem tipos any, sempre lidar com erros explicitamente, preferir o estilo funcional. O agente produz uma boa revisão.
Na segunda execução, o usuário envia outro arquivo. O agente esqueceu tudo. Ele perde as mesmas violações de convenção que sinalizou ontem.
Este é o problema central: agentes são sem estado, mas agentes úteis precisam de continuidade.
A memória dá aos agentes três capacidades:
- Recuperação — buscar fatos de mais cedo em uma conversa ou de uma sessão anterior
- Personalização — adaptar-se às preferências do usuário ao longo do tempo
- Coordenação — em sistemas multi-agente, compartilhar estado entre agentes
Os Quatro Tipos de Memória
Antes de escrever código, é útil nomear as estratégias com clareza. A memória de agentes se divide em quatro padrões:
| Tipo | Onde armazenado | Escopo | Melhor para |
|---|---|---|---|
| Buffer | Em contexto (array de mensagens) | Sessão atual | Conversas curtas |
| Resumo | Em contexto (comprimido) | Sessão atual | Conversas longas |
| Semântica | Externa (banco de dados vetorial) | Entre sessões | Recuperação de conhecimento |
| Episódica | Externa (armazenamento chave-valor) | Entre sessões | Fatos do usuário, preferências |
Cada estratégia tem uma compensação diferente entre simplicidade, custo e capacidade. Escolha o mínimo que resolva seu problema.
Estratégia 1: Buffer de Conversa
A estratégia mais simples é passar o histórico completo da conversa para cada chamada ao modelo. O modelo vê cada mensagem anterior e responde com contexto completo.
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
interface Message { role: "user" | "assistant"; content: string;}
// O buffer contém o histórico completo da conversaconst buffer: Message[] = [];
async function chat(userMessage: string): Promise<string> { // Adicionar a nova mensagem do usuário ao buffer buffer.push({ role: "user", content: userMessage });
const response = await client.messages.create({ model: "claude-sonnet-4-6", max_tokens: 1024, // Passar o buffer completo em cada chamada messages: buffer, });
const assistantMessage = response.content[0].type === "text" ? response.content[0].text : "";
// Adicionar a resposta do assistente ao buffer buffer.push({ role: "assistant", content: assistantMessage });
return assistantMessage;}
// Exemplo de usoawait chat("Meu nome é Alex e prefiro Python a TypeScript.");await chat("Qual linguagem devo usar para meu próximo projeto?");// O modelo se lembra da preferência declarada na primeira mensagemQuando usar: Conversas com menos de 20 turnos. Chatbots simples. Protótipos.
O limite: Janelas de contexto são finitas. Uma sessão longa eventualmente excede o limite de tokens, e as mensagens mais antigas devem ser descartadas. O modelo perde o contexto inicial silenciosamente, causando comportamento inconsistente.
Estratégia 2: Resumo Progressivo
Quando as conversas se prolongam, resuma os turnos antigos em vez de descartá-los. Mantenha um resumo comprimido do que aconteceu antes de uma janela deslizante de mensagens recentes.
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
interface ConversationState { summary: string; // Histórico comprimido de turnos mais antigos recentMessages: Array<{ role: "user" | "assistant"; content: string }>; maxRecentTurns: number; // Manter os últimos N turnos literalmente}
const state: ConversationState = { summary: "", recentMessages: [], maxRecentTurns: 6, // 3 turnos do usuário + 3 do assistente};
// Comprime os turnos mais antigos no resumo progressivoasync function compressSummary( currentSummary: string, messagesToCompress: Array<{ role: string; content: string }>): Promise<string> { const formatted = messagesToCompress .map((m) => `${m.role.toUpperCase()}: ${m.content}`) .join("\n");
const response = await client.messages.create({ model: "claude-haiku-4-5-20251001", max_tokens: 512, messages: [ { role: "user", content: `Atualize o resumo da conversa adicionando as novas trocas abaixo.Retorne apenas o resumo atualizado. Mantenha-o em menos de 200 palavras.
Resumo atual:${currentSummary || "(nenhum)"}
Novas trocas:${formatted}`, }, ], });
return response.content[0].type === "text" ? response.content[0].text : currentSummary;}
async function chat(userMessage: string): Promise<string> { // Quando o buffer excede o limite, comprimir a metade mais antiga if (state.recentMessages.length >= state.maxRecentTurns * 2) { const toCompress = state.recentMessages.splice(0, state.maxRecentTurns); state.summary = await compressSummary(state.summary, toCompress); }
state.recentMessages.push({ role: "user", content: userMessage });
// Construir o array de mensagens: resumo do sistema + turnos recentes literais const messages = [ ...(state.summary ? [ { role: "user" as const, content: `Contexto do início desta conversa:\n${state.summary}`, }, { role: "assistant" as const, content: "Entendido. Usarei esse contexto.", }, ] : []), ...state.recentMessages, ];
const response = await client.messages.create({ model: "claude-sonnet-4-6", max_tokens: 1024, messages, });
const assistantMessage = response.content[0].type === "text" ? response.content[0].text : "";
state.recentMessages.push({ role: "assistant", content: assistantMessage }); return assistantMessage;}A chamada de resumo usa um modelo rápido e econômico (Haiku). A conversa principal usa o modelo mais capaz (Sonnet). Essa divisão mantém os custos baixos enquanto preserva a qualidade.
Quando usar: Sessões com mais de 20 turnos. Agentes de suporte. Agentes de tarefas de longa duração.
O limite: Resumos perdem detalhes. Fatos específicos declarados no início de uma conversa podem ser comprimidos em declarações vagas. Para recuperação precisa de fatos específicos, use memória episódica.
Estratégia 3: Memória Episódica
A memória episódica armazena fatos específicos sobre um usuário ou sessão em um armazenamento chave-valor. O agente extrai fatos durante uma conversa e os recupera em sessões futuras.
import Anthropic from "@anthropic-ai/sdk";import fs from "fs/promises";
const client = new Anthropic();
// Em produção, substitua este armazenamento de arquivo por Redis ou um banco de dadosconst MEMORY_PATH = "./memory.json";
interface EpisodicStore { [userId: string]: Record<string, string>;}
async function loadMemory(): Promise<EpisodicStore> { try { const data = await fs.readFile(MEMORY_PATH, "utf-8"); return JSON.parse(data); } catch { return {}; }}
async function saveMemory(store: EpisodicStore): Promise<void> { await fs.writeFile(MEMORY_PATH, JSON.stringify(store, null, 2));}
// Extrair fatos estruturados da troca mais recenteasync function extractFacts( userMessage: string, assistantReply: string): Promise<Record<string, string>> { const response = await client.messages.create({ model: "claude-haiku-4-5-20251001", max_tokens: 256, messages: [ { role: "user", content: `Extraia quaisquer fatos pessoais, preferências ou decisões importantes desta troca.Retorne um objeto JSON de pares chave-valor. Retorne {} se não houver nada que valha a pena lembrar.
Usuário: ${userMessage}Assistente: ${assistantReply}`, }, ], });
try { const text = response.content[0].type === "text" ? response.content[0].text : "{}"; const match = text.match(/\{[\s\S]*\}/); return match ? JSON.parse(match[0]) : {}; } catch { return {}; }}
// Formatar os fatos armazenados como uma injeção no prompt do sistemafunction formatMemory(facts: Record<string, string>): string { const entries = Object.entries(facts); if (entries.length === 0) return ""; return ( "O que você sabe sobre este usuário:\n" + entries.map(([k, v]) => `- ${k}: ${v}`).join("\n") );}
async function chat(userId: string, userMessage: string): Promise<string> { const store = await loadMemory(); const userFacts = store[userId] ?? {};
const systemPrompt = formatMemory(userFacts);
const response = await client.messages.create({ model: "claude-sonnet-4-6", max_tokens: 1024, system: systemPrompt || undefined, messages: [{ role: "user", content: userMessage }], });
const assistantMessage = response.content[0].type === "text" ? response.content[0].text : "";
// Extrair fatos e persistir para sessões futuras const newFacts = await extractFacts(userMessage, assistantMessage); if (Object.keys(newFacts).length > 0) { store[userId] = { ...userFacts, ...newFacts }; await saveMemory(store); }
return assistantMessage;}
// Sessão 1await chat("usuario-123", "Prefiro respostas concisas. Sem marcadores a menos que necessário.");// Sessão 2 (um processo diferente, dias depois)await chat("usuario-123", "Explique como funcionam os handshakes TCP.");// O agente se lembra da preferência de formatação da sessão 1Quando usar: Agentes voltados ao usuário que funcionam em várias sessões. Personalização. Rastreamento de preferências.
O limite: Os fatos se acumulam ao longo do tempo. Fatos obsoletos podem produzir comportamento incorreto. Adicione um mecanismo para atualizar ou expirar fatos.
Estratégia 4: Memória Semântica (Busca Vetorial)
A memória episódica armazena fatos discretos. A memória semântica armazena documentos, código ou fragmentos de conversa indexados por significado. Quando o agente precisa de informações, ele pesquisa o índice usando uma consulta.
Esta é a base da Geração Aumentada por Recuperação (RAG).
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
interface Document { id: string; content: string; embedding?: number[];}
// Calcular a similaridade de cosseno entre dois vetoresfunction cosineSimilarity(a: number[], b: number[]): number { const dot = a.reduce((sum, val, i) => sum + val * b[i], 0); const magA = Math.sqrt(a.reduce((sum, val) => sum + val * val, 0)); const magB = Math.sqrt(b.reduce((sum, val) => sum + val * val, 0)); return dot / (magA * magB);}
class SemanticMemory { private documents: Document[] = [];
// Indexar um documento para recuperação posterior async add(id: string, content: string): Promise<void> { const embedding = await embed(content); // Use um modelo de embedding dedicado this.documents.push({ id, content, embedding }); }
// Recuperar os k documentos mais relevantes para uma consulta async search(query: string, topK = 3): Promise<Document[]> { const queryEmbedding = await embed(query); return this.documents .filter((doc) => doc.embedding) .map((doc) => ({ doc, score: cosineSimilarity(queryEmbedding, doc.embedding!), })) .sort((a, b) => b.score - a.score) .slice(0, topK) .map(({ doc }) => doc); }}
const memory = new SemanticMemory();
async function answerWithContext(question: string): Promise<string> { // Recuperar documentos relevantes const relevant = await memory.search(question); const context = relevant.map((d) => d.content).join("\n\n---\n\n");
const response = await client.messages.create({ model: "claude-sonnet-4-6", max_tokens: 1024, system: context ? `Responda a pergunta usando o seguinte contexto:\n\n${context}` : undefined, messages: [{ role: "user", content: question }], });
return response.content[0].type === "text" ? response.content[0].text : "";}
// Exemplo: indexar uma base de conhecimento e consultá-laawait memory.add("doc-1", "A equipe usa React 18 com o padrão de app router.");await memory.add("doc-2", "Todas as rotas de API devem validar a entrada com esquemas Zod.");await memory.add("doc-3", "As consultas de banco de dados usam Drizzle ORM. Evitar SQL bruto.");
const answer = await answerWithContext("Como devo escrever um novo endpoint de API?");// O agente recupera doc-2 e doc-3 e gera uma resposta fundamentadaPara uso em produção, substitua o armazenamento em memória por um banco de dados vetorial dedicado. Pgvector é a opção mais simples se você já usa PostgreSQL. Chroma e Qdrant são boas opções independentes.
Quando usar: Assistentes de documentação. Agentes de busca de código. Perguntas e respostas sobre bases de conhecimento.
Conectando a Memória ao MCP
Se você seguiu o guia do servidor MCP, pode expor a memória como um recurso MCP. Isso torna sua camada de memória acessível a qualquer cliente compatível com MCP.
import { Server } from "@modelcontextprotocol/sdk/server/index.js";import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new Server( { name: "memory-server", version: "1.0.0" }, { capabilities: { tools: {}, resources: {} } });
// Ferramenta: armazenar um fatoserver.setRequestHandler("tools/call", async (request) => { if (request.params.name === "remember") { const { key, value, userId } = request.params.arguments as { key: string; value: string; userId: string; }; await storeEpisodicFact(userId, key, value); return { content: [{ type: "text", text: `Armazenado: ${key} = ${value}` }] }; }
if (request.params.name === "recall") { const { userId } = request.params.arguments as { userId: string }; const facts = await loadEpisodicFacts(userId); return { content: [{ type: "text", text: JSON.stringify(facts, null, 2) }], }; }});
const transport = new StdioServerTransport();await server.connect(transport);Qualquer agente que fale MCP pode agora chamar remember e recall como ferramentas padrão. A memória se torna um serviço compartilhado em vez de uma implementação por agente.
Escolhendo a Estratégia Certa
Use esta tabela de decisão como ponto de partida:
| Situação | Estratégia |
|---|---|
| Sessão única, menos de 20 turnos | Buffer |
| Sessão única, longa duração | Resumo |
| Multi-sessão, preferências do usuário | Episódica |
| Grande base de conhecimento para consultar | Semântica |
| Tudo acima | Combinar: resumo para a sessão + episódica/semântica entre sessões |
Comece com o buffer. Adicione uma camada de resumo quando as conversas se tornarem longas. Adicione memória episódica quando os usuários retornarem em várias sessões. Adicione memória semântica quando tiver um corpus de documentos para recuperar.
Não adicione complexidade antes de precisar dela. Um buffer de conversa é a resposta certa para a maioria dos protótipos.
Conclusão
Memória não é uma única coisa — é um conjunto de estratégias que abordam diferentes problemas em diferentes escopos. O buffer lida com a sessão atual. O resumo estende essa sessão. A memória episódica conecta sessões. A memória semântica fundamenta as respostas em uma base de conhecimento.
Escolha a camada que resolve o problema que você tem hoje. Envolva-a atrás de uma interface limpa para poder trocar implementações mais tarde. E se seus agentes se comunicam via MCP, exponha a memória como uma ferramenta compartilhada — isso mantém cada agente simples enquanto dá ao sistema como um todo um cérebro persistente.
Artigos Relacionados
- Construindo Seu Primeiro Servidor MCP
- Padrões Multi-Agente: Orquestradores, Workers e Pipelines
- Padrões de Uso de Ferramentas: Interfaces Agente-Ferramenta Confiáveis
- Introdução ao Desenvolvimento Agêntico