Artigo

Memória de agente de IA que você verifica, não só confia

Minha memória de agentes de IA falhava em silêncio havia semanas e nenhum log acusava. A lição não foi consertar o bug: foi perceber que a categoria inteira otimiza a coisa errada.

Publicado em 24 de junho de 2026. ~9 min de leitura.

TL;DR

Construí uma memória compartilhada self-hosted para os meus agentes de código: cada sessão é salva no meu próprio Postgres e relembrada na próxima, entre máquinas e ferramentas. Semana passada descobri, por acaso, que a captura falhava em silêncio havia semanas — três bugs distintos faziam sessões inteiras nunca chegarem ao banco, e os logs ficavam limpos. A lição não foi "conserta o bug". Foi perceber que o mercado de memória de agente corre atrás de "lembrar melhor" — que virou commodity — enquanto a dor real e desatendida é outra: como você prova que a captura funcionou? Construí observabilidade para verificar em vez de confiar, e, antes de sonhar em virar produto, rodei um scan adversarial de mercado para me provar errado. Conclusão honesta: dogfood que me serve todo dia, não startup.

Para quem é este artigo

  • Devs que rodam agentes de IA com memória persistente e assumem que ela "só funciona".
  • Quem constrói ou avalia tooling de memória (mem0, Zep, claude-mem, ou caseiro).
  • Quem gosta de pós-morte honesto: bug silencioso de pipeline somado a uma decisão de produto deliberada.

O contexto: por que uma memória própria

Todo agente de código tem o mesmo defeito de fábrica: começa do zero a cada sessão. Você re-explica o projeto, as decisões, as convenções, o que já tentou. De novo, e de novo.

A solução óbvia é dar memória persistente ao agente. Há várias ferramentas para isso. Escolhi construir a minha em cima do meu próprio Postgres (via Supabase) em vez de um SaaS, por três razões: posse dos dados, auditabilidade, e a capacidade de rodar pg_dump a qualquer momento sem lock-in. O desenho é simples: um hook captura cada sessão, outro injeta um resumo das sessões relevantes no início da próxima, busca híbrida (palavra-chave + pgvector) por cima, e uma camada opcional de fatos. Funciona entre máquinas e entre ferramentas (Claude Code e Codex).

A promessa que eu vendia para mim mesmo era confortável: "memória em que você confia". Era exatamente essa a palavra errada.

O sintoma: "será que isso foi salvo?"

A descoberta começou com uma pergunta banal. No meio de uma sessão longa, quis confirmar se a conversa atual tinha mesmo sido capturada. Fui checar as duas fontes de verdade — o log de captura e a tabela no banco — e a sessão não estava em nenhuma das duas.

O log contava uma história desconfortável: erros intermitentes, sessão sim, sessão não. O hook de captura tinha um except que engolia qualquer erro e seguia em frente com exit 0. Do ponto de vista do Claude Code, tudo certo. Do ponto de vista do banco, faltavam sessões inteiras.

E isso é pior do que um crash. Crash você vê: ele para, grita, aparece. Silêncio você não vê. O agente simplesmente "esquece" coisas que deveria saber, e você atribui isso a uma limitação do modelo, não a um pipeline quebrado. A memória parecia funcionar — até a hora em que eu olhei de perto.

Três bugs, todos silenciosos

O que parecia um problema virou três, em camadas diferentes do mesmo pipeline. Vale o detalhe, porque o padrão se repete em qualquer sistema de ingestão.

Bug 1 — caracteres de controle

O payload de cada sessão é JSON. O parser estrito do Python rejeitava qualquer payload com caracteres de controle não-escapados — e o conteúdo de uma sessão de terminal está cheio deles (output de comandos, box-drawing, sequências ANSI). O resultado era um JSONDecodeError que o except engolia.

A correção é de uma linha, mas só funciona porque o erro estava no parse:

payload = json.load(sys.stdin, strict=False)  # aceita control chars dentro das strings

Bug 2 — o byte nulo

Com o parser tolerante, outra sessão falhou — agora no banco. O Postgres recusa o byte nulo em coluna text, com o erro 22P05: unsupported Unicode escape sequence. Ou seja: o parse passava, mas o INSERT era rejeitado. A correção foi sanitizar o conteúdo, recursivamente, antes de subir:

def strip_nul(obj):
    if isinstance(obj, str):
        return obj.replace("\x00", "")
    if isinstance(obj, dict):
        return {k: strip_nul(v) for k, v in obj.items()}
    if isinstance(obj, list):
        return [strip_nul(v) for v in obj]
    return obj

Bug 3 — o echo que corrompia o JSON

Este foi o mais sutil, e o mais instrutivo. O comando do hook capturava o payload e o repassava ao Python assim:

payload=$(cat); echo "$payload" | python3 capture_session.py ...

O problema é o echo. Em sh/dash, echo interpreta sequências de barra invertida. Qualquer payload com \ — e sessões têm muitos: sequências ANSI, regex, \u — chegava corrompido ao parser, com o erro Invalid \escape. Por isso alternava: sessão com barra invertida falhava, sessão sem barra passava. O parser nunca teve chance; o dado já chegava quebrado.

A correção é trocar echo por printf '%s', que não interpreta escapes. O teste lado a lado prova o ponto, com a mesma entrada JSON válida:

# mesma entrada válida; via echo quebra, via printf passa
sh -c 'p=$(cat in.json); echo "$p"'    | python3 -c 'import json,sys; json.load(sys.stdin)'
# -> JSONDecodeError: Invalid \escape

sh -c 'p=$(cat in.json); printf "%s" "$p"' | python3 -c 'import json,sys; json.load(sys.stdin)'
# -> ok

A lição transversal: cada camada — parser, banco, shell — tinha uma armadilha diferente para o mesmo dado. Um pipeline "simples" de três etapas escondia três modos de falha independentes, todos silenciosos.

A virada: o problema não é lembrar, é verificar

Consertar os bugs foi o de menos. O que ficou foi a pergunta que eles forçaram: eu passei semanas confiando numa coisa quebrada e não soube. Por quê?

Porque a métrica que a categoria inteira otimiza é "lembrar melhor" — recall mais esperto, embeddings melhores, resumos mais densos. Isso é importante, mas virou commodity: há dezenas de ferramentas, é um oceano vermelho. A dor que quase ninguém ataca está um passo antes: prova de que a captura aconteceu. Ninguém te avisa quando a memória falha. Não há recibo, não há health check, não há detecção de gap. Você descobre quando o agente esquece — se descobrir.

Então parei de só confiar e construí para verificar. Duas peças:

Um health check de cobertura. Um comando que reconcilia os transcripts locais (de todas as ferramentas) contra o que está no banco, ignora sessões vazias, e vigia a taxa de erro da captura nas últimas 24 horas. A saída é direta:

agent-memory-hub · health

✓ cobertura   ██████████████████████ 86/86 sessões locais salvas
✓ captura     últimas 24h: 51 ok, 0 erros
✓ subagentes  13/13 sessões com subagentes anexados

Quando algo quebra, isso aparece — em vez de passar batido. É a diferença entre memória que você confia e memória que você verifica.

Um servidor MCP para recall sob demanda. O recall padrão é passivo: injeta um resumo no início da sessão, antes de você sequer dizer o que quer fazer. Expus a memória como ferramentas MCP (em stdlib puro, sem dependências) para que o agente consulte a memória com o contexto da tarefa em mãosrecall_relevant, recent_sessions, get_facts, get_session. O coração do servidor é um handler JSON-RPC minimalista:

elif method == "tools/call":
    text = _call(params["name"], params.get("arguments") or {})
    _reply(rid, {"content": [{"type": "text", "text": text}]})

Os números, depois de fechar tudo: 148 sessões, 36 projetos, cobertura local↔banco reconciliada, com as sessões de uma segunda conta e os transcripts de subagentes finalmente incluídos no backfill.

O que não funcionou: matei a ideia de propósito

A parte que mais me orgulho não é o código. É o que fiz antes de me empolgar.

A tentação óbvia, ao encontrar uma dor real e desatendida, é transformá-la em produto. Antes de sonhar, rodei um scan adversarial de mercado — com o objetivo explícito de me provar errado, não de confirmar que havia espaço. O resultado foi sóbrio:

  • MemGuard já se vende como "Datadog para memória de agente": health e trust-scoring.
  • Zep já cobre a governança enterprise: SOC 2 Type II, HIPAA, audit de acesso.
  • MemTrust (um paper de 2026) já propõe audit cripto-encadeado e attestation.

O tema "observar/confiar na memória" está enchendo rápido. O recorte exato que sobra aberto — prova de captura, detecção de gap silencioso, "recibos de memória" — é real, mas é fino, e copiável pelos incumbentes em semanas (o mem0, por exemplo, já tem um Events API de auditoria).

A decisão saiu fácil depois disso: isso é dogfood que me serve todos os dias, não startup. Matar a ideia de propósito foi a jogada certa, e foi libertador. O scan me poupou semanas construindo um produto fino, e o trabalho continua valendo — só que como ferramenta pessoal, não como aposta de negócio.

Um limite técnico honesto, para quem for replicar: a correção do hook só vale em sessões novas, porque hooks são carregados no início da sessão. A rede de segurança é o evento de fim de sessão, que salva o transcript inteiro de forma limpa — então mesmo com os checkpoints intermediários falhando, o estado final não se perde.

Conclusão

Se você roda agentes com memória, faça uma coisa esta semana: cheque se ela está capturando de verdade. Não assuma. O modo de falha mais perigoso de um pipeline de ingestão não é o que grita — é o que falha calado e te deixa confiante.

O princípio generaliza para além de agentes de IA: para qualquer sistema que ingere dados e pode falhar em silêncio, observabilidade vale mais que otimização. Verifique antes de confiar. E o meta-aprendizado de produto, talvez o mais útil: rodar pesquisa adversarial para matar a própria ideia é um superpoder, não uma derrota. Às vezes o melhor resultado de uma investigação é a permissão honesta para não construir.

O projeto é open-source e self-hosted. Se te economizar uma única sessão re-explicando seu projeto ao agente, já valeu.