obsidian:~/vault$ search --hybrid obsidian

Example vault location

#

words: 12630 read_time: 58m updated: 2026-03-05 08:04
$ retriever search --hybrid obsidian

Conclusiones Clave

Ingeniería de contexto, no toma de notas. El valor de un vault de Obsidian para la IA no son las notas en sí mismas, sino la capa de recuperación que las hace consultables. Un vault de 16.000 archivos sin recuperación es una base de datos de solo escritura. Un vault de 200 archivos con búsqueda híbrida e integración MCP es una base de conocimiento para IA. La infraestructura de recuperación es el producto. Las notas son la materia prima.

La recuperación híbrida supera a la búsqueda puramente por palabras clave o puramente semántica. BM25 captura identificadores exactos y nombres de funciones. La búsqueda vectorial captura sinónimos y coincidencias conceptuales entre terminología diferente. Reciprocal Rank Fusion (RRF) combina ambos métodos sin requerir calibración de puntuaciones. Ningún método por sí solo cubre ambos modos de fallo. La investigación sobre MS MARCO passage ranking confirma el patrón: la recuperación híbrida supera consistentemente a cualquiera de los métodos de forma aislada.1 El análisis detallado del recuperador híbrido cubre las matemáticas de RRF, ejemplos prácticos con números reales, análisis de modos de fallo y una calculadora interactiva de fusión.

MCP otorga a las herramientas de IA acceso directo al vault. Los servidores de Model Context Protocol (MCP) exponen el recuperador como una herramienta que Claude Code, Codex CLI, Cursor y otras herramientas de IA pueden invocar directamente. El agente consulta el vault, recibe resultados clasificados con atribución de fuentes y utiliza el contexto sin cargar archivos completos. El servidor MCP es un envoltorio ligero alrededor del motor de recuperación.

Local-first significa cero costos de API y privacidad total. Toda la pila tecnológica se ejecuta en una sola máquina: SQLite para almacenamiento, Model2Vec para embeddings, FTS5 para búsqueda por palabras clave, sqlite-vec para KNN vectorial. Sin servicios en la nube, sin llamadas a API, sin dependencia de red. Las notas personales nunca salen de la máquina. El re-embedding completo de 49.746 fragmentos costaría aproximadamente $0,30 a precios de API de OpenAI, pero los costos reales son la latencia, la exposición de privacidad y la dependencia de red para un sistema que debería funcionar sin conexión.2

La indexación incremental mantiene el sistema actualizado en menos de 10 segundos. La comparación del tiempo de modificación de archivos detecta los cambios. Solo los archivos modificados se re-fragmentan y re-embeben. Una reindexación completa toma aproximadamente cuatro minutos en hardware Apple M-series. Las actualizaciones incrementales de las ediciones de un día típico se ejecutan en menos de diez segundos. El sistema se mantiene actualizado sin intervención manual.

La arquitectura escala de 200 a más de 20.000 notas. El mismo diseño de tres capas (ingesta, recuperación, integración) funciona con cualquier tamaño de vault. Comience con búsqueda solo por BM25 sobre un vault pequeño. Agregue búsqueda vectorial cuando las colisiones de palabras clave se conviertan en un problema. Agregue fusión RRF cuando necesite coincidencias tanto exactas como semánticas. Cada capa es independientemente útil e independientemente removible.


Cómo Usar Esta Guía

Esta guía cubre el sistema completo. Su punto de partida depende de dónde se encuentre:

Usted es… Comience aquí Luego explore
Nuevo en Obsidian + IA ¿Por qué Obsidian para infraestructura de IA?, Inicio rápido Arquitectura del vault, Arquitectura del servidor MCP
Vault existente, desea acceso de IA Arquitectura del servidor MCP, Integración con Claude Code Modelos de embedding, Búsqueda de texto completo
Construyendo un sistema de recuperación El pipeline de recuperación completo, Reciprocal Rank Fusion Optimización de rendimiento, Solución de problemas
Contexto de equipo o empresa Marco de decisión, Patrones de grafo de conocimiento Recetas de flujo de trabajo para desarrolladores, Guía de migración

Las secciones marcadas como Contrato incluyen detalles de implementación, bloques de configuración y modos de fallo. Las secciones marcadas como Narrativa se enfocan en conceptos, decisiones de arquitectura y el razonamiento detrás de las decisiones de diseño. Las secciones marcadas como Receta proporcionan flujos de trabajo paso a paso.


¿Por Qué Obsidian para Infraestructura de IA?

La tesis de esta guía: Los vaults de Obsidian son el mejor sustrato para bases de conocimiento personales de IA porque son local-first, en texto plano, con estructura de grafo, y el usuario controla cada capa de la pila tecnológica.

Lo que Obsidian ofrece a la IA que las alternativas no ofrecen

Archivos markdown en texto plano. Cada nota es un archivo .md en su sistema de archivos. Sin formato propietario, sin exportación de base de datos, sin API requerida para leer el contenido. Cualquier herramienta que lea archivos puede leer su vault. grep, ripgrep, pathlib de Python, SQLite FTS5 — todos funcionan directamente sobre los archivos fuente. Cuando construye un sistema de recuperación, está indexando archivos, no respuestas de API. El índice siempre es consistente con la origen porque el origen es el sistema de archivos.

Arquitectura local-first. El vault reside en su máquina. Sin servidor, sin dependencia de sincronización en la nube, sin límites de tasa de API, sin términos de servicio que regulen cómo procesa su propio contenido. Puede embeber, indexar, fragmentar y buscar en sus notas sin ningún servicio externo. Esto importa para la infraestructura de IA porque el pipeline de recuperación se ejecuta tan rápido como su disco lo permita, no tan rápido como responda un endpoint de API. También importa para la privacidad: las notas personales que contienen credenciales, datos de salud, información financiera y reflexiones privadas nunca salen de su máquina.

Estructura de grafo a través de wiki-links. La sintaxis [[wiki-link]] de Obsidian crea un grafo dirigido a través de las notas. Una nota sobre la implementación de OAuth enlaza a notas sobre rotación de tokens, gestión de sesiones y seguridad de API. La estructura de grafo codifica relaciones curadas por humanos entre conceptos. Los embeddings vectoriales capturan similitud semántica, pero los wiki-links capturan conexiones intencionales que el autor realizó mientras reflexionaba sobre el tema. El grafo es una señal que los embeddings no pueden replicar.

Ecosistema de plugins. Obsidian cuenta con más de 1.800 plugins comunitarios. Dataview consulta su vault como una base de datos. Templater genera notas a partir de plantillas con lógica de JavaScript. La integración con Git sincroniza su vault con un repositorio. Linter impone consistencia de formato. Estos plugins agregan estructura al vault sin cambiar el formato subyacente de texto plano. El sistema de recuperación indexa la salida de estos plugins, no los plugins en sí mismos.

Más de 5 millones de usuarios. Obsidian tiene una comunidad activa y amplia que produce plantillas, flujos de trabajo, plugins y documentación. Cuando encuentra un problema con la organización del vault o la configuración de plugins, es probable que alguien haya documentado una solución. La comunidad también produce herramientas adyacentes a Obsidian: servidores MCP, scripts de indexación, pipelines de publicación y envoltorios de API.

Lo que un sistema de archivos por sí solo no le ofrece

Un directorio de archivos markdown tiene la ventaja del texto plano, pero carece de tres cosas que Obsidian agrega:

  1. Enlaces bidireccionales. Obsidian rastrea backlinks automáticamente. Cuando enlaza desde la Nota A hacia la Nota B, la Nota B muestra que la Nota A la referencia. El panel de grafo visualiza grupos de conexiones. Esta conciencia bidireccional es metadata que un sistema de archivos sin procesar no proporciona.

  2. Vista previa en vivo con renderizado de plugins. Las consultas de Dataview, los diagramas Mermaid y los bloques de llamada se renderizan en tiempo real. La experiencia de escritura es más rica que la de un editor de texto, mientras que el formato de almacenamiento permanece en texto plano. Usted escribe y organiza en un entorno enriquecido; el sistema de recuperación indexa el markdown sin procesar.

  3. Infraestructura comunitaria. Descubrimiento de plugins, mercado de temas, servicio de sincronización (opcional), servicio de publicación (opcional) y un ecosistema de documentación. Puede replicar cualquier función individual con herramientas independientes, pero Obsidian las empaqueta en un flujo de trabajo coherente.

Lo que Obsidian NO hace (y lo que usted construye)

Obsidian no incluye infraestructura de recuperación. Tiene búsqueda básica (texto completo, nombre de archivo, etiqueta) pero no pipeline de embedding, no búsqueda vectorial, no clasificación por fusión, no servidor MCP, no filtrado de credenciales, no estrategia de fragmentación y no hooks de integración para herramientas de IA externas. Esta guía cubre la infraestructura que usted construye sobre Obsidian. El vault es el sustrato. El pipeline de recuperación, el servidor MCP y los hooks de integración son la infraestructura.

La arquitectura descrita aquí es markdown-first, no exclusiva de Obsidian. Si utiliza Logseq, Foam, Dendron o un directorio simple de archivos markdown, el pipeline de recuperación funciona de manera idéntica. El fragmentador lee archivos .md. El embebedor procesa cadenas de texto. El indexador escribe en SQLite. Ninguno de estos componentes depende de funciones específicas de Obsidian. La contribución de Obsidian es el entorno de escritura y organización que produce los archivos markdown que el recuperador indexa.


Inicio Rápido: Su Primera Bóveda Conectada a IA

Esta sección le permite conectar una bóveda a una herramienta de IA en cinco minutos. Instalará Obsidian, creará una bóveda, instalará un servidor MCP y ejecutará su primera consulta. El inicio rápido utiliza un servidor MCP de la comunidad para obtener resultados inmediatos. Las secciones posteriores cubren la construcción de un pipeline de recuperación personalizado para uso en producción.

Requisitos Previos

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

Paso 1: Crear una bóveda

Descargue Obsidian desde obsidian.md y cree una nueva bóveda. Elija una ubicación que pueda recordar — el servidor MCP necesita la ruta absoluta.

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

Agregue algunas notas para darle al recuperador algo con lo que trabajar. Incluso 10-20 notas son suficientes para ver resultados. Cada nota debe ser un archivo .md con un título significativo y al menos un párrafo de contenido.

Paso 2: Instalar un servidor MCP

Varios servidores MCP de la comunidad proporcionan acceso inmediato a la bóveda. El ecosistema ha crecido significativamente durante 2025-2026:

Servidor Autor Transporte Requiere Plugin Función Principal
obsidian-mcp-server StevenStavrakis STDIO No Ligero, basado en archivos
mcp-obsidian MarkusPfundstein STDIO API REST local CRUD completo de bóveda vía REST
obsidian-mcp-tools jacksteamdev STDIO Sí (plugin) Búsqueda semántica + Templater
obsidian-claude-code-mcp iansinnott WebSocket Sí (plugin) Auto-descubrimiento para Claude Code
obsidian-mcp-server cyanheads STDIO API REST local Gestión de tags y frontmatter

Para el inicio rápido, la opción más sencilla es un servidor basado en archivos que lee archivos .md directamente:

npm install -g obsidian-mcp-server

Paso 3: Configurar su herramienta de IA

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

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

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

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

Cursor — agregue a .cursor/mcp.json:

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

Paso 4: Ejecutar su primera consulta

Abra su herramienta de IA y haga una pregunta que sus notas de la bóveda puedan responder:

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

La herramienta de IA llama al servidor MCP, que busca en su bóveda y devuelve el contenido coincidente. Debería ver resultados con rutas de archivos y extractos relevantes.

Lo que acaba de construir

Conectó una base de conocimiento local a una herramienta de IA a través de un protocolo estándar. El servidor MCP lee los archivos de su bóveda, realiza una búsqueda básica y devuelve resultados. Esta es la versión mínima viable.

Lo que este inicio rápido NO le proporciona: - Recuperación híbrida (BM25 + búsqueda vectorial + fusión RRF) - Búsqueda semántica basada en embeddings - Filtrado de credenciales - Indexación incremental - Inyección automática de contexto basada en hooks

El resto de esta guía cubre la construcción de cada una de estas capacidades. El inicio rápido demuestra el concepto. El pipeline completo ofrece recuperación con calidad de producción.


Marco de Decisión: Obsidian vs Alternativas

No todos los casos de uso necesitan Obsidian. Esta sección mapea cuándo Obsidian es el sustrato adecuado, cuándo es excesivo y cuándo otra cosa encaja mejor.

Árbol de Decisión

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 Comparación

Criterio Obsidian Notion Apple Notes Sistema de Archivos CLAUDE.md
Local-first No (nube) Parcial (iCloud)
Texto plano Sí (markdown) No (bloques) No (propietario)
Estructura de grafo Sí (wiki-links) Parcial (menciones) No No No
Indexable por IA Acceso directo a archivos Requiere API Requiere exportación Acceso directo a archivos Ya en contexto
Ecosistema de plugins Más de 1.800 plugins Integraciones Ninguno N/A N/A
Funciona sin conexión Completo Solo lectura en caché Parcial Completo Completo
Escala a más de 10K notas Sí (con API) Se degrada No (archivo único)
Costo Gratis (núcleo) $10/mes+ Gratis Gratis Gratis

Cuándo Obsidian es excesivo

  • Contexto de un solo proyecto. Si la IA solo necesita contexto sobre el código base actual, colóquelo en CLAUDE.md, AGENTS.md o documentación a nivel de proyecto. Estos archivos viajan con el repositorio y se cargan automáticamente.
  • Datos estructurados. Si el contenido son tablas, registros o esquemas, utilice una base de datos. Las notas de Obsidian están orientadas a prosa. Dataview puede consultar campos de frontmatter, pero una base de datos real maneja consultas estructuradas de mejor manera.
  • Investigación temporal. Si las notas se descartarán después de que termine el proyecto, un directorio temporal con archivos markdown es más sencillo. No construya infraestructura de recuperación para contenido efímero.

Cuándo Obsidian es la elección correcta

  • Conocimiento acumulado durante meses o años. El valor se compone a medida que el corpus crece. Una bóveda de 200 notas consultada diariamente durante seis meses proporciona más valor que una bóveda de 5.000 notas consultada una sola vez.
  • Múltiples dominios en un solo corpus. Una bóveda que contiene notas sobre programación, arquitectura, seguridad, diseño y proyectos personales se beneficia de la recuperación entre dominios que un CLAUDE.md específico de proyecto no puede proporcionar.
  • Contenido sensible en cuanto a privacidad. Local-first significa que el pipeline de recuperación nunca envía contenido a servicios externos. La bóveda contiene lo que usted coloque en ella, incluyendo contenido que no subiría a un servicio en la nube.

Modelo Mental: Tres Capas

El sistema tiene tres capas que operan de forma independiente pero que se potencian cuando se combinan. Cada capa tiene una responsabilidad distinta y un modo de fallo 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        
└─────────────────────────────────────────────────────┘

Ingesta determina qué entra en la bóveda. Sin curación, la bóveda acumula ruido: capturas de pantalla de tweets, artículos copiados y pegados sin anotaciones, pensamientos a medio terminar sin contexto. La capa de ingesta es responsable del control de calidad en el punto de entrada. Un pipeline de puntuación, una convención de etiquetado o un proceso de revisión manual — cualquier mecanismo que asegure que la bóveda contenga contenido que valga la pena recuperar.

Recuperación hace que la bóveda sea consultable. Este es el motor: fragmentar notas en unidades de búsqueda, convertir fragmentos en vectores en el espacio vectorial (embeddings), indexar para búsqueda por palabras clave y semántica, y fusionar resultados con RRF. La capa de recuperación transforma un directorio de archivos en una base de conocimiento consultable. Sin esta capa, la bóveda es navegable mediante exploración manual y búsqueda básica, pero no es accesible programáticamente para herramientas de IA.

Integración conecta la capa de recuperación con las herramientas de IA. Un servidor MCP expone la recuperación como una herramienta invocable. Los hooks inyectan contexto automáticamente. Los skills capturan nuevo conocimiento de vuelta a la bóveda. La capa de integración es la interfaz entre la base de conocimiento y los agentes de IA que la consumen.

Las capas están desacopladas por diseño. El pipeline de puntuación de ingesta no sabe nada sobre embeddings. El recuperador no sabe nada sobre las reglas de enrutamiento de señales. El servidor MCP no sabe nada sobre cómo se crearon las notas. Este desacoplamiento significa que puede mejorar cualquier capa de forma independiente. Reemplace el modelo de embeddings sin cambiar el pipeline de ingesta. Agregue una nueva capacidad MCP sin modificar el recuperador. Cambie las heurísticas de puntuación de señales sin tocar el índice.


Arquitectura de la Bóveda para Consumo por IA

Una bóveda optimizada para recuperación por IA sigue convenciones diferentes a una bóveda optimizada para navegación personal. Esta sección cubre la estructura de carpetas, el esquema de notas, las convenciones de frontmatter y los patrones específicos que mejoran la calidad de la recuperación.

Estructura de Carpetas

Utilice prefijos numéricos para las carpetas de nivel superior para crear una jerarquía organizacional predecible. Los números no implican prioridad — agrupan dominios relacionados y hacen que la estructura sea fácil de escanear.

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

Carpetas que deben indexarse: Todo lo que contenga prosa en markdown — proyectos, áreas, recursos, señales, notas diarias.

Carpetas que deben excluirse de la indexación: Plantillas (contienen variables de marcador de posición, no contenido), archivos adjuntos (archivos binarios), configuración de Obsidian y cualquier carpeta que contenga contenido sensible que no desee incluir en el índice de recuperación.

El archivo .indexignore

Cree un archivo .indexignore en la raíz de la bóveda para excluir explícitamente rutas del índice de recuperación. La sintaxis es la misma que .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/

El indexador lee este archivo antes de escanear y omite completamente las rutas coincidentes. Los archivos en rutas excluidas nunca se fragmentan, nunca se convierten en embeddings y nunca aparecen en los resultados de búsqueda.

Esquema de Notas

Cada nota debe tener frontmatter YAML. El recuperador utiliza los campos de frontmatter para filtrado y enriquecimiento 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 requeridos para la recuperación:

  • title — Se utiliza en la visualización de resultados de búsqueda y en el contexto de encabezados para BM25
  • type — Permite consultas filtradas por tipo (“mostrar solo MOCs” o “solo señales”)
  • tags — Se indexan en el contexto de encabezados de FTS5 con un peso de 0.3, proporcionando coincidencias por palabras clave incluso cuando el cuerpo utiliza terminología diferente

Campos opcionales pero valiosos:

  • domain — Permite consultas con alcance por dominio (“buscar solo en notas de seguridad”)
  • source — Atribución para contenido capturado; el recuperador puede incluir URLs de origen en los resultados
  • status — Permite excluir notas archivadas o en borrador de la búsqueda activa

Convenciones de Fragmentación (Chunking)

El recuperador fragmenta en los límites de encabezados H2 (##). Esto significa que la estructura de sus notas afecta directamente la granularidad de la recuperación:

Bueno para la recuperación:

## 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...

Tres secciones H2 producen tres fragmentos buscables de forma independiente. Cada fragmento tiene suficiente contexto para que el embedding capture su significado. Una consulta sobre “manejo de tokens expirados” coincide específicamente con el tercer fragmento.

Deficiente para la recuperación:

# 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...

Una sola sección larga sin encabezados H2 produce un único fragmento grande. El embedding promedia todos los temas de la sección. Una consulta sobre cualquier subtema coincide con la nota completa por igual.

Regla general: Si una sección cubre más de un concepto, divídala en subsecciones H2. El fragmentador se encarga del resto.

Qué No Incluir en las Notas

Contenido que degrada la calidad de la recuperación:

  • Copias sin procesar de artículos completos sin anotaciones. El recuperador indexa las palabras clave del artículo original, diluyendo su bóveda con contenido que usted no escribió. Agregue un resumen, extraiga los puntos clave o enlace a la URL de origen en su lugar.
  • Capturas de pantalla sin descripción textual. El recuperador indexa texto en markdown. Una imagen sin texto alternativo o descripción circundante es invisible tanto para BM25 como para la búsqueda vectorial.
  • Cadenas de credenciales. Claves API, tokens, contraseñas, cadenas de conexión. Incluso con filtrado de credenciales, el enfoque más seguro es nunca pegar secretos en las notas. Refiérase a ellos por nombre (“el token API de Cloudflare en ~/.env”) en su lugar.
  • Contenido autogenerado sin curación. Si una herramienta genera una nota (transcripción de reunión, destacados de Readwise, importación de RSS), revísela y anótela antes de que entre en la bóveda permanente. Las importaciones automáticas sin curar agregan volumen sin agregar valor recuperable.

Ecosistema de plugins para flujos de trabajo con IA

Los plugins de Obsidian que mejoran la calidad del vault para la recuperación con IA se dividen en tres categorías: estructurales (imponen consistencia), de consulta (exponen metadatos) y de sincronización (mantienen el vault actualizado).

Plugins esenciales

Dataview. Consulta su vault como una base de datos utilizando campos de frontmatter. Cree índices dinámicos: “todas las notas etiquetadas con security actualizadas en los últimos 30 días” o “todas las notas de proyecto con estado active.” Dataview no ayuda directamente con la recuperación, pero le permite identificar vacíos en la cobertura de su vault y encontrar notas que necesitan actualización.

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

Templater. Crea notas a partir de plantillas con campos dinámicos. Asegúrese de que cada nota nueva comience con el frontmatter correcto utilizando una plantilla que pre-llene los campos created, type y domain. Un frontmatter consistente mejora el filtrado en la recuperación.

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

## Key Points

## Details

## References

Linter. Aplica reglas de formato en todo el vault. Una jerarquía de encabezados consistente (H1 para el título, H2 para secciones, H3 para subsecciones) garantiza que el chunker produzca resultados predecibles. Reglas de Linter que importan para la recuperación:

  • Incremento de encabezados: aplica niveles de encabezado secuenciales (sin saltar de H1 a H3)
  • YAML title: debe coincidir con el nombre del archivo
  • Espacios finales: eliminar (evita artefactos de tokenización en FTS5)
  • Líneas en blanco consecutivas: limitar a 1 (chunks más limpios)

Integración con Git. Control de versiones para su vault. Rastree cambios a lo largo del tiempo, sincronice entre máquinas y recupérese de eliminaciones accidentales. Git también proporciona datos de mtime que el indexador utiliza para la detección incremental de cambios.

Plugins que ayudan a la indexación

Smart Connections. Un plugin de Obsidian que proporciona búsqueda semántica impulsada por IA dentro del propio Obsidian. Smart Connections v4 crea embeddings locales por defecto — una vez que su vault está indexado, las conexiones semánticas y la búsqueda funcionan completamente sin conexión, sin llamadas a API.21 Aunque el sistema de recuperación de esta guía es externo a Obsidian (se ejecuta como un pipeline de Python), Smart Connections es útil para explorar relaciones semánticas mientras escribe. Ambos sistemas indexan el mismo contenido pero sirven para casos de uso diferentes: Smart Connections para el descubrimiento dentro del editor, el recuperador externo para la integración con herramientas de IA a través de MCP.

Metadata Menu. Proporciona edición estructurada de frontmatter con autocompletado para valores de campos. Reduce errores tipográficos en los campos type, domain y tags. Metadatos consistentes mejoran la precisión del filtrado en la recuperación.

Plugins que perjudican la indexación

Excalidraw. Almacena dibujos como JSON incrustado en archivos markdown. El JSON es markdown sintácticamente válido, pero produce resultados inservibles cuando se divide en chunks y se convierte en embeddings. Excluya los archivos de Excalidraw del índice mediante .indexignore o filtre por extensión de archivo.

Kanban. Almacena el estado del tablero como markdown con formato especial. El formato está diseñado para la visualización Kanban, no para la recuperación de prosa. El chunker produce fragmentos de títulos de tarjetas y metadatos que no generan buenos embeddings. Excluya los tableros Kanban del índice.

Calendar. Crea notas diarias con contenido mínimo (a menudo solo un encabezado con la fecha). Las notas vacías o casi vacías producen chunks de baja calidad. Si utiliza notas diarias, escriba contenido sustancial en ellas o excluya la carpeta de notas diarias del índice.

Configuración de plugins que importa

Recuperación de archivos → Habilitado. Protege contra la eliminación accidental de notas. No está directamente relacionado con la recuperación, pero es crítico para una base de conocimiento de la que depende.

Saltos de línea estrictos → Deshabilitado. Los saltos de línea estándar de markdown (doble salto de línea para párrafo) producen chunks más limpios que el modo estricto de Obsidian (salto de línea simple para <br>).

Ubicación predeterminada de archivos nuevos → Carpeta designada. Dirija los archivos nuevos a 00-inbox/ para que las notas sin categorizar no contaminen las carpetas de dominio. La bandeja de entrada es un área de preparación; los archivos se mueven a carpetas de dominio después de la clasificación.

Formato de wiki-link → Ruta más corta cuando sea posible. Los destinos de enlace más cortos son más fáciles de resolver para el recuperador al indexar la estructura de enlaces.


Modelos de Embedding: Selección y Configuración

El modelo de embedding convierte fragmentos de texto en vectores numéricos para la búsqueda semántica. La elección del modelo determina la calidad de recuperación, el tamaño del índice, la velocidad de embedding y las dependencias en tiempo de ejecución. Esta sección explica por qué potion-base-8M de Model2Vec es la opción predeterminada y cuándo elegir alternativas.

Por qué Model2Vec potion-base-8M

Modelo: minishlab/potion-base-8M Parámetros: 7,6 millones Dimensiones: 256 Tamaño: ~30 MB Dependencias: model2vec (solo numpy, sin PyTorch) Inferencia: solo CPU, embeddings de palabras estáticos (sin capas de atención)

Model2Vec destila el conocimiento de un sentence transformer en embeddings de tokens estáticos. En lugar de ejecutar capas de atención sobre la entrada (como hacen BERT, MiniLM y otros modelos transformer), Model2Vec produce vectores mediante el promedio ponderado de embeddings de tokens precalculados.3 La consecuencia práctica: la velocidad de embedding es entre 50 y 500 veces más rápida que los modelos basados en transformer, ya que no hay computación secuencial.

En el conjunto de benchmarks MTEB, potion-base-8M alcanza el 89% del rendimiento de all-MiniLM-L6-v2 (50,03 frente a 56,09 de promedio).4 La brecha de calidad del 11% es la compensación por las ventajas de velocidad y simplicidad. Para fragmentos cortos de markdown (promedio de 200-400 palabras en un vault típico), la diferencia de calidad es menos pronunciada que en documentos más largos, ya que ambos modelos convergen en representaciones similares para textos cortos y enfocados.

Configuración

# 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]

Carga diferida. El modelo se carga en el primer uso, no al momento de importación. Importar el módulo del embedder no tiene costo cuando el recuperador opera en modo de respaldo BM25 (por ejemplo, cuando el entorno virtual de embedding no está instalado).

Entorno virtual aislado. El modelo se ejecuta en un venv dedicado (por ejemplo, ~/.claude/venvs/memory/) para evitar conflictos de dependencias con el resto de la cadena de herramientas. La función _activate_venv() agrega los site-packages del venv a sys.path en tiempo de ejecución.

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

Procesamiento por lotes. El embedder procesa textos en lotes de 64 para amortizar la sobrecarga de Model2Vec. El indexador alimenta los fragmentos a embed_batch() en lugar de generar el embedding de un fragmento a la vez.

Cuándo Elegir Alternativas

Modelo Dim Tamaño Velocidad Calidad (MTEB) Ideal para
potion-base-8M 256 30 MB 500x 50,03 Predeterminado: local, rápido, sin GPU
potion-base-32M 256 120 MB 400x 52,46 Mayor calidad, aún estático
potion-retrieval-32M 256 120 MB 400x 36,35 (recuperación) Estático optimizado para recuperación
all-MiniLM-L6-v2 384 80 MB 1x 56,09 Mayor calidad, aún local
nomic-embed-text-v1.5 768 270 MB 0,5x 62,28 Mejor calidad local
text-embedding-3-small 1536 API N/A 62,30 Basado en API, máxima calidad

Elija potion-base-32M cuando desee mejor calidad que potion-base-8M sin abandonar la familia de embeddings estáticos. Lanzado en enero de 2025, utiliza un vocabulario más amplio destilado de baai/bge-base-en-v1.5, alcanzando 52,46 de promedio en MTEB (5% de mejora sobre potion-base-8M) mientras mantiene la misma salida de 256 dimensiones y dependencia exclusiva de numpy.18 El archivo del modelo 4 veces más grande aumenta el uso de memoria, pero la velocidad de embedding sigue siendo órdenes de magnitud más rápida que los modelos transformer.

Elija potion-retrieval-32M cuando su caso de uso principal sea la recuperación (que es exactamente lo que hace la búsqueda en el vault). Esta variante está ajustada a partir de potion-base-32M específicamente para tareas de recuperación, obteniendo 36,35 en los benchmarks de recuperación de MTEB frente a 33,52 del modelo base.18 El promedio general en MTEB baja a 49,73 porque el ajuste fino sacrifica rendimiento de propósito general a favor de mejoras específicas en recuperación.

Elija all-MiniLM-L6-v2 cuando la calidad de recuperación importe más que la velocidad y tenga PyTorch instalado. Los vectores de 384 dimensiones aumentan el tamaño de la base de datos SQLite en aproximadamente un 50% en comparación con los vectores de 256 dimensiones. La velocidad de embedding disminuye de menos de 1 minuto a aproximadamente 10 minutos para una reindexación completa de 15.000 archivos en hardware de la serie M.

Elija nomic-embed-text-v1.5 cuando necesite la mejor calidad de recuperación local posible y acepte una indexación más lenta. Los vectores de 768 dimensiones aproximadamente triplican el tamaño de la base de datos. Requiere PyTorch y una CPU moderna o GPU.

Elija text-embedding-3-small cuando la latencia de red y la privacidad sean compensaciones aceptables. API produce los embeddings de mayor calidad, pero introduce una dependencia en la nube, un costo por token ($0,02/millón de tokens) y envía su contenido a los servidores de OpenAI.

Mantenga potion-base-8M en todos los demás casos. La ventaja de velocidad es fundamental para la indexación iterativa (reindexar durante el desarrollo), la dependencia exclusiva de numpy evita la complejidad de instalación de PyTorch, y los vectores de 256 dimensiones mantienen la base de datos compacta.

Cuantización y Reducción de Dimensionalidad

Model2Vec v0.5.0+ admite la carga de modelos con precisión y dimensiones reducidas.18 Esto es útil para implementación en hardware con recursos limitados o para reducir el tamaño de la base de datos sin cambiar de modelo:

from model2vec import StaticModel

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

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

Los modelos cuantizados conservan una calidad de recuperación casi idéntica con una fracción del consumo de memoria. La reducción de dimensionalidad sigue un truncamiento estilo Matryoshka: las primeras N dimensiones contienen la mayor cantidad de información. Reducir de 256 a 128 dimensiones reduce el almacenamiento de vectores a la mitad con una pérdida de calidad mínima para la recuperación de textos cortos.

A partir de mayo de 2025, Model2Vec también admite tokenizadores BPE y Unigram (además de WordPiece), lo que amplía el conjunto de sentence transformers que pueden destilarse en modelos estáticos.20

Ajuste Fino para Embeddings Específicos del Vault

Model2Vec v0.4.0+ admite el entrenamiento de modelos de clasificación personalizados sobre embeddings estáticos, y la v0.7.0 agrega cuantización de vocabulario y pooling configurable para la destilación.20 Esto es relevante para vaults con vocabulario especializado (notas médicas, referencias legales, jerga específica de un dominio) donde los modelos potion predeterminados pueden no capturar los matices semánticos:

from model2vec import StaticModel
from model2vec.train import train_model

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

Para la mayoría de los vaults, el potion-base-8M predeterminado produce una calidad de recuperación suficiente. El ajuste fino vale la pena solo cuando la recuperación falla consistentemente en detectar conexiones específicas del dominio que un modelo de propósito general no puede capturar.

Seguimiento del Hash del Modelo

El indexador almacena un hash derivado del nombre del modelo y el tamaño del vocabulario. Si cambia el modelo de embedding, el indexador detecta la discrepancia en la siguiente ejecución incremental y activa una reindexación completa automáticamente.

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]

Esto evita mezclar vectores de diferentes modelos en la misma base de datos, lo que produciría puntuaciones de cosine similarity sin sentido.

Modos de Fallo

Fallo en la descarga del modelo. La primera ejecución descarga el modelo desde Hugging Face. Si la descarga falla (problema de red, firewall corporativo), el recuperador recurre al modo BM25. El modelo se almacena en caché localmente después de la primera descarga.

Discrepancia de dimensiones. Si cambia de modelo sin limpiar la base de datos, los vectores almacenados tienen una dimensión diferente a los nuevos embeddings. El indexador detecta esto mediante el hash del modelo y activa una reindexación completa. Si la verificación del hash falla (modelo personalizado sin hash adecuado), sqlite-vec generará un error en las consultas KNN con dimensiones no coincidentes.

Presión de memoria en vaults grandes. Generar embeddings de más de 50.000 fragmentos en un solo lote puede consumir una cantidad significativa de memoria. El indexador procesa en lotes de 64 para limitar el uso máximo de memoria. Si la memoria sigue siendo un problema, reduzca el tamaño del lote.


Búsqueda de texto completo con FTS5

La extensión FTS5 de SQLite proporciona búsqueda de texto completo con clasificación BM25. FTS5 es el componente de búsqueda por palabras clave del pipeline de recuperación híbrida (hybrid retrieval). Esta sección cubre la configuración de FTS5, cuándo BM25 sobresale y sus modos de falla específicos.

Tabla virtual FTS5

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

Modo de sincronización de contenido. El parámetro content=chunks indica a FTS5 que haga referencia a la tabla chunks directamente en lugar de almacenar una copia duplicada del texto. Esto reduce el almacenamiento a la mitad, pero significa que FTS5 debe sincronizarse manualmente cuando se insertan, actualizan o eliminan chunks.

Columnas. Se indexan tres columnas: - chunk_text — El contenido principal de cada chunk (peso BM25: 1.0) - section — El texto del encabezado H2 (peso BM25: 0.5) - heading_context — Título de la nota, etiquetas y metadatos (peso BM25: 0.3)

Clasificación BM25

BM25 clasifica documentos por frecuencia de términos, frecuencia inversa de documentos y normalización de longitud de documento. La función auxiliar bm25() en FTS5 acepta pesos por columna:

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;

Los pesos de columna (1.0, 0.5, 0.3) significan: - Una coincidencia de palabra clave en chunk_text contribuye más a la puntuación - Una coincidencia en section (encabezado) contribuye la mitad - Una coincidencia en heading_context (título, etiquetas) contribuye un 30%

Estos pesos son ajustables. Si su vault tiene encabezados descriptivos que predicen fuertemente la calidad del contenido, aumente el peso de section. Si sus etiquetas son completas y precisas, aumente el peso de heading_context.

Cuándo gana BM25

BM25 sobresale en consultas que contienen identificadores exactos:

  • Nombres de funciones: _rrf_fuse, embed_batch, get_stale_files
  • CLI flags: --incremental, --vault, --model
  • Claves de configuración: bm25_weight, max_tokens, batch_size
  • Mensajes de error: SQLITE_LOCKED, ConnectionRefusedError
  • Términos técnicos específicos: PostToolUse, PreToolUse, AGENTS.md

Para estas consultas, BM25 encuentra la coincidencia exacta de inmediato. La búsqueda vectorial devolvería contenido semánticamente relacionado, pero podría clasificar la coincidencia exacta por debajo de una discusión conceptual.

Cuándo falla BM25

BM25 falla en consultas que utilizan terminología diferente al contenido almacenado:

  • Consulta: “how to handle authentication failures” → El vault contiene notas sobre “login error recovery” y “session expiration handling”. BM25 no encuentra coincidencias porque las palabras clave difieren.
  • Consulta: “what is the best way to manage state” → El vault contiene notas sobre “Redux store patterns” y “context providers”. BM25 falla porque “state management” se expresa a través de nombres de tecnologías específicas.

BM25 también falla con colisión de palabras clave a gran escala. En un vault de 15.000 archivos, una búsqueda de “configuration” coincide con cientos de notas porque casi todas las notas de proyecto mencionan configuración. Los resultados son técnicamente correctos pero prácticamente inútiles — la clasificación no puede determinar cuál nota de “configuration” es relevante para la consulta actual.

Tokenizador FTS5

FTS5 utiliza el tokenizador unicode61 por defecto, que maneja texto ASCII y Unicode. Para vaults con contenido significativo en CJK (chino, japonés, coreano), considere el 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'
);

El tokenizador unicode61 predeterminado divide por límites de palabras, lo cual funciona mal para idiomas sin espacios entre palabras. El tokenizador trigram divide cada tres caracteres, permitiendo coincidencias de subcadenas a costa del tamaño del índice (aproximadamente 3 veces más grande).

Mantenimiento

FTS5 requiere sincronización explícita cuando la tabla chunks subyacente cambia:

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

El comando rebuild reconstruye el índice FTS5 a partir de la tabla de contenido. Ejecútelo después de inserciones masivas (reindexación completa), pero no después de actualizaciones incrementales individuales — para esas, use INSERT INTO chunks_fts(rowid, chunk_text, section, heading_context) para sincronizar filas individuales.


Búsqueda vectorial con sqlite-vec

La extensión sqlite-vec incorpora búsqueda vectorial KNN (K-Nearest Neighbors) en SQLite. Esta sección cubre la configuración de sqlite-vec, el pipeline de embeddings desde la nota hasta el vector consultable, y los patrones de consulta específicos.

Tabla virtual sqlite-vec

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

El módulo vec0 almacena vectores flotantes de 256 dimensiones como datos binarios empaquetados. La columna id tiene correspondencia 1:1 con la tabla chunks, permitiendo joins entre resultados vectoriales y metadatos de chunks.

Pipeline de embeddings

El pipeline fluye desde la nota hasta el vector consultable:

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

Serialización de vectores

El módulo struct de Python serializa vectores flotantes para el almacenamiento en 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

Una consulta de búsqueda vectorial genera el embedding de la consulta de entrada y luego encuentra los K chunks más cercanos por distancia coseno:

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

El operador MATCH en sqlite-vec realiza búsqueda aproximada de vecinos más cercanos. El parámetro k controla cuántos resultados devolver. La columna distance contiene la distancia coseno (0 = idéntico, 2 = opuesto).

Cuándo gana la búsqueda vectorial

La búsqueda vectorial sobresale en consultas donde el concepto importa más que las palabras específicas:

  • Consulta: “how to handle authentication failures” → Encuentra notas sobre “login error recovery” (mismo espacio semántico, palabras clave diferentes)
  • Consulta: “what patterns exist for caching” → Encuentra notas sobre “memoization”, “Redis TTL strategies” y “HTTP cache headers” (conceptos relacionados, terminología diversa)
  • Consulta: “approaches to testing asynchronous code” → Encuentra notas sobre “pytest-asyncio fixtures”, “mock event loops” y “async test patterns” (mismo concepto expresado a través de detalles de implementación)

Cuándo falla la búsqueda vectorial

La búsqueda vectorial tiene dificultades con identificadores exactos:

  • Consulta: _rrf_fuse → Devuelve notas sobre “fusion algorithms” y “rank merging”, pero puede clasificar la definición real de la función por debajo de discusiones conceptuales
  • Consulta: PostToolUse → Devuelve notas sobre “tool lifecycle hooks” y “post-execution handlers” en lugar del nombre específico del hook

La búsqueda vectorial también tiene dificultades con datos estructurados. Los archivos de configuración JSON, los bloques YAML y los fragmentos de código producen embeddings que capturan patrones estructurales en lugar de significado semántico. Un archivo JSON con "review": true genera un embedding diferente al de una discusión en prosa sobre revisión de código.

Degradación elegante

Si sqlite-vec no logra cargarse (extensión faltante, plataforma incompatible, biblioteca corrupta), el recuperador recurre a búsqueda solo con 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

El recuperador verifica vec_available antes de intentar consultas vectoriales. Cuando está deshabilitado, todas las búsquedas utilizan solo BM25, y el paso de fusión RRF se omite.


Reciprocal Rank Fusion (RRF)

RRF combina dos listas ordenadas sin requerir calibración de puntuaciones. Esta sección cubre el algoritmo, un rastreo detallado de consulta, el ajuste del parámetro k y por qué se elige RRF sobre las alternativas. Para una calculadora interactiva con rangos editables, escenarios predefinidos y un explorador visual de arquitectura, consulte la inmersión profunda en el recuperador híbrido.

El Algoritmo

RRF asigna a cada documento una puntuación basada únicamente en su posición de rango en cada lista:

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

Donde: - k es una constante de suavizado (60, siguiendo a Cormack et al.1) - rank_i es el rango basado en 1 del documento en la lista de resultados i - weight_i es un multiplicador opcional por lista (predeterminado 1.0)

Los documentos que se posicionan bien en múltiples listas reciben puntuaciones fusionadas más altas. Los documentos que aparecen en una sola lista reciben una puntuación de esa única fuente.

Por qué RRF sobre las alternativas

La combinación lineal ponderada requiere calibrar las puntuaciones de BM25 contra las distancias de coseno. Las puntuaciones de BM25 no tienen límite superior y escalan con el tamaño del corpus. Las distancias de coseno están acotadas en [0, 2]. Combinarlas requiere normalización, y los parámetros de normalización dependen del conjunto de datos. RRF utiliza únicamente posiciones de rango, que siempre son enteros comenzando en 1 independientemente del método de puntuación.

Los modelos de fusión entrenados requieren datos de entrenamiento etiquetados — pares de relevancia consulta-documento. Para una base de conocimiento personal, estos datos de entrenamiento no existen. Sería necesario evaluar manualmente cientos de pares consulta-documento para entrenar un modelo útil. RRF funciona sin ningún dato de entrenamiento.

Los métodos de votación Condorcet (conteo Borda, método Schulze) son teóricamente elegantes pero más complejos de implementar y ajustar. El artículo original de RRF demostró que RRF supera a los métodos Condorcet en datos de evaluación TREC.1

Fusión en la práctica

Consulta: “how does the review aggregator handle disagreements”

BM25 posiciona review-aggregator.py en la posición 3 (coincidencias exactas de palabras clave en “review,” “aggregator,” “disagreements”) pero coloca dos archivos de configuración más arriba (coinciden con “review” de forma más prominente). La búsqueda vectorial posiciona el mismo fragmento en la posición 1 (coincidencia semántica sobre resolución de conflictos). Después de la fusión RRF:

Fragmento BM25 Vec Puntuación fusionada
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

Los fragmentos que se posicionan bien en ambas listas emergen hacia la parte superior. Los fragmentos que solo aparecen en una lista obtienen una puntuación de fuente única y caen por debajo de los resultados con doble posicionamiento. La lógica real de resolución de desacuerdos gana porque ambos métodos la encontraron — BM25 a través de palabras clave, la búsqueda vectorial a través de semántica.

Para el rastreo completo paso a paso con las matemáticas de RRF por rango, pruebe diferentes valores de k en la calculadora interactiva de RRF.

Implementación

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

Ajuste de k

La constante k controla cuánto peso se otorga a los resultados mejor posicionados frente a los de posiciones inferiores:

  • k bajo (ej., 10): Los resultados mejor posicionados dominan. El rango 1 puntúa 1/11 = 0,091, el rango 10 puntúa 1/20 = 0,050 (diferencia de 1,8x). Útil cuando confía en que los clasificadores individuales aciertan con el resultado superior.
  • k predeterminado (60): Equilibrado. El rango 1 puntúa 1/61 = 0,0164, el rango 10 puntúa 1/70 = 0,0143 (diferencia de 1,15x). Las diferencias de rango se comprimen, otorgando más peso a aparecer en múltiples listas.
  • k alto (ej., 200): Aparecer en ambas listas importa mucho más que la posición de rango. El rango 1 puntúa 1/201, el rango 10 puntúa 1/210 — casi idénticos. Úselo cuando los clasificadores individuales producen posicionamientos ruidosos pero la concordancia entre listas es confiable.

Comience con k=60. El artículo original de RRF encontró que este valor es robusto en diversos conjuntos de datos TREC. Ajuste únicamente después de medir casos de fallo en su propia distribución de consultas.

Desempate

Cuando dos fragmentos tienen puntuaciones RRF idénticas (poco frecuente pero posible con el mismo rango en una lista y sin aparición en la otra), resuelva los empates mediante:

  1. Preferir fragmentos que aparecen en ambas listas sobre fragmentos que aparecen en solo una
  2. Entre fragmentos en ambas listas, preferir el que tiene el rango combinado más bajo
  3. Entre fragmentos en solo una lista, preferir el que tiene el rango más bajo en esa lista

El pipeline completo de recuperación

Esta sección traza una consulta desde la entrada hasta la salida a través de todo el pipeline: búsqueda BM25, búsqueda vectorial, fusión RRF, truncamiento por presupuesto de tokens y ensamblaje de contexto.

Flujo de extremo a extremo

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

Latencia total: ~23ms para una base de datos de 49.746 chunks en hardware Apple M3 Pro.

La API de búsqueda

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

Truncamiento por presupuesto de tokens

El parámetro max_tokens evita que el recuperador devuelva más contexto del que la herramienta de IA puede utilizar. La estimación usa 4 caracteres por token (una aproximación razonable para prosa en inglés). Los resultados se truncan de forma voraz: se agregan resultados en orden de clasificación hasta que el presupuesto se agota.

Esta es una estrategia conservadora. Un enfoque más sofisticado consideraría puntuaciones de calidad por resultado y preferiría resultados más cortos y de mayor calidad sobre resultados más largos y de menor calidad. El enfoque voraz es más simple y funciona bien en la práctica porque la clasificación RRF ya ordena los resultados por relevancia.

Esquema de la base de datos (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
);

Ruta de degradación gradual

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

El recuperador verifica las capacidades disponibles durante la inicialización y adapta su estrategia de consulta. Un componente faltante degrada la calidad pero no causa errores. El único fallo irrecuperable es un archivo de base de datos ausente.

Estadísticas de producción

Medido en un vault de 16.894 archivos, 49.746 chunks, base de datos SQLite de 83 MB, Apple M3 Pro:

Métrica Valor
Total de archivos 16.894
Total de chunks 49.746
Tamaño de la base de datos 83 MB
Latencia de consulta BM25 (p50) 12ms
Latencia de consulta vectorial (p50) 8ms
Latencia de fusión RRF 3ms
Latencia de búsqueda de extremo a extremo (p50) 23ms
Tiempo de reindexación completa ~4 minutos
Tiempo de reindexación incremental <10 segundos
Modelo de embeddings potion-base-8M (256-dim)
Pool de candidatos BM25 30
Pool de candidatos vectoriales 30
Límite de resultados por defecto 10
Presupuesto de tokens por defecto 4.000 tokens

Hashing de contenido y detección de cambios

El indexador necesita saber qué archivos han cambiado desde la última ejecución del índice. Esta sección cubre el mecanismo de detección de cambios y la estrategia de hashing.

Comparación del tiempo de modificación de archivos

El indexador almacena mtime_ns (tiempo de modificación del archivo en nanosegundos) para cada chunk en la tabla chunks. En una ejecución incremental, el indexador:

  1. Escanea el vault en busca de todos los archivos .md en las carpetas permitidas
  2. Lee el mtime_ns de cada archivo desde el sistema de archivos
  3. Compara contra el mtime_ns almacenado en la base de datos
  4. Identifica tres categorías:
  5. Archivos nuevos: la ruta existe en el sistema de archivos pero no en la base de datos
  6. Archivos modificados: la ruta existe en ambos pero el mtime_ns difiere
  7. Archivos eliminados: la ruta existe en la base de datos pero no en el sistema de archivos
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 qué mtime y no hash de contenido?

El hashing de contenido (SHA-256 del contenido del archivo) sería más confiable que la comparación de mtime — detectaría casos en los que un archivo fue tocado sin cambiar (por ejemplo, git checkout restaurando el mtime original). Sin embargo, el hashing requiere leer cada archivo en cada ejecución incremental. Para 16.894 archivos, leer el contenido toma 2-3 segundos. Leer los mtimes del sistema de archivos toma <100ms.

La compensación: la comparación de mtime ocasionalmente provoca la reindexación innecesaria de archivos sin cambios (falsos positivos), pero nunca omite cambios reales. Los falsos positivos cuestan algunas llamadas adicionales de embeddings por ejecución. La diferencia de velocidad (100ms vs 3 segundos) hace del mtime la elección pragmática para un sistema que se ejecuta en cada interacción con IA.

Manejo de eliminaciones

Cuando un archivo se elimina del vault, el indexador remueve todos sus chunks de la base de datos:

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],
    )

Las tablas FTS5 con sincronización de contenido requieren eliminación explícita mediante INSERT INTO chunks_fts(chunks_fts, rowid, ...) VALUES('delete', ?, ...) para cada fila removida. El indexador maneja esto como parte del proceso de eliminación de archivos.


Reindexación incremental vs completa

El indexador admite dos modos: incremental (rápido, uso diario) y completo (lento, ocasional). Esta sección cubre cuándo utilizar cada uno, las garantías de idempotencia y la recuperación ante corrupción.

Reindexación incremental

Cuándo usar: Indexación diaria después de editar notas. Es el modo predeterminado.

Qué hace: 1. Escanear el vault en busca de cambios en archivos (comparación de mtime) 2. Eliminar chunks de archivos borrados 3. Re-fragmentar y re-generar embeddings de archivos modificados 4. Insertar nuevos chunks para archivos nuevos 5. Sincronizar el índice FTS5

Duración típica: <10 segundos para las ediciones de un día en un vault de 16.000 archivos.

python index_vault.py --incremental

Reindexación completa

Cuándo usar: - Después de cambiar el modelo de embeddings (se detecta discrepancia en el hash del modelo) - Después de una migración de esquema (nuevas columnas, índices modificados) - Después de corrupción de la base de datos (falla la verificación de integridad) - Cuando la indexación incremental produce resultados inesperados

Qué hace: 1. Eliminar todos los datos existentes (chunks, vectores, entradas FTS5) 2. Escanear todo el vault 3. Fragmentar todos los archivos 4. Generar embeddings de todos los chunks 5. Construir el índice FTS5 desde cero

Duración típica: ~4 minutos para 16.894 archivos en Apple M3 Pro.

python index_vault.py --full

Idempotencia

Ambos modos son idempotentes: ejecutar el mismo comando dos veces produce el mismo resultado. El indexador elimina los chunks existentes de un archivo antes de insertar los nuevos, por lo que una re-ejecución de la indexación incremental en una base de datos ya actualizada produce cero cambios. Una re-ejecución de la indexación completa produce una base de datos idéntica.

Recuperación ante corrupción

Si la base de datos SQLite se corrompe (pérdida de energía durante la escritura, error de disco, proceso terminado a mitad de transacción):

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

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

La fuente de verdad son siempre los archivos del vault, no la base de datos. La base de datos es un artefacto derivado que puede reconstruirse en cualquier momento. Esta es una propiedad de diseño crítica: nunca necesita hacer respaldo de la base de datos.

La flag --incremental

Cuando el indexador se ejecuta con --incremental:

  1. Verificación del hash del modelo. Compara el hash del modelo almacenado con el modelo actual. Si difieren, cambia automáticamente al modo de reindexación completa y advierte al usuario.
  2. Escaneo de archivos. Recorre las carpetas permitidas, recopila rutas de archivos y mtimes.
  3. Detección de cambios. Compara contra los datos almacenados.
  4. Procesamiento por lotes. Re-fragmenta y re-genera embeddings de archivos modificados en lotes de 64.
  5. Reporte de progreso. Imprime la cantidad de archivos procesados y el tiempo transcurrido.
  6. Apagado graceful. Maneja SIGINT terminando el archivo actual antes de detenerse.

Filtrado de credenciales y límites de datos

Las notas personales contienen secretos: claves API, tokens bearer, cadenas de conexión a bases de datos, claves privadas pegadas durante sesiones de depuración. El filtro de credenciales evita que estos ingresen al índice de recuperación.

El problema

Una nota sobre la depuración de una integración con OAuth podría contener:

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

Sin filtrado, tanto el JWT como la clave API serían fragmentados, convertidos en embeddings y almacenados en la base de datos. Una búsqueda de “authentication” devolvería el chunk que contiene secretos reales. Peor aún, si el recuperador alimenta los resultados a una herramienta de IA a través de MCP, los secretos aparecen en la ventana de contexto de la IA y potencialmente en los registros de la herramienta.

Filtrado basado en patrones

El filtro de credenciales se ejecuta en cada chunk antes del almacenamiento, coincidiendo con 25 patrones específicos de proveedores más patrones genéricos:

Patrones específicos de proveedores:

Patrón Ejemplo Regex
Clave API de OpenAI sk-... sk-[a-zA-Z0-9_-]{20,}
Clave API de Anthropic sk-ant-api03-... sk-ant-api\d{2}-[a-zA-Z0-9_-]{20,}
PAT de GitHub ghp_... gh[ps]_[a-zA-Z0-9]{36,}
Clave de acceso AWS AKIA... AKIA[0-9A-Z]{16}
Clave de Stripe sk_live_... [sr]k_(live\|test)_[a-zA-Z0-9]{24,}
Token de Cloudflare ... Varios patrones

Patrones genéricos:

Patrón Detección
Tokens JWT eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+
Tokens Bearer Bearer\s+[a-zA-Z0-9_\-\.]+
Claves privadas -----BEGIN (RSA\|EC\|OPENSSH) PRIVATE KEY-----
base64 de alta entropía Cadenas con >4,5 bits/carácter de entropía, 40+ caracteres
Asignaciones de contraseñas password\s*[:=]\s*["'][^"']+["']

Implementación del 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

Decisiones clave de diseño:

  1. Filtrar antes de generar embeddings. El texto limpio es lo que se convierte en embeddings. La representación vectorial nunca codifica patrones de credenciales. Una consulta por “clave API” devuelve notas que discuten la gestión de claves API, no notas que contienen claves reales.

  2. Reemplazar, no eliminar. El token [REDACTED:pattern-name] preserva el contexto semántico del texto circundante. El embedding captura que “algo similar a una credencial estaba aquí” sin codificar la credencial en sí.

  3. Registrar patrones, no valores. El filtro registra qué patrones coincidieron (por ejemplo, “Scrubbed 2 credential(s) from oauth-debug.md [jwt, bearer-token]”) pero nunca registra el valor de la credencial.

Exclusión basada en rutas

El archivo .indexignore proporciona exclusión gruesa por ruta. El filtro de credenciales proporciona limpieza fina dentro de los archivos indexados. Ambos son necesarios:

  • .indexignore para carpetas completas que usted sabe que contienen contenido sensible (notas de salud, registros financieros, documentos de carrera)
  • Filtro de credenciales para secretos accidentalmente incluidos en contenido que de otro modo sería indexable

Clasificación de datos

Para vaults que contienen contenido diverso, considere clasificar las notas por sensibilidad:

Nivel Ejemplos ¿Indexar? ¿Filtrar?
Público Borradores de blog, notas técnicas
Interno Planes de proyecto, decisiones de arquitectura
Sensible Datos salariales, registros de salud No (.indexignore) N/A
Restringido Credenciales, claves privadas No (.indexignore) N/A

Arquitectura del servidor MCP

Los servidores de Model Context Protocol (MCP) exponen el recuperador como una herramienta que los agentes de IA pueden invocar. Esta sección cubre el diseño del servidor, la superficie de capacidades y los límites de permisos.

Elección de protocolo: STDIO vs HTTP

MCP admite dos modos de transporte:

STDIO — La herramienta de IA inicia el servidor MCP como un proceso hijo y se comunica a través de stdin/stdout. Este es el modo estándar para herramientas locales. Claude Code, Codex CLI y Cursor admiten 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 — El servidor MCP se ejecuta como un servicio HTTP independiente. Es útil para acceso remoto, configuraciones con múltiples clientes o configuraciones de equipo donde la bóveda se encuentra en un servidor compartido.

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

Recomendación: Use STDIO para bóvedas personales. Es más simple, más seguro (sin exposición de red) y el ciclo de vida del servidor es gestionado por la herramienta de IA. Use HTTP solo cuando múltiples herramientas o múltiples máquinas necesiten acceso concurrente a la misma bóveda.

Evolución de la especificación MCP. La especificación MCP de junio de 2025 añadió autorización OAuth 2.1, salidas de herramientas estructuradas (esquemas de retorno tipados) y elicitación (solicitudes al usuario iniciadas por el servidor). La versión de noviembre de 2025 incluyó Streamable HTTP como modo de transporte de primera clase, descubrimiento de URL .well-known para navegación automática de capacidades del servidor, anotaciones de herramientas estructuradas que declaran si una herramienta es de solo lectura o mutante, y un sistema de estandarización de niveles SDK.1619 La próxima versión de la especificación (tentativamente a mediados de 2026) propone operaciones asíncronas para tareas de larga duración, extensiones de protocolo específicas por dominio para industrias como salud y finanzas, y estándares de comunicación agente a agente para flujos de trabajo multi-agente.19 Para servidores de bóvedas personales, STDIO sigue siendo la ruta más simple. El transporte Streamable HTTP y el descubrimiento .well-known benefician principalmente a implementaciones HTTP empresariales con enrutamiento multi-inquilino y balanceo de carga. Consulte la hoja de ruta de MCP para actualizaciones que afecten su elección de transporte.

Diseño de capacidades

El servidor MCP debe exponer un conjunto mínimo de herramientas:

search — La herramienta principal. Ejecuta la recuperación híbrida y devuelve resultados clasificados.

{
  "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 — Lee el contenido completo de una nota específica por su ruta. Útil cuando el agente desea ver el contexto completo de un resultado de búsqueda.

{
  "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 coincidan con un filtro (por carpeta, etiqueta, tipo o rango de fechas). Útil para exploración cuando el agente no tiene una 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 — Una herramienta de conveniencia que ejecuta una búsqueda y formatea los resultados como un bloque de contexto adecuado para inyección en una conversación.

{
  "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 }
  }
}

Límites de permisos

El servidor MCP debe aplicar límites estrictos:

  1. Solo lectura. El servidor lee la bóveda y la base de datos del índice. No crea, modifica ni elimina notas. Las operaciones de escritura (capturar nuevas notas) son gestionadas por hooks o skills separados, no por el servidor MCP.

  2. Alcance limitado a la bóveda. El servidor solo lee archivos dentro de la ruta de bóveda configurada. Los intentos de traversal de rutas (../../etc/passwd) deben ser rechazados.

  3. Filtrado de credenciales en la salida. Incluso si la base de datos contiene contenido prefiltrado, aplique filtrado de credenciales en la salida como medida de defensa en profundidad.

  4. Respuestas limitadas por tokens. Aplique max_tokens en todas las respuestas de herramientas para evitar que la herramienta de IA reciba bloques de contexto excesivamente grandes.

Manejo de errores

Las herramientas MCP deben devolver mensajes de error estructurados que ayuden a la herramienta de IA a recuperarse:

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,
    }

Integración con Claude Code

Claude Code es el consumidor principal del sistema de recuperación de Obsidian. Esta sección cubre la configuración de MCP, la integración con hooks y el patrón obsidian_bridge.py.

Configuración de MCP

Añada el servidor MCP de Obsidian a ~/.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"
      }
    }
  }
}

Después de añadir la configuración, reinicie Claude Code. El servidor MCP se iniciará como un proceso hijo. Verifique que está en ejecución:

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

Claude Code debería listar las herramientas disponibles (obsidian_search, obsidian_read_note, etc.).

Integración con hooks

Los hooks extienden el comportamiento de Claude Code en puntos definidos del ciclo de vida. Dos hooks son relevantes para la integración con Obsidian:

Hook PreToolUse — Consulta la bóveda antes de que el agente procese una llamada a herramienta. Inyecta contexto relevante automáticamente.

#!/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 salidas significativas de herramientas de vuelta a la bóveda para futura recuperación.

#!/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

El patrón obsidian_bridge.py

Un módulo puente proporciona una Python API que los hooks y skills pueden invocar:

# 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)

El skill /capture

Un skill de Claude Code para capturar conocimientos de vuelta a la bóveda:

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

El skill crea una nueva nota en 00-inbox/ con frontmatter apropiado y desencadena una reindexación incremental para que la nueva nota sea inmediatamente buscable.

Gestión de la ventana de contexto

La integración debe ser consciente de la ventana de contexto de Claude Code:

  • Limite el contexto inyectado a 1.500-2.000 tokens por consulta. Más que esto compite con la memoria de trabajo del agente.
  • Incluya atribución de fuentes. Siempre incluya la ruta del archivo y el encabezado de sección para que el agente pueda referenciar la fuente.
  • Trunque el texto de los fragmentos. Los fragmentos largos deben truncarse con ... en lugar de omitirse por completo. Los primeros 300-500 caracteres generalmente contienen la información clave.
  • No inyecte en cada llamada a herramienta. El hook PreToolUse debe inyectar contexto selectivamente según la herramienta que se esté invocando. Las operaciones de lectura no necesitan contexto de la bóveda. Las operaciones de escritura y edición se benefician de él.

Integración con Codex CLI

Codex CLI se conecta a servidores MCP a través de config.toml. El patrón de integración difiere de Claude Code en la sintaxis de configuración y la entrega de instrucciones.

Configuración de MCP

Agregue a .codex/config.toml o ~/.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"

Patrones de AGENTS.md

Codex CLI lee AGENTS.md para obtener instrucciones a nivel de proyecto. Incluya orientación sobre la búsqueda en el vault:

## Available Tools

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

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

Diferencias con Claude Code

Característica Claude Code Codex CLI
Configuración de MCP settings.json config.toml
Hooks ~/.claude/hooks/ No soportado
Skills ~/.claude/skills/ No soportado
Archivo de instrucciones CLAUDE.md AGENTS.md
Modos de aprobación --dangerously-skip-permissions suggest / auto-edit / full-auto

Diferencia clave: Codex CLI no soporta hooks. El patrón de inyección automática de contexto (hook PreToolUse) no está disponible. En su lugar, incluya instrucciones explícitas en AGENTS.md indicando al agente que busque en el vault antes de comenzar a trabajar.


Cursor y otras herramientas

Cursor y otras herramientas de IA que soportan MCP pueden conectarse al mismo servidor MCP de Obsidian. Esta sección cubre la configuración para herramientas comunes.

Cursor

Agregue a .cursor/mcp.json en la raíz de su proyecto:

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

El archivo .cursorrules de Cursor puede incluir instrucciones para usar el 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 compatibilidad

Herramienta Soporte MCP Transporte Ubicación de configuración
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 En desarrollo STDIO Interfaz de configuración

Alternativa para herramientas sin MCP

Para herramientas que no soportan MCP, el retriever puede envolverse 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

La CLI genera texto estructurado que puede copiarse y pegarse manualmente en la entrada de cualquier herramienta de IA. Es menos elegante que la integración MCP, pero funciona universalmente.


Almacenamiento en caché de prompts a partir de notas estructuradas

Las notas estructuradas en el vault pueden servir como bloques de contexto reutilizables que reducen el uso de tokens en las interacciones con IA. Esta sección cubre el diseño de claves de caché y la gestión del presupuesto de tokens.

El patrón

En lugar de buscar contexto en cada interacción, construya previamente bloques de contexto a partir de notas bien estructuradas del vault y almacénelos en caché:

# 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
    },
}

Invalidación de caché

La invalidación de caché se basa en dos señales:

  1. Expiración del TTL. Cada bloque de contexto tiene un tiempo de vida (time-to-live). Cuando el TTL expira, el bloque se reconstruye volviendo a consultar el vault.
  2. Detección de cambios en el vault. Cuando el indexador detecta cambios en archivos que contribuyeron a un bloque de contexto en caché, el bloque se invalida inmediatamente.

Gestión del presupuesto de tokens

Una sesión comienza con un presupuesto total de contexto. Los bloques en caché consumen parte de ese presupuesto:

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)

Los bloques en caché se cargan al inicio de la sesión. Los resultados de búsqueda dinámica llenan el presupuesto restante por consulta. Este enfoque híbrido proporciona al agente una base de contexto frecuentemente necesario mientras preserva presupuesto para consultas específicas.

Comparación del uso de tokens: antes y después

Sin caché: Cada consulta relevante activa una búsqueda en el vault, devolviendo entre 1.500 y 2.000 tokens de contexto. A lo largo de 10 consultas en una sesión, el agente consume entre 15.000 y 20.000 tokens de contexto del vault.

Con caché: Tres bloques de contexto preconstruidos consumen 4.500 tokens en total. Las búsquedas adicionales agregan entre 1.500 y 2.000 tokens por consulta única. A lo largo de 10 consultas donde 6 están cubiertas por bloques en caché, el agente consume 4.500 + (4 × 1.500) = 10.500 tokens — aproximadamente la mitad del uso sin caché.


Hooks PostToolUse para compresión de contexto

Las salidas de herramientas pueden ser extensas: trazas de pila, listados de archivos, resultados de pruebas. Un hook PostToolUse puede comprimir estas salidas antes de que consuman espacio en la ventana de contexto.

El problema

Una llamada a la herramienta Bash que ejecuta pruebas podría devolver:

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

La salida completa ocupa 5.000 tokens, pero la información relevante está en 2 líneas: 200 aprobadas, 1 fallida.

Implementación del 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

Prevención de activación recursiva

Un hook de compresión que emite salida podría activarse a sí mismo si no se protege:

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

Heurísticas de compresión

Tipo de salida Detección Estrategia de compresión
Resultados de pruebas Palabras clave PASSED / FAILED Contar aprobadas/fallidas, mostrar solo las fallidas
Listados de archivos ls o find en el comando Truncar a las primeras 20 entradas + conteo
Trazas de pila Palabra clave Traceback Conservar el primer y último frame + mensaje de error
Estado de Git modified: / new file: Resumir conteos por estado
Salida de compilación warning: / error: Eliminar líneas informativas, conservar advertencias/errores

Flujo de Ingesta y Triaje de Señales

La capa de ingesta determina qué entra en la bóveda. Sin curación, la bóveda acumula ruido. Esta sección cubre el flujo de puntuación que enruta señales a las carpetas de dominio.

Fuentes

Las señales provienen de múltiples canales:

  • Fuentes RSS: Blogs técnicos, avisos de seguridad, notas de versión
  • Marcadores: Marcadores del navegador guardados mediante Obsidian Web Clipper o bookmarklet
  • Boletines: Extractos clave de boletines por correo electrónico
  • Captura manual: Notas escritas durante la lectura, conversaciones o investigación
  • Salida de herramientas: Resultados significativos de herramientas de IA capturados mediante hooks

Dimensiones de Puntuación

Cada señal se evalúa en cuatro dimensiones (de 0.0 a 1.0 cada una):

Dimensión Pregunta Puntuación Baja (0.0-0.3) Puntuación Alta (0.7-1.0)
Relevancia ¿Se relaciona con mis dominios activos? Tangencial, fuera de alcance Directamente relevante al trabajo activo
Accionabilidad ¿Puedo usar esta información? Teoría pura, sin aplicación Técnica o patrón específico que puedo aplicar
Profundidad ¿Qué tan sustancial es el contenido? Titulares, resumen superficial Análisis detallado con ejemplos
Autoridad ¿Qué tan creíble es la fuente? Blog anónimo, sin verificar Fuente primaria, revisada por pares, experto reconocido

Puntuación Compuesta y Enrutamiento

composite = (relevance * 0.35) + (actionability * 0.25) +
            (depth * 0.25) + (authority * 0.15)
Rango de Puntuación Acción
0.55+ Enrutar automáticamente a carpeta de dominio
0.40 - 0.55 Poner en cola para revisión manual
< 0.40 Descartar (no almacenar)

Enrutamiento por Dominio

Las señales con puntuación superior a 0.55 se enrutan a una de 12 carpetas de dominio según coincidencia de palabras clave y clasificación de temas:

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

Estadísticas de Producción

A lo largo de 14 meses de operación:

Métrica Valor
Total de señales procesadas 7.771
Enrutadas automáticamente (>0.55) 4.832 (62%)
En cola para revisión (0.40-0.55) 1.543 (20%)
Descartadas (<0.40) 1.396 (18%)
Carpetas de dominio activas 12
Promedio de señales por día ~18

Patrones del Grafo de Conocimiento

El grafo de wiki-links de Obsidian codifica las relaciones entre notas. Esta sección cubre la semántica de enlaces, el recorrido del grafo para expansión de contexto y los anti-patrones que degradan la calidad del grafo.

Cada wiki-link crea una arista dirigida en el grafo. Obsidian rastrea tanto los enlaces directos como los backlinks:

  • Enlace directo: La Nota A contiene [[Nota B]] → A enlaza a B
  • Backlink: La Nota B muestra que la Nota A la referencia

El grafo codifica diferentes tipos de relaciones según el contexto:

Patrón de Enlace Semántica Ejemplo
Enlace en línea “Está relacionado con” “Consulte [[OAuth Token Rotation]] para más detalles”
Enlace de encabezado “Tiene subtema” ”## Relacionados\n- [[Token Rotation]]\n- [[Session Management]]”
Enlace tipo etiqueta “Está categorizado como” ”[[type/reference]]”
Enlace MOC “Es parte de” Una nota Maps of Content que lista notas relacionadas

Maps of Content (MOCs)

Los MOCs son notas índice que organizan notas relacionadas en una estructura navegable:

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

Los MOCs benefician la recuperación de dos maneras:

  1. Coincidencia directa. Una búsqueda de “descripción general de autenticación” coincide con el propio MOC, proporcionando al agente una lista curada de notas relacionadas.
  2. Expansión de contexto. Después de encontrar una nota específica, el recuperador puede verificar si la nota aparece en algún MOC e incluir la estructura del MOC en los resultados, proporcionando al agente un mapa del tema más amplio.

Recorrido del Grafo para Expansión de Contexto

Una mejora futura para el recuperador: después de encontrar los mejores resultados, expandir el contexto siguiendo los enlaces:

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)

Esto no está implementado en el recuperador actual, pero representa una extensión natural de la estructura del grafo.

Anti-Patrones

Clusters huérfanos. Grupos de notas que se enlazan entre sí pero no tienen conexiones con el resto de la bóveda. El panel de grafo en Obsidian hace visibles estos grupos como islas desconectadas. Los clusters huérfanos indican MOCs faltantes o enlaces inter-dominio ausentes.

Proliferación de etiquetas. Usar etiquetas de manera inconsistente o crear demasiadas etiquetas de granularidad fina. Una bóveda con 500 etiquetas únicas en 5.000 notas promedia 1 nota por cada 10 etiquetas — las etiquetas no son útiles para filtrar. Consolide a 20-50 etiquetas de alto nivel que se correspondan con sus carpetas de dominio.

Notas con muchos enlaces y poco contenido. Notas que consisten enteramente en wiki-links sin prosa. Estas notas se indexan pobremente porque el chunker no tiene texto para generar embeddings. Agregue al menos un párrafo de contexto explicando por qué las notas enlazadas están relacionadas.

Enlaces bidireccionales para todo. No toda referencia necesita ser un wiki-link. Mencionar “OAuth” de paso no requiere [[OAuth 2.0 Overview]]. Reserve los wiki-links para relaciones intencionales y navegables donde hacer clic en el enlace proporcionaría contexto útil.


Recetas de Flujo de Trabajo para Desarrolladores

Flujos de trabajo prácticos que combinan la recuperación de la bóveda con tareas de desarrollo diarias.

Carga de Contexto Matutina

Comience el día cargando contexto relevante:

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

El recuperador devuelve notas recientes sobre su proyecto activo, proporcionándole un repaso rápido de dónde lo dejó. Más efectivo que releer los mensajes de commit del día anterior.

Captura de Investigación Durante la Codificación

Mientras implementa una función, capture conocimientos sin salir del editor:

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

El conocimiento capturado se indexa inmediatamente y queda disponible para futuras recuperaciones. Con el paso de los meses, estas micro-capturas construyen un corpus de conocimiento específico de implementación.

Inicio de Proyecto

Al comenzar un nuevo proyecto o función:

  1. Busque en la bóveda: “¿Qué sé sobre [tecnología/patrón]?”
  2. Revise los 5 mejores resultados en busca de decisiones previas y problemas conocidos
  3. Verifique si existe un MOC para el dominio; si no, cree uno
  4. Busque modos de fallo: “problemas con [tecnología]”

Depuración con Búsqueda en la Bóveda

Cuando encuentre un error o comportamiento inesperado:

Search my vault for [error message or symptom]

Las notas de depuración previas a menudo contienen la causa raíz y la solución. Esto es particularmente valioso para problemas recurrentes entre proyectos — la bóveda recuerda lo que usted olvida.

Preparación para Revisión de Código

Antes de revisar un PR:

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

La bóveda devuelve decisiones previas, restricciones arquitectónicas y estándares de codificación relevantes al código bajo revisión. La revisión se informa con conocimiento institucional, no solo con el diff.

Optimización del rendimiento

Esta sección cubre estrategias de optimización para diferentes tamaños de vault y patrones de uso.

Gestión del tamaño del índice

Tamaño del vault Chunks Tamaño de BD Reindexación 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

Con más de 50.000 notas, considere: - Aumentar el batch size de 64 a 128 para acelerar la generación de embeddings - Usar el modo WAL (predeterminado) para acceso concurrente - Ejecutar la reindexación completa fuera del horario de actividad

Optimización de consultas

Modo WAL. El modo Write-Ahead Logging de SQLite permite lecturas concurrentes mientras el indexador escribe:

db.execute("PRAGMA journal_mode=WAL")

Esto es fundamental cuando el servidor MCP procesa consultas mientras el indexador ejecuta una actualización incremental.

Reutilización de conexiones. El servidor MCP debe reutilizar las conexiones a la base de datos en lugar de abrir una nueva conexión por cada consulta. Una única conexión persistente con modo WAL permite lecturas concurrentes.

# 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

E/S con mapeo en memoria. El pragma mmap_size indica a SQLite que utilice E/S con mapeo en memoria para el archivo de la base de datos. Para una base de datos de 83 MB, mapear el archivo completo en memoria elimina la mayoría de las lecturas de disco.

Optimización de FTS5. Después de una reindexación completa, ejecute:

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

Esto fusiona los segmentos internos del b-tree de FTS5, reduciendo la latencia de consulta en búsquedas posteriores.

Benchmarks de escalabilidad

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

Operación 500 notas 5K notas 15K notas 50K notas
Consulta BM25 2ms 5ms 12ms 25ms
Consulta vectorial 1ms 3ms 8ms 20ms
Fusión RRF <1ms <1ms 3ms 5ms
Búsqueda completa 3ms 8ms 23ms 50ms

Todos los benchmarks incluyen acceso a la base de datos, ejecución de consultas y formateo de resultados. La latencia de red para la comunicación MCP STDIO añade 1-2ms.


Solución de problemas

Desfase del índice

Síntoma: La búsqueda devuelve resultados obsoletos o no encuentra notas añadidas recientemente.

Causa: El indexador incremental no se ejecutó después de añadir notas, o la marca de tiempo (mtime) de un archivo no se actualizó (por ejemplo, sincronizado desde otra computadora con marcas de tiempo preservadas).

Solución: Ejecute una reindexación completa: python index_vault.py --full

Cambio de modelo de embeddings

Síntoma: Después de cambiar el modelo de embeddings, la búsqueda vectorial devuelve resultados incoherentes.

Causa: Los vectores anteriores (del modelo previo) se están comparando contra los nuevos vectores de consulta. Las dimensiones o la semántica del espacio vectorial son incompatibles.

Solución: El indexador debería detectar la discrepancia en el hash del modelo y activar una reindexación completa automáticamente. Si no lo hace, elimine manualmente la base de datos y reindexe:

rm vectors.db
python index_vault.py --full

Mantenimiento de FTS5

Síntoma: Las consultas FTS5 devuelven resultados incorrectos o incompletos después de muchas actualizaciones incrementales.

Causa: Los segmentos internos de FTS5 pueden fragmentarse después de muchas actualizaciones pequeñas.

Solución: Reconstruya y optimice:

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

Tiempo de espera de MCP

Síntoma: La herramienta de IA informa que el servidor MCP agotó el tiempo de espera.

Causa: La primera consulta activa la carga del modelo (inicialización diferida), lo cual toma 2-5 segundos. El tiempo de espera predeterminado de MCP en la herramienta de IA puede ser menor.

Solución: Precaliente el modelo al iniciar el servidor:

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

Bloqueos de archivo SQLite

Síntoma: Errores SQLITE_BUSY o SQLITE_LOCKED.

Causa: Múltiples procesos escribiendo en la base de datos simultáneamente. El modo WAL permite lecturas concurrentes, pero solo un escritor.

Solución: Asegúrese de que solo un proceso (el indexador) escriba en la base de datos. El servidor MCP y los hooks solo deben leer. Si necesita escrituras concurrentes, use el modo WAL y configure un tiempo de espera por ocupación:

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

sqlite-vec no se carga

Síntoma: La búsqueda vectorial está deshabilitada; el retriever funciona en modo solo BM25.

Causa: La extensión sqlite-vec no está instalada, no se encuentra en la ruta de bibliotecas, o es incompatible con la versión de SQLite.

Solución:

# Install via pip
pip install sqlite-vec

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

Verifique que la extensión se carga correctamente:

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

Problemas de memoria en vaults grandes

Síntoma: Errores de falta de memoria durante la reindexación completa de un vault grande (más de 50.000 notas).

Causa: El batch size de embeddings es demasiado grande, o todo el contenido de los archivos se carga en memoria simultáneamente.

Solución: Reduzca el batch size y procese los archivos de forma incremental:

BATCH_SIZE = 32  # Reduce from 64

También asegúrese de que el indexador procese los archivos uno por uno (leyendo, dividiendo en chunks y generando embeddings para cada archivo antes de pasar al siguiente) en lugar de cargar todos los archivos en memoria.


Guía de migración

Desde Apple Notes

  1. Exporte Apple Notes mediante la opción “Export All” (macOS) o utilice una herramienta de migración como apple-notes-liberator
  2. Convierta las exportaciones HTML a markdown usando markdownify o pandoc
  3. Mueva los archivos convertidos a la carpeta 00-inbox/ de su vault
  4. Revise y añada frontmatter a cada nota
  5. Mueva las notas a las carpetas de dominio correspondientes

Desde Notion

  1. Exporte desde Notion: Settings → Export → Markdown & CSV
  2. Descomprima la exportación en la carpeta 00-inbox/ de su vault
  3. Corrija los artefactos de markdown específicos de Notion:
  4. Notion usa - [ ] para listas de verificación — esto es markdown estándar
  5. Notion incluye tablas de propiedades como HTML — conviértalas a frontmatter YAML
  6. Notion incrusta imágenes como rutas relativas — copie las imágenes a su carpeta de adjuntos
  7. Añada frontmatter estándar (type, domain, tags)
  8. Reemplace los enlaces de páginas de Notion con wiki-links de Obsidian

Desde Google Docs

  1. Use Google Takeout para exportar todos los documentos
  2. Convierta archivos .docx a markdown: pandoc -f docx -t markdown input.docx -o output.md
  3. Conversión por lotes: for f in *.docx; do pandoc -f docx -t markdown "$f" -o "${f%.docx}.md"; done
  4. Mueva al vault, añada frontmatter y organice en carpetas

Desde markdown sin formato (sin Obsidian)

Si ya tiene un directorio de archivos markdown:

  1. Abra el directorio como un vault de Obsidian (Obsidian → Open Vault → Open folder)
  2. Añada .obsidian/ a .gitignore si el directorio está bajo control de versiones
  3. Cree plantillas de frontmatter y aplíquelas a los archivos existentes
  4. Comience a enlazar notas con [[wiki-links]] a medida que lee y organiza
  5. Ejecute el indexador inmediatamente — el sistema de recuperación funciona desde el primer día

Desde otro sistema de recuperación

Si está migrando desde un sistema diferente de embeddings/búsqueda:

  1. No intente migrar los vectores. Diferentes modelos producen espacios vectoriales incompatibles. Ejecute una reindexación completa con el nuevo modelo.
  2. Migre el contenido, no el índice. Los archivos del vault son la fuente de verdad. El índice es un artefacto derivado.
  3. Verifique después de la migración. Ejecute 10-20 consultas cuyas respuestas conozca y verifique que los resultados coincidan con sus expectativas.

Registro de cambios

Fecha Cambio
2026-03-03 Actualización de la evolución de la especificación MCP (noviembre 2025: Streamable HTTP, .well-known, tool annotations). Añadido fine-tuning de Model2Vec y soporte para tokenizador BPE/Unigram. Añadida tabla comparativa de servidores MCP de la comunidad. Actualización de Smart Connections a v4.
2026-03-02 Añadidos potion-base-32M y potion-retrieval-32M a la comparación de modelos. Añadida sección de cuantización/reducción de dimensionalidad. Añadida nota sobre la evolución de la especificación MCP.
2026-03-01 Publicación inicial

Referencias


  1. Cormack, G.V., Clarke, C.L.A., y Buettcher, S. Reciprocal Rank Fusion outperforms Condorcet and individual Rank Learning Methods. SIGIR, 2009. Introduce RRF con k=60 como método libre de parámetros para combinar listas clasificadas. 

  2. OpenAI Embeddings Pricing. text-embedding-3-small: $0,02 por millón de tokens. Costo estimado del vault por reindexación completa: ~$0,30. 

  3. van Dongen, T. et al. Model2Vec: Turn any Sentence Transformer into a Small Fast Model. arXiv, 2025. Describe el enfoque de destilación que produce embeddings estáticos a partir de transformadores de oraciones. 

  4. MTEB: Massive Text Embedding Benchmark. potion-base-8M obtiene 50,03 de promedio frente a 56,09 de all-MiniLM-L6-v2 (89% de retención). 

  5. SQLite FTS5 Extension. FTS5 proporciona búsqueda de texto completo con clasificación BM25 y pesos de columna configurables. 

  6. sqlite-vec: A vector search SQLite extension. Proporciona tablas virtuales vec0 para búsqueda vectorial KNN dentro de SQLite. 

  7. Robertson, S. y 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. Las representaciones densas superan a BM25 en un 9-19% en respuesta a preguntas de dominio abierto. 

  9. Reimers, N. y Gurevych, I. Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks. EMNLP, 2019. Trabajo fundacional sobre similitud semántica densa. 

  10. Luan, Y. et al. Sparse, Dense, and Attentional Representations for Text Retrieval. TACL, 2021. La recuperación híbrida (hybrid retrieval) supera consistentemente los enfoques de modalidad única en MS MARCO. 

  11. SQLite Write-Ahead Logging. Modo WAL para lecturas concurrentes con un único escritor. 

  12. Gao, Y. et al. Retrieval-Augmented Generation for Large Language Models: A Survey. arXiv, 2024. Revisión de arquitecturas RAG y estrategias de fragmentación (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. Documentación oficial de Obsidian. 

  16. Model Context Protocol Specification. El estándar MCP para conectar herramientas de IA con fuentes de datos. 

  17. Datos de producción del autor. 16.894 archivos, 49.746 fragmentos, 83,56 MB de base de datos SQLite, 7.771 señales procesadas a lo largo de 14 meses. Latencia de consulta medida mediante time.perf_counter()

  18. Model2Vec Potion Models. Minish Lab, 2025. Potion-base-32M (MTEB 52,46), potion-retrieval-32M (MTEB retrieval 36,35), y funciones de cuantización/reducción de dimensionalidad en v0.5.0+. 

  19. Update on the Next MCP Protocol Release. La versión de noviembre de 2025 incluyó transporte Streamable HTTP, descubrimiento de URL .well-known, anotaciones estructuradas de herramientas y estandarización de niveles SDK. La próxima versión está prevista tentativamente para mediados de 2026 con operaciones asíncronas, extensiones de dominio específico y comunicación entre agentes. 

  20. Model2Vec Releases. v0.4.0 (feb 2025): soporte para entrenamiento/ajuste fino. v0.5.0 (abr 2025): reescritura del backend, cuantización, reducción de dimensionalidad. v0.7.0 (oct 2025): cuantización de vocabulario, soporte para tokenizadores BPE/Unigram. 

  21. Smart Connections for Obsidian. Smart Connections v4: embeddings de IA con prioridad local, la búsqueda semántica funciona sin conexión después de la indexación inicial. 

VAULT obsidian.md INDEXED