# Memoirs: Ensinando Agentes a Lembrar (Sem Perder a Cabeça na Nuvem)


## A Amnésia de Mil Tokens

Todo agente conversacional com que já trabalhei — Claude, Cursor, vários loops de CLI — compartilha a mesma falha silenciosa. Eles acordam sem nenhuma memória de ontem. As janelas de contexto ficaram enormes, mas eles ainda começam de uma página em branco a cada nova sessão. Se você tenta contornar isso mantendo uma thread contínua, acaba com algo pior: uma monstruosidade arquitetural que despeja todo o histórico do chat na janela de contexto a cada turno. Você paga o custo de inferência de 40K tokens, e o agente ainda enterra o fato mais importante em algum lugar no meio — invisível, ignorado. Esse é o efeito *Lost in the Middle*. É caro e nem sequer funciona.

Existem soluções corporativas para isso. Pesadas. Bancos de dados vetoriais hospedados na nuvem com idas e voltas pela rede medidas em segundos num dia bom. Mas eu não vou enviar minhas notas de arquitetura, minhas credenciais meio censuradas e minhas convenções pessoais para alguma API externa que não controlo. Eu queria algo *local-first*. Privado por construção. Algo que de fato entendesse minhas variáveis de ambiente, minhas escolhas de framework, meu estilo — e mantivesse esse conhecimento por perto.

Foi isso que se tornou o [memoirs](https://github.com/misaelzapata/memoirs). Não um wrapper de RAG. Isso teria sido fácil demais, e entediante demais. Eu queria um sistema de memória de verdade — um que esquece organicamente e consolida o que realmente importa.

## A Anatomia Subjacente: 6 Camadas sobre o SQLite

Construí a camada de persistência sobre algo que a maioria das pessoas subestima: o SQLite. Não só um arquivo `.db` plano. Uma instância de SQLite turbinada com `sqlite-vec` para busca vetorial densa e `FTS5` nativo para busca lexical por índice invertido com pontuação BM25.

A arquitetura se assentou em seis camadas, porque um sistema de memória não é uma caixa de armazenamento — é uma estação de tratamento de água:

1. **Logs brutos**: O tubo de entrada. Recebe tudo — histórico completo de conversa, diffs ruidosos, saída bruta.
2. **Extração**: Um pequeno LLM curador rodando localmente (atualmente Qwen 2.5 3B — rápido o suficiente para não se notar) que filtra sinal do ruído: heurísticas, credenciais, preferências, marcadores de estilo.
3. **Grafo**: O tecido conjuntivo. As memórias são interligadas usando princípios no estilo Zettelkasten, encontrando nós semânticos compartilhados entre conceitos e sessões.
4. **Indexação Dupla**: Escritas atômicas paralelas no sqlite-vec e no FTS5. Ambos os índices permanecem sincronizados.
5. **Motor de Memória**: A camada de curadoria. As memórias ganham e perdem pontuação ao longo do tempo, com suporte bi-temporal completo — você pode consultar o que o sistema acreditava em qualquer momento passado.
6. **Superfície**: A camada de exposição. HTTP + REST, e mais importante, um servidor MCP de 22 endpoints que permite a qualquer agente consultar o sistema de forma limpa sem saber nada sobre as entranhas.

## RRF e BM25: Vetores Não Resolvem Tudo

No começo, bati numa parede. Os embeddings do `sentence-transformers` falhavam de um jeito muito específico: a recuperação exata de identificadores. Peça ao `memoirs` para recuperar um nome de ferramenta específico ou uma string de dependência versionada e ele voltava com ruído semanticamente adjacente. Os espaços vetoriais são brilhantes com significado. Eles sabem que "carro" está perto de "veículo". Mas quando uma memória contém *"o sistema antigo usava psql-driver-v9"*, o embedding não dá nenhum peso especial a essa string exata. Para um agente tentando reconstruir uma decisão, isso é um erro crítico.

A solução foi combinar algo novo com algo muito antigo: Reciprocal Rank Fusion (RRF). Rodamos as duas buscas em paralelo — uma consulta semântica densa pelo sqlite-vec, e uma consulta de índice invertido por correspondência exata pelo FTS5 com pontuação BM25. Se o BM25 encontra a string exatamente e a busca semântica entende o contexto ao redor, o RRF estabiliza as pontuações e empurra o resultado certo firmemente para a posição um. O resultado: a latência de recuperação p50 caiu para cerca de 3,9 ms. Num banco de dados SQLite local. Isso supera a maioria dos armazenamentos vetoriais na nuvem por uma boa margem.

## Ebbinghaus, Curvas de Esquecimento e Sono Assíncrono

Um sistema não tem memória de longo prazo se não consegue esquecer. Os agentes têm tendência a se afogar em conhecimento obsoleto. "Ontem eu estava brigando com a API do Docker" seguido imediatamente de "Hoje abandonamos o Docker por completo e estamos em chamadas OCI puras." Se o motor de memória traz à tona ambos os fatos com confiança igual, o agente vai tomar a decisão errada. Toda vez.

Voltei a um caderno antigo e implementei funções de decaimento baseadas na curva de esquecimento de Hermann Ebbinghaus: $R(t) = e^{-\Delta t_h / (S \cdot 24)}$. As memórias novas perdem peso assintoticamente com o tempo. Mas a pontuação de Força delas — a qualidade do sinal original — é multiplicada cada vez que o sistema recupera e confirma positivamente aquela memória. Decisões arquiteturais centrais e preferências profundas decaem muito mais devagar do que um relato de guerra de depuração de uma única sessão de madrugada.

A computação que isso exige — consolidação, resolução de conflitos semânticos, compactação de grafo — é cara demais para rodar em tempo real. Então recorri a um mecanismo que os humanos usam constantemente, mas que o software quase nunca usa: o *sono*.

Escrevi um daemon (`sleep_consolidation.py`) que vigia os vales de CPU. Ele só roda quando o desenvolvedor e os agentes se afastaram — literalmente as janelas de ociosidade em que o lock está livre e a ociosidade do sistema esteve acima de N minutos. Nessa janela assíncrona cega, o modelo Qwen ou Phi local acorda, revisa os perfis armazenados recentemente, caça contradições entre versões de memória e consolida ou arquiva as obsoletas. O grafo é compactado silenciosamente em segundo plano antes da próxima sessão começar.

No fim do ciclo de benchmarking, alcançar pontuações fortes de MRR em benchmarks de recuperação de memória como o LoCoMo — com um overhead de RAM de apenas 231 MB, comparado ao que o LlamaIndex ou o Mem0 consomem — confirmou a hipótese. Os melhores recursos de agentes não são construídos enviando milhões de tokens para provedores remotos. Eles são construídos entendendo índices básicos a fundo e impondo uma disciplina de recursos implacável na sua própria máquina.

