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ãos — recall_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.