Elton José logo
Elton José
Agentic AI

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

Memória para Agentes: A Diferença Entre um Protótipo e um Sistema em Produção
0 visualizações
11 minutos de leitura
#Agentic AI

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

Arquitetura de memória para agentes com 4 camadas: Working Memory (contexto ativo), Episodic (Mem0/Zep), Semantic (pgvector/Qdrant) e Procedural (SDD/Specs)

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}")] + recent

2. 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 frontend

3. 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 prescritiva

Essa 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.
"""
Compreensão de Procedural Memory por agentes de IA lendo especificações técnicas diretamente

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 response

LangMem: 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

Foto de Elton José

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