Memória imutável de cinco camadas para um agente de IA: por que um único banco vetorial nunca é suficiente
Tentar armazenar toda a memória de longo prazo de um agente de IA em um único índice vetorial fracassa, porque busca exata por palavras, similaridade semântica, travessia de relações em múltiplos saltos e histórico de mudanças de decisão exigem estruturas de dados fisicamente diferentes. Um índice incremental SQLite FTS5 com ranking BM25 sobre 66.995 sessões e 538.956 mensagens (1,35 GB) eliminou as travadas no ambiente de desenvolvimento e acelerou o início de sessão de 15 segundos para décimos de segundo. Para proteger a memória contra degradação, foram implementadas a sanitização determinística de chaves via UUID5, a escrita bitemporal sem exclusão de fatos antigos e um ciclo noturno de consolidação com decaimento segundo a distribuição de Weibull.

- Volume do índice episódico: 66.995 sessões e 538.956 mensagens em um banco SQLite FTS5 de 1.353 MB
- Banco do segundo cérebro e embeddings: 10.019 cartões de conhecimento estruturados e um `brain.db` de 2,37 GB
- Velocidade de inicialização após abandonar hooks pesados: a latência de inicialização cai de 5–15 s para < 0,2 s
- Limiares da consolidação noturna: fusão de duplicatas com similaridade de cosseno ≥ 0,70 e detector de contradições com limiar de 0,40, sem chamada a LLM
O ponto de partida para minha arquitetura de memória foi uma travada feia no editor de código. Ao tentar abrir o histórico de conversas com o agente de IA, o processo Extension Host ultrapassou o limite de memória e caiu, levando o terminal junto. Em meio ano de trabalho, acumulei 2,45 GB de logs de sessão brutos em JSONL, e a extensão tentava ler todos os cabeçalhos do disco a cada inicialização. Para piorar, a configuração padrão `cleanupPeriodDays=30` apagava silenciosamente sessões com mais de um mês — e quando finalmente troquei para `99999`, parte do meu arquivo de inverno já tinha desaparecido.
Uma auditoria revelou que dois dos meus quatro bancos de memória estavam praticamente mortos, pois exigiam a inicialização manual de daemons em segundo plano. A real descoberta, porém, foi que "memória do agente" na verdade são cinco classes diferentes de perguntas, e cada uma precisa do seu próprio motor.
Por que o SQLite FTS5 venceu o
Qdrant e o grep em meio milhão de mensagens?
Para a memória episódica ("onde discutimos a configuração de webhooks três meses atrás?"), o `grep` em gigabytes de texto era muito lento, e subir uma instância local do `Qdrant` com `fastembed` no início da sessão acrescentava 5 a 15 segundos à inicialização e consumia centenas de megabytes de RAM.
O vencedor foi o motor SQLite FTS5, nativo do Python, com ranking BM25 e modo `WAL`. O indexador `search_chats.py` compara o `file_mtime` com a tabela `sessions`: se a diferença for menor que um segundo, o arquivo é ignorado.
Logs brutos com mais de 14 dias e dumps acima de 20 MB vão para um arquivo frio — o diretório de trabalho encolheu de 2,45 GB para 527 MB, as travadas pararam, e o índice cresceu para 66.995 sessões e 538.956 mensagens (290.
544 do humano e 248.412 do assistente), totalizando 1.353 MB.
Para referências críticas (como um manual de 20 capítulos montado a partir de 2.240 mensagens e 177 notas de voz transcritas via Deepgram), aplica-se um backup em 4 níveis: um resumo na memória de trabalho, um ponteiro no `MEMORY.md` (estritamente limitado a 200 linhas e ~22 KB), uma regra de roteamento e um snapshot vetorial.
Para que serve um UUID5 determinístico em um banco vetorial e como
funciona a bitemporalidade?
Quando a consulta não contém as palavras exatas (alguém pergunta "como lavar frutas sem produtos químicos" enquanto o banco tem uma nota sobre o modo alcalino `pH 11,5` de um ionizador), entra em cena a camada semântica baseada em embeddings (384 dimensões localmente e 1536 para livros). Na prática, os identificadores de documentos aparecem como strings do tipo `knowledge:42` ou `session/2026-04-10/msg-17` com caracteres cirílicos, mas o motor vetorial Qdrant exige que o `point_id` seja um número inteiro ou um UUID estrito. Um `uuid4()` aleatório gera duplicatas na reindexação, então introduzimos um `uuid5` determinístico sobre o namespace DNS:
```python import uuid
def to_deterministic_point_id(raw_doc_id: str) -> str: # Transforma qualquer string com cirílico e caracteres especiais em um UUID5 ASCII estável return str(uuid.uuid5(uuid.NAMESPACE_DNS, raw_doc_id))
point_id = to_deterministic_point_id(doc_id) payload = {**metadata, "doc_id": doc_id, "content": text_chunk} ```
A mesma string sempre gera um UUID idêntico, transformando cada regravação em um `upsert` idempotente, enquanto a chave original é preservada no `payload`.
Para que o agente lembre o histórico de mudanças de decisão ("por que na primavera trocamos de motor?"), tomamos de empréstimo uma ideia do Datomic e proibimos `UPDATE`s que sobrescrevem o passado. Cada relação tem os campos `valid_from`, `valid_to` e `ingested_at`, e o banco do segundo cérebro também mantém contadores de uso `use_count` e `last_used`. Quando uma decisão fica obsoleta, a aresta antiga recebe um `valid_to`, e um novo registro é inserido ao lado com um link `supersedes`.
Como o sono algorítmico com curva de
Weibull limpa contradições da memória?
Para que o banco não se transforme em um depósito de lixo, um pipeline de consolidação em segundo plano (os módulos `dream` e `sleeptime`) executa periodicamente uma limpeza em três etapas:
1. Agrupamento de duplicatas (SHMR): uma matriz de similaridade de cosseno sobre embeddings de 1024 dimensões (`Qwen3-Embedding-0.6B` no LanceDB) funde registros com similaridade `>= 0,70`. 2. Decaimento seletivo via distribuição de Weibull: observações passageiras (`observation`) decaem em 336 horas (~2 semanas), fatos de trabalho decaem em 2.160 horas, enquanto preferências pessoais (`preference`), procedimentos e padrões de erro (`error_pattern`) são protegidos por uma lista `EXEMPT_TYPES`. 3. Busca de contradições sem LLM: um escâner determinístico procura conflitos em uma janela de 7 dias usando sinais ponderados (`negation: 0,35`, `antonym: 0,25`, `numeric: 0,25`, `temporal: 0,15`) com limiar de `0,40`.
Esse esquema de memória em cinco camadas (SQLite FTS5, LanceDB e um grafo temporal) está integrado ao núcleo do [Nomad AI](https://nomadnet.ai) e da plataforma em nuvem [agent.nomadnet.ai](https://agent.nomadnet.ai). Um curso sobre engenharia de memória está disponível na [Hamidun Academy](https://academy.hamidun.com) (teste de 72 horas), e os scripts de indexação ficam na biblioteca [Vibe Club](https://hamidun.com/club).
Perguntas frequentes sobre a notícia
Por que o SQLite FTS5 venceu o Qdrant e o grep em meio milhão de mensagens?
Para a memória episódica ("onde discutimos a configuração de webhooks três meses atrás?"), o `grep` em gigabytes de texto era muito lento, e subir uma instância local do `Qdrant` com `fastembed` no início da sessão acrescentava 5 a 15 segundos à inicialização e consumia centenas de megabytes de RAM. O vencedor foi o motor SQLite FTS5, nativo do Python, com ranking BM25 e modo `WAL`. O indexador `search_chats.py` compara o `file_mtime` com a tabela `sessions`: se a diferença for menor que um segundo, o arquivo é ignorado. Logs brutos com mais de 14 dias e dumps acima de 20 M
Para que serve um UUID5 determinístico em um banco vetorial e como funciona a bitemporalidade?
Quando a consulta não contém as palavras exatas (alguém pergunta "como lavar frutas sem produtos químicos" enquanto o banco tem uma nota sobre o modo alcalino `pH 11,5` de um ionizador), entra em cena a camada semântica baseada em embeddings (384 dimensões localmente e 1536 para livros). Na prática, os identificadores de documentos aparecem como strings do tipo `knowledge:42` ou `session/2026-04-10/msg-17` com caracteres cirílicos, mas o motor vetorial Qdrant exige que o `point_id` seja um número inteiro ou um UUID estrito. Um `uuid4()` aleatório gera duplicatas na reindexa
Como o sono algorítmico com curva de Weibull limpa contradições da memória?
Para que o banco não se transforme em um depósito de lixo, um pipeline de consolidação em segundo plano (os módulos `dream` e `sleeptime`) executa periodicamente uma limpeza em três etapas: 1. Agrupamento de duplicatas (SHMR): uma matriz de similaridade de cosseno sobre embeddings de 1024 dimensões (`Qwen3-Embedding-0.6B` no LanceDB) funde registros com similaridade `>= 0,70`. 2. Decaimento seletivo via distribuição de Weibull: observações passageiras (`observation`) decaem em 336 horas (~2 semanas), fatos de trabalho decaem em 2.160 horas, enquanto preferências pessoais (`
Delegate Deep Research, Coding & Multi-Step Workflows to Nomad Agent
Deploy autonomous task execution with Nomad Agent (agent.nomadnet.ai) — automated web research, data synthesis, code generation, and MCP tool orchestration.
O essencial da IA — uma vez por semana
Sete histórias que realmente importaram, escolhidas a dedo. Sem ruído nem releases.
Pronto! Verifique seu e-mail para a confirmação.