SISTEMA DE ACESSO COMPUTACIONAL

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:

TipoOnde armazenadoEscopoMelhor para
BufferEm contexto (array de mensagens)Sessão atualConversas curtas
ResumoEm contexto (comprimido)Sessão atualConversas longas
SemânticaExterna (banco de dados vetorial)Entre sessõesRecuperação de conhecimento
EpisódicaExterna (armazenamento chave-valor)Entre sessõesFatos 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 conversa
const 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 uso
await 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 mensagem

Quando 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 progressivo
async 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 dados
const 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 recente
async 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 sistema
function 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 1
await 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 1

Quando 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 vetores
function 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á-la
await 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 fundamentada

Para 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 fato
server.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çãoEstratégia
Sessão única, menos de 20 turnosBuffer
Sessão única, longa duraçãoResumo
Multi-sessão, preferências do usuárioEpisódica
Grande base de conhecimento para consultarSemântica
Tudo acimaCombinar: 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