obsidian:~/vault$ search --hybrid obsidian

Example vault location

#

words: 12202 read_time: 47m updated: 2026-03-05 06:39
$ retriever search --hybrid obsidian

Principais Conclusões

Engenharia de contexto, não tomada de notas. O valor de um vault do Obsidian para IA não está nas notas em si, mas na camada de recuperação que as torna consultáveis. Um vault com 16.000 arquivos sem recuperação é um banco de dados somente escrita. Um vault com 200 arquivos com busca híbrida e integração MCP é uma base de conhecimento de IA. A infraestrutura de recuperação é o produto. As notas são a matéria-prima.

A recuperação híbrida supera a busca puramente por palavras-chave ou puramente semântica. BM25 captura identificadores exatos e nomes de funções. A busca vetorial captura sinônimos e correspondências conceituais entre terminologias diferentes. Reciprocal Rank Fusion (RRF) combina ambos sem exigir calibração de pontuação. Nenhum método isolado cobre ambos os modos de falha. Pesquisas sobre classificação de passagens no MS MARCO confirmam o padrão: a recuperação híbrida supera consistentemente qualquer método isolado.1 O aprofundamento sobre o recuperador híbrido cobre a matemática do RRF, exemplos práticos com números reais, análise de modos de falha e uma calculadora interativa de fusão.

MCP dá às ferramentas de IA acesso direto ao vault. Servidores Model Context Protocol (MCP) expõem o recuperador como uma ferramenta que Claude Code, Codex CLI, Cursor e outras ferramentas de IA podem chamar diretamente. O agente consulta o vault, recebe resultados ranqueados com atribuição de fonte e utiliza o contexto sem carregar arquivos inteiros. O servidor MCP é um wrapper fino em torno do motor de recuperação.

Local-first significa zero custos de API e privacidade total. Toda a stack roda em uma única máquina: SQLite para armazenamento, Model2Vec para embeddings, FTS5 para busca por palavras-chave, sqlite-vec para KNN vetorial. Sem serviços em nuvem, sem chamadas de API, sem dependência de rede. Notas pessoais nunca saem da máquina. O re-embedding completo de 49.746 chunks custaria aproximadamente US$ 0,30 nos preços de API da OpenAI, mas os custos reais são latência, exposição de privacidade e a dependência de rede para um sistema que deveria funcionar offline.2

A indexação incremental mantém o sistema atualizado em menos de 10 segundos. A comparação do tempo de modificação do arquivo detecta alterações. Apenas arquivos modificados são re-divididos em chunks e re-embeddados. Uma reindexação completa leva cerca de quatro minutos em hardware Apple M-series. Atualizações incrementais nas edições de um dia típico rodam em menos de dez segundos. O sistema se mantém atualizado sem intervenção manual.

A arquitetura escala de 200 a mais de 20.000 notas. O mesmo design de três camadas (ingestão, recuperação, integração) funciona em qualquer tamanho de vault. Comece com busca somente BM25 em um vault pequeno. Adicione busca vetorial quando colisões de palavras-chave se tornarem um problema. Adicione fusão RRF quando precisar de correspondências exatas e semânticas. Cada camada é independentemente útil e independentemente removível.


Como usar este guia

Este guia cobre o sistema completo. Seu ponto de partida depende de onde você está:

Você é… Comece aqui Depois explore
Novo em Obsidian + IA Por que Obsidian para infraestrutura de IA, Início rápido Arquitetura do vault, Arquitetura do servidor MCP
Vault existente, quer acesso por IA Arquitetura do servidor MCP, Integração com Claude Code Modelos de embedding, Busca full-text com FTS5
Construindo um sistema de recuperação O pipeline completo de recuperação, Reciprocal Rank Fusion Ajuste de desempenho, Solução de problemas
Contexto de equipe ou empresarial Framework de decisão, Padrões de grafo de conhecimento Receitas de workflow para desenvolvedores, Guia de migração

Seções marcadas como Contract incluem detalhes de implementação, blocos de configuração e modos de falha. Seções marcadas como Narrative focam em conceitos, decisões de arquitetura e o raciocínio por trás das escolhas de design. Seções marcadas como Recipe fornecem workflows passo a passo.


Por que Obsidian para infraestrutura de IA

A tese deste guia: Vaults do Obsidian são o melhor substrato para bases de conhecimento pessoais de IA porque são local-first, plaintext, estruturados em grafo, e o usuário controla cada camada da stack.

O que o Obsidian oferece à IA que as alternativas não oferecem

Arquivos markdown em texto puro. Cada nota é um arquivo .md no seu sistema de arquivos. Sem formato proprietário, sem exportação de banco de dados, sem API necessária para ler o conteúdo. Qualquer ferramenta que lê arquivos pode ler seu vault. grep, ripgrep, pathlib do Python, SQLite FTS5 — todos funcionam diretamente nos arquivos-fonte. Quando você constrói um sistema de recuperação, está indexando arquivos, não respostas de API. O índice é sempre consistente com a fonte porque a fonte é o sistema de arquivos.

Arquitetura local-first. O vault vive na sua máquina. Sem servidor, sem dependência de sincronização em nuvem, sem limites de taxa de API, sem termos de serviço governando como você processa seu próprio conteúdo. Você pode gerar embeddings, indexar, dividir em chunks e pesquisar suas notas sem nenhum serviço externo. Isso importa para infraestrutura de IA porque o pipeline de recuperação roda tão rápido quanto seu disco permite, não tão rápido quanto um endpoint de API responde. Também importa para privacidade: notas pessoais contendo credenciais, dados de saúde, informações financeiras e reflexões privadas nunca saem da sua máquina.

Estrutura de grafo através de wiki-links. A sintaxe [[wiki-link]] do Obsidian cria um grafo direcionado entre notas. Uma nota sobre implementação de OAuth faz link para notas sobre rotação de tokens, gerenciamento de sessões e segurança de API. A estrutura de grafo codifica relacionamentos curados por humanos entre conceitos. Embeddings vetoriais capturam similaridade semântica, mas wiki-links capturam conexões intencionais que o autor fez enquanto pensava sobre o tópico. O grafo é um sinal que embeddings não conseguem replicar.

Ecossistema de plugins. O Obsidian possui mais de 1.800 plugins da comunidade. Dataview consulta seu vault como um banco de dados. Templater gera notas a partir de templates com lógica JavaScript. Integração com Git sincroniza seu vault com um repositório. Linter garante consistência de formatação. Esses plugins adicionam estrutura ao vault sem alterar o formato plaintext subjacente. O sistema de recuperação indexa a saída desses plugins, não os plugins em si.

Mais de 5 milhões de usuários. O Obsidian tem uma grande comunidade ativa produzindo templates, workflows, plugins e documentação. Quando você encontra um problema com organização de vault ou configuração de plugin, alguém provavelmente já documentou uma solução. A comunidade também produz ferramentas adjacentes ao Obsidian: servidores MCP, scripts de indexação, pipelines de publicação e wrappers de API.

O que um sistema de arquivos sozinho não oferece

Um diretório de arquivos markdown tem a vantagem do texto puro, mas carece de três coisas que o Obsidian adiciona:

  1. Links bidirecionais. O Obsidian rastreia backlinks automaticamente. Quando você cria um link da Nota A para a Nota B, a Nota B mostra que a Nota A a referencia. O painel de grafo visualiza clusters de conexões. Essa consciência bidirecional é metadado que um sistema de arquivos puro não fornece.

  2. Pré-visualização ao vivo com renderização de plugins. Consultas Dataview, diagramas Mermaid e blocos de callout são renderizados em tempo real. A experiência de escrita é mais rica que um editor de texto, enquanto o formato de armazenamento permanece plaintext. Você escreve e organiza em um ambiente rico; o sistema de recuperação indexa o markdown bruto.

  3. Infraestrutura da comunidade. Descoberta de plugins, marketplace de temas, serviço de sincronização (opcional), serviço de publicação (opcional) e um ecossistema de documentação. Você pode replicar qualquer recurso individual com ferramentas independentes, mas o Obsidian os empacota em um workflow coerente.

O que o Obsidian NÃO faz (e o que você constrói)

O Obsidian não inclui infraestrutura de recuperação. Ele possui busca básica (full-text, nome de arquivo, tag), mas nenhum pipeline de embedding, nenhuma busca vetorial, nenhum ranking de fusão, nenhum servidor MCP, nenhuma filtragem de credenciais, nenhuma estratégia de chunking e nenhum hook de integração para ferramentas de IA externas. Este guia cobre a infraestrutura que você constrói sobre o Obsidian. O vault é o substrato. O pipeline de recuperação, o servidor MCP e os hooks de integração são a infraestrutura.

A arquitetura descrita aqui é markdown-first, não exclusiva do Obsidian. Se você usa Logseq, Foam, Dendron ou um diretório simples de arquivos markdown, o pipeline de recuperação funciona de forma idêntica. O chunker lê arquivos .md. O embedder processa strings de texto. O indexador escreve no SQLite. Nenhum desses componentes depende de recursos específicos do Obsidian. A contribuição do Obsidian é o ambiente de escrita e organização que produz os arquivos markdown que o recuperador indexa.


Início Rápido: Primeiro Vault Conectado a IA

Esta seção conecta um vault a uma ferramenta de IA em cinco minutos. Você vai instalar o Obsidian, criar um vault, instalar um servidor MCP e executar sua primeira consulta. O início rápido utiliza um servidor MCP da comunidade para resultados imediatos. Seções posteriores cobrem a construção de um pipeline de recuperação personalizado para uso em produção.

Pré-requisitos

  • macOS, Linux ou Windows
  • Node.js 18+ (para o servidor MCP)
  • Claude Code, Codex CLI ou Cursor instalado

Passo 1: Criar um vault

Baixe o Obsidian em obsidian.md e crie um novo vault. Escolha um local que você vai lembrar — o servidor MCP precisa do caminho absoluto.

# Example vault location
~/Documents/knowledge-base/

Adicione algumas notas para dar ao sistema de recuperação algo com que trabalhar. Mesmo 10-20 notas são suficientes para ver resultados. Cada nota deve ser um arquivo .md com um título significativo e pelo menos um parágrafo de conteúdo.

Passo 2: Instalar um servidor MCP

Vários servidores MCP da comunidade oferecem acesso imediato ao vault. O ecossistema cresceu significativamente entre 2025-2026:

Servidor Autor Transporte Requer Plugin Recurso Principal
obsidian-mcp-server StevenStavrakis STDIO Não Leve, baseado em arquivos
mcp-obsidian MarkusPfundstein STDIO REST API local CRUD completo do vault via REST
obsidian-mcp-tools jacksteamdev STDIO Sim (plugin) Busca semântica + Templater
obsidian-claude-code-mcp iansinnott WebSocket Sim (plugin) Descoberta automática para Claude Code
obsidian-mcp-server cyanheads STDIO REST API local Tags, gerenciamento de frontmatter

Para o início rápido, a opção mais simples é um servidor baseado em arquivos que lê diretamente os arquivos .md:

npm install -g obsidian-mcp-server

Passo 3: Configurar sua ferramenta de IA

Claude Code — adicione a ~/.claude/settings.json:

{
  "mcpServers": {
    "obsidian": {
      "command": "obsidian-mcp-server",
      "args": ["--vault", "/absolute/path/to/your/vault"]
    }
  }
}

Codex CLI — adicione a .codex/config.toml:

[mcp_servers.obsidian]
command = "obsidian-mcp-server"
args = ["--vault", "/absolute/path/to/your/vault"]

Cursor — adicione a .cursor/mcp.json:

{
  "mcpServers": {
    "obsidian": {
      "command": "obsidian-mcp-server",
      "args": ["--vault", "/absolute/path/to/your/vault"]
    }
  }
}

Passo 4: Executar sua primeira consulta

Abra sua ferramenta de IA e faça uma pergunta que suas notas do vault possam responder:

Search my Obsidian vault for notes about [topic you wrote about]

A ferramenta de IA chama o servidor MCP, que busca no seu vault e retorna o conteúdo correspondente. Você deve ver resultados com caminhos de arquivo e trechos relevantes.

O que você acabou de construir

Você conectou uma base de conhecimento local a uma ferramenta de IA através de um protocolo padrão. O servidor MCP lê os arquivos do seu vault, realiza uma busca básica e retorna os resultados. Esta é a versão mínima viável.

O que este início rápido NÃO oferece: - Recuperação híbrida (BM25 + busca vetorial + fusão RRF) - Busca semântica baseada em embeddings - Filtragem de credenciais - Indexação incremental - Injeção automática de contexto baseada em hooks

O restante deste guia cobre a construção de cada uma dessas capacidades. O início rápido comprova o conceito. O pipeline completo entrega recuperação com qualidade de produção.


Framework de Decisão: Obsidian vs Alternativas

Nem todo caso de uso precisa do Obsidian. Esta seção mapeia quando o Obsidian é o substrato certo, quando é excessivo e quando outra coisa se encaixa melhor.

Árvore de Decisão

START: What is your primary content type?

├─ Structured data (tables, records, schemas)
   Use a database. SQLite, PostgreSQL, or a spreadsheet.
   Obsidian is for prose, not tabular data.

├─ Ephemeral context (current project, temporary notes)
   Use CLAUDE.md / AGENTS.md in the project repo.
   These travel with the code and reset per project.

├─ Team wiki (shared documentation, onboarding)
   Evaluate Notion, Confluence, or a shared git repo.
   Obsidian vaults are personal-first. Team sync is possible
    but not native.

└─ Growing personal knowledge corpus
   
   ├─ < 50 notes
      A folder of markdown files + grep is sufficient.
      Obsidian adds value mainly through the link graph,
       which needs density to be useful.
   
   ├─ 50 - 500 notes
      Obsidian adds value. Wiki-links create a navigable graph.
      BM25-only search (FTS5) is sufficient at this scale.
      Skip vector search and RRF until keyword collisions appear.
   
   ├─ 500 - 5,000 notes
      Full hybrid retrieval becomes valuable. Keyword collisions
       increase. Semantic search catches queries that BM25 misses.
      Add vector search + RRF fusion at this scale.
   
   └─ 5,000+ notes
       Full pipeline is essential. BM25-only returns too much noise.
       Credential filtering becomes critical (more notes = more
        accidentally pasted secrets).
       Incremental indexing matters (full reindex takes minutes).
       MCP integration pays dividends on every AI interaction.

Matriz de Comparação

Critério Obsidian Notion Apple Notes Sistema de Arquivos CLAUDE.md
Local-first Sim Não (nuvem) Parcial (iCloud) Sim Sim
Texto puro Sim (markdown) Não (blocos) Não (proprietário) Sim Sim
Estrutura em grafo Sim (wiki-links) Parcial (menções) Não Não Não
Indexável por IA Acesso direto aos arquivos API necessária Exportação necessária Acesso direto aos arquivos Já no contexto
Ecossistema de plugins 1.800+ plugins Integrações Nenhum N/A N/A
Funciona offline Completo Somente leitura em cache Parcial Completo Completo
Escala para 10K+ notas Sim Sim (com API) Degrada Sim Não (arquivo único)
Custo Gratuito (core) $10/mês+ Gratuito Gratuito Gratuito

Quando o Obsidian é excessivo

  • Contexto de projeto único. Se a IA precisa apenas de contexto sobre o codebase atual, coloque em CLAUDE.md, AGENTS.md ou na documentação no nível do projeto. Esses arquivos acompanham o repositório e são carregados automaticamente.
  • Dados estruturados. Se o conteúdo consiste em tabelas, registros ou esquemas, use um banco de dados. Notas do Obsidian são voltadas para prosa. O Dataview pode consultar campos de frontmatter, mas um banco de dados real lida melhor com consultas estruturadas.
  • Pesquisa temporária. Se as notas serão descartadas após o término do projeto, um diretório temporário com arquivos markdown é mais simples. Não construa infraestrutura de recuperação para conteúdo efêmero.

Quando o Obsidian é a escolha certa

  • Conhecimento acumulado ao longo de meses ou anos. O valor se acumula conforme o corpus cresce. Um vault de 200 notas consultado diariamente por seis meses oferece mais valor do que um vault de 5.000 notas consultado uma única vez.
  • Múltiplos domínios em um único corpus. Um vault contendo notas sobre programação, arquitetura, segurança, design e projetos pessoais se beneficia da recuperação entre domínios que um CLAUDE.md específico de projeto não pode oferecer.
  • Conteúdo sensível à privacidade. Local-first significa que o pipeline de recuperação nunca envia conteúdo para serviços externos. O vault contém o que você colocar nele, incluindo conteúdo que você não enviaria para um serviço na nuvem.

Modelo Mental: Três Camadas

O sistema possui três camadas que operam de forma independente, mas se potencializam quando combinadas. Cada camada tem uma preocupação diferente e um modo de falha diferente.

┌─────────────────────────────────────────────────────┐
                 INTEGRATION LAYER                     
  MCP servers, hooks, skills, context injection        
  Concern: delivering context to AI tools              
  Failure: wrong context, too much context, stale      
└──────────────────────┬──────────────────────────────┘
                        query + ranked results
┌──────────────────────┴──────────────────────────────┐
                  RETRIEVAL LAYER                      
  BM25, vector KNN, RRF fusion, token budget           
  Concern: finding the right content for any query     
  Failure: wrong ranking, missed results, slow queries 
└──────────────────────┬──────────────────────────────┘
                        chunked, embedded, indexed
┌──────────────────────┴──────────────────────────────┐
                   INTAKE LAYER                        
  Note creation, signal triage, vault organization     
  Concern: what enters the vault and how it's stored   │
  Failure: noise, duplicates, missing structure        
└─────────────────────────────────────────────────────┘

Intake determina o que entra no vault. Sem curadoria, o vault acumula ruído: capturas de tela de tweets, artigos copiados e colados sem anotação, pensamentos inacabados sem contexto. A camada de intake é responsável pelo controle de qualidade no ponto de entrada. Um pipeline de pontuação, convenção de tags ou processo de revisão manual — qualquer mecanismo que garanta que o vault contenha conteúdo que vale a pena recuperar.

Retrieval torna o vault consultável. Este é o motor: dividindo notas em unidades de busca (chunking), convertendo fragmentos em espaço vetorial (embeddings), indexando para busca por palavra-chave e semântica, fundindo resultados com RRF. A camada de retrieval transforma um diretório de arquivos em uma base de conhecimento consultável. Sem essa camada, o vault é navegável por meio de busca manual e pesquisa básica, mas não é acessível programaticamente por ferramentas de IA.

Integration conecta a camada de retrieval às ferramentas de IA. Um servidor MCP expõe o retrieval como uma ferramenta invocável. Hooks injetam contexto automaticamente. Skills capturam novos conhecimentos de volta ao vault. A camada de integration é a interface entre a base de conhecimento e os agentes de IA que a consomem.

As camadas são desacopladas por design. O pipeline de pontuação do intake não sabe nada sobre embeddings. O retriever não sabe nada sobre regras de roteamento de sinais. O servidor MCP não sabe nada sobre como as notas foram criadas. Esse desacoplamento significa que você pode melhorar qualquer camada de forma independente. Substitua o modelo de embedding sem alterar o pipeline de intake. Adicione uma nova capacidade MCP sem modificar o retriever. Mude as heurísticas de pontuação de sinais sem tocar no índice.


Arquitetura do Vault para Consumo por IA

Um vault otimizado para retrieval por IA segue convenções diferentes de um vault otimizado para navegação pessoal. Esta seção cobre a estrutura de pastas, o esquema de notas, as convenções de frontmatter e os padrões específicos que melhoram a qualidade do retrieval.

Estrutura de Pastas

Use prefixos numéricos para as pastas de nível superior para criar uma hierarquia organizacional previsível. Os números não indicam prioridade — eles agrupam domínios relacionados e tornam a estrutura fácil de escanear.

vault/
├── 00-inbox/              # Unsorted captures, pending triage
├── 01-projects/           # Active project notes
├── 02-areas/              # Ongoing areas of responsibility
├── 03-resources/          # Reference material by topic
   ├── programming/
   ├── security/
   ├── ai-engineering/
   ├── design/
   └── devops/
├── 04-archive/            # Completed projects, old references
├── 05-signals/            # Scored signal intake
   ├── ai-tooling/
   ├── security/
   ├── systems/
   └── ...12 domain folders
├── 06-daily/              # Daily notes (if used)
├── 07-templates/          # Note templates (excluded from index)
├── 08-attachments/        # Images, PDFs (excluded from index)
├── .obsidian/             # Obsidian config (excluded from index)
└── .indexignore            # Paths to exclude from retrieval index

Pastas que devem ser indexadas: Tudo que contenha prosa em markdown — projetos, áreas, recursos, sinais, notas diárias.

Pastas que devem ser excluídas da indexação: Templates (contêm variáveis de placeholder, não conteúdo), anexos (arquivos binários), configuração do Obsidian e qualquer pasta contendo conteúdo sensível que você não quer no índice de retrieval.

O arquivo .indexignore

Crie um arquivo .indexignore na raiz do vault para excluir explicitamente caminhos do índice de retrieval. A sintaxe é a mesma do .gitignore:

# Obsidian internal
.obsidian/

# Templates contain placeholders, not content
07-templates/

# Binary attachments
08-attachments/

# Personal health/medical notes
02-areas/health/

# Financial records
02-areas/finance/personal/

# Career documents (resumes, salary data)
02-areas/career/private/

O indexador lê este arquivo antes de escanear e ignora completamente os caminhos correspondentes. Arquivos em caminhos excluídos nunca são fragmentados, nunca recebem embeddings e nunca aparecem nos resultados de busca.

Esquema de Notas

Toda nota deve ter frontmatter YAML. O retriever usa os campos do frontmatter para filtragem e enriquecimento de contexto:

---
title: "OAuth Token Rotation Patterns"
type: note           # note | signal | project | moc | daily
domain: security     # primary domain for routing
tags:
  - authentication
  - oauth
  - token-management
created: 2026-01-15
updated: 2026-02-28
source: ""           # URL if captured from external source
status: active       # active | archived | draft
---

Campos obrigatórios para retrieval:

  • title — Usado na exibição dos resultados de busca e no contexto de cabeçalho para BM25
  • type — Permite consultas filtradas por tipo (“mostrar apenas MOCs” ou “apenas sinais”)
  • tags — Indexado no contexto de cabeçalho do FTS5 com peso 0,3, fornecendo correspondências por palavra-chave mesmo quando o corpo usa terminologia diferente

Campos opcionais, mas valiosos:

  • domain — Permite consultas com escopo de domínio (“buscar apenas nas notas de segurança”)
  • source — Atribuição para conteúdo capturado; o retriever pode incluir URLs de origem nos resultados
  • status — Permite excluir notas arquivadas ou em rascunho da busca ativa

Convenções de Chunking

O retriever fragmenta nos limites de cabeçalhos H2 (##). Isso significa que a estrutura da sua nota afeta diretamente a granularidade do retrieval:

Bom para retrieval:

## Token Rotation Strategy

The rotation interval depends on the threat model...

## Implementation with refresh_token

The OAuth 2.0 refresh token flow requires...

## Error Handling: Expired Tokens

When a token expires mid-request...

Três seções H2 produzem três fragmentos pesquisáveis de forma independente. Cada fragmento tem contexto suficiente para que o embedding capture seu significado. Uma consulta sobre “tratamento de token expirado” corresponde especificamente ao terceiro fragmento.

Ruim para retrieval:

# OAuth Notes

Token rotation depends on threat model. The OAuth 2.0 refresh
token flow requires storing the refresh token securely. When a
token expires mid-request, the client should retry after refresh.
The rotation interval is typically 15-30 minutes for access tokens
and 7-30 days for refresh tokens...

Uma seção longa sem cabeçalhos H2 produz um único fragmento grande. O embedding calcula a média de todos os tópicos da seção. Uma consulta sobre qualquer subtópico corresponde à nota inteira de forma igual.

Regra prática: Se uma seção cobre mais de um conceito, divida-a em subseções H2. O chunker cuida do resto.

O Que Não Colocar nas Notas

Conteúdo que degrada a qualidade do retrieval:

  • Cópias brutas de artigos inteiros sem anotação. O retriever indexa as palavras-chave do artigo original, diluindo seu vault com conteúdo que você não escreveu. Adicione um resumo, extraia os pontos principais ou vincule à URL de origem.
  • Capturas de tela sem descrição textual. O retriever indexa texto em markdown. Uma imagem sem texto alternativo ou descrição ao redor é invisível tanto para BM25 quanto para busca vetorial.
  • Strings de credenciais. Chaves API, tokens, senhas, strings de conexão. Mesmo com filtragem de credenciais, a abordagem mais segura é nunca colar segredos nas notas. Faça referência a eles pelo nome (“o token API do Cloudflare em ~/.env”).
  • Conteúdo gerado automaticamente sem curadoria. Se uma ferramenta gera uma nota (transcrição de reunião, destaques do Readwise, importação RSS), revise e anote antes que ela entre no vault permanente. Importações automáticas sem curadoria adicionam volume sem adicionar valor recuperável.

Ecossistema de Plugins para Fluxos de Trabalho com IA

Os plugins do Obsidian que melhoram a qualidade do vault para recuperação por IA se dividem em três categorias: estruturais (garantem consistência), de consulta (expõem metadados) e de sincronização (mantêm o vault atualizado).

Plugins Essenciais

Dataview. Consulta seu vault como um banco de dados usando campos do frontmatter. Crie índices dinâmicos: “todas as notas com tag security atualizadas nos últimos 30 dias” ou “todas as notas de projeto com status active.” O Dataview não auxilia diretamente na recuperação, mas ajuda a identificar lacunas na cobertura do seu vault e encontrar notas que precisam de atualização.

TABLE type, domain, updated
FROM "03-resources"
WHERE status = "active"
SORT updated DESC
LIMIT 20

Templater. Cria notas a partir de templates com campos dinâmicos. Garanta que toda nova nota comece com o frontmatter correto usando um template que preencha automaticamente os campos created, type e domain. Um frontmatter consistente melhora a filtragem na recuperação.

<%* /* New Resource Note Template */ %>
---
title: "<% tp.file.cursor() %>"
type: note
domain: <% tp.system.suggester(["programming", "security", "ai-engineering", "design", "devops"], ["programming", "security", "ai-engineering", "design", "devops"]) %>
tags: []
created: <% tp.date.now("YYYY-MM-DD") %>
updated: <% tp.date.now("YYYY-MM-DD") %>
source: ""
status: active
---

## Key Points

## Details

## References

Linter. Aplica regras de formatação em todo o vault. Uma hierarquia de títulos consistente (H1 para o título, H2 para seções, H3 para subseções) garante que o chunker produza resultados previsíveis. Regras do Linter que importam para a recuperação:

  • Incremento de títulos: impor níveis sequenciais de títulos (sem pular de H1 para H3)
  • YAML title: corresponder ao nome do arquivo
  • Espaços à direita: remover (evita artefatos de tokenização do FTS5)
  • Linhas em branco consecutivas: limitar a 1 (chunks mais limpos)

Integração com Git. Controle de versão para o seu vault. Acompanhe alterações ao longo do tempo, sincronize entre máquinas e recupere-se de exclusões acidentais. O Git também fornece dados de mtime que o indexador usa para detecção incremental de mudanças.

Plugins que Auxiliam a Indexação

Smart Connections. Um plugin do Obsidian que oferece busca semântica com IA dentro do próprio Obsidian. O Smart Connections v4 cria embeddings locais por padrão — uma vez que seu vault esteja indexado, as conexões semânticas e a busca funcionam inteiramente offline, sem chamadas de API.21 Embora o sistema de recuperação deste guia seja externo ao Obsidian (executado como um pipeline Python), o Smart Connections é útil para explorar relações semânticas durante a escrita. Os dois sistemas indexam o mesmo conteúdo, mas atendem a casos de uso diferentes: Smart Connections para descoberta dentro do editor, o recuperador externo para integração de ferramentas de IA via MCP.

Metadata Menu. Oferece edição estruturada de frontmatter com autocompletar para valores de campos. Reduz erros de digitação nos campos type, domain e tags. Metadados consistentes melhoram a precisão da filtragem na recuperação.

Plugins que Prejudicam a Indexação

Excalidraw. Armazena desenhos como JSON embutido em arquivos markdown. O JSON é markdown sintaticamente válido, mas produz resultados inutilizáveis quando segmentado e convertido em embeddings. Exclua os arquivos do Excalidraw do índice via .indexignore ou filtre por extensão de arquivo.

Kanban. Armazena o estado do quadro como markdown com formatação especial. O formato é projetado para renderização Kanban, não para recuperação de texto. O chunker produz fragmentos de títulos de cartões e metadados que não geram bons embeddings. Exclua os quadros Kanban do índice.

Calendar. Cria notas diárias com conteúdo mínimo (geralmente apenas um cabeçalho com a data). Notas vazias ou quase vazias produzem chunks de baixa qualidade. Se você usa notas diárias, escreva conteúdo substancial nelas ou exclua a pasta de notas diárias do índice.

Configurações de Plugins que Importam

Recuperação de arquivos → Habilitada. Protege contra exclusão acidental de notas. Não está diretamente relacionada à recuperação, mas é essencial para uma base de conhecimento da qual você depende.

Quebras de linha estritas → Desabilitada. Quebras de linha padrão do markdown (dupla quebra de linha para parágrafo) produzem chunks mais limpos do que o modo estrito do Obsidian (quebra de linha simples para <br>).

Local padrão para novos arquivos → Pasta designada. Direcione novos arquivos para 00-inbox/ para que notas não categorizadas não poluam as pastas de domínio. A inbox é uma área de triagem; os arquivos são movidos para as pastas de domínio após a classificação.

Formato de wiki-link → Caminho mais curto quando possível. Alvos de links mais curtos são mais fáceis para o recuperador resolver ao indexar a estrutura de links.


Modelos de Embedding: Escolhendo e Configurando

O modelo de embedding converte trechos de texto em vetores numéricos para busca semântica. A escolha do modelo determina a qualidade da recuperação, o tamanho do índice, a velocidade de embedding e as dependências de runtime. Esta seção explica por que o potion-base-8M do Model2Vec é a escolha padrão e quando optar por alternativas.

Por que Model2Vec potion-base-8M

Modelo: minishlab/potion-base-8M Parâmetros: 7,6 milhões Dimensões: 256 Tamanho: ~30 MB Dependências: model2vec (apenas numpy, sem PyTorch) Inferência: Apenas CPU, embeddings de palavras estáticos (sem camadas de atenção)

O Model2Vec destila o conhecimento de um sentence transformer em embeddings de tokens estáticos. Em vez de executar camadas de atenção sobre a entrada (como fazem BERT, MiniLM e outros modelos transformer), o Model2Vec produz vetores por meio da média ponderada de embeddings de tokens pré-computados.3 A consequência prática: a velocidade de embedding é 50-500x mais rápida do que modelos baseados em transformer, pois não há computação sequencial.

No conjunto de benchmarks MTEB, o potion-base-8M atinge 89% do desempenho do all-MiniLM-L6-v2 (50,03 vs 56,09 de média).4 A diferença de qualidade de 11% é o trade-off pelas vantagens de velocidade e simplicidade. Para trechos curtos em markdown (média de 200-400 palavras em um vault típico), a diferença de qualidade é menos pronunciada do que em documentos mais longos, pois ambos os modelos convergem para representações similares em textos curtos e focados.

Configuração

# embedder.py
DEFAULT_MODEL = "minishlab/potion-base-8M"
EMBEDDING_DIM = 256

class Model2VecEmbedder:
    def __init__(self, model_name=DEFAULT_MODEL):
        self._model_name = model_name
        self._model = None

    def _ensure_model(self):
        if self._model is not None:
            return
        _activate_venv()  # Add isolated venv to sys.path
        from model2vec import StaticModel
        self._model = StaticModel.from_pretrained(self._model_name)

    def embed_batch(self, texts):
        self._ensure_model()
        vecs = self._model.encode(texts)
        return [v.tolist() for v in vecs]

Carregamento sob demanda. O modelo é carregado no primeiro uso, não no momento da importação. Importar o módulo do embedder não tem custo quando o retriever opera no modo de fallback BM25-only (por exemplo, quando o venv de embedding não está instalado).

Ambiente virtual isolado. O modelo roda em um venv dedicado (por exemplo, ~/.claude/venvs/memory/) para evitar conflitos de dependências com o restante do toolchain. A função _activate_venv() adiciona o site-packages do venv ao sys.path em tempo de execução.

# Create isolated venv
python3 -m venv ~/.claude/venvs/memory
~/.claude/venvs/memory/bin/pip install model2vec

Processamento em lote. O embedder processa textos em lotes de 64 para amortizar o overhead do Model2Vec. O indexador alimenta os trechos para embed_batch() em vez de gerar o embedding de um trecho por vez.

Quando escolher alternativas

Modelo Dim Tamanho Velocidade Qualidade (MTEB) Melhor para
potion-base-8M 256 30 MB 500x 50,03 Padrão: local, rápido, sem GPU
potion-base-32M 256 120 MB 400x 52,46 Maior qualidade, ainda estático
potion-retrieval-32M 256 120 MB 400x 36,35 (recuperação) Estático otimizado para recuperação
all-MiniLM-L6-v2 384 80 MB 1x 56,09 Maior qualidade, ainda local
nomic-embed-text-v1.5 768 270 MB 0,5x 62,28 Melhor qualidade local
text-embedding-3-small 1536 API N/A 62,30 Baseado em API, maior qualidade

Escolha potion-base-32M quando quiser melhor qualidade do que o potion-base-8M sem sair da família de embeddings estáticos. Lançado em janeiro de 2025, utiliza um vocabulário maior destilado do baai/bge-base-en-v1.5, alcançando 52,46 de média no MTEB (melhoria de 5% em relação ao potion-base-8M) mantendo a mesma saída de 256 dimensões e dependência apenas de numpy.18 O arquivo do modelo 4x maior aumenta o uso de memória, mas a velocidade de embedding permanece ordens de magnitude mais rápida do que modelos transformer.

Escolha potion-retrieval-32M quando seu caso de uso principal for recuperação (que é o caso da busca em vaults). Esta variante é ajustada (fine-tuned) a partir do potion-base-32M especificamente para tarefas de recuperação, alcançando 36,35 nos benchmarks de recuperação do MTEB versus 33,52 para o modelo base.18 A média geral do MTEB cai para 49,73 porque o fine-tuning troca desempenho de propósito geral por ganhos específicos de recuperação.

Escolha all-MiniLM-L6-v2 quando a qualidade da recuperação for mais importante do que a velocidade e você tiver o PyTorch instalado. Os vetores de 384 dimensões aumentam o tamanho do banco de dados SQLite em ~50% comparado a vetores de 256 dimensões. A velocidade de embedding cai de <1 minuto para ~10 minutos para um reindex completo de 15.000 arquivos em hardware da série M.

Escolha nomic-embed-text-v1.5 quando precisar da melhor qualidade de recuperação local possível e aceitar indexação mais lenta. Os vetores de 768 dimensões aproximadamente triplicam o tamanho do banco de dados. Requer PyTorch e uma CPU moderna ou GPU.

Escolha text-embedding-3-small quando latência de rede e privacidade forem trade-offs aceitáveis. O API produz os embeddings de maior qualidade, mas introduz uma dependência de nuvem, custo por token ($0,02/milhão de tokens) e envia seu conteúdo para os servidores da OpenAI.

Permaneça com potion-base-8M em todos os outros casos. A vantagem de velocidade é crítica para indexação iterativa (reindex durante o desenvolvimento), a dependência apenas de numpy evita a complexidade de instalação do PyTorch, e os vetores de 256 dimensões mantêm o banco de dados compacto.

Quantização e redução de dimensionalidade

O Model2Vec v0.5.0+ suporta o carregamento de modelos com precisão e dimensões reduzidas.18 Isso é útil para implantação em hardware com recursos limitados ou para reduzir o tamanho do banco de dados sem trocar de modelo:

from model2vec import StaticModel

# Load with int8 quantization (25% of original size)
model = StaticModel.from_pretrained("minishlab/potion-base-8M", quantize=True)

# Load with reduced dimensions (e.g., 128 instead of 256)
model = StaticModel.from_pretrained("minishlab/potion-base-8M", dimensionality=128)

Modelos quantizados mantêm qualidade de recuperação quase idêntica com uma fração do consumo de memória. A redução de dimensionalidade segue truncamento no estilo Matryoshka — as primeiras N dimensões carregam a maior parte da informação. Reduzir de 256 para 128 dimensões corta pela metade o armazenamento de vetores com perda mínima de qualidade para recuperação de textos curtos.

A partir de maio de 2025, o Model2Vec também suporta tokenizadores BPE e Unigram (além do WordPiece), o que expande o conjunto de sentence transformers que podem ser destilados em modelos estáticos.20

Fine-tuning para embeddings específicos do vault

O Model2Vec v0.4.0+ suporta o treinamento de modelos de classificação personalizados sobre embeddings estáticos, e a v0.7.0 adiciona quantização de vocabulário e pooling configurável para destilação.20 Isso é relevante para vaults com vocabulário especializado (notas médicas, referências jurídicas, jargão de domínio específico) onde os modelos potion padrão podem não capturar nuances semânticas:

from model2vec import StaticModel
from model2vec.train import train_model

# Fine-tune on vault-specific data
model = StaticModel.from_pretrained("minishlab/potion-base-8M")
trained_model = train_model(model, train_texts, train_labels)
trained_model.save_pretrained("./vault-embeddings")

Para a maioria dos vaults, o potion-base-8M padrão produz qualidade de recuperação suficiente. O fine-tuning vale a pena apenas quando a recuperação consistentemente deixa de captar conexões específicas do domínio que um modelo de propósito geral não consegue identificar.

Rastreamento de hash do modelo

O indexador armazena um hash derivado do nome do modelo e do tamanho do vocabulário. Se você trocar o modelo de embedding, o indexador detecta a incompatibilidade na próxima execução incremental e aciona um reindex completo automaticamente.

def _compute_model_hash(self):
    """Hash model name + vocab size for compatibility tracking."""
    key = f"{self._model_name}:{self._model.vocab_size}"
    return hashlib.sha256(key.encode()).hexdigest()[:16]

Isso impede a mistura de vetores de modelos diferentes no mesmo banco de dados, o que produziria pontuações de cosine similarity sem sentido.

Modos de falha

Falha no download do modelo. A primeira execução baixa o modelo do Hugging Face. Se o download falhar (problema de rede, firewall corporativo), o retriever recorre ao modo BM25-only. O modelo é armazenado em cache localmente após o primeiro download.

Incompatibilidade de dimensões. Se você trocar de modelo sem limpar o banco de dados, os vetores armazenados terão uma dimensão diferente dos novos embeddings. O indexador detecta isso via hash do modelo e aciona um reindex completo. Se a verificação de hash falhar (modelo personalizado sem hash adequado), o sqlite-vec retornará erro em consultas KNN com dimensões incompatíveis.

Pressão de memória em vaults grandes. Gerar embeddings de 50.000+ trechos em um único lote pode consumir memória significativa. O indexador processa em lotes de 64 para limitar o pico de uso de memória. Se a memória ainda for um problema, reduza o tamanho do lote.


Busca de texto completo com FTS5

A extensão FTS5 do SQLite oferece busca de texto completo com ranqueamento BM25. O FTS5 é o componente de busca por palavras-chave do pipeline de recuperação híbrida (hybrid retrieval). Esta seção aborda a configuração do FTS5, quando o BM25 se destaca e seus modos de falha específicos.

Tabela virtual FTS5

CREATE VIRTUAL TABLE chunks_fts USING fts5(
    chunk_text,
    section,
    heading_context,
    content=chunks,
    content_rowid=id
);

Modo content-sync. O parâmetro content=chunks indica ao FTS5 para referenciar a tabela chunks diretamente, em vez de armazenar uma cópia duplicada do texto. Isso reduz pela metade o requisito de armazenamento, mas significa que o FTS5 precisa ser sincronizado manualmente quando chunks são inseridos, atualizados ou excluídos.

Colunas. Três colunas são indexadas: - chunk_text — O conteúdo principal de cada chunk (peso BM25: 1.0) - section — O texto do cabeçalho H2 (peso BM25: 0.5) - heading_context — Título da nota, tags e metadados (peso BM25: 0.3)

Ranqueamento BM25

O BM25 classifica documentos pela frequência do termo, frequência inversa do documento e normalização pelo tamanho do documento. A função auxiliar bm25() no FTS5 aceita pesos por coluna:

SELECT
    c.id, c.file_path, c.section, c.chunk_text,
    bm25(chunks_fts, 1.0, 0.5, 0.3) AS score
FROM chunks_fts
JOIN chunks c ON chunks_fts.rowid = c.id
WHERE chunks_fts MATCH ?
ORDER BY score
LIMIT 30;

Os pesos das colunas (1.0, 0.5, 0.3) significam: - Uma correspondência de palavra-chave em chunk_text contribui mais para a pontuação - Uma correspondência em section (cabeçalho) contribui com metade - Uma correspondência em heading_context (título, tags) contribui com 30%

Esses pesos são ajustáveis. Se o seu vault possui cabeçalhos descritivos que indicam fortemente a qualidade do conteúdo, aumente o peso de section. Se suas tags são abrangentes e precisas, aumente o peso de heading_context.

Quando o BM25 se destaca

O BM25 se destaca em consultas que contêm identificadores exatos:

  • Nomes de funções: _rrf_fuse, embed_batch, get_stale_files
  • Flags de CLI: --incremental, --vault, --model
  • Chaves de configuração: bm25_weight, max_tokens, batch_size
  • Mensagens de erro: SQLITE_LOCKED, ConnectionRefusedError
  • Termos técnicos específicos: PostToolUse, PreToolUse, AGENTS.md

Para essas consultas, o BM25 encontra a correspondência exata imediatamente. A busca vetorial retornaria conteúdo semanticamente relacionado, mas poderia classificar a correspondência exata abaixo de uma discussão conceitual.

Quando o BM25 falha

O BM25 falha em consultas que usam terminologia diferente do conteúdo armazenado:

  • Consulta: “how to handle authentication failures” → O vault contém notas sobre “login error recovery” e “session expiration handling.” O BM25 não encontra correspondência porque as palavras-chave diferem.
  • Consulta: “what is the best way to manage state” → O vault contém notas sobre “Redux store patterns” e “context providers.” O BM25 não encontra porque “state management” é expresso por meio de nomes de tecnologias específicas.

O BM25 também falha com colisão de palavras-chave em escala. Em um vault com 15.000 arquivos, uma busca por “configuration” corresponde a centenas de notas porque praticamente toda nota de projeto menciona configuração. Os resultados são tecnicamente corretos, mas praticamente inúteis — o ranqueamento não consegue determinar qual nota sobre “configuration” é relevante para a consulta atual.

Tokenizador FTS5

O FTS5 usa o tokenizador unicode61 por padrão, que lida com texto ASCII e Unicode. Para vaults com conteúdo significativo em CJK (chinês, japonês, coreano), considere o tokenizador trigram:

-- For CJK-heavy vaults
CREATE VIRTUAL TABLE chunks_fts USING fts5(
    chunk_text, section, heading_context,
    content=chunks, content_rowid=id,
    tokenize='trigram'
);

O tokenizador padrão unicode61 divide o texto nos limites de palavras, o que funciona mal para idiomas sem espaços entre as palavras. O tokenizador trigram divide a cada três caracteres, permitindo busca por substring ao custo do tamanho do índice (aproximadamente 3x maior).

Manutenção

O FTS5 requer sincronização explícita quando a tabela chunks subjacente é alterada:

# After inserting chunks
cursor.execute("""
    INSERT INTO chunks_fts(chunks_fts)
    VALUES('rebuild')
""")

O comando rebuild reconstrói o índice FTS5 a partir da tabela de conteúdo. Execute-o após inserções em massa (reindexação completa), mas não após atualizações incrementais individuais — para essas, use INSERT INTO chunks_fts(rowid, chunk_text, section, heading_context) para sincronizar linhas individuais.


Busca vetorial com sqlite-vec

A extensão sqlite-vec traz busca vetorial KNN (K-Nearest Neighbors) para dentro do SQLite. Esta seção aborda a configuração do sqlite-vec, o pipeline de embeddings da nota até o vetor pesquisável e os padrões de consulta específicos.

Tabela virtual sqlite-vec

CREATE VIRTUAL TABLE chunk_vecs USING vec0(
    id INTEGER PRIMARY KEY,
    embedding float[256]
);

O módulo vec0 armazena vetores float de 256 dimensões como dados binários compactados. A coluna id mapeia 1:1 para a tabela chunks, permitindo joins entre resultados vetoriais e metadados dos chunks.

Pipeline de embeddings

O pipeline flui da nota até o vetor pesquisável:

Note (.md file)
   Chunker: split at H2 boundaries
     Chunks (30-2000 chars each)
       Credential filter: scrub secrets
         Embedder: Model2Vec encode
           Vectors (256-dim float arrays)
             sqlite-vec: store as packed binary
               Ready for KNN queries

Serialização de vetores

O módulo struct do Python serializa vetores float para armazenamento no sqlite-vec:

import struct

def _serialize_vector(vec):
    """Pack float list into binary for sqlite-vec."""
    return struct.pack(f"{len(vec)}f", *vec)

def _deserialize_vector(blob, dim=256):
    """Unpack binary blob to float list."""
    return list(struct.unpack(f"{dim}f", blob))

Consulta KNN

Uma consulta de busca vetorial gera o embedding da consulta de entrada e então encontra os K chunks mais próximos por distância cosseno:

def _vector_search(self, query_text, limit=30):
    query_vec = self.embedder.embed_batch([query_text])[0]
    packed = _serialize_vector(query_vec)

    results = self.db.execute("""
        SELECT
            cv.id,
            cv.distance,
            c.file_path,
            c.section,
            c.chunk_text
        FROM chunk_vecs cv
        JOIN chunks c ON cv.id = c.id
        WHERE embedding MATCH ?
            AND k = ?
        ORDER BY distance
    """, [packed, limit]).fetchall()

    return results

O operador MATCH no sqlite-vec realiza busca aproximada de vizinho mais próximo. O parâmetro k controla quantos resultados retornar. A coluna distance contém a distância cosseno (0 = idêntico, 2 = oposto).

Quando a busca vetorial se destaca

A busca vetorial se destaca em consultas onde o conceito importa mais do que as palavras específicas:

  • Consulta: “how to handle authentication failures” → Encontra notas sobre “login error recovery” (mesmo espaço semântico, palavras-chave diferentes)
  • Consulta: “what patterns exist for caching” → Encontra notas sobre “memoization,” “Redis TTL strategies,” e “HTTP cache headers” (conceitos relacionados, terminologia diversa)
  • Consulta: “approaches to testing asynchronous code” → Encontra notas sobre “pytest-asyncio fixtures,” “mock event loops,” e “async test patterns” (mesmo conceito expresso por meio de detalhes de implementação)

Quando a busca vetorial falha

A busca vetorial tem dificuldade com identificadores exatos:

  • Consulta: _rrf_fuse → Retorna notas sobre “fusion algorithms” e “rank merging”, mas pode classificar a definição real da função abaixo de discussões conceituais
  • Consulta: PostToolUse → Retorna notas sobre “tool lifecycle hooks” e “post-execution handlers” em vez do nome específico do hook

A busca vetorial também tem dificuldade com dados estruturados. Arquivos de configuração JSON, blocos YAML e trechos de código produzem embeddings que capturam padrões estruturais em vez de significado semântico. Um arquivo JSON com "review": true gera um embedding diferente de uma discussão em prosa sobre revisão de código.

Degradação elegante

Se o sqlite-vec falhar ao carregar (extensão ausente, plataforma incompatível, biblioteca corrompida), o recuperador recorre à busca apenas com BM25:

class VectorIndex:
    def __init__(self, db_path):
        self.db = sqlite3.connect(db_path)
        self._vec_available = False
        try:
            self.db.enable_load_extension(True)
            self.db.load_extension("vec0")
            self._vec_available = True
        except Exception:
            pass  # BM25-only mode

    @property
    def vec_available(self):
        return self._vec_available

O recuperador verifica vec_available antes de tentar consultas vetoriais. Quando desabilitado, todas as buscas usam apenas BM25, e a etapa de fusão RRF é ignorada.


Reciprocal Rank Fusion (RRF)

RRF mescla duas listas ranqueadas sem exigir calibração de pontuação. Esta seção cobre o algoritmo, um rastreamento detalhado de consulta, ajuste do parâmetro k e por que RRF é escolhido em vez de alternativas. Para uma calculadora interativa com ranks editáveis, presets de cenários e um explorador visual de arquitetura, veja o aprofundamento sobre hybrid retrieval.

O Algoritmo

RRF atribui a cada documento uma pontuação baseada apenas em sua posição de rank em cada lista:

score(d) = Σ (weight_i / (k + rank_i))

Onde: - k é uma constante de suavização (60, seguindo Cormack et al.1) - rank_i é o rank do documento com base 1 na lista de resultados i - weight_i é um multiplicador opcional por lista (padrão 1.0)

Documentos que ranqueiam bem em múltiplas listas recebem pontuações fundidas mais altas. Documentos que aparecem em apenas uma lista recebem uma pontuação dessa única fonte.

Por que RRF em vez de Alternativas

Combinação linear ponderada requer calibrar pontuações BM25 contra distâncias de cosseno. Pontuações BM25 são ilimitadas e escalam com o tamanho do corpus. Distâncias de cosseno são limitadas [0, 2]. Combiná-las requer normalização, e os parâmetros de normalização dependem do dataset. RRF usa apenas posições de rank, que são sempre inteiros começando em 1 independentemente do método de pontuação.

Modelos de fusão aprendidos requerem dados de treinamento rotulados — pares de relevância consulta-documento. Para uma base de conhecimento pessoal, esses dados de treinamento não existem. Você precisaria julgar manualmente centenas de pares consulta-documento para treinar um modelo útil. RRF funciona sem nenhum dado de treinamento.

Métodos de votação Condorcet (contagem de Borda, método Schulze) são teoricamente elegantes, mas mais complexos de implementar e ajustar. O artigo original de RRF demonstrou que RRF supera métodos Condorcet em dados de avaliação TREC.1

Fusão na Prática

Consulta: “how does the review aggregator handle disagreements”

BM25 ranqueia review-aggregator.py na posição 3 (correspondências exatas de palavras-chave em “review,” “aggregator,” “disagreements”), mas posiciona dois arquivos de configuração acima (eles correspondem “review” de forma mais proeminente). A busca vetorial ranqueia o mesmo chunk na posição 1 (correspondência semântica sobre resolução de conflitos). Após a fusão RRF:

Chunk BM25 Vec Fused Score
review-aggregator.py “Disagreement Resolution” #3 #1 0.0323
code-review-patterns.md “Multi-Reviewer” #4 #2 0.0317
deliberation-config.json “Review Weights” #1 0.0164

Chunks que ranqueiam bem em ambas as listas emergem para o topo. Chunks que aparecem apenas em uma lista recebem uma pontuação de fonte única e caem abaixo dos resultados com ranqueamento duplo. A lógica real de resolução de desacordos vence porque ambos os métodos a encontraram — BM25 através de palavras-chave, busca vetorial através de semântica.

Para o rastreamento completo passo a passo com a matemática RRF por rank, experimente diferentes valores de k na calculadora interativa de RRF.

Implementação

RRF_K = 60

def _rrf_fuse(self, bm25_results, vec_results,
              bm25_weight=1.0, vec_weight=1.0):
    """Fuse BM25 and vector results using Reciprocal Rank Fusion."""
    scores = {}

    for rank, r in enumerate(bm25_results, start=1):
        cid = r["id"]
        if cid not in scores:
            scores[cid] = {
                "rrf_score": 0.0,
                "file_path": r["file_path"],
                "section": r["section"],
                "chunk_text": r["chunk_text"],
                "bm25_rank": None,
                "vec_rank": None,
            }
        scores[cid]["rrf_score"] += bm25_weight / (self._rrf_k + rank)
        scores[cid]["bm25_rank"] = rank

    for rank, r in enumerate(vec_results, start=1):
        cid = r["id"]
        if cid not in scores:
            scores[cid] = {
                "rrf_score": 0.0,
                "file_path": r["file_path"],
                "section": r["section"],
                "chunk_text": r["chunk_text"],
                "bm25_rank": None,
                "vec_rank": None,
            }
        scores[cid]["rrf_score"] += vec_weight / (self._rrf_k + rank)
        scores[cid]["vec_rank"] = rank

    fused = sorted(
        scores.values(),
        key=lambda x: x["rrf_score"],
        reverse=True,
    )
    return fused

Ajustando k

A constante k controla quanta importância é dada aos resultados de rank mais alto versus resultados de rank mais baixo:

  • k mais baixo (ex.: 10): Resultados de rank mais alto dominam. Rank 1 pontua 1/11 = 0,091, rank 10 pontua 1/20 = 0,050 (diferença de 1,8x). Bom quando você confia que os ranqueadores individuais acertam o resultado principal.
  • k padrão (60): Equilibrado. Rank 1 pontua 1/61 = 0,0164, rank 10 pontua 1/70 = 0,0143 (diferença de 1,15x). Diferenças de rank são comprimidas, dando mais peso a aparecer em múltiplas listas.
  • k mais alto (ex.: 200): Aparecer em ambas as listas importa muito mais do que a posição de rank. Rank 1 pontua 1/201, rank 10 pontua 1/210 — quase idênticos. Use quando os ranqueadores individuais produzem rankings ruidosos, mas a concordância entre listas é confiável.

Comece com k=60. O artigo original de RRF considerou esse valor robusto em diversos datasets TREC. Ajuste apenas após medir casos de falha na sua própria distribuição de consultas.

Desempate

Quando dois chunks têm pontuações RRF idênticas (raro, mas possível com o mesmo rank em uma lista e nenhuma aparição na outra), desempate por:

  1. Prefira chunks que aparecem em ambas as listas em vez de chunks que aparecem em apenas uma
  2. Entre chunks em ambas as listas, prefira o que tem o menor rank combinado
  3. Entre chunks em apenas uma lista, prefira o que tem o menor rank naquela lista

O Pipeline Completo de Recuperação

Esta seção rastreia uma consulta desde a entrada até a saída através de todo o pipeline: busca BM25, busca vetorial, fusão RRF, truncamento por orçamento de tokens e montagem de contexto.

Fluxo de Ponta a Ponta

User query: "PostToolUse hook for context compression"
  │
  ├─ BM25 Search (FTS5)
  │    → MATCH "PostToolUse hook context compression"
  │    → Top 30 results ranked by BM25 score
  │    → 12ms
  │
  ├─ Vector Search (sqlite-vec)
  │    → Embed query with Model2Vec
  │    → KNN k=30 on chunk_vecs
  │    → Top 30 results ranked by cosine distance
  │    → 8ms
  │
  └─ RRF Fusion
       → Merge 60 candidates (may overlap)
       → Score by rank position
       → Top 10 results
       → 3ms
       │
       └─ Token Budget
            → Truncate to max_tokens (default 4000)
            → Estimate at 4 chars per token
            → Return results with metadata
            → <1ms

Latência total: ~23ms para um banco de dados de 49.746 chunks em hardware Apple M3 Pro.

O API de Busca

class HybridRetriever:
    def search(self, query, limit=10, max_tokens=4000,
               bm25_weight=1.0, vec_weight=1.0):
        """
        Search the vault using hybrid BM25 + vector retrieval.

        Args:
            query: Search query text
            limit: Maximum results to return
            max_tokens: Token budget for total result text
            bm25_weight: Weight for BM25 results in RRF
            vec_weight: Weight for vector results in RRF

        Returns:
            List of SearchResult with file_path, section,
            chunk_text, rrf_score, bm25_rank, vec_rank
        """
        # BM25 search
        bm25_results = self._bm25_search(query, limit=30)

        # Vector search (if available)
        if self.index.vec_available:
            vec_results = self._vector_search(query, limit=30)
            fused = self._rrf_fuse(
                bm25_results, vec_results,
                bm25_weight, vec_weight,
            )
        else:
            fused = bm25_results  # BM25-only fallback

        # Token budget truncation
        results = []
        token_count = 0
        for r in fused[:limit]:
            chunk_tokens = len(r["chunk_text"]) // 4
            if token_count + chunk_tokens > max_tokens:
                break
            results.append(r)
            token_count += chunk_tokens

        return results

Truncamento por Orçamento de Tokens

O parâmetro max_tokens impede que o recuperador retorne mais contexto do que a ferramenta de IA consegue utilizar. A estimativa usa 4 caracteres por token (uma aproximação razoável para prosa em inglês). Os resultados são truncados de forma gulosa: resultados são adicionados em ordem de classificação até que o orçamento se esgote.

Esta é uma estratégia conservadora. Uma abordagem mais sofisticada consideraria pontuações de qualidade por resultado e preferiria resultados mais curtos e de maior qualidade em vez de resultados mais longos e de menor qualidade. A abordagem gulosa é mais simples e funciona bem na prática porque a classificação RRF já ordena os resultados por relevância.

Esquema do Banco de Dados (Completo)

-- Chunk content and metadata
CREATE TABLE chunks (
    id INTEGER PRIMARY KEY,
    file_path TEXT NOT NULL,
    section TEXT NOT NULL,
    chunk_text TEXT NOT NULL,
    heading_context TEXT DEFAULT '',
    mtime_ns INTEGER NOT NULL,
    embedded_at REAL NOT NULL
);

CREATE INDEX idx_chunks_file ON chunks(file_path);
CREATE INDEX idx_chunks_mtime ON chunks(mtime_ns);

-- FTS5 for BM25 search (content-synced to chunks table)
CREATE VIRTUAL TABLE chunks_fts USING fts5(
    chunk_text, section, heading_context,
    content=chunks, content_rowid=id
);

-- sqlite-vec for vector KNN search
CREATE VIRTUAL TABLE chunk_vecs USING vec0(
    id INTEGER PRIMARY KEY,
    embedding float[256]
);

-- Model metadata for compatibility tracking
CREATE TABLE model_meta (
    key TEXT PRIMARY KEY,
    value TEXT
);

Caminho de Degradação Gradual

Full pipeline:     BM25 + Vector + RRF    Best results
No sqlite-vec:     BM25 only              Good results (no semantic)
No model download:  BM25 only              Good results (no semantic)
No FTS5:           Vector only             Decent results (no keyword)
No database:       Error                   Prompt user to run indexer

O recuperador verifica as capacidades disponíveis na inicialização e adapta sua estratégia de consulta. Um componente ausente degrada a qualidade, mas não causa erros. A única falha crítica é a ausência do arquivo de banco de dados.

Estatísticas de Produção

Medido em um vault de 16.894 arquivos, 49.746 chunks, banco de dados SQLite de 83 MB, Apple M3 Pro:

Métrica Valor
Total de arquivos 16.894
Total de chunks 49.746
Tamanho do banco de dados 83 MB
Latência de consulta BM25 (p50) 12ms
Latência de consulta vetorial (p50) 8ms
Latência da fusão RRF 3ms
Latência de busca ponta a ponta (p50) 23ms
Tempo de reindexação completa ~4 minutos
Tempo de reindexação incremental <10 segundos
Modelo de embeddings potion-base-8M (256-dim)
Pool de candidatos BM25 30
Pool de candidatos vetorial 30
Limite padrão de resultados 10
Orçamento padrão de tokens 4.000 tokens

Hashing de Conteúdo e Detecção de Alterações

O indexador precisa saber quais arquivos foram modificados desde a última execução de indexação. Esta seção aborda o mecanismo de detecção de alterações e a estratégia de hashing.

Comparação do Tempo de Modificação de Arquivos

O indexador armazena mtime_ns (tempo de modificação do arquivo em nanossegundos) para cada chunk na tabela chunks. Em uma execução incremental, o indexador:

  1. Varre o vault em busca de todos os arquivos .md nas pastas permitidas
  2. Lê o mtime_ns de cada arquivo a partir do sistema de arquivos
  3. Compara com o mtime_ns armazenado no banco de dados
  4. Identifica três categorias:
  5. Arquivos novos: caminho existe no sistema de arquivos, mas não no banco de dados
  6. Arquivos modificados: caminho existe em ambos, mas o mtime_ns é diferente
  7. Arquivos excluídos: caminho existe no banco de dados, mas não no sistema de arquivos
def get_stale_files(self, vault_mtimes):
    """Find files whose mtime changed or are new."""
    stored = dict(self.db.execute(
        "SELECT DISTINCT file_path, mtime_ns FROM chunks"
    ).fetchall())

    stale = []
    for path, mtime in vault_mtimes.items():
        if path not in stored or stored[path] != mtime:
            stale.append(path)
    return stale

def get_deleted_files(self, vault_paths):
    """Find files in database that no longer exist in vault."""
    stored_paths = set(r[0] for r in self.db.execute(
        "SELECT DISTINCT file_path FROM chunks"
    ).fetchall())
    return stored_paths - set(vault_paths)

Por Que mtime e Não Hash de Conteúdo

O hashing de conteúdo (SHA-256 do conteúdo do arquivo) seria mais confiável que a comparação de mtime — ele detectaria casos onde um arquivo foi tocado sem ser alterado (por exemplo, um git checkout restaurando o mtime original). No entanto, o hashing requer a leitura de cada arquivo em toda execução incremental. Para 16.894 arquivos, ler o conteúdo leva de 2 a 3 segundos. Ler os mtimes do sistema de arquivos leva menos de 100ms.

O trade-off: a comparação de mtime ocasionalmente aciona reindexações desnecessárias de arquivos não modificados (falsos positivos), mas nunca perde alterações reais. Falsos positivos custam algumas chamadas extras de embedding por execução. A diferença de velocidade (100ms vs 3 segundos) torna o mtime a escolha pragmática para um sistema que executa a cada interação com IA.

Tratamento de Exclusões

Quando um arquivo é excluído do vault, o indexador remove todos os seus chunks do banco de dados:

def remove_file(self, file_path):
    """Remove all chunks and vectors for a file."""
    chunk_ids = [r[0] for r in self.db.execute(
        "SELECT id FROM chunks WHERE file_path = ?",
        [file_path],
    ).fetchall()]

    for cid in chunk_ids:
        self.db.execute(
            "DELETE FROM chunk_vecs WHERE id = ?", [cid]
        )
    self.db.execute(
        "DELETE FROM chunks WHERE file_path = ?",
        [file_path],
    )

Tabelas FTS5 com sincronização de conteúdo requerem exclusão explícita via INSERT INTO chunks_fts(chunks_fts, rowid, ...) VALUES('delete', ?, ...) para cada linha removida. O indexador trata isso como parte do processo de remoção de arquivos.


Reindexação incremental vs completa

O indexador suporta dois modos: incremental (rápido, uso diário) e completo (lento, ocasional). Esta seção aborda quando usar cada um, as garantias de idempotência e a recuperação de corrupção.

Reindexação incremental

Quando usar: Indexação diária após editar notas. É o modo padrão.

O que faz: 1. Escaneia o vault em busca de alterações em arquivos (comparação de mtime) 2. Exclui chunks de arquivos deletados 3. Refaz o chunking e os embeddings dos arquivos alterados 4. Insere novos chunks para arquivos novos 5. Sincroniza o índice FTS5

Duração típica: <10 segundos para as edições de um dia em um vault de 16.000 arquivos.

python index_vault.py --incremental

Reindexação completa

Quando usar: - Após trocar o modelo de embedding (incompatibilidade de hash do modelo detectada) - Após migração de schema (novas colunas, índices alterados) - Após corrupção do banco de dados (verificação de integridade falha) - Quando a indexação incremental produz resultados inesperados

O que faz: 1. Remove todos os dados existentes (chunks, vetores, entradas FTS5) 2. Escaneia o vault inteiro 3. Faz o chunking de todos os arquivos 4. Gera embeddings de todos os chunks 5. Reconstrói o índice FTS5 do zero

Duração típica: ~4 minutos para 16.894 arquivos em um Apple M3 Pro.

python index_vault.py --full

Idempotência

Ambos os modos são idempotentes: executar o mesmo comando duas vezes produz o mesmo resultado. O indexador exclui os chunks existentes de um arquivo antes de inserir novos, então reexecutar a indexação incremental em um banco de dados já atualizado produz zero alterações. Reexecutar a indexação completa produz um banco de dados idêntico.

Recuperação de corrupção

Se o banco de dados SQLite ficar corrompido (queda de energia durante escrita, erro de disco, processo encerrado no meio de uma transação):

# Check integrity
sqlite3 vectors.db "PRAGMA integrity_check;"

# If corruption detected, full reindex rebuilds from source files
python index_vault.py --full

A fonte da verdade são sempre os arquivos do vault, não o banco de dados. O banco de dados é um artefato derivado que pode ser reconstruído a qualquer momento. Esta é uma propriedade de design fundamental: você nunca precisa fazer backup do banco de dados.

O flag --incremental

Quando o indexador é executado com --incremental:

  1. Verificação do hash do modelo. Compara o hash do modelo armazenado com o modelo atual. Se forem diferentes, alterna automaticamente para o modo de reindexação completa e avisa o usuário.
  2. Escaneamento de arquivos. Percorre as pastas permitidas, coletando caminhos de arquivos e mtimes.
  3. Detecção de alterações. Compara com os dados armazenados.
  4. Processamento em lote. Refaz o chunking e os embeddings dos arquivos alterados em lotes de 64.
  5. Relatório de progresso. Exibe a contagem de arquivos processados e o tempo decorrido.
  6. Encerramento gracioso. Trata SIGINT finalizando o arquivo atual antes de parar.

Filtragem de credenciais e limites de dados

Notas pessoais contêm segredos: chaves de API, bearer tokens, strings de conexão de banco de dados, chaves privadas coladas durante sessões de depuração. O filtro de credenciais impede que estes entrem no índice de recuperação.

O problema

Uma nota sobre a depuração de uma integração com OAuth pode conter:

The token was: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
I used this curl command:
  curl -H "Authorization: Bearer sk-ant-api03-abc123..."

Sem filtragem, tanto o JWT quanto a chave API seriam divididos em chunks, convertidos em embeddings e armazenados no banco de dados. Uma busca por “autenticação” retornaria o chunk contendo segredos reais. Pior ainda, se o retriever alimentar resultados para uma ferramenta de IA através de MCP, os segredos aparecem na janela de contexto da IA e potencialmente nos logs da ferramenta.

Filtragem baseada em padrões

O filtro de credenciais é executado em cada chunk antes do armazenamento, correspondendo 25 padrões específicos de fornecedores mais padrões genéricos:

Padrões específicos de fornecedores:

Padrão Exemplo Regex
Chave API OpenAI sk-... sk-[a-zA-Z0-9_-]{20,}
Chave API Anthropic sk-ant-api03-... sk-ant-api\d{2}-[a-zA-Z0-9_-]{20,}
PAT GitHub ghp_... gh[ps]_[a-zA-Z0-9]{36,}
Chave de acesso AWS AKIA... AKIA[0-9A-Z]{16}
Chave Stripe sk_live_... [sr]k_(live\|test)_[a-zA-Z0-9]{24,}
Token Cloudflare ... Diversos padrões

Padrões genéricos:

Padrão Detecção
Tokens JWT eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+
Bearer tokens Bearer\s+[a-zA-Z0-9_\-\.]+
Chaves privadas -----BEGIN (RSA\|EC\|OPENSSH) PRIVATE KEY-----
base64 de alta entropia Strings com >4,5 bits/caractere de entropia, 40+ caracteres
Atribuições de senha password\s*[:=]\s*["'][^"']+["']

Implementação do filtro

def clean_content(text):
    """Scrub credentials from text before indexing."""
    result = ScanResult(is_clean=True, match_count=0, patterns=[])

    for pattern in CREDENTIAL_PATTERNS:
        matches = pattern.regex.findall(text)
        if matches:
            text = pattern.regex.sub(
                f"[REDACTED:{pattern.name}]", text
            )
            result.is_clean = False
            result.match_count += len(matches)
            result.patterns.append(pattern.name)

    return text, result

Decisões de design fundamentais:

  1. Filtrar antes de gerar embeddings. O texto limpo é o que recebe o embedding. A representação vetorial nunca codifica padrões de credenciais. Uma consulta por “chave API” retorna notas que discutem gerenciamento de chaves API, não notas que contêm chaves reais.

  2. Substituir, não remover. O token [REDACTED:pattern-name] preserva o contexto semântico do texto ao redor. O embedding captura que “algo semelhante a uma credencial estava aqui” sem codificar a credencial em si.

  3. Registrar padrões, não valores. O filtro registra quais padrões foram encontrados (ex.: “Scrubbed 2 credential(s) from oauth-debug.md [jwt, bearer-token]”) mas nunca registra o valor da credencial.

Exclusão baseada em caminho

O arquivo .indexignore oferece exclusão de granularidade grossa por caminho. O filtro de credenciais oferece sanitização de granularidade fina dentro dos arquivos indexados. Ambos são necessários:

  • .indexignore para pastas inteiras que você sabe que contêm conteúdo sensível (notas de saúde, registros financeiros, documentos de carreira)
  • Filtro de credenciais para segredos acidentalmente incorporados em conteúdo que seria normalmente indexável

Classificação de dados

Para vaults com conteúdo diversificado, considere classificar as notas por sensibilidade:

Nível Exemplos Indexar? Filtrar?
Público Rascunhos de blog, notas técnicas Sim Sim
Interno Planos de projeto, decisões de arquitetura Sim Sim
Sensível Dados salariais, registros de saúde Não (.indexignore) N/A
Restrito Credenciais, chaves privadas Não (.indexignore) N/A

Arquitetura do Servidor MCP

O Model Context Protocol (MCP) expõe o recuperador como uma ferramenta que agentes de IA podem chamar. Esta seção aborda o design do servidor, a superfície de capacidades e os limites de permissão.

Escolha de Protocolo: STDIO vs HTTP

O MCP suporta dois modos de transporte:

STDIO — A ferramenta de IA inicia o servidor MCP como um processo filho e se comunica via stdin/stdout. Este é o modo padrão para ferramentas locais. Claude Code, Codex CLI e Cursor suportam servidores MCP STDIO.

{
  "mcpServers": {
    "obsidian": {
      "command": "python",
      "args": ["/path/to/obsidian_mcp.py"],
      "env": {
        "VAULT_PATH": "/path/to/vault",
        "DB_PATH": "/path/to/vectors.db"
      }
    }
  }
}

HTTP — O servidor MCP roda como um serviço HTTP independente. Útil para acesso remoto, configurações com múltiplos clientes ou configurações de equipe onde o vault está em um servidor compartilhado.

{
  "mcpServers": {
    "obsidian": {
      "url": "http://localhost:3333/mcp"
    }
  }
}

Recomendação: Use STDIO para vaults pessoais. É mais simples, mais seguro (sem exposição de rede) e o ciclo de vida do servidor é gerenciado pela ferramenta de IA. Use HTTP apenas quando múltiplas ferramentas ou múltiplas máquinas precisam de acesso simultâneo ao mesmo vault.

Evolução da Especificação MCP. A especificação MCP de junho de 2025 adicionou autorização OAuth 2.1, saídas estruturadas de ferramentas (esquemas de retorno tipados) e elicitação (prompts iniciados pelo servidor para o usuário). A versão de novembro de 2025 incluiu Streamable HTTP como modo de transporte de primeira classe, descoberta de URL via .well-known para navegação automática de capacidades do servidor, anotações estruturadas de ferramentas que declaram se uma ferramenta é somente leitura ou mutável, e um sistema de padronização de camadas SDK.1619 A próxima versão da especificação (provisoriamente meados de 2026) propõe operações assíncronas para tarefas de longa duração, extensões de protocolo específicas por domínio para setores como saúde e finanças, e padrões de comunicação entre agentes para fluxos de trabalho multi-agente.19 Para servidores de vault pessoais, STDIO continua sendo o caminho mais simples. O transporte Streamable HTTP e a descoberta via .well-known beneficiam principalmente implantações HTTP corporativas com roteamento multi-tenant e balanceamento de carga. Acompanhe o roadmap do MCP para atualizações que afetem sua escolha de transporte.

Design de Capacidades

O servidor MCP deve expor um conjunto mínimo de ferramentas:

search — A ferramenta principal. Executa a recuperação híbrida e retorna resultados ranqueados.

{
  "name": "obsidian_search",
  "description": "Search the Obsidian vault using hybrid BM25 + vector retrieval",
  "parameters": {
    "query": { "type": "string", "description": "Search query" },
    "limit": { "type": "integer", "default": 5 },
    "max_tokens": { "type": "integer", "default": 2000 }
  }
}

read_note — Lê o conteúdo completo de uma nota específica pelo caminho. Útil quando o agente deseja ver o contexto completo de um resultado de busca.

{
  "name": "obsidian_read_note",
  "description": "Read the full content of a note by file path",
  "parameters": {
    "file_path": { "type": "string", "description": "Relative path within vault" }
  }
}

list_notes — Lista notas que correspondem a um filtro (por pasta, tag, tipo ou intervalo de datas). Útil para exploração quando o agente não tem uma consulta específica.

{
  "name": "obsidian_list_notes",
  "description": "List notes matching filters",
  "parameters": {
    "folder": { "type": "string", "description": "Folder path within vault" },
    "tag": { "type": "string", "description": "Tag to filter by" },
    "limit": { "type": "integer", "default": 20 }
  }
}

get_context — Uma ferramenta de conveniência que executa uma busca e formata os resultados como um bloco de contexto adequado para injeção em uma conversa.

{
  "name": "obsidian_get_context",
  "description": "Get formatted context from vault for a topic",
  "parameters": {
    "topic": { "type": "string", "description": "Topic to get context for" },
    "max_tokens": { "type": "integer", "default": 2000 }
  }
}

Limites de Permissão

O servidor MCP deve aplicar limites rigorosos:

  1. Somente leitura. O servidor lê o vault e o banco de dados de índice. Ele não cria, modifica ou exclui notas. Operações de escrita (captura de novas notas) são tratadas por hooks ou skills separados, não pelo servidor MCP.

  2. Escopo limitado ao vault. O servidor lê apenas arquivos dentro do caminho de vault configurado. Tentativas de travessia de caminho (../../etc/passwd) devem ser rejeitadas.

  3. Saída com filtragem de credenciais. Mesmo que o banco de dados contenha conteúdo pré-filtrado, aplique filtragem de credenciais na saída como medida de defesa em profundidade.

  4. Respostas com limite de tokens. Aplique max_tokens em todas as respostas de ferramentas para evitar que a ferramenta de IA receba blocos de contexto excessivamente grandes.

Tratamento de Erros

As ferramentas MCP devem retornar mensagens de erro estruturadas que ajudem a ferramenta de IA a se recuperar:

def search(self, query, limit=5, max_tokens=2000):
    if not self.db_path.exists():
        return {
            "error": "Index database not found. Run the indexer first.",
            "suggestion": "python index_vault.py --full"
        }

    results = self.retriever.search(query, limit, max_tokens)

    if not results:
        return {
            "results": [],
            "message": f"No results found for '{query}'. Try broader terms."
        }

    return {
        "results": [
            {
                "file_path": r["file_path"],
                "section": r["section"],
                "text": r["chunk_text"],
                "score": round(r["rrf_score"], 4),
            }
            for r in results
        ],
        "count": len(results),
        "query": query,
    }

Integração com Claude Code

O Claude Code é o principal consumidor do sistema de recuperação do Obsidian. Esta seção aborda a configuração MCP, integração com hooks e o padrão obsidian_bridge.py.

Configuração MCP

Adicione o servidor MCP do Obsidian ao ~/.claude/settings.json:

{
  "mcpServers": {
    "obsidian": {
      "command": "python",
      "args": ["/path/to/obsidian_mcp.py"],
      "env": {
        "VAULT_PATH": "/absolute/path/to/vault",
        "DB_PATH": "/absolute/path/to/vectors.db"
      }
    }
  }
}

Após adicionar a configuração, reinicie o Claude Code. O servidor MCP será iniciado como um processo filho. Verifique se está em execução:

> What tools do you have from the obsidian MCP server?

O Claude Code deve listar as ferramentas disponíveis (obsidian_search, obsidian_read_note, etc.).

Integração com Hooks

Hooks estendem o comportamento do Claude Code em pontos definidos do ciclo de vida. Dois hooks são relevantes para a integração com o Obsidian:

Hook PreToolUse — Consulta o vault antes que o agente processe uma chamada de ferramenta. Injeta contexto relevante automaticamente.

#!/bin/bash
# ~/.claude/hooks/pre-tool-use/obsidian-context.sh
# Automatically inject vault context before tool execution

TOOL_NAME="$1"
PROMPT="$2"

# Only inject context for code-related tools
case "$TOOL_NAME" in
    Edit|Write|Bash)
        # Query the vault
        CONTEXT=$(python /path/to/retriever.py search "$PROMPT" --limit 3 --max-tokens 1500)
        if [ -n "$CONTEXT" ]; then
            echo "---"
            echo "Relevant vault context:"
            echo "$CONTEXT"
            echo "---"
        fi
        ;;
esac

Hook PostToolUse — Captura saídas significativas de ferramentas de volta ao vault para recuperação futura.

#!/bin/bash
# ~/.claude/hooks/post-tool-use/capture-insight.sh
# Capture significant outputs to vault (selective)

TOOL_NAME="$1"
OUTPUT="$2"

# Only capture substantial outputs
if [ ${#OUTPUT} -gt 500 ]; then
    python /path/to/capture.py --text "$OUTPUT" --source "claude-code-$TOOL_NAME"
fi

O Padrão obsidian_bridge.py

Um módulo bridge fornece uma Python API que hooks e skills podem chamar:

# obsidian_bridge.py
from retriever import HybridRetriever

_retriever = None

def get_retriever():
    global _retriever
    if _retriever is None:
        _retriever = HybridRetriever(
            db_path="/path/to/vectors.db",
            vault_path="/path/to/vault",
        )
    return _retriever

def search_vault(query, limit=5, max_tokens=2000):
    """Search vault and return formatted context."""
    retriever = get_retriever()
    results = retriever.search(query, limit, max_tokens)

    if not results:
        return ""

    lines = ["## Vault Context\n"]
    for r in results:
        lines.append(f"**{r['file_path']}** — {r['section']}")
        lines.append(f"> {r['chunk_text'][:500]}")
        lines.append("")

    return "\n".join(lines)

O Skill /capture

Um skill do Claude Code para capturar insights de volta ao vault:

/capture "OAuth token rotation requires both access and refresh token invalidation"
  --domain security
  --tags oauth,tokens

O skill cria uma nova nota em 00-inbox/ com frontmatter apropriado e aciona uma reindexação incremental para que a nova nota seja imediatamente pesquisável.

Gerenciamento da Janela de Contexto

A integração deve ser consciente da janela de contexto do Claude Code:

  • Limite o contexto injetado a 1.500-2.000 tokens por consulta. Mais do que isso compete com a memória de trabalho do agente.
  • Inclua atribuição de fonte. Sempre inclua o caminho do arquivo e o título da seção para que o agente possa referenciar a fonte.
  • Trunque o texto dos chunks. Chunks longos devem ser truncados com ... em vez de omitidos completamente. Os primeiros 300-500 caracteres geralmente contêm as informações-chave.
  • Não injete em toda chamada de ferramenta. O hook PreToolUse deve injetar contexto seletivamente com base na ferramenta sendo chamada. Operações de leitura não precisam de contexto do vault. Operações de escrita e edição se beneficiam dele.

Integração com Codex CLI

O Codex CLI se conecta a servidores MCP através do config.toml. O padrão de integração difere do Claude Code na sintaxe de configuração e na entrega de instruções.

Configuração MCP

Adicione ao .codex/config.toml ou ~/.codex/config.toml:

[mcp_servers.obsidian]
command = "python"
args = ["/path/to/obsidian_mcp.py"]

[mcp_servers.obsidian.env]
VAULT_PATH = "/absolute/path/to/vault"
DB_PATH = "/absolute/path/to/vectors.db"

Padrões do AGENTS.md

O Codex CLI lê o AGENTS.md para instruções em nível de projeto. Inclua orientações de busca no vault:

## Available Tools

### Obsidian Vault (MCP: obsidian)
Use the `obsidian_search` tool to find relevant context from the knowledge base.
Search the vault when you need:
- Background on a concept or pattern
- Prior decisions or rationale
- Reference material for implementation

Example queries:
- "authentication patterns in FastAPI"
- "how does the review aggregator work"
- "sqlite-vec configuration"

Diferenças em relação ao Claude Code

Recurso Claude Code Codex CLI
Configuração MCP settings.json config.toml
Hooks ~/.claude/hooks/ Não suportado
Skills ~/.claude/skills/ Não suportado
Arquivo de instruções CLAUDE.md AGENTS.md
Modos de aprovação --dangerously-skip-permissions suggest / auto-edit / full-auto

Diferença principal: O Codex CLI não suporta hooks. O padrão de injeção automática de contexto (hook PreToolUse) não está disponível. Em vez disso, inclua instruções explícitas no AGENTS.md orientando o agente a buscar no vault antes de iniciar o trabalho.


Cursor e outras ferramentas

O Cursor e outras ferramentas de IA que suportam MCP podem se conectar ao mesmo servidor MCP do Obsidian. Esta seção cobre a configuração para ferramentas comuns.

Cursor

Adicione ao .cursor/mcp.json na raiz do seu projeto:

{
  "mcpServers": {
    "obsidian": {
      "command": "python",
      "args": ["/path/to/obsidian_mcp.py"],
      "env": {
        "VAULT_PATH": "/absolute/path/to/vault",
        "DB_PATH": "/absolute/path/to/vectors.db"
      }
    }
  }
}

O arquivo .cursorrules do Cursor pode incluir instruções para usar o vault:

When working on implementation tasks, search the Obsidian vault
for relevant context before writing code. Use the obsidian_search
tool with descriptive queries about the concept you're implementing.

Matriz de compatibilidade

Ferramenta Suporte MCP Transporte Local da configuração
Claude Code Completo STDIO ~/.claude/settings.json
Codex CLI Completo STDIO .codex/config.toml
Cursor Completo STDIO .cursor/mcp.json
Windsurf Completo STDIO .windsurf/mcp.json
Continue.dev Parcial HTTP ~/.continue/config.json
Zed Em desenvolvimento STDIO Interface de configurações

Alternativa para ferramentas sem MCP

Para ferramentas que não suportam MCP, o retriever pode ser encapsulado como CLI:

# Search from command line
python retriever_cli.py search "query text" --limit 5

# Output formatted for copy-paste into any tool
python retriever_cli.py context "query text" --format markdown

O CLI gera texto estruturado que pode ser colado manualmente na entrada de qualquer ferramenta de IA. Isso é menos elegante que a integração via MCP, mas funciona universalmente.


Cache de prompts a partir de notas estruturadas

Notas estruturadas no vault podem servir como blocos de contexto reutilizáveis que reduzem o uso de tokens nas interações com IA. Esta seção cobre o design de chaves de cache e o gerenciamento de orçamento de tokens.

O padrão

Em vez de buscar contexto a cada interação, pré-construa blocos de contexto a partir de notas bem estruturadas do vault e armazene-os em cache:

# cache_keys.py
CONTEXT_BLOCKS = {
    "auth-patterns": {
        "vault_query": "authentication patterns implementation",
        "max_tokens": 1500,
        "ttl_hours": 24,  # Rebuild daily
    },
    "api-conventions": {
        "vault_query": "API design conventions REST patterns",
        "max_tokens": 1000,
        "ttl_hours": 168,  # Rebuild weekly
    },
    "project-architecture": {
        "vault_query": "current project architecture decisions",
        "max_tokens": 2000,
        "ttl_hours": 12,  # Rebuild twice daily
    },
}

Invalidação de cache

A invalidação de cache é baseada em dois sinais:

  1. Expiração do TTL. Cada bloco de contexto possui um tempo de vida (time-to-live). Quando o TTL expira, o bloco é reconstruído re-consultando o vault.
  2. Detecção de mudanças no vault. Quando o indexador detecta alterações em arquivos que contribuíram para um bloco de contexto em cache, o bloco é invalidado imediatamente.

Gerenciamento de orçamento de tokens

Uma sessão começa com um orçamento total de contexto. Os blocos em cache consomem parte desse orçamento:

Total context budget:    8,000 tokens
├─ System prompt:        1,500 tokens
├─ Cached blocks:        3,000 tokens (pre-loaded)
├─ Dynamic search:       2,000 tokens (on-demand)
└─ Conversation:         1,500 tokens (remaining)

Os blocos em cache são carregados no início da sessão. Os resultados de busca dinâmica preenchem o orçamento restante por consulta. Essa abordagem híbrida oferece ao agente uma base de contexto frequentemente necessário, preservando orçamento para consultas específicas.

Uso de tokens: antes e depois

Sem cache: Cada consulta relevante aciona uma busca no vault, retornando 1.500-2.000 tokens de contexto. Ao longo de 10 consultas em uma sessão, o agente consome 15.000-20.000 tokens de contexto do vault.

Com cache: Três blocos de contexto pré-construídos consomem 4.500 tokens no total. Buscas adicionais adicionam 1.500-2.000 tokens por consulta única. Ao longo de 10 consultas em que 6 são cobertas pelos blocos em cache, o agente consome 4.500 + (4 × 1.500) = 10.500 tokens — aproximadamente metade do uso sem cache.


Hooks PostToolUse para compressão de contexto

As saídas de ferramentas podem ser verbosas: stack traces, listagens de arquivos, resultados de testes. Um hook PostToolUse pode comprimir essas saídas antes que elas consumam espaço na janela de contexto.

O problema

Uma chamada da ferramenta Bash que executa testes pode retornar:

PASSED tests/test_auth.py::test_login_success
PASSED tests/test_auth.py::test_login_failure
PASSED tests/test_auth.py::test_token_refresh
PASSED tests/test_auth.py::test_session_expiry
... (200 more lines)
FAILED tests/test_api.py::test_rate_limit_exceeded

A saída completa tem 5.000 tokens, mas o sinal está em 2 linhas: 200 aprovados, 1 falhou.

Implementação do hook

#!/bin/bash
# ~/.claude/hooks/post-tool-use/compress-output.sh
# Compress verbose tool outputs to preserve context window

TOOL_NAME="$1"
OUTPUT="$2"
OUTPUT_LEN=${#OUTPUT}

# Only compress large outputs
if [ "$OUTPUT_LEN" -lt 2000 ]; then
    exit 0  # Pass through unchanged
fi

case "$TOOL_NAME" in
    Bash)
        # Compress test output
        if echo "$OUTPUT" | grep -q "PASSED\|FAILED"; then
            PASSED=$(echo "$OUTPUT" | grep -c "PASSED")
            FAILED=$(echo "$OUTPUT" | grep -c "FAILED")
            FAILURES=$(echo "$OUTPUT" | grep "FAILED")
            echo "Tests: $PASSED passed, $FAILED failed"
            if [ "$FAILED" -gt 0 ]; then
                echo "Failures:"
                echo "$FAILURES"
            fi
        fi
        ;;
esac

Prevenção de acionamento recursivo

Um hook de compressão que emite saída pode acionar a si mesmo se não for protegido:

# Guard against recursive invocation
if [ -n "$COMPRESS_HOOK_ACTIVE" ]; then
    exit 0
fi
export COMPRESS_HOOK_ACTIVE=1

Heurísticas de compressão

Tipo de saída Detecção Estratégia de compressão
Resultados de testes Palavras-chave PASSED / FAILED Contar aprovados/falhos, mostrar apenas falhas
Listagens de arquivos ls ou find no comando Truncar nas primeiras 20 entradas + contagem
Stack traces Palavra-chave Traceback Manter primeiro e último frame + mensagem de erro
Status do Git modified: / new file: Resumir contagens por status
Saída de build warning: / error: Remover linhas informativas, manter avisos/erros

Pipeline de recepção e triagem de sinais

A camada de recepção determina o que entra no vault. Sem curadoria, o vault acumula ruído. Esta seção aborda o pipeline de pontuação que direciona sinais para pastas de domínio.

Fontes

Os sinais vêm de múltiplos canais:

  • Feeds RSS: Blogs técnicos, avisos de segurança, notas de lançamento
  • Marcadores: Marcadores do navegador salvos via Obsidian Web Clipper ou bookmarklet
  • Newsletters: Trechos-chave de newsletters por e-mail
  • Captura manual: Anotações escritas durante leitura, conversas ou pesquisa
  • Saída de ferramentas: Saídas significativas de ferramentas de IA capturadas via hooks

Dimensões de pontuação

Cada sinal é pontuado em quatro dimensões (0,0 a 1,0 cada):

Dimensão Pergunta Pontuação baixa (0,0-0,3) Pontuação alta (0,7-1,0)
Relevância Isso se relaciona com meus domínios ativos? Tangencial, fora do escopo Diretamente relevante ao trabalho ativo
Acionabilidade Posso usar essa informação? Teoria pura, sem aplicação Técnica ou padrão específico que posso aplicar
Profundidade Quão substancial é o conteúdo? Manchetes, resumo superficial Análise detalhada com exemplos
Autoridade Quão confiável é a fonte? Blog anônimo, não verificado Fonte primária, revisada por pares, especialista reconhecido

Pontuação composta e roteamento

composite = (relevance * 0.35) + (actionability * 0.25) +
            (depth * 0.25) + (authority * 0.15)
Faixa de pontuação Ação
0,55+ Rotear automaticamente para pasta de domínio
0,40 - 0,55 Enfileirar para revisão manual
< 0,40 Descartar (não armazenar)

Roteamento por domínio

Sinais com pontuação acima de 0,55 são roteados para uma das 12 pastas de domínio com base em correspondência de palavras-chave e classificação de tópicos:

05-signals/
├── ai-tooling/        # Claude, LLMs, AI development tools
├── security/          # Vulnerabilities, auth, cryptography
├── systems/           # Architecture, distributed systems
├── programming/       # Languages, patterns, algorithms
├── web/               # Frontend, backends, APIs
├── data/              # Databases, data engineering
├── devops/            # CI/CD, containers, infrastructure
├── design/            # UI/UX, product design
├── mobile/            # iOS, Android, cross-platform
├── career/            # Industry trends, hiring, growth
├── research/          # Academic papers, whitepapers
└── other/             # Signals that don't fit a domain

Estatísticas de produção

Ao longo de 14 meses de operação:

Métrica Valor
Total de sinais processados 7.771
Roteados automaticamente (>0,55) 4.832 (62%)
Enfileirados para revisão (0,40-0,55) 1.543 (20%)
Descartados (<0,40) 1.396 (18%)
Pastas de domínio ativas 12
Média de sinais por dia ~18

Padrões de grafo de conhecimento

O grafo de wiki-link do Obsidian codifica relacionamentos entre notas. Esta seção aborda a semântica de links, travessia de grafo para expansão de contexto e anti-padrões que degradam a qualidade do grafo.

Cada wiki-link cria uma aresta direcionada no grafo. O Obsidian rastreia tanto links diretos quanto backlinks:

  • Link direto: A Nota A contém [[Nota B]] → A aponta para B
  • Backlink: A Nota B mostra que a Nota A a referencia

O grafo codifica diferentes tipos de relacionamentos dependendo do contexto:

Padrão de link Semântica Exemplo
Link inline “Está relacionado a” “Veja [[OAuth Token Rotation]] para detalhes”
Link em cabeçalho “Tem subtópico” ”## Relacionados\n- [[Token Rotation]]\n- [[Session Management]]”
Link tipo tag “É categorizado como” ”[[type/reference]]”
Link MOC “Faz parte de” Uma nota Maps of Content listando notas relacionadas

Maps of Content (MOCs)

MOCs são notas de índice que organizam notas relacionadas em uma estrutura navegável:

---
title: "Authentication & Security MOC"
type: moc
domain: security
---

## Core Concepts
- [[OAuth 2.0 Overview]]
- [[JWT Token Anatomy]]
- [[Session Management Patterns]]

## Implementation Patterns
- [[OAuth Token Rotation]]
- [[Refresh Token Security]]
- [[PKCE Flow Implementation]]

## Failure Modes
- [[Token Expiry Handling]]
- [[Session Fixation Prevention]]
- [[CSRF Defense Strategies]]

Os MOCs beneficiam a recuperação de duas formas:

  1. Correspondência direta. Uma busca por “visão geral de autenticação” corresponde ao próprio MOC, fornecendo ao agente uma lista curada de notas relacionadas.
  2. Expansão de contexto. Após encontrar uma nota específica, o recuperador pode verificar se a nota aparece em algum MOC e incluir a estrutura do MOC nos resultados, fornecendo ao agente um mapa do tópico mais amplo.

Travessia de grafo para expansão de contexto

Uma melhoria futura para o recuperador: após encontrar os melhores resultados, expandir o contexto seguindo links:

def expand_context(results, depth=1):
    """Follow wiki-links from top results to find related context."""
    expanded = set()
    for result in results:
        # Parse wiki-links from chunk text
        links = extract_wiki_links(result["chunk_text"])
        for link_target in links:
            # Resolve link to file path
            target_path = resolve_wiki_link(link_target)
            if target_path and target_path not in expanded:
                expanded.add(target_path)
                # Include target's most relevant chunk
                target_chunks = get_chunks_for_file(target_path)
                # ... rank and include best chunk
    return results + list(expanded_results)

Isso não está implementado no recuperador atual, mas representa uma extensão natural da estrutura de grafo.

Anti-padrões

Clusters órfãos. Grupos de notas que se ligam entre si, mas não possuem conexões com o restante do vault. O painel de grafo do Obsidian torna esses grupos visíveis como ilhas desconectadas. Clusters órfãos indicam MOCs ausentes ou links entre domínios faltando.

Proliferação de tags. Usar tags de forma inconsistente ou criar tags em granularidade excessiva. Um vault com 500 tags únicas em 5.000 notas tem em média 1 nota a cada 10 tags — as tags não são úteis para filtragem. Consolide para 20-50 tags de alto nível que mapeiem para suas pastas de domínio.

Notas com muitos links e pouco conteúdo. Notas que consistem inteiramente de wiki-links sem texto em prosa. Essas notas são indexadas de forma deficiente porque o chunker não tem texto para gerar embeddings. Adicione ao menos um parágrafo de contexto explicando por que as notas vinculadas estão relacionadas.

Links bidirecionais para tudo. Nem toda referência precisa ser um wiki-link. Mencionar “OAuth” de passagem não requer [[OAuth 2.0 Overview]]. Reserve wiki-links para relacionamentos intencionais e navegáveis, onde clicar no link forneceria contexto útil.


Receitas de workflow para desenvolvedores

Workflows práticos que combinam a recuperação do vault com tarefas diárias de desenvolvimento.

Carga de contexto matinal

Comece o dia carregando contexto relevante:

Search my vault for notes about [current project] updated in the last week

O recuperador retorna notas recentes sobre seu projeto ativo, proporcionando uma recapitulação rápida de onde você parou. Mais eficaz do que reler as mensagens de commit do dia anterior.

Captura de pesquisa durante a codificação

Enquanto implementa um recurso, capture insights sem sair do editor:

/capture "FastAPI dependency injection with async generators requires yield,
not return. The generator is the dependency lifecycle."
  --domain programming
  --tags fastapi,dependency-injection

O insight capturado é imediatamente indexado e disponível para recuperação futura. Ao longo de meses, essas micro-capturas constroem um corpus de conhecimento específico de implementação.

Início de projeto

Ao começar um novo projeto ou funcionalidade:

  1. Pesquise no vault: “O que eu sei sobre [tecnologia/padrão]?”
  2. Revise os 5 melhores resultados em busca de decisões anteriores e armadilhas
  3. Verifique se existe um MOC para o domínio; caso contrário, crie um
  4. Pesquise por modos de falha: “problemas com [tecnologia]”

Depuração com busca no vault

Ao encontrar um erro ou comportamento inesperado:

Search my vault for [error message or symptom]

Notas anteriores de depuração frequentemente contêm a causa raiz e a correção. Isso é particularmente valioso para problemas recorrentes entre projetos — o vault lembra o que você esquece.

Preparação para revisão de código

Antes de revisar um PR:

Search my vault for patterns and conventions about [module being changed]

O vault retorna decisões anteriores, restrições arquiteturais e padrões de codificação relevantes para o código em revisão. A revisão é informada por conhecimento institucional, não apenas pelo diff.

Otimização de Desempenho

Esta seção aborda estratégias de otimização para diferentes tamanhos de vault e padrões de uso.

Gerenciamento do Tamanho do Índice

Tamanho do Vault Chunks Tamanho do DB Reindexação Completa Incremental
500 notas ~1.500 3 MB 15 segundos <1 segundo
2.000 notas ~6.000 12 MB 45 segundos 2 segundos
5.000 notas ~15.000 30 MB 2 minutos 4 segundos
15.000 notas ~50.000 83 MB 4 minutos <10 segundos
50.000 notas ~150.000 250 MB 15 minutos 30 segundos

Com 50.000+ notas, considere: - Aumentar o batch size de 64 para 128 para geração de embeddings mais rápida - Usar o modo WAL (padrão) para acesso concorrente - Executar a reindexação completa fora do horário de pico

Otimização de Consultas

Modo WAL. O modo Write-Ahead Logging do SQLite permite leituras concorrentes enquanto o indexador escreve:

db.execute("PRAGMA journal_mode=WAL")

Isso é essencial quando o servidor MCP processa consultas enquanto o indexador executa uma atualização incremental.

Pool de conexões. O servidor MCP deve reutilizar conexões com o banco de dados em vez de abrir uma nova conexão por consulta. Uma única conexão de longa duração com o modo WAL suporta leituras concorrentes.

# MCP server initialization
db = sqlite3.connect(DB_PATH, check_same_thread=False)
db.execute("PRAGMA journal_mode=WAL")
db.execute("PRAGMA mmap_size=268435456")  # 256 MB mmap

I/O com mapeamento de memória. O pragma mmap_size instrui o SQLite a usar I/O com mapeamento de memória para o arquivo do banco de dados. Para um banco de dados de 83 MB, mapear o arquivo inteiro na memória elimina a maioria das leituras de disco.

Otimização do FTS5. Após uma reindexação completa, execute:

INSERT INTO chunks_fts(chunks_fts) VALUES('optimize');

Isso mescla os segmentos internos da b-tree do FTS5, reduzindo a latência de consulta para buscas subsequentes.

Benchmarks de Escalabilidade

Medido em Apple M3 Pro, 36 GB de RAM, SSD NVMe:

Operação 500 notas 5 mil notas 15 mil notas 50 mil notas
Consulta BM25 2ms 5ms 12ms 25ms
Consulta vetorial 1ms 3ms 8ms 20ms
Fusão RRF <1ms <1ms 3ms 5ms
Busca completa 3ms 8ms 23ms 50ms

Todos os benchmarks incluem acesso ao banco de dados, execução da consulta e formatação dos resultados. A latência de rede para comunicação MCP STDIO adiciona 1-2ms.


Solução de Problemas

Desatualização do Índice

Sintoma: A busca retorna resultados desatualizados ou não encontra notas adicionadas recentemente.

Causa: O indexador incremental não foi executado após adicionar notas, ou o mtime de um arquivo não foi atualizado (por exemplo, sincronizado de outra máquina com timestamps preservados).

Solução: Execute uma reindexação completa: python index_vault.py --full

Troca do Modelo de Embedding

Sintoma: Após trocar o modelo de embedding, a busca vetorial retorna resultados sem sentido.

Causa: Vetores antigos (do modelo anterior) estão sendo comparados com novos vetores de consulta. As dimensões ou a semântica do espaço vetorial são incompatíveis.

Solução: O indexador deve detectar a incompatibilidade do hash do modelo e acionar uma reindexação completa automaticamente. Se isso não acontecer, limpe o banco de dados manualmente e reindexe:

rm vectors.db
python index_vault.py --full

Manutenção do FTS5

Sintoma: Consultas FTS5 retornam resultados incorretos ou incompletos após muitas atualizações incrementais.

Causa: Os segmentos internos do FTS5 podem ficar fragmentados após muitas atualizações pequenas.

Solução: Reconstrua e otimize:

INSERT INTO chunks_fts(chunks_fts) VALUES('rebuild');
INSERT INTO chunks_fts(chunks_fts) VALUES('optimize');

Timeout do MCP

Sintoma: A ferramenta de IA informa que o servidor MCP expirou o tempo limite.

Causa: A primeira consulta aciona o carregamento do modelo (inicialização lazy), que leva de 2 a 5 segundos. O timeout padrão do MCP na ferramenta de IA pode ser menor.

Solução: Pré-aqueça o modelo na inicialização do servidor:

# In MCP server initialization
retriever = HybridRetriever(db_path, vault_path)
retriever.search("warmup", limit=1)  # Trigger model load

Bloqueios de Arquivo do SQLite

Sintoma: Erros SQLITE_BUSY ou SQLITE_LOCKED.

Causa: Múltiplos processos escrevendo no banco de dados simultaneamente. O modo WAL permite leituras concorrentes, mas apenas um escritor.

Solução: Garanta que apenas um processo (o indexador) escreva no banco de dados. O servidor MCP e os hooks devem apenas ler. Se você precisar de escritas concorrentes, use o modo WAL e defina um timeout de espera:

db.execute("PRAGMA busy_timeout=5000")  # Wait up to 5 seconds

sqlite-vec Não Carrega

Sintoma: A busca vetorial está desativada; o retriever opera apenas no modo BM25.

Causa: A extensão sqlite-vec não está instalada, não foi encontrada no caminho da biblioteca, ou é incompatível com a versão do SQLite.

Solução:

# Install via pip
pip install sqlite-vec

# Or compile from source
git clone https://github.com/asg017/sqlite-vec
cd sqlite-vec && make

Verifique se a extensão carrega:

import sqlite3
db = sqlite3.connect(":memory:")
db.enable_load_extension(True)
db.load_extension("vec0")
print("sqlite-vec loaded successfully")

Problemas de Memória em Vaults Grandes

Sintoma: Erros de falta de memória durante a reindexação completa de um vault grande (50.000+ notas).

Causa: O batch size de embedding é muito grande, ou todo o conteúdo dos arquivos é carregado na memória simultaneamente.

Solução: Reduza o batch size e processe os arquivos incrementalmente:

BATCH_SIZE = 32  # Reduce from 64

Também garanta que o indexador processe os arquivos um por vez (lendo, dividindo em chunks e gerando embeddings de cada arquivo antes de passar para o próximo) em vez de carregar todos os arquivos na memória.


Guia de Migração

Do Apple Notes

  1. Exporte o Apple Notes pela opção “Exportar Tudo” (macOS) ou use uma ferramenta de migração como apple-notes-liberator
  2. Converta as exportações HTML para markdown usando markdownify ou pandoc
  3. Mova os arquivos convertidos para a pasta 00-inbox/ do seu vault
  4. Revise e adicione frontmatter a cada nota
  5. Mova as notas para as pastas de domínio apropriadas

Do Notion

  1. Exporte do Notion: Configurações → Exportar → Markdown & CSV
  2. Descompacte a exportação na pasta 00-inbox/ do seu vault
  3. Corrija artefatos de markdown específicos do Notion:
  4. O Notion usa - [ ] para checklists — estes são markdown padrão
  5. O Notion inclui tabelas de propriedades como HTML — converta para frontmatter YAML
  6. O Notion incorpora imagens como caminhos relativos — copie as imagens para sua pasta de anexos
  7. Adicione frontmatter padrão (type, domain, tags)
  8. Substitua os links de página do Notion por wiki-links do Obsidian

Do Google Docs

  1. Use o Google Takeout para exportar todos os documentos
  2. Converta os arquivos .docx para markdown: pandoc -f docx -t markdown input.docx -o output.md
  3. Conversão em lote: for f in *.docx; do pandoc -f docx -t markdown "$f" -o "${f%.docx}.md"; done
  4. Mova para o vault, adicione frontmatter e organize em pastas

De Markdown Puro (Sem Obsidian)

Se você já tem um diretório de arquivos markdown:

  1. Abra o diretório como um vault do Obsidian (Obsidian → Abrir Vault → Abrir pasta)
  2. Adicione .obsidian/ ao .gitignore se o diretório estiver sob controle de versão
  3. Crie templates de frontmatter e aplique aos arquivos existentes
  4. Comece a vincular notas com [[wiki-links]] conforme você lê e organiza
  5. Execute o indexador imediatamente — o sistema de recuperação funciona desde o primeiro dia

De Outro Sistema de Recuperação

Se você está migrando de um sistema diferente de embedding/busca:

  1. Não tente migrar vetores. Modelos diferentes produzem espaços vetoriais incompatíveis. Execute uma reindexação completa com o novo modelo.
  2. Migre o conteúdo, não o índice. Os arquivos do vault são a fonte da verdade. O índice é um artefato derivado.
  3. Verifique após a migração. Execute de 10 a 20 consultas cujas respostas você já conhece e verifique se os resultados correspondem às suas expectativas.

Changelog

Data Alteração
03/03/2026 Atualização da evolução da especificação MCP (nov. 2025 lançado: Streamable HTTP, .well-known, tool annotations). Adição de fine-tuning do Model2Vec e suporte a tokenizador BPE/Unigram. Adição de tabela comparativa de servidores MCP da comunidade. Atualização do Smart Connections para v4.
02/03/2026 Adição de potion-base-32M e potion-retrieval-32M à comparação de modelos. Adição da seção de quantização/redução de dimensionalidade. Adição de nota sobre evolução da especificação MCP.
01/03/2026 Lançamento inicial

Referências


  1. Cormack, G.V., Clarke, C.L.A., e Buettcher, S. Reciprocal Rank Fusion outperforms Condorcet and individual Rank Learning Methods. SIGIR, 2009. Introduz o RRF com k=60 como um método livre de parâmetros para combinar listas ranqueadas. 

  2. OpenAI Embeddings Pricing. text-embedding-3-small: $0,02 por milhão de tokens. Custo estimado do vault por reindexação completa: ~$0,30. 

  3. van Dongen, T. et al. Model2Vec: Turn any Sentence Transformer into a Small Fast Model. arXiv, 2025. Descreve a abordagem de destilação que produz embeddings estáticos a partir de sentence transformers. 

  4. MTEB: Massive Text Embedding Benchmark. potion-base-8M obtém 50,03 de média vs 56,09 para all-MiniLM-L6-v2 (89% de retenção). 

  5. SQLite FTS5 Extension. FTS5 fornece busca de texto completo com ranqueamento BM25 e pesos de coluna configuráveis. 

  6. sqlite-vec: A vector search SQLite extension. Fornece tabelas virtuais vec0 para busca vetorial KNN dentro do SQLite. 

  7. Robertson, S. e Zaragoza, H. The Probabilistic Relevance Framework: BM25 and Beyond. Foundations and Trends in Information Retrieval, 2009. 

  8. Karpukhin, V. et al. Dense Passage Retrieval for Open-Domain Question Answering. EMNLP, 2020. Representações densas superam o BM25 em 9-19% em QA de domínio aberto. 

  9. Reimers, N. e Gurevych, I. Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks. EMNLP, 2019. Trabalho fundacional sobre similaridade semântica densa. 

  10. Luan, Y. et al. Sparse, Dense, and Attentional Representations for Text Retrieval. TACL, 2021. A recuperação híbrida supera consistentemente as abordagens de modalidade única no MS MARCO. 

  11. SQLite Write-Ahead Logging. Modo WAL para leituras concorrentes com um único escritor. 

  12. Gao, Y. et al. Retrieval-Augmented Generation for Large Language Models: A Survey. arXiv, 2024. Levantamento de arquiteturas RAG e estratégias de chunking. 

  13. Thakur, N. et al. BEIR: A Heterogeneous Benchmark for Zero-shot Evaluation of Information Retrieval Models. NeurIPS, 2021. 

  14. Model2Vec: Distill a Small Fast Model from any Sentence Transformer. Minish Lab, 2024. 

  15. Obsidian Documentation. Documentação oficial do Obsidian. 

  16. Model Context Protocol Specification. O padrão MCP para conectar ferramentas de IA a fontes de dados. 

  17. Dados de produção do autor. 16.894 arquivos, 49.746 chunks, 83,56 MB de banco de dados SQLite, 7.771 sinais processados ao longo de 14 meses. Latência de consulta medida via time.perf_counter()

  18. Model2Vec Potion Models. Minish Lab, 2025. Potion-base-32M (MTEB 52,46), potion-retrieval-32M (MTEB retrieval 36,35), e recursos de quantização/redução de dimensionalidade na v0.5.0+. 

  19. Update on the Next MCP Protocol Release. O lançamento de novembro de 2025 incluiu transporte Streamable HTTP, descoberta de URL .well-known, anotações estruturadas de ferramentas e padronização de níveis SDK. O próximo lançamento está previsto provisoriamente para meados de 2026, com operações assíncronas, extensões específicas de domínio e comunicação entre agentes. 

  20. Model2Vec Releases. v0.4.0 (fev 2025): suporte a treinamento/ajuste fino. v0.5.0 (abr 2025): reescrita do backend, quantização, redução de dimensionalidade. v0.7.0 (out 2025): quantização de vocabulário, suporte a tokenizadores BPE/Unigram. 

  21. Smart Connections for Obsidian. Smart Connections v4: embeddings de IA local-first, busca semântica funciona offline após a indexação inicial. 

VAULT obsidian.md INDEXED