obsidian:~/vault$ search --hybrid obsidian

Example vault location

#

words: 10560 read_time: 40m updated: 2026-03-02 07:54
$ 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 usa um servidor MCP da comunidade para resultados imediatos. As seções posteriores cobrem a construção de um pipeline de recuperação customizado 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 recuperador 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

O servidor comunitário obsidian-mcp fornece acesso imediato ao vault. Instale-o:

npm install -g obsidian-mcp-server

Passo 3: Configurar sua ferramenta de IA

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

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

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

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

Cursor — adicione ao .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 podem responder:

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

A ferramenta de IA chama o servidor MCP, que pesquisa seu vault e retorna 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 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 prova 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 de 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 (núcleo) $10/mês+ Gratuito Gratuito Gratuito

Quando o Obsidian é excessivo

  • Contexto de projeto único. Se a IA só precisa de contexto sobre o codebase atual, coloque em CLAUDE.md, AGENTS.md ou na documentação do projeto. Esses arquivos acompanham o repositório e são carregados automaticamente.
  • Dados estruturados. Se o conteúdo são tabelas, registros ou schemas, use um banco de dados. Notas do Obsidian são voltadas para prosa. O Dataview pode consultar campos do 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 compõe à medida que o corpus cresce. Um vault de 200 notas consultado diariamente por seis meses fornece mais valor do que um vault de 5.000 notas consultado uma 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 consegue fornecer.
  • 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 faria upload para um serviço na nuvem.

Modelo Mental: Três Camadas

O sistema possui três camadas que operam independentemente, 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 incompletos 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: dividir notas em unidades de busca, converter chunks em espaço vetorial, indexar para busca por palavras-chave e busca semântica, fundir resultados com RRF. A camada de retrieval transforma um diretório de arquivos em uma base de conhecimento consultável. Sem esta camada, o vault é navegável por meio de exploração manual e busca básica, mas não é programaticamente acessível para ferramentas de IA.

Integration conecta a camada de retrieval às ferramentas de IA. Um servidor MCP expõe a recuperação como uma ferramenta invocável. Hooks injetam contexto automaticamente. Skills capturam novo conhecimento de volta no 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 independentemente. Substitua o modelo de embedding sem alterar o pipeline de intake. Adicione uma nova capacidade MCP sem modificar o retriever. Altere as heurísticas de pontuação de sinais sem tocar no índice.


Arquitetura do vault para consumo por IA

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

Estrutura de pastas

Use prefixos numéricos para 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 percorrer.

vault/
├── 00-inbox/              # Capturas não organizadas, pendentes de triagem
├── 01-projects/           # Notas de projetos ativos
├── 02-areas/              # Áreas de responsabilidade contínuas
├── 03-resources/          # Material de referência por tópico
   ├── programming/
   ├── security/
   ├── ai-engineering/
   ├── design/
   └── devops/
├── 04-archive/            # Projetos concluídos, referências antigas
├── 05-signals/            # Sinais recebidos com pontuação
   ├── ai-tooling/
   ├── security/
   ├── systems/
   └── ...12 domain folders
├── 06-daily/              # Notas diárias (se utilizadas)
├── 07-templates/          # Templates de notas (excluídos do índice)
├── 08-attachments/        # Imagens, PDFs (excluídos do índice)
├── .obsidian/             # Configuração do Obsidian (excluída do índice)
└── .indexignore            # Caminhos a excluir do índice de recuperação

Pastas que devem ser indexadas: Tudo que contém 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 deseja no índice de recuperação.

O arquivo .indexignore

Crie um arquivo .indexignore na raiz do vault para excluir explicitamente caminhos do índice de recuperação. A sintaxe segue o padrão 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 divididos em chunks, nunca recebem embeddings e nunca aparecem nos resultados de busca.

Esquema de notas

Toda nota deve ter frontmatter YAML. O retriever usa campos de 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 recuperação:

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

Campos opcionais, mas valiosos:

  • domain — Permite consultas com escopo por domínio (“buscar apenas em 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 divide o conteúdo em chunks nos limites de cabeçalhos H2 (##). Isso significa que a estrutura da sua nota afeta diretamente a granularidade da recuperação:

Bom para recuperação:

## 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 chunks pesquisáveis independentemente. Cada chunk tem contexto suficiente para que o embedding capture seu significado. Uma consulta sobre “tratamento de token expirado” corresponde especificamente ao terceiro chunk.

Ruim para recuperação:

# 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 chunk grande. O embedding calcula a média de todos os tópicos da seção. Uma consulta sobre qualquer subtópico corresponde à nota inteira igualmente.

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 da recuperação:

  • 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 inclua um link para a 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 de 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 de RSS), revise e anote antes de incorporá-la ao vault permanente. Importações automáticas sem curadoria adicionam volume sem agregar valor recuperável.

Ecossistema de plugins para fluxos de trabalho com IA

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 de 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 ajuda 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 preenche automaticamente os campos created, type e domain. 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

## Referências

**Linter.** Aplica regras de formatação em todo o vault. Uma hierarquia de títulos consistente (H1 para 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: aplicar níveis sequenciais de títulos (sem pular de H1 para H3)
- Título do YAML: corresponder ao nome do arquivo
- Espaços finais: 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 exclusões acidentais. O Git também fornece dados de `mtime` que o indexador usa para detecção incremental de alterações.

### Plugins que ajudam na indexação

**Smart Connections.** Um plugin do Obsidian que fornece busca semântica com IA dentro do próprio Obsidian. Ele cria seu próprio índice de embeddings. Embora o sistema de recuperação neste 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 casos de uso diferentes: Smart Connections para descoberta no editor, o recuperador externo para integração com ferramentas de IA.

**Metadata Menu.** Fornece 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 incorporado em arquivos markdown. O JSON é markdown sintaticamente válido, mas produz lixo quando dividido em chunks e convertido em embeddings. Exclua 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 quadros Kanban do índice.

**Calendar.** Cria notas diárias com conteúdo mínimo (geralmente apenas um cabeçalho com 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ção de plugins que importa

**Recuperação de arquivos → Habilitado.** Protege contra exclusão acidental de notas. Não está diretamente relacionado à recuperação, mas é crítico para uma base de conhecimento da qual você depende.

**Quebras de linha estritas → Desabilitado.** 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 única 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 caixa de entrada é uma área de triagem; os arquivos são movidos para pastas de domínio após a classificação.

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

---

## Modelos de Embedding: Escolha e Configuração

O modelo de embedding converte chunks 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 execução. Esta seção explica por que o potion-base-8M do Model2Vec é a escolha padrão e quando escolher 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 BERT, MiniLM e outros modelos transformer fazem), o Model2Vec produz vetores através 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, porque não há computação sequencial.

No benchmark MTEB, o potion-base-8M atinge 89% do desempenho do all-MiniLM-L6-v2 (50,03 vs 56,09 na média).[^4] A diferença de 11% na qualidade é o trade-off pelas vantagens de velocidade e simplicidade. Para chunks curtos de 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, porque ambos os modelos convergem para representações similares em textos curtos e focados.

### Configuração

```python
# 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 lazy. O modelo é carregado no primeiro uso, não no momento da importação. Importar o módulo do embedder não custa nada quando o recuperador opera em modo de fallback apenas BM25 (por exemplo, quando o venv de embedding não está instalado).

Ambiente virtual isolado. O modelo é executado em um venv dedicado (por exemplo, ~/.claude/venvs/memory/) para evitar conflitos de dependência com o restante da 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 chunks para embed_batch() em vez de gerar embeddings de um chunk 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
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 all-MiniLM-L6-v2 quando a qualidade da recuperação importa mais do que a velocidade e você tem o PyTorch instalado. Os vetores de 384 dimensões aumentam o tamanho do banco de dados SQLite em ~50% comparado com vetores de 256 dimensões. A velocidade de embedding cai de <1 minuto para ~10 minutos para uma reindexação completa de 15.000 arquivos em hardware M-series.

Escolha nomic-embed-text-v1.5 quando você precisa da melhor qualidade de recuperação local possível e aceita uma 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 são trade-offs aceitáveis. A 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.

Fique com potion-base-8M em todos os outros casos. A vantagem de velocidade é crítica para indexação iterativa (reindexar 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.

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 uma reindexação completa 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 scores 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 recuperador volta ao modo apenas BM25. 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 uma reindexação completa. 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 mais de 50.000 chunks 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 Full-Text com FTS5

A extensão FTS5 do SQLite fornece busca full-text com ranqueamento BM25. O FTS5 é o componente de busca por palavras-chave do pipeline de recuperação híbrida (hybrid retrieval). Esta seção cobre 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 o requisito de armazenamento pela metade, mas significa que o FTS5 deve 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 ranqueia documentos por frequência de termos, frequência inversa de documentos e normalização por 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 predizem 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 vence

O BM25 se destaca em consultas contendo 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 ranquear 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 através 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 por 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 correspondência de substrings 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 o SQLite. Esta seção cobre a configuração do sqlite-vec, o pipeline de embeddings da nota ao 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 ao 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 vizinhos mais próximos. 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 vence

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 através 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 ranquear 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 Gradual

Se o sqlite-vec falhar ao carregar (extensão ausente, plataforma incompatível, biblioteca corrompida), o retriever 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 retriever 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 aborda 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, cenários predefinidos e um explorador visual de arquitetura, veja o aprofundamento sobre hybrid retrieval.

O Algoritmo

RRF atribui a cada documento uma pontuação baseada apenas na 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 baseado em 1 do documento na lista de resultados i - weight_i é um multiplicador opcional por lista (padrão 1.0)

Documentos que se classificam 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 exige 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 exige normalização, e os parâmetros de normalização dependem do conjunto de dados. 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 exigem 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 classifica review-aggregator.py na posição 3 (correspondências exatas de palavras-chave em “review,” “aggregator,” “disagreements”), mas coloca dois arquivos de configuração acima (eles correspondem a “review” de forma mais proeminente). A busca vetorial classifica 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 se classificam bem em ambas as listas sobem ao topo. Chunks que aparecem em apenas uma lista recebem uma pontuação de fonte única e ficam abaixo dos resultados com rank duplo. A lógica real de resolução de desacordos vence porque ambos os métodos a encontraram — BM25 por palavras-chave, busca vetorial por 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 quanto peso é dado aos resultados no topo do rank versus resultados em posições mais baixas:

  • k mais baixo (ex.: 10): Resultados no topo 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 ao fato de aparecer em múltiplas listas.
  • k mais alto (ex.: 200): Aparecer em ambas as listas importa muito mais do que a posição no rank. Rank 1 pontua 1/201, rank 10 pontua 1/210 — praticamente 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 encontrou esse valor robusto em diversos conjuntos de dados TREC. Ajuste somente 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 sem 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 nessa lista

O Pipeline Completo de Retrieval

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 retriever 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: adicionam-se resultados em ordem de ranking até que o orçamento se esgote.

Essa é uma estratégia conservadora. Uma abordagem mais sofisticada consideraria scores de qualidade por resultado e priorizaria resultados mais curtos e de maior qualidade em detrimento de resultados mais longos e de menor qualidade. A abordagem gulosa é mais simples e funciona bem na prática porque o ranking RRF já ordena os resultados por relevância.

Schema 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 Graceful

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 retriever 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 de 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 embedding 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 Mudanças

O indexador precisa saber quais arquivos mudaram desde a última execução de indexação. Esta seção cobre o mecanismo de detecção de mudanças e a estratégia de hashing.

Comparação de Tempo de Modificação de Arquivo

O indexador armazena o 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: o caminho existe no sistema de arquivos, mas não no banco de dados
  6. Arquivos modificados: o caminho existe em ambos, mas o mtime_ns difere
  7. Arquivos excluídos: o 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

Hashing de conteúdo (SHA-256 do conteúdo do arquivo) seria mais confiável do que a comparação de mtime — detectaria casos em que 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 dos arquivos leva 2-3 segundos. Ler os mtimes do sistema de arquivos leva <100ms.

O trade-off: a comparação de mtime ocasionalmente dispara reindexações desnecessárias de arquivos inalterados (falsos positivos), mas nunca deixa de detectar mudanças 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 em 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 arquivo.


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 de 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 alterar o modelo de embedding (divergência 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 todo o vault 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 os novos, então uma reexecução da indexação incremental em um banco de dados já atualizado não gera nenhuma alteração. Uma reexecução da indexação completa produz um banco de dados idêntico.

Recuperação de corrupção

Se o banco de dados SQLite for corrompido (queda de energia durante gravação, 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 de 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 crítica do design: você nunca precisa fazer backup do banco de dados.

A flag --incremental

Quando o indexador executa com --incremental:

  1. Verificação de 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 esses dados entrem no índice de recuperação.

O problema

Uma nota sobre 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 de API seriam divididos em chunks, convertidos em embeddings e armazenados no banco de dados. Uma busca por “authentication” 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 de API OpenAI sk-... sk-[a-zA-Z0-9_-]{20,}
Chave de API Anthropic sk-ant-api03-... sk-ant-api\d{2}-[a-zA-Z0-9_-]{20,}
PAT do 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 ... Vários 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/char 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 de API” retorna notas que discutem gerenciamento de chaves de 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 nos logs 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 fornece exclusão de granularidade grossa por caminho. O filtro de credenciais fornece limpeza 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 deveria ser indexado

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 retriever 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

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 executa 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 precisarem de acesso simultâneo ao mesmo vault.

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 quer 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 impor limites rigorosos:

  1. Somente leitura. O servidor lê o vault e o banco de dados do í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 só lê arquivos dentro do caminho configurado do vault. 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. Imponha 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

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?

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 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 para o 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 adequado 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 cabeçalho 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 a informação principal.
  • Não injete em todas as chamadas 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

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 para AGENTS.md

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

## Ferramentas Disponíveis

### Vault do Obsidian (MCP: obsidian)
Use a ferramenta `obsidian_search` para encontrar contexto relevante da base de conhecimento.
Pesquise no vault quando precisar de:
- Informações sobre um conceito ou padrão
- Decisões anteriores ou justificativas
- Material de referência para implementação

Consultas de exemplo:
- "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 pesquisar o 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 andamento 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 do 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 faça cache deles:

# 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 através de uma nova consulta ao vault.
  2. Detecção de alterações 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 do 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 fornece ao agente uma base de contexto frequentemente necessário, preservando orçamento para consultas específicas.

Antes/Depois do Uso de Tokens

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 acrescentam 1.500-2.000 tokens por consulta única. Ao longo de 10 consultas onde 6 são cobertas por 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 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 relevante está em 2 linhas: 200 passaram, 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 houver uma proteção:

# 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/reprovados, 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 Entrada e Triagem de Sinais

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

Fontes

Sinais vêm de múltiplos canais:

  • Feeds RSS: Blogs técnicos, avisos de segurança, notas de lançamento
  • Favoritos: Favoritos do navegador salvos via Obsidian Web Clipper ou bookmarklet
  • Newsletters: Trechos relevantes de newsletters por e-mail
  • Captura manual: Notas 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
Aplicabilidade Consigo 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 direcionados a 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 cobre 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 linka 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 de cabeçalho “Tem subtópico” ”## Relacionados\n- [[Token Rotation]]\n- [[Session Management]]”
Link tipo tag “Está 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]]

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 retriever pode verificar se a nota aparece em algum MOC e incluir a estrutura do MOC nos resultados, dando ao agente um mapa do tópico mais amplo.

Travessia de Grafo para Expansão de Contexto

Uma melhoria futura para o retriever: 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 retriever atual, mas representa uma extensão natural da estrutura do grafo.

Anti-Padrões

Clusters órfãos. Grupos de notas que se linkam entre si, mas não têm conexões com o restante do vault. O painel de grafo no 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. Uso inconsistente de tags ou criação de tags granulares demais. Um vault com 500 tags únicas em 5.000 notas tem uma média de 1 nota para cada 10 tags — as tags não são úteis para filtragem. Consolide para 20-50 tags de alto nível que correspondam às suas pastas de domínio.

Notas com muitos links e pouco conteúdo. Notas que consistem inteiramente de wiki-links sem texto descritivo. Essas notas são indexadas de forma precária porque o chunker não tem texto para gerar embeddings. Adicione pelo menos um parágrafo de contexto explicando por que as notas linkadas 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 recuperação do vault com tarefas diárias de desenvolvimento.

Carregamento de Contexto Matinal

Comece o dia carregando contexto relevante:

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

O retriever retorna notas recentes sobre seu projeto ativo, dando a você uma rápida atualização de onde parou. Mais eficaz do que reler as mensagens de commit de ontem.

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; se não, crie um
  4. Pesquise por modos de falha: “problemas com [tecnologia]”

Depuração com Pesquisa no Vault

Ao encontrar um erro ou comportamento inesperado:

Search my vault for [error message or symptom]

Notas de depuração anteriores 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 Code Review

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 cobre 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 embeddings mais rápidos - 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 é fundamental 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 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 mapeado em memória. O pragma mmap_size instrui o SQLite a usar I/O mapeado em 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 consultas para buscas subsequentes.

Benchmarks de Escalabilidade

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

Operação 500 notas 5K notas 15K notas 50K 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

Desalinhamento 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 a adição de notas, ou o mtime de um arquivo não foi atualizado (por exemplo, sincronizado de outra máquina com timestamps preservados).

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

Troca de Modelo de Embedding

Sintoma: Após alterar 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.

Correção: O indexador deve detectar a incompatibilidade de hash do modelo e acionar uma reindexação completa automaticamente. Se isso não ocorrer, 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.

Correçã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 relata que o servidor MCP expirou.

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 mais curto.

Correçã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.

Correçã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 no modo somente 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.

Correçã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 todos os conteúdos dos arquivos são carregados na memória simultaneamente.

Correçã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, fazendo chunking 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 — isso é 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 links de páginas do Notion por wiki-links do Obsidian

Do Google Docs

  1. Use o Google Takeout para exportar todos os documentos
  2. Converta 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, organize em pastas

De Markdown Puro (Sem Obsidian)

Se você já possui 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 versionado
  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 retrieval funciona desde o primeiro dia

De Outro Sistema de Retrieval

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 10 a 20 consultas cujas respostas você conhece e verifique se os resultados correspondem às suas expectativas.

Changelog

Data Alteração
01 de março de 2026 Versão inicial

Referências


  1. Cormack, G.V., Clarke, C.L.A., and 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 em 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. and 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. and Gurevych, I. Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks. EMNLP, 2019. Trabalho fundamental 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 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. Panorama das arquiteturas de 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, banco de dados SQLite de 83,56 MB, 7.771 sinais processados ao longo de 14 meses. Latência de consulta medida via time.perf_counter()

VAULT obsidian.md INDEXED