Home / Uncategorized / Engrim: memória episódica local em SQLite para agentes de código como Claude Code, Cursor e Antigravity

Engrim: memória episódica local em SQLite para agentes de código como Claude Code, Cursor e Antigravity

Se você usa mais de um agente de código (Claude Code num dia, Cursor no outro, testa o Antigravity de vez em quando), já bateu no mesmo problema: cada sessão nova começa do zero. O agente não sabe que você decidiu trocar de MongoDB para Postgres semana passada, não lembra por que rejeitou aquela abordagem de cache, e você acaba re-explicando a mesma arquitetura toda vez que abre o terminal.

O Engrim é uma tentativa concreta de resolver isso por fora do modelo, guardando decisões num arquivo SQLite local e injetando esse contexto no início de cada sessão, independente de qual agente você está usando.

O que é o Engrim e quem publicou

O Engrim foi publicado por Tim Gordon como projeto open source no GitHub, sob licença MIT, e distribuído no PyPI como pacote Python (pip install engrim). A descrição oficial no repositório o define como um motor de memória em SQLite, local-first, com escopo de projeto, que permite ao desenvolvedor trocar livremente entre modelos e ambientes (Google Antigravity, Claude Code, Cursor MCP, Windsurf) no mesmo projeto sem perder decisões arquiteturais, restrições de usuário ou estado do projeto.

A ideia central, nas palavras do autor no README: “Por que pagar por 200.000 tokens de ruído esquecido a cada turno? Os modelos são utilidades descartáveis; as decisões do seu projeto não são.” É um argumento retórico forte, mas o que interessa aqui é o mecanismo por baixo.

Como o Engrim funciona por dentro

A camada de armazenamento

Tudo vive em um único arquivo SQLite em ~/.engrim/memory.db. Não há servidor, não há sync com nuvem, não há telemetria. O banco tem três estruturas relevantes:

  • Uma tabela de memórias curadas (decisões, fatos, feedback, estado, referências)
  • Um índice FTS5 com stemmer porter, mantido em sincronia por triggers
  • Uma tabela de embeddings vetoriais gerada com model2vec

O uso de FTS5 aqui não é decorativo. É a extensão nativa do SQLite para busca textual com ranking BM25, e é ela que faz a parte lexical do sistema funcionar sem dependência externa.

A camada de embeddings: por que model2vec e não OpenAI embeddings

Essa é a escolha técnica mais interessante do projeto. O Engrim não chama nenhuma API de embeddings. Ele usa o model2vec, uma biblioteca que produz embeddings estáticos.

Model2Vec é uma técnica para transformar qualquer sentence transformer em um modelo estático pequeno, reduzindo o tamanho do modelo por um fator de até 50 e deixando os modelos até 500 vezes mais rápidos, com uma pequena queda de performance, segundo a documentação da biblioteca. O funcionamento, resumidamente: passa um vocabulário pelo transformer para criar uma biblioteca de embeddings por token, reduz as dimensões (por exemplo, com PCA), aplica uma ponderação (tipo Zipf) para enfatizar tokens frequentes, e na inferência você essencialmente calcula a média dos embeddings dos tokens.

Na prática isso significa: nada de GPU, nada de chamada de rede, nada de conta em provedor. O embedding de uma frase é uma lookup de vetores e uma média. É pior que um embedding contextual de última geração em qualidade, mas é ordens de magnitude mais barato em latência e dá para rodar em CPU comum. Para o caso de uso do Engrim (algumas centenas de memórias curtas por projeto), é uma escolha coerente.

Como a busca híbrida funciona

Quando o agente pede um engrim_recall, o sistema executa duas buscas em paralelo:

  1. Uma busca BM25 no índice FTS5 (peso lexical, boa para termos exatos e siglas)
  2. Uma busca por similaridade cosseno nos embeddings model2vec (peso semântico, boa para paráfrase)

Os dois rankings são combinados por Reciprocal Rank Fusion (RRF). RRF é uma técnica bem estabelecida: cada documento nas listas ranqueadas recebe um rank numérico (1 para o topo, 2 para o segundo, etc.), seu rank recíproco é computado como 1/(rank+k), onde k é uma constante usada para reduzir a influência de resultados de rank baixo, e para cada documento os ranks recíprocos em todas as listas são somados para gerar um score RRF agregado.

O valor de k costuma ficar em 60 na literatura clássica sobre RRF. O ganho prático da fusão é que busca por palavra-chave (BM25) encontra palavras mas erra o sentido, busca vetorial (embedding) captura o sentido mas erra abreviações, e quando você combina com RRF os resultados que ambos os métodos ranqueiam alto sobem para o topo. É exatamente o tipo de compromisso que faz sentido para memória de projeto, onde você tem tanto “PostgreSQL” (termo exato) quanto “escolha do banco relacional” (paráfrase).

Como cada agente se conecta

O Engrim se pluga em cada CLI de um jeito diferente, usando o mecanismo nativo daquele agente:

  • Claude Code: hooks SessionStart, SessionEnd, Stop e UserPromptSubmit registrados em ~/.claude/settings.json
  • Google Antigravity: hooks PreInvocation e Stop em ~/.gemini/config/hooks.json
  • Cursor / Windsurf: servidor MCP via stdio, adicionado em ~/.cursor/mcp.json
  • Codex CLI: hooks em ~/.codex/hooks.json mais registro MCP em ~/.codex/config.toml

O comando engrim setup sem argumentos detecta o que está instalado e configura tudo. Para os agentes que suportam MCP (Model Context Protocol, o padrão da Anthropic para plugar ferramentas em agentes), o Engrim expõe um servidor JSON-RPC 2.0 em stdio com quatro ferramentas: engrim_recall, engrim_add, engrim_context e engrim_review.

Um detalhe de implementação que vale mencionar: no modo MCP, o stdout é reservado estritamente para mensagens JSON-RPC, e todos os logs de diagnóstico vão para stderr. Isso é o correto para MCP stdio, mas é um bug comum em servidores caseiros e importa saber que o Engrim faz certo.

O que isso significa na prática

O fluxo básico é este. Você trabalha normalmente no Claude Code, toma uma decisão arquitetural, e ou o próprio agente chama engrim_add via MCP, ou você digita:

engrim add -t decision -s "Trocado MongoDB por PostgreSQL para transações ACID" --tags db,schema

No dia seguinte você abre o Cursor no mesmo repositório. O hook de boot roda:

engrim context -b 4000

E injeta no início da sessão um pacote de memória ordenado por prioridade, dentro de um orçamento de 4000 caracteres. O agente do Cursor começa sabendo que o banco é Postgres, sem você ter que dizer.

A alegação de eficiência de contexto no README é agressiva: à medida que janelas de contexto escalam para mais de 1M de tokens, desenvolvedores enfrentam diluição de atenção, o raciocínio degrada, custo multiplica a cada turno conversacional, e limpar o contexto causa amnésia total; o Engrim se propõe a substituir a diluição de atenção por 4000 caracteres de memória episódica de trabalho curada. Vale ler isso com ceticismo: o número 4000 é o orçamento default do engrim_context, não uma prova de suficiência. Se o seu projeto precisa de mais, você aumenta.

Onde o Engrim quebra ou dá problema

Alguns pontos honestos, baseados na leitura do código e da documentação pública:

  • Qualidade semântica limitada. Embeddings estáticos do model2vec são bons, mas não são embeddings contextuais. Se o seu corpus de memória tem muita ambiguidade dependente de contexto (a mesma palavra significando coisas diferentes em áreas diferentes do projeto), a recuperação vetorial vai errar mais. O RRF ajuda porque o BM25 pega termos exatos, mas não resolve tudo.
  • Confiança no agente para popular a memória. Se o agente não chamar engrim_add nos momentos certos, ou se você não adicionar manualmente, o banco fica pobre e o benefício some. O comando engrim review tenta escanear os logs em busca de decisões não capturadas antes de você limpar a sessão, mas isso é um paliativo, não uma solução completa.
  • Nada de compartilhamento entre máquinas por padrão. O SQLite é local. Se você trabalha em dois computadores no mesmo projeto, precisa sincronizar o arquivo por conta própria (ele é gitignored por default, o que é bom para segurança e ruim para colaboração).
  • Escopo de projeto por diretório. A resolução de “qual projeto sou eu” é feita pelo caminho. Isso é simples e funciona, mas se você renomeia ou move o repositório, a memória fica órfã até você reapontar.
  • Categorização manual. Os tipos (decision, fact, feedback, state, user, reference) e tags são responsabilidade sua ou do agente. Sem disciplina, vira lixo pesquisável.

O que não dá para afirmar ainda

O README fala em trocar entre Google Antigravity CLI, Claude Code e Cursor MCP em repositórios idênticos com zero drift de modelo ou regressão arquitetural, apoiado num estudo de caso do próprio autor com 105 sessões sobre um sistema de trading algorítmico de 50 mil linhas. Esse número não vem de benchmark independente, e “zero regressão” depende muito da definição. Trate como evidência anedótica do autor, não como resultado reprodutível.

Também não é claro qual o comportamento em projetos muito grandes (dezenas de milhares de memórias) nem como o tempo de resposta do engrim_context escala. Para as centenas ou baixos milhares de registros que um projeto típico gera, deve ser tranquilo, mas não há número publicado no repositório.

O ecossistema de “memória local em SQLite para agentes” está cheio de projetos parecidos surgindo ao mesmo tempo (existem pelo menos dois outros chamados Engram, um em Go e outro em Rust, com propostas quase idênticas). Ainda é cedo para dizer qual arquitetura vai virar padrão, se é que alguma vai.

Como testar isso você mesmo

O caminho mínimo para experimentar em uns 10 minutos:

pip install engrim
cd seu-projeto
engrim setup --dry-run # ver o que ele quer configurar
engrim setup # configura os agentes detectados

Adicione uma decisão manualmente para testar:

engrim add -t decision -s "Usando Pydantic v2 em toda a API" --tags api,types
engrim add -t fact -s "Rodamos Python 3.11 em produção" --tags runtime
engrim recall -q "validação"
engrim context -b 2000

Abra o agente que você usa (Claude Code, Cursor, etc.) no mesmo diretório e veja se o pacote de contexto aparece no boot da sessão. Depois faça uma pergunta que dependa daquelas decisões e veja se o agente as usa.

Se quiser rodar sem embeddings (só busca lexical BM25), basta setar a variável de ambiente:

ENGRIM_EMBED=off engrim recall -q "pydantic"

Nesse modo você elimina até a dependência do model2vec e roda com zero dependência opcional, útil para entender quanto valor o vetor está agregando no seu caso específico. Se a diferença for pequena, você pode preferir a versão pura lexical pela simplicidade.

O interessante de mexer no Engrim não é decidir se você vai adotar. É que ele expõe de forma bem enxuta um padrão que vai ficar comum: memória externa ao modelo, indexada localmente, injetada por hook ou MCP. Entender esse padrão vale mais do que a ferramenta específica.

Deixe um Comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *