{"id":700,"date":"2026-09-07T18:34:33","date_gmt":"2026-09-07T21:34:33","guid":{"rendered":"https:\/\/yellowkode.com\/blog\/engrim-memoria-episodica-local-sqlite-agentes-codigo\/"},"modified":"2026-09-07T18:34:33","modified_gmt":"2026-09-07T21:34:33","slug":"engrim-memoria-episodica-local-sqlite-agentes-codigo","status":"publish","type":"post","link":"https:\/\/yellowkode.com\/blog\/engrim-memoria-episodica-local-sqlite-agentes-codigo\/","title":{"rendered":"Engrim: mem\u00f3ria epis\u00f3dica local em SQLite para agentes de c\u00f3digo como Claude Code, Cursor e Antigravity"},"content":{"rendered":"<p>Se voc\u00ea usa mais de um agente de c\u00f3digo (Claude Code num dia, Cursor no outro, testa o Antigravity de vez em quando), j\u00e1 bateu no mesmo problema: cada sess\u00e3o nova come\u00e7a do zero. O agente n\u00e3o sabe que voc\u00ea decidiu trocar de MongoDB para Postgres semana passada, n\u00e3o lembra por que rejeitou aquela abordagem de cache, e voc\u00ea acaba re-explicando a mesma arquitetura toda vez que abre o terminal.<\/p>\n<p>O Engrim \u00e9 uma tentativa concreta de resolver isso por fora do modelo, guardando decis\u00f5es num arquivo SQLite local e injetando esse contexto no in\u00edcio de cada sess\u00e3o, independente de qual agente voc\u00ea est\u00e1 usando.<\/p>\n<h2>O que \u00e9 o Engrim e quem publicou<\/h2>\n<p>O Engrim foi publicado por Tim Gordon como projeto open source no GitHub, sob licen\u00e7a MIT, e distribu\u00eddo no PyPI como pacote Python (<code>pip install engrim<\/code>). A descri\u00e7\u00e3o oficial no reposit\u00f3rio o define como um motor de mem\u00f3ria 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\u00f5es arquiteturais, restri\u00e7\u00f5es de usu\u00e1rio ou estado do projeto.<\/p>\n<p>A ideia central, nas palavras do autor no README: &#8220;Por que pagar por 200.000 tokens de ru\u00eddo esquecido a cada turno? Os modelos s\u00e3o utilidades descart\u00e1veis; as decis\u00f5es do seu projeto n\u00e3o s\u00e3o.&#8221; \u00c9 um argumento ret\u00f3rico forte, mas o que interessa aqui \u00e9 o mecanismo por baixo.<\/p>\n<h2>Como o Engrim funciona por dentro<\/h2>\n<h3>A camada de armazenamento<\/h3>\n<p>Tudo vive em um \u00fanico arquivo SQLite em <code>~\/.engrim\/memory.db<\/code>. N\u00e3o h\u00e1 servidor, n\u00e3o h\u00e1 sync com nuvem, n\u00e3o h\u00e1 telemetria. O banco tem tr\u00eas estruturas relevantes:<\/p>\n<ul>\n<li>Uma tabela de mem\u00f3rias curadas (decis\u00f5es, fatos, feedback, estado, refer\u00eancias)<\/li>\n<li>Um \u00edndice FTS5 com stemmer porter, mantido em sincronia por triggers<\/li>\n<li>Uma tabela de embeddings vetoriais gerada com <code>model2vec<\/code><\/li>\n<\/ul>\n<p>O uso de FTS5 aqui n\u00e3o \u00e9 decorativo. \u00c9 a extens\u00e3o nativa do SQLite para busca textual com ranking BM25, e \u00e9 ela que faz a parte lexical do sistema funcionar sem depend\u00eancia externa.<\/p>\n<h3>A camada de embeddings: por que model2vec e n\u00e3o OpenAI embeddings<\/h3>\n<p>Essa \u00e9 a escolha t\u00e9cnica mais interessante do projeto. O Engrim n\u00e3o chama nenhuma API de embeddings. Ele usa o <code>model2vec<\/code>, uma biblioteca que produz embeddings est\u00e1ticos.<\/p>\n<p>Model2Vec \u00e9 uma t\u00e9cnica para transformar qualquer sentence transformer em um modelo est\u00e1tico pequeno, reduzindo o tamanho do modelo por um fator de at\u00e9 50 e deixando os modelos at\u00e9 500 vezes mais r\u00e1pidos, com uma pequena queda de performance, segundo a documenta\u00e7\u00e3o da biblioteca. O funcionamento, resumidamente: passa um vocabul\u00e1rio pelo transformer para criar uma biblioteca de embeddings por token, reduz as dimens\u00f5es (por exemplo, com PCA), aplica uma pondera\u00e7\u00e3o (tipo Zipf) para enfatizar tokens frequentes, e na infer\u00eancia voc\u00ea essencialmente calcula a m\u00e9dia dos embeddings dos tokens.<\/p>\n<p>Na pr\u00e1tica isso significa: nada de GPU, nada de chamada de rede, nada de conta em provedor. O embedding de uma frase \u00e9 uma lookup de vetores e uma m\u00e9dia. \u00c9 pior que um embedding contextual de \u00faltima gera\u00e7\u00e3o em qualidade, mas \u00e9 ordens de magnitude mais barato em lat\u00eancia e d\u00e1 para rodar em CPU comum. Para o caso de uso do Engrim (algumas centenas de mem\u00f3rias curtas por projeto), \u00e9 uma escolha coerente.<\/p>\n<h3>Como a busca h\u00edbrida funciona<\/h3>\n<p>Quando o agente pede um <code>engrim_recall<\/code>, o sistema executa duas buscas em paralelo:<\/p>\n<ol>\n<li>Uma busca BM25 no \u00edndice FTS5 (peso lexical, boa para termos exatos e siglas)<\/li>\n<li>Uma busca por similaridade cosseno nos embeddings model2vec (peso sem\u00e2ntico, boa para par\u00e1frase)<\/li>\n<\/ol>\n<p>Os dois rankings s\u00e3o combinados por Reciprocal Rank Fusion (RRF). RRF \u00e9 uma t\u00e9cnica bem estabelecida: cada documento nas listas ranqueadas recebe um rank num\u00e9rico (1 para o topo, 2 para o segundo, etc.), seu rank rec\u00edproco \u00e9 computado como 1\/(rank+k), onde k \u00e9 uma constante usada para reduzir a influ\u00eancia de resultados de rank baixo, e para cada documento os ranks rec\u00edprocos em todas as listas s\u00e3o somados para gerar um score RRF agregado.<\/p>\n<p>O valor de <code>k<\/code> costuma ficar em 60 na literatura cl\u00e1ssica sobre RRF. O ganho pr\u00e1tico da fus\u00e3o \u00e9 que busca por palavra-chave (BM25) encontra palavras mas erra o sentido, busca vetorial (embedding) captura o sentido mas erra abrevia\u00e7\u00f5es, e quando voc\u00ea combina com RRF os resultados que ambos os m\u00e9todos ranqueiam alto sobem para o topo. \u00c9 exatamente o tipo de compromisso que faz sentido para mem\u00f3ria de projeto, onde voc\u00ea tem tanto &#8220;PostgreSQL&#8221; (termo exato) quanto &#8220;escolha do banco relacional&#8221; (par\u00e1frase).<\/p>\n<h3>Como cada agente se conecta<\/h3>\n<p>O Engrim se pluga em cada CLI de um jeito diferente, usando o mecanismo nativo daquele agente:<\/p>\n<ul>\n<li><strong>Claude Code:<\/strong> hooks <code>SessionStart<\/code>, <code>SessionEnd<\/code>, <code>Stop<\/code> e <code>UserPromptSubmit<\/code> registrados em <code>~\/.claude\/settings.json<\/code><\/li>\n<li><strong>Google Antigravity:<\/strong> hooks <code>PreInvocation<\/code> e <code>Stop<\/code> em <code>~\/.gemini\/config\/hooks.json<\/code><\/li>\n<li><strong>Cursor \/ Windsurf:<\/strong> servidor MCP via stdio, adicionado em <code>~\/.cursor\/mcp.json<\/code><\/li>\n<li><strong>Codex CLI:<\/strong> hooks em <code>~\/.codex\/hooks.json<\/code> mais registro MCP em <code>~\/.codex\/config.toml<\/code><\/li>\n<\/ul>\n<p>O comando <code>engrim setup<\/code> sem argumentos detecta o que est\u00e1 instalado e configura tudo. Para os agentes que suportam MCP (Model Context Protocol, o padr\u00e3o da Anthropic para plugar ferramentas em agentes), o Engrim exp\u00f5e um servidor JSON-RPC 2.0 em stdio com quatro ferramentas: <code>engrim_recall<\/code>, <code>engrim_add<\/code>, <code>engrim_context<\/code> e <code>engrim_review<\/code>.<\/p>\n<p>Um detalhe de implementa\u00e7\u00e3o que vale mencionar: no modo MCP, o stdout \u00e9 reservado estritamente para mensagens JSON-RPC, e todos os logs de diagn\u00f3stico v\u00e3o para stderr. Isso \u00e9 o correto para MCP stdio, mas \u00e9 um bug comum em servidores caseiros e importa saber que o Engrim faz certo.<\/p>\n<h2>O que isso significa na pr\u00e1tica<\/h2>\n<p>O fluxo b\u00e1sico \u00e9 este. Voc\u00ea trabalha normalmente no Claude Code, toma uma decis\u00e3o arquitetural, e ou o pr\u00f3prio agente chama <code>engrim_add<\/code> via MCP, ou voc\u00ea digita:<\/p>\n<pre><code>engrim add -t decision -s \"Trocado MongoDB por PostgreSQL para transa\u00e7\u00f5es ACID\" --tags db,schema<\/code><\/pre>\n<p>No dia seguinte voc\u00ea abre o Cursor no mesmo reposit\u00f3rio. O hook de boot roda:<\/p>\n<pre><code>engrim context -b 4000<\/code><\/pre>\n<p>E injeta no in\u00edcio da sess\u00e3o um pacote de mem\u00f3ria ordenado por prioridade, dentro de um or\u00e7amento de 4000 caracteres. O agente do Cursor come\u00e7a sabendo que o banco \u00e9 Postgres, sem voc\u00ea ter que dizer.<\/p>\n<p>A alega\u00e7\u00e3o de efici\u00eancia de contexto no README \u00e9 agressiva: \u00e0 medida que janelas de contexto escalam para mais de 1M de tokens, desenvolvedores enfrentam dilui\u00e7\u00e3o de aten\u00e7\u00e3o, o racioc\u00ednio degrada, custo multiplica a cada turno conversacional, e limpar o contexto causa amn\u00e9sia total; o Engrim se prop\u00f5e a substituir a dilui\u00e7\u00e3o de aten\u00e7\u00e3o por 4000 caracteres de mem\u00f3ria epis\u00f3dica de trabalho curada. Vale ler isso com ceticismo: o n\u00famero 4000 \u00e9 o or\u00e7amento default do <code>engrim_context<\/code>, n\u00e3o uma prova de sufici\u00eancia. Se o seu projeto precisa de mais, voc\u00ea aumenta.<\/p>\n<h2>Onde o Engrim quebra ou d\u00e1 problema<\/h2>\n<p>Alguns pontos honestos, baseados na leitura do c\u00f3digo e da documenta\u00e7\u00e3o p\u00fablica:<\/p>\n<ul>\n<li><strong>Qualidade sem\u00e2ntica limitada.<\/strong> Embeddings est\u00e1ticos do model2vec s\u00e3o bons, mas n\u00e3o s\u00e3o embeddings contextuais. Se o seu corpus de mem\u00f3ria tem muita ambiguidade dependente de contexto (a mesma palavra significando coisas diferentes em \u00e1reas diferentes do projeto), a recupera\u00e7\u00e3o vetorial vai errar mais. O RRF ajuda porque o BM25 pega termos exatos, mas n\u00e3o resolve tudo.<\/li>\n<li><strong>Confian\u00e7a no agente para popular a mem\u00f3ria.<\/strong> Se o agente n\u00e3o chamar <code>engrim_add<\/code> nos momentos certos, ou se voc\u00ea n\u00e3o adicionar manualmente, o banco fica pobre e o benef\u00edcio some. O comando <code>engrim review<\/code> tenta escanear os logs em busca de decis\u00f5es n\u00e3o capturadas antes de voc\u00ea limpar a sess\u00e3o, mas isso \u00e9 um paliativo, n\u00e3o uma solu\u00e7\u00e3o completa.<\/li>\n<li><strong>Nada de compartilhamento entre m\u00e1quinas por padr\u00e3o.<\/strong> O SQLite \u00e9 local. Se voc\u00ea trabalha em dois computadores no mesmo projeto, precisa sincronizar o arquivo por conta pr\u00f3pria (ele \u00e9 gitignored por default, o que \u00e9 bom para seguran\u00e7a e ruim para colabora\u00e7\u00e3o).<\/li>\n<li><strong>Escopo de projeto por diret\u00f3rio.<\/strong> A resolu\u00e7\u00e3o de &#8220;qual projeto sou eu&#8221; \u00e9 feita pelo caminho. Isso \u00e9 simples e funciona, mas se voc\u00ea renomeia ou move o reposit\u00f3rio, a mem\u00f3ria fica \u00f3rf\u00e3 at\u00e9 voc\u00ea reapontar.<\/li>\n<li><strong>Categoriza\u00e7\u00e3o manual.<\/strong> Os tipos (decision, fact, feedback, state, user, reference) e tags s\u00e3o responsabilidade sua ou do agente. Sem disciplina, vira lixo pesquis\u00e1vel.<\/li>\n<\/ul>\n<h2>O que n\u00e3o d\u00e1 para afirmar ainda<\/h2>\n<p>O README fala em trocar entre Google Antigravity CLI, Claude Code e Cursor MCP em reposit\u00f3rios id\u00eanticos com zero drift de modelo ou regress\u00e3o arquitetural, apoiado num estudo de caso do pr\u00f3prio autor com 105 sess\u00f5es sobre um sistema de trading algor\u00edtmico de 50 mil linhas. Esse n\u00famero n\u00e3o vem de benchmark independente, e &#8220;zero regress\u00e3o&#8221; depende muito da defini\u00e7\u00e3o. Trate como evid\u00eancia aned\u00f3tica do autor, n\u00e3o como resultado reprodut\u00edvel.<\/p>\n<p>Tamb\u00e9m n\u00e3o \u00e9 claro qual o comportamento em projetos muito grandes (dezenas de milhares de mem\u00f3rias) nem como o tempo de resposta do <code>engrim_context<\/code> escala. Para as centenas ou baixos milhares de registros que um projeto t\u00edpico gera, deve ser tranquilo, mas n\u00e3o h\u00e1 n\u00famero publicado no reposit\u00f3rio.<\/p>\n<p>O ecossistema de &#8220;mem\u00f3ria local em SQLite para agentes&#8221; est\u00e1 cheio de projetos parecidos surgindo ao mesmo tempo (existem pelo menos dois outros chamados <em>Engram<\/em>, um em Go e outro em Rust, com propostas quase id\u00eanticas). Ainda \u00e9 cedo para dizer qual arquitetura vai virar padr\u00e3o, se \u00e9 que alguma vai.<\/p>\n<h2>Como testar isso voc\u00ea mesmo<\/h2>\n<p>O caminho m\u00ednimo para experimentar em uns 10 minutos:<\/p>\n<pre><code>pip install engrim\ncd seu-projeto\nengrim setup --dry-run # ver o que ele quer configurar\nengrim setup # configura os agentes detectados<\/code><\/pre>\n<p>Adicione uma decis\u00e3o manualmente para testar:<\/p>\n<pre><code>engrim add -t decision -s \"Usando Pydantic v2 em toda a API\" --tags api,types\nengrim add -t fact -s \"Rodamos Python 3.11 em produ\u00e7\u00e3o\" --tags runtime\nengrim recall -q \"valida\u00e7\u00e3o\"\nengrim context -b 2000<\/code><\/pre>\n<p>Abra o agente que voc\u00ea usa (Claude Code, Cursor, etc.) no mesmo diret\u00f3rio e veja se o pacote de contexto aparece no boot da sess\u00e3o. Depois fa\u00e7a uma pergunta que dependa daquelas decis\u00f5es e veja se o agente as usa.<\/p>\n<p>Se quiser rodar sem embeddings (s\u00f3 busca lexical BM25), basta setar a vari\u00e1vel de ambiente:<\/p>\n<pre><code>ENGRIM_EMBED=off engrim recall -q \"pydantic\"<\/code><\/pre>\n<p>Nesse modo voc\u00ea elimina at\u00e9 a depend\u00eancia do model2vec e roda com zero depend\u00eancia opcional, \u00fatil para entender quanto valor o vetor est\u00e1 agregando no seu caso espec\u00edfico. Se a diferen\u00e7a for pequena, voc\u00ea pode preferir a vers\u00e3o pura lexical pela simplicidade.<\/p>\n<p>O interessante de mexer no Engrim n\u00e3o \u00e9 decidir se voc\u00ea vai adotar. \u00c9 que ele exp\u00f5e de forma bem enxuta um padr\u00e3o que vai ficar comum: mem\u00f3ria externa ao modelo, indexada localmente, injetada por hook ou MCP. Entender esse padr\u00e3o vale mais do que a ferramenta espec\u00edfica.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Como funciona o Engrim, motor local de mem\u00f3ria em SQLite com FTS5 e model2vec que persiste decis\u00f5es entre Claude Code, Cursor, Antigravity e Codex via MCP.<\/p>\n","protected":false},"author":2,"featured_media":699,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-700","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/yellowkode.com\/blog\/wp-json\/wp\/v2\/posts\/700","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/yellowkode.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/yellowkode.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/yellowkode.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/yellowkode.com\/blog\/wp-json\/wp\/v2\/comments?post=700"}],"version-history":[{"count":0,"href":"https:\/\/yellowkode.com\/blog\/wp-json\/wp\/v2\/posts\/700\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/yellowkode.com\/blog\/wp-json\/wp\/v2\/media\/699"}],"wp:attachment":[{"href":"https:\/\/yellowkode.com\/blog\/wp-json\/wp\/v2\/media?parent=700"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/yellowkode.com\/blog\/wp-json\/wp\/v2\/categories?post=700"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/yellowkode.com\/blog\/wp-json\/wp\/v2\/tags?post=700"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}