Memória para Agentes: A Diferença Entre um Protótipo e um Sistema em Produção

Sumário
- Memória para Agentes: A Diferença Entre um Protótipo e um Sistema em Produção
- Por Que Memória É Mais Difícil do Que Parece
- Os 4 Tipos de Memória
- 1. Working Memory — O Contexto Ativo
- Compressão de contexto quando próximo do limite
- 2. Episodic Memory — O Que Aconteceu
- Após uma sessão com o usuário
- Na próxima sessão, recuperar memórias relevantes
- Injeta no system prompt
- Zep extrai automaticamente fatos das mensagens
- Fatos extraídos automaticamente por Zep:
- - usuário: prefere TypeScript para frontend
- - usuário: trabalha com projetos que têm frontend
- 3. Semantic Memory — O Conhecimento do Domínio
- Busca híbrida: semântica + filtro por metadata
- 4. Procedural Memory — Como Fazer
- Spec: Análise de Pull Request
- Objetivo
- Inputs
- Processo
- Output
- Regras
- Procedimento de Revisão
- Contexto do Projeto
- Memory Router: Orquestrando as 4 Camadas
- Uso no agente
- LangMem: Memória Integrada para LangChain/LangGraph
- Cria gerenciador de memória
- Integra no grafo LangGraph
- Estratégia de Adoção: Por Onde Começar
- Armadilhas Comuns em Produção
- Recursos
Memória para Agentes: A Diferença Entre um Protótipo e um Sistema em Produção
Existe um padrão que aparece com quase todo desenvolvedor que começa a construir sistemas agênticos. O protótipo é impressionante — o agente raciocina bem, executa as tarefas, entrega resultados. Você mostra para o time, todo mundo fica animado.
Então alguém pergunta: "e o que acontece quando eu voltar amanhã e continuar de onde parei?"
O agente não sabe. Começa do zero.
Ou: "e se eu trabalhar com esse agente por um mês — ele vai aprender meus padrões?"
Também não. Cada sessão é uma folha em branco.
Esse é o problema de memória em sistemas agênticos. E é o que separa um demo de um produto.
Por Que Memória É Mais Difícil do Que Parece
A primeira intuição de todo dev é "é só guardar o histórico de conversa". E de fato, para agentes simples com sessões curtas, isso funciona.
Mas conforme o sistema escala, essa abordagem quebra de formas previsíveis:
Limite de contexto: uma janela de 1M de tokens parece enorme, mas uma conversa de trabalho real de um mês são potencialmente bilhões de tokens. Você não consegue colocar tudo no contexto.
Ruído: nem tudo que foi dito antes é relevante para a tarefa atual. Jogar 3 meses de histórico no prompt dilui a atenção do modelo em detalhes que não importam — e degrada a qualidade das respostas.
Custo: cada token no contexto custa dinheiro. Um agente que carrega o histórico completo de todas as interações vai ter custo crescendo linearmente — inviável em produção.
Conhecimento de domínio: há coisas que o agente precisa saber sobre seu sistema, empresa, convenções de código, preferências de time — e esse conhecimento não vem do histórico de conversa.
A solução é não tratar "memória" como um único conceito, mas como quatro camadas distintas, cada uma com propósito e implementação diferente.
Os 4 Tipos de Memória
1. Working Memory — O Contexto Ativo
É o que está na janela de contexto agora: as mensagens da conversa atual, os resultados de tools calls recentes, o estado intermediário da tarefa em andamento.
É a memória mais cara (cada token = custo) e mais rápida (o modelo acessa diretamente). A gestão de Working Memory — decidir o que entra, o que sai, como comprimir — é Context Engineering, e é onde a maioria dos problemas de performance de agentes em produção vive.
Ferramentas e padrões:
- Compaction/Summarization: quando o contexto está ficando longo, resumir as partes antigas e manter os detalhes recentes
- Selective context: em vez de passar todo o histórico, selecionar apenas as mensagens relevantes para a tarefa atual (via embedding similarity)
- Structured state: manter o estado do agente como um objeto JSON estruturado em vez de texto livre — mais eficiente em tokens e mais previsível para o modelo
# Compressão de contexto quando próximo do limite
def compress_context(messages: list[Message], max_tokens: int = 80_000) -> list[Message]:
if count_tokens(messages) < max_tokens * 0.8:
return messages # Ainda tem espaço
# Preserva system prompt + últimas N mensagens
system = [m for m in messages if m.role == "system"]
recent = messages[-10:] # Últimas 10 mensagens sempre inteiras
# Comprime o meio
middle = messages[len(system):-10]
summary = llm.summarize(middle, instruction="Resumo conciso das decisões e contexto relevante")
return system + [Message(role="system", content=f"[Histórico resumido]: {summary}")] + recent2. Episodic Memory — O Que Aconteceu
Memória de episódios: conversas passadas, decisões tomadas, resultados de tarefas anteriores, preferências demonstradas pelo usuário ao longo do tempo.
O objetivo é que o agente consiga responder perguntas como "o que discutimos sobre autenticação na semana passada?" ou "lembre-me das minhas preferências de naming convention".
Mem0: A Ferramenta Principal
from mem0 import Memory
memory = Memory()
# Após uma sessão com o usuário
memory.add(
messages=conversa_da_sessao,
user_id="elton",
metadata={"session_date": "2026-04-06", "project": "ecommerce-api"}
)
# Na próxima sessão, recuperar memórias relevantes
def get_relevant_memories(user_id: str, query: str) -> str:
memories = memory.search(query=query, user_id=user_id, limit=5)
if not memories["results"]:
return ""
return "\n".join([f"- {m['memory']}" for m in memories["results"]])
# Injeta no system prompt
relevant = get_relevant_memories("elton", "autenticação JWT")
system_prompt = f"""Você é o assistente de Elton.
{f'Memórias relevantes:\n{relevant}' if relevant else ''}
"""Zep: Memória Episódica com Self-Hosting
O Zep vai além do Mem0 com extração automática de fatos a partir de conversas — ele não apenas armazena, mas identifica entidades, relacionamentos e fatos implícitos no texto.
from zep_cloud.client import AsyncZep
client = AsyncZep(api_key="...")
# Zep extrai automaticamente fatos das mensagens
await client.memory.add(
session_id="session-123",
messages=[
{"role": "user", "content": "Prefiro usar TypeScript no frontend"},
{"role": "assistant", "content": "Anotado, vou usar TypeScript nos exemplos"}
]
)
# Fatos extraídos automaticamente por Zep:
# - usuário: prefere TypeScript para frontend
# - usuário: trabalha com projetos que têm frontend3. Semantic Memory — O Conhecimento do Domínio
Memória semântica é o conhecimento estruturado sobre o mundo — documentos, base de código, especificações, documentação interna. É o que você indexa em um vector database e recupera via RAG.
A distinção em relação à memória episódica: episódica é "o que aconteceu", semântica é "o que é verdade sobre esse domínio".
pgvector: Memória Semântica no PostgreSQL
Para times que já usam PostgreSQL, pgvector é a opção de menor fricção operacional:
-- Habilitando a extensão
CREATE EXTENSION IF NOT EXISTS vector;
-- Tabela de conhecimento com embeddings
CREATE TABLE knowledge_base (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
content TEXT NOT NULL,
embedding vector(1536), -- dimensão do text-embedding-3-small
source TEXT,
metadata JSONB,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Índice para busca por similaridade
CREATE INDEX ON knowledge_base
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);import openai
import psycopg2
import json
def index_document(content: str, source: str, metadata: dict):
embedding = openai.embeddings.create(
input=content,
model="text-embedding-3-small"
).data[0].embedding
conn = psycopg2.connect(DATABASE_URL)
with conn.cursor() as cur:
cur.execute(
"INSERT INTO knowledge_base (content, embedding, source, metadata) VALUES (%s, %s, %s, %s)",
(content, json.dumps(embedding), source, json.dumps(metadata))
)
conn.commit()
def semantic_search(query: str, limit: int = 5) -> list[dict]:
query_embedding = openai.embeddings.create(
input=query,
model="text-embedding-3-small"
).data[0].embedding
conn = psycopg2.connect(DATABASE_URL)
with conn.cursor() as cur:
cur.execute("""
SELECT content, source, metadata,
1 - (embedding <=> %s::vector) as similarity
FROM knowledge_base
ORDER BY embedding <=> %s::vector
LIMIT %s
""", (json.dumps(query_embedding), json.dumps(query_embedding), limit))
return [
{"content": row[0], "source": row[1], "metadata": row[2], "similarity": row[3]}
for row in cur.fetchall()
]Qdrant: Quando Você Precisa de Mais
Para casos com volume maior, filtragem complexa por metadados, ou multi-tenancy, o Qdrant oferece mais flexibilidade:
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct
client = QdrantClient(url="http://localhost:6333")
# Busca híbrida: semântica + filtro por metadata
results = client.search(
collection_name="codebase",
query_vector=query_embedding,
query_filter={
"must": [
{"key": "language", "match": {"value": "typescript"}},
{"key": "module", "match": {"value": "auth"}}
]
},
limit=10
)4. Procedural Memory — Como Fazer
Memória procedural é o conhecimento de como executar tarefas — não fatos sobre o mundo, mas procedimentos, regras, workflows. É a memória que faz um humano experiente diferente de um iniciante: não é que o experiente sabe mais fatos, é que ele internalizou os procedimentos certos.
Para agentes, procedural memory são as especificações e instruções que definem como o agente deve se comportar — e aqui é onde o SDD (Spec-Driven Development) entra de forma natural.
SDD como Procedural Memory
No SDD, você escreve specs detalhadas do comportamento esperado antes de implementar. Essas specs — quando bem escritas — são exatamente o que um agente precisa para executar tarefas consistentemente: regras claras, exemplos de input/output, critérios de sucesso, edge cases.
# Spec: Análise de Pull Request
## Objetivo
Revisar um PR e fornecer feedback estruturado.
## Inputs
- URL ou número do PR
- Contexto do projeto (linguagem, padrões de código, área de negócio)
## Processo
1. Buscar diff completo do PR
2. Identificar mudanças por categoria: lógica, estilo, testes, docs
3. Para cada mudança de lógica: verificar edge cases, error handling, performance
4. Para cada mudança de estilo: verificar aderência ao guia de estilo do projeto
5. Gerar comentários contextuais por linha quando relevante
## Output
- Resumo executivo (máximo 3 parágrafos)
- Lista de issues críticos (bloqueantes para merge)
- Lista de sugestões (não-bloqueantes)
- Score de qualidade (1-10) com justificativa
## Regras
- Nunca aprovar PR sem cobertura de testes para lógica nova
- Sempre mencionar impacto de performance para mudanças em hot paths
- Usar linguagem construtiva, não prescritivaEssa spec é injetada no system prompt do agente de revisão de PR. É a sua procedural memory — o agente "sabe" como fazer code review porque você especificou o procedimento em detalhe.
def build_pr_review_agent_prompt(spec: str, project_context: str) -> str:
return f"""Você é um agente de revisão de código especializado.
## Procedimento de Revisão
{spec}
## Contexto do Projeto
{project_context}
Execute a revisão seguindo o procedimento acima rigorosamente.
"""
Memory Router: Orquestrando as 4 Camadas
Na prática, você não quer que o agente decida manualmente qual camada de memória consultar. Um Memory Router faz isso de forma automática:
class MemoryRouter:
def __init__(self):
self.mem0 = Memory() # Episodic
self.qdrant = QdrantClient() # Semantic
self.specs = SpecLoader() # Procedural
async def get_context(self, query: str, user_id: str, task_type: str) -> str:
# Consulta todas as camadas em paralelo
episodic, semantic, procedural = await asyncio.gather(
self.get_episodic(query, user_id),
self.get_semantic(query),
self.get_procedural(task_type)
)
context_parts = []
if episodic:
context_parts.append(f"## Histórico Relevante\n{episodic}")
if semantic:
context_parts.append(f"## Conhecimento do Domínio\n{semantic}")
if procedural:
context_parts.append(f"## Como Executar Esta Tarefa\n{procedural}")
return "\n\n".join(context_parts)
async def get_episodic(self, query: str, user_id: str) -> str:
results = self.mem0.search(query=query, user_id=user_id, limit=3)
return "\n".join([f"- {r['memory']}" for r in results["results"]])
async def get_semantic(self, query: str) -> str:
results = self.qdrant.search(
collection_name="knowledge_base",
query_vector=embed(query),
limit=5
)
return "\n\n".join([r.payload["content"] for r in results])
async def get_procedural(self, task_type: str) -> str:
return self.specs.load(task_type) # Carrega spec do filesystem/DB# Uso no agente
router = MemoryRouter()
async def run_agent(user_message: str, user_id: str, task_type: str):
# 1. Recupera contexto das 3 camadas externas
memory_context = await router.get_context(
query=user_message,
user_id=user_id,
task_type=task_type
)
# 2. Constrói prompt com Working Memory + contexto recuperado
messages = build_messages(
system=f"Você é um assistente especializado.\n\n{memory_context}",
history=get_compressed_history(user_id), # Working Memory
user_message=user_message
)
# 3. Executa o agente
response = await llm.complete(messages)
# 4. Salva a sessão na Episodic Memory
await router.mem0.add(
messages=[{"role": "user", "content": user_message},
{"role": "assistant", "content": response}],
user_id=user_id
)
return responseLangMem: Memória Integrada para LangChain/LangGraph
Para quem já usa o ecossistema LangChain, o LangMem oferece memória integrada com suporte a todas as 4 camadas:
from langmem import create_memory_manager
from langgraph.graph import StateGraph
# Cria gerenciador de memória
memory_manager = create_memory_manager(
episodic_store="mem0", # Backend para memória episódica
semantic_store="pgvector", # Backend para memória semântica
auto_extract=True # Extrai automaticamente fatos de conversas
)
# Integra no grafo LangGraph
graph = StateGraph(AgentState)
graph.add_node("memory_load", memory_manager.load_node)
graph.add_node("agent", agent_node)
graph.add_node("memory_save", memory_manager.save_node)
graph.set_entry_point("memory_load")
graph.add_edge("memory_load", "agent")
graph.add_edge("agent", "memory_save")Estratégia de Adoção: Por Onde Começar
Não precisa implementar as 4 camadas de uma vez. Uma sequência recomendada:
Fase 1 — Working Memory bem gerenciada: implemente compressão de contexto e selective history. Isso já resolve 80% dos problemas de custo e qualidade para a maioria dos casos.
Fase 2 — Procedural Memory via SDD: escreva specs detalhadas para as principais tarefas do seu agente. Injete no system prompt. Custo zero de infra, ganho imediato em consistência.
Fase 3 — Semantic Memory com pgvector: indexe o conhecimento do domínio (docs, codebase, FAQs) e implemente RAG. Começa simples, evolui para hybrid search conforme necessário.
Fase 4 — Episodic Memory com Mem0 ou Zep: adiciona personalização e continuidade entre sessões. Última fase porque exige mais infra e manutenção.
Armadilhas Comuns em Produção
Recuperação sem relevância: buscar memória episódica e devolver os 5 resultados mais similares independentemente da relevância mínima. Adicione um threshold de similarity (ex: só inclui se similarity > 0.75).
Memória contaminada: o agente salva informação incorreta (usuário estava errado, foi uma hipótese descartada). Implemente revisão antes de salvar ou use TTL para informações não confirmadas.
Custo de memória descontrolado: recuperar muito contexto de todas as camadas a cada chamada. O Memory Router deve ser inteligente sobre quando cada camada é necessária — nem toda query precisa das 4 camadas.
Falta de namespace por tenant: em sistemas multi-usuário ou multi-empresa, cada usuário/empresa precisa de namespace isolado em todas as camadas. É mais fácil desenhar isso no início do que migrar depois.
Recursos
Newsletter
Receba os artigos mais relevantes da semana, sem quebrar seu ritmo de leitura
Um resumo semanal com os melhores posts sobre IA, engenharia de software e tecnologia, enviado no melhor momento para continuar a conversa depois da leitura.

Escrito por
eltonjose
Engenheiro de software e estrategista de produtos digitais, focado em IA pragmática e em transformar experiências de trabalho remoto em aprendizados aplicáveis. Compartilho frameworks e decisões reais que uso em consultorias e projetos.
- Principais temasAgentic AI, Memória
- Formato do conteúdoGuia prático + insights de carreira
