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
Newsletter
Receba os melhores artigos toda semana
Sem spam. Só conteúdo de qualidade sobre IA & Dev.
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

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
