obsidian:~/vault$ search --hybrid obsidian

Ubicación del vault de ejemplo

# Obsidian como infraestructura de IA: La referencia técnica definitiva

words: 14853 read_time: 69m updated: 2026-04-16 20:39
$ 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í, 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) fusiona ambos métodos sin necesidad de calibrar puntuaciones. Ninguno de los dos métodos por sí solo cubre ambos modos de fallo. La investigación sobre el ranking de pasajes en MS MARCO confirma el patrón: la recuperación híbrida supera consistentemente a cualquiera de los métodos por separado.3 La inmersión profunda en el 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 da 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 fuente 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. Todo el stack 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. La re-generación completa de embeddings de 49.746 chunks 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.4

La indexación incremental mantiene el sistema actualizado en menos de 10 segundos. La comparación del tiempo de modificación de archivos detecta 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. Comienza con búsqueda solo por BM25 en un vault pequeño. Agrega búsqueda vectorial cuando las colisiones de palabras clave se conviertan en un problema. Agrega fusión RRF cuando necesites tanto coincidencias 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. Tu punto de partida depende de dónde te encuentras:

Tú eres… Comienza aquí Luego explora
Nuevo en Obsidian + IA Por qué Obsidian para infraestructura de IA, Inicio rápido Arquitectura del vault, Arquitectura del servidor MCP
Vault existente, quieres 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 Ajuste 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 centran en conceptos, decisiones arquitectónicas y el razonamiento detrás de las elecciones 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, texto plano, tienen estructura de grafo y el usuario controla cada capa del stack.

Lo que Obsidian ofrece a la IA y las alternativas no

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

Arquitectura local-first. El vault vive en tu 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 procesas tu propio contenido. Puedes generar embeddings, indexar, fragmentar y buscar en tus 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 tu disco lo permita, no tan rápido como responda un endpoint de API. También importa para la privacidad: notas personales que contienen credenciales, datos de salud, información financiera y reflexiones privadas nunca salen de tu máquina.

Estructura de grafo mediante wiki-links. La sintaxis [[wiki-link]] de Obsidian crea un grafo dirigido entre 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 estableció 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 2.500 plugins comunitarios (a marzo de 2026, frente a más de 1.800 a mediados de 2025). Dataview consulta tu vault como una base de datos. Templater genera notas a partir de plantillas con lógica de JavaScript. La integración con Git sincroniza tu vault con un repositorio. Linter aplica consistencia de formato. El plugin central Bases (introducido en la v1.9.10) agrega vistas tipo base de datos — tablas, galerías, calendarios y tableros kanban — sobre los archivos del vault usando propiedades de frontmatter como campos, guardados como archivos .base.27 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í.

Más de 5 millones de usuarios. Obsidian tiene una gran comunidad activa que produce plantillas, flujos de trabajo, plugins y documentación. Cuando encuentres un problema con la organización del vault o la configuración de plugins, es probable que alguien ya 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 wrappers de API.

Lo que un sistema de archivos por sí solo no te da

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 enlazas desde la Nota A a la Nota B, la Nota B muestra que la Nota A la referencia. El panel de grafo visualiza clústeres de conexiones. Esta conciencia bidireccional es metadatos 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 callout se renderizan en tiempo real. La experiencia de escritura es más rica que un editor de texto, mientras que el formato de almacenamiento sigue siendo texto plano. Escribes y organizas en un entorno enriquecido; el sistema de recuperación indexa el markdown sin procesar.

  3. Infraestructura comunitaria. Descubrimiento de plugins, marketplace de temas, servicio de sincronización (opcional), servicio de publicación (opcional) y un ecosistema de documentación. Puedes 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 tú construyes)

Obsidian no incluye infraestructura de recuperación. Tiene búsqueda básica (texto completo, nombre de archivo, etiqueta) pero no tiene pipeline de embeddings, ni búsqueda vectorial, ni ranking de fusión, ni servidor MCP, ni filtrado de credenciales, ni estrategia de chunking, ni hooks de integración para herramientas externas de IA. Esta guía cubre la infraestructura que construyes 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 usas 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 generador de embeddings 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: Tu primer vault conectado a IA

Esta sección conecta un vault con una herramienta de IA en cinco minutos. Instalarás Obsidian, crearás un vault, instalarás un servidor MCP y ejecutarás tu primera consulta. Este inicio rápido usa 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)
  • Obsidian 1.12+ (para integración con CLI; versiones anteriores funcionan para configuraciones solo con MCP)
  • Claude Code, Codex CLI o Cursor instalados

Paso 1: Crear un vault

Descarga Obsidian desde obsidian.md y crea un nuevo vault. Elige una ubicación que recuerdes — el servidor MCP necesita la ruta absoluta.

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

Agrega 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 al vault. El ecosistema ha crecido significativamente durante 2025-2026. Entre las actualizaciones recientes más destacadas se encuentra MCPVault v0.11.0 (marzo 2026), que añadió list_all_tags para escanear frontmatter y hashtags con conteos, mejoró el manejo de carpetas con punto y agregó soporte para archivos .base y .canvas.25 El paquete también fue renombrado a @bitbonsai/mcpvault en npm.

Servidor Autor Transporte Requiere plugin Función clave
obsidian-mcp-server StevenStavrakis STDIO No Ligero, basado en archivos
mcp-obsidian MarkusPfundstein STDIO REST local API CRUD completo del vault 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 REST local API Tags, gestión de frontmatter

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

npm install -g obsidian-mcp-server

Paso 3: Configura tu herramienta de IA

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

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

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

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

Cursor — agrega a .cursor/mcp.json:

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

Paso 4: Ejecuta tu primera consulta

Abre tu herramienta de IA y haz una pregunta que tus notas del vault puedan responder:

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

La herramienta de IA llama al servidor MCP, que busca en tu vault y devuelve contenido relevante. Deberías ver resultados con rutas de archivos y extractos pertinentes.

Lo que acabas de construir

Conectaste 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 tu vault, realiza búsquedas básicas y devuelve resultados. Esta es la versión mínima viable.

Lo que este inicio rápido NO te 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 mediante 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.


CLI de Obsidian para flujos de trabajo con IA

Obsidian 1.12 (febrero de 2026) introdujo una interfaz de línea de comandos integrada que abre una nueva superficie de integración para flujos de trabajo con IA.28 El CLI actúa como un control remoto para la interfaz gráfica de Obsidian — Obsidian debe estar en ejecución (o se iniciará automáticamente con el primer comando). Habilítalo en Configuración > General > Command line interface.

Por qué el CLI importa para la infraestructura de IA

El CLI proporciona acceso programático a operaciones nativas de Obsidian que antes requerían la interfaz gráfica o APIs de plugins. Para flujos de trabajo con IA, las capacidades clave son:

  • Búsqueda desde scripts y hooks. obsidian search "query" y obsidian search:context "query" ejecutan búsquedas en el vault desde cualquier script de shell, hook o pipeline de automatización. La variante search:context devuelve líneas coincidentes con contexto circundante, útil para alimentar resultados en prompts de IA.
  • Automatización de notas diarias. obsidian daily abre o crea la nota diaria de hoy. Combinado con scripting de shell, esto permite flujos de trabajo automatizados de resúmenes diarios — un hook puede añadir resúmenes generados por IA a la nota diaria.
  • Creación de notas basada en plantillas. obsidian template list y obsidian template create generan notas a partir de plantillas de Templater o plantillas del núcleo, lo que permite a los agentes de IA crear entradas estructuradas en el vault sin escribir directamente archivos markdown.
  • Gestión de propiedades. obsidian property set y obsidian property get leen y escriben propiedades de frontmatter, permitiendo actualizaciones de metadatos desde scripts sin analizar YAML.
  • Control de plugins. obsidian plugin enable/disable/list gestiona plugins de forma programática, útil para activar o desactivar plugins de indexación durante operaciones por lotes.
  • Gestión de tareas. obsidian task list/add/complete proporciona acceso estructurado a tareas, útil para agentes de IA que administran elementos de trabajo en el vault.

CLI vs MCP para acceso con IA

El CLI y los servidores MCP cumplen roles diferentes y son complementarios, no competidores:

Aspecto CLI de Obsidian Servidor MCP
Quién lo llama Scripts de shell, hooks, tareas cron Agentes de IA (Claude Code, Codex, Cursor)
Protocolo Proceso POSIX (stdin/stdout/stderr) MCP (JSON-RPC sobre STDIO o HTTP)
Fortaleza Operaciones nativas de Obsidian (plantillas, plugins, propiedades) Recuperación personalizada (embeddings, BM25, fusión RRF)
Limitación Sin búsqueda vectorial, sin pipeline de embeddings Sin acceso a operaciones internas de Obsidian
Ideal para Scripts de automatización, pipelines de ingesta, acciones de hooks Consultas de agentes de IA en tiempo real durante sesiones

Recomendación: Usa el CLI para automatización de ingesta (crear notas, gestionar propiedades, ejecutar búsquedas nativas de Obsidian) y MCP para recuperación (búsqueda híbrida con embeddings). Un hook PreToolUse puede llamar a obsidian search:context como verificación rápida previa antes de recurrir al recuperador MCP completo para resultados clasificados.

Ejemplo: Hook de ingesta con el CLI

#!/bin/bash
# Hook: append today's signals to daily note via CLI
DATE=$(date +%Y-%m-%d)
SUMMARY="$1"
obsidian daily  # ensure daily note exists
obsidian file append "Daily Notes/${DATE}.md" "## AI Summary\n${SUMMARY}"

Plugins de agentes para Obsidian

Una categoría creciente de plugins de Obsidian integra agentes de IA de codificación directamente en la interfaz del vault, proporcionando una alternativa a la configuración de servidores MCP externos. Estos plugins ejecutan el agente de IA dentro de la barra lateral de Obsidian en lugar de conectarse desde una herramienta externa.

Claudian

Claudian integra Claude Code como un colaborador de IA en el vault. El directorio del vault se convierte en el directorio de trabajo de Claude, otorgándole capacidades agénticas completas: lectura/escritura de archivos, búsqueda, comandos bash y flujos de trabajo de múltiples pasos.29

Funciones clave para infraestructura de IA: - Prompts con reconocimiento de contexto. Adjunta automáticamente la nota enfocada, admite menciones de archivos con @notename, exclusión basada en tags y selección del editor como contexto. - Soporte de visión. Analiza imágenes mediante arrastrar y soltar, pegar o ruta de archivo — útil para procesar capturas de pantalla y diagramas almacenados en el vault. - Slash commands. Crea plantillas de prompts reutilizables activadas con /command, lo que permite operaciones estandarizadas en el vault. - Modos de permisos. Modos YOLO (aprobación automática), Safe (aprobar cada acción) y Plan (solo planificación), con una lista de bloqueo de seguridad y confinamiento al vault.

Agent Client

Agent Client lleva Claude Code, Codex CLI y Gemini CLI a una barra lateral unificada de Obsidian a través del Agent Client Protocol (ACP).30

Funciones clave: - Cambio entre múltiples agentes. Chatea con Claude Code, Codex o Gemini CLI desde el mismo panel, alternando entre agentes según sea necesario. - Menciones de notas. Usa @notename para incluir contenido de notas en los prompts, similar a Claudian pero agnóstico al agente. - Ejecución de shell. Ejecuta comandos de terminal directamente en el chat — scripts de build, comandos git o cualquier operación de terminal sin abandonar la conversación. - Aprobación de acciones. Control detallado sobre lecturas de archivos, ediciones y ejecuciones de comandos.

Cuándo usar plugins de agentes vs MCP externo

Escenario Plugin de agente MCP externo
Escribir y editar notas del vault con asistencia de IA Mejor — el agente ve el contexto del editor Funciona, pero sin reconocimiento del editor
Desarrollo de código en múltiples repositorios Limitado — alcance restringido al vault Mejor — alcance del proyecto con acceso completo al sistema de archivos
Recuperación desde un corpus grande indexado Solo búsqueda básica Pipeline completo de recuperación híbrida
Preguntas rápidas al vault durante sesiones de toma de notas Ideal — sin cambio de contexto Requiere cambiar a la terminal

Recomendación: Usa plugins de agentes para flujos de trabajo centrados en el vault (escribir, organizar, resumir notas). Usa servidores MCP externos para flujos de trabajo de desarrollo donde el agente de IA necesita el pipeline completo de recuperación y acceso a repositorios de código fuera del vault. Ambos enfoques pueden coexistir — ejecuta Claudian dentro de Obsidian para trabajo con notas y Claude Code con MCP externamente para desarrollo.


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 herramienta encaja mejor.

Árbol de decisión

INICIO: ¿Cuál es tu tipo de contenido principal?

├─ Datos estructurados (tablas, registros, esquemas)
   Usa una base de datos. SQLite, PostgreSQL o una hoja de cálculo.
   Obsidian es para prosa, no para datos tabulares.

├─ Contexto efímero (proyecto actual, notas temporales)
   Usa CLAUDE.md / AGENTS.md en el repositorio del proyecto.
   Estos viajan con el código y se reinician por proyecto.

├─ Wiki de equipo (documentación compartida, onboarding)
   Evalúa Notion, Confluence o un repositorio git compartido.
   Los vaults de Obsidian son primero personales. La sincronización
    en equipo es posible pero no nativa.

└─ Corpus de conocimiento personal en crecimiento
   
   ├─ < 50 notas
      Una carpeta de archivos markdown + grep es suficiente.
      Obsidian agrega valor principalmente a través del grafo de
       enlaces, que necesita densidad para ser útil.
   
   ├─ 50 - 500 notas
      Obsidian agrega valor. Los wiki-links crean un grafo navegable.
      La búsqueda solo con BM25 (FTS5) es suficiente a esta escala.
      Omite la búsqueda vectorial y RRF hasta que aparezcan
       colisiones de palabras clave.
   
   ├─ 500 - 5,000 notas
      La recuperación híbrida (hybrid retrieval) cobra valor. Las
       colisiones de palabras clave aumentan. La búsqueda semántica
       captura consultas que BM25 no encuentra.
      Agrega búsqueda vectorial + fusión RRF a esta escala.
   
   └─ 5,000+ notas
       El pipeline completo es esencial. BM25 solo devuelve
        demasiado ruido.
       El filtrado de credenciales se vuelve crítico (más notas =
        más secretos pegados accidentalmente).
       La indexación incremental importa (la reindexación completa
        toma minutos).
       La integración con MCP rinde dividendos en cada
        interacción con IA.

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 Se requiere API Requiere exportación Acceso directo a archivos Ya en contexto
Ecosistema de plugins 2,500+ plugins Integraciones Ninguno N/A N/A
Funciona sin conexión Completo Solo lectura en caché Parcial Completo Completo
Escala a 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 actual, ponlo 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, usa una base de datos. Las notas de Obsidian son primero prosa. Dataview puede consultar campos de frontmatter, pero una base de datos real maneja consultas estructuradas mejor.
  • Investigación temporal. Si las notas se descartarán al terminar el proyecto, un directorio temporal con archivos markdown es más simple. No construyas infraestructura de recuperación para contenido efímero.

Cuándo Obsidian es la elección correcta

  • Acumulación de conocimiento durante meses o años. El valor se acumula conforme el corpus crece. Un vault de 200 notas consultado diariamente durante seis meses aporta más valor que uno de 5,000 notas consultado una sola vez.
  • Múltiples dominios en un solo corpus. Un vault que contiene notas sobre programación, arquitectura, seguridad, diseño y proyectos personales se beneficia de la recuperación entre dominios, algo que un CLAUDE.md específico de proyecto no puede ofrecer.
  • Contenido sensible en términos de privacidad. Local-first significa que el pipeline de recuperación nunca envía contenido a servicios externos. El vault contiene lo que tú pongas en él, incluyendo contenido que no subirías a un servicio en la nube.

Modelo mental: tres capas

El sistema tiene tres capas que operan de forma independiente pero se potencian al combinarse. Cada capa tiene una responsabilidad diferente y un modo de fallo distinto.

┌─────────────────────────────────────────────────────┐
                 CAPA DE INTEGRACIÓN                   
  Servidores MCP, hooks, skills, inyección de contexto 
  Responsabilidad: entregar contexto a herramientas IA 
  Fallo: contexto erróneo, excesivo o desactualizado   
└──────────────────────┬──────────────────────────────┘
                        consulta + resultados rankeados
┌──────────────────────┴──────────────────────────────┐
                 CAPA DE RECUPERACIÓN                  
  BM25, vector KNN, fusión RRF, token budget           
  Responsabilidad: encontrar el contenido correcto     
  Fallo: ranking incorrecto, resultados faltantes,     
         consultas lentas                              
└──────────────────────┬──────────────────────────────┘
                        fragmentado, embebido, indexado
┌──────────────────────┴──────────────────────────────┐
                  CAPA DE INGESTA                      
  Creación de notas, triaje de señales, organización   
  Responsabilidad: qué entra al vault y cómo se guarda 
  Fallo: ruido, duplicados, estructura faltante        
└─────────────────────────────────────────────────────┘

Ingesta determina qué entra al vault. Sin curación, el vault acumula ruido: capturas de pantalla de tweets, artículos copiados y pegados sin anotación, 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 el vault contiene contenido que vale la pena recuperar.

Recuperación hace que el vault sea consultable. Este es el motor: fragmentar notas en unidades de búsqueda, generar embeddings de los fragmentos en espacio vectorial, indexar para búsqueda por palabras clave y semántica, fusionar resultados con RRF. La capa de recuperación transforma un directorio de archivos en una base de conocimiento consultable. Sin esta capa, el vault 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 al vault. 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 reglas de enrutamiento de señales. El servidor MCP no sabe nada sobre cómo se crearon las notas. Este desacoplamiento significa que puedes mejorar cualquier capa de forma independiente. Reemplaza el modelo de embeddings sin cambiar el pipeline de ingesta. Agrega una nueva capacidad MCP sin modificar el recuperador. Cambia las heurísticas de puntuación de señales sin tocar el índice.


Arquitectura del vault para consumo por IA

Un vault optimizado para recuperación por IA sigue convenciones distintas a uno optimizado 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 recuperación.

Estructura de carpetas

Usa prefijos numéricos para las carpetas de nivel superior para crear una jerarquía organizativa predecible. Los números no implican prioridad — agrupan dominios relacionados y hacen la estructura más fácil de recorrer.

vault/
├── 00-inbox/              # Capturas sin clasificar, pendientes de triaje
├── 01-projects/           # Notas de proyectos activos
├── 02-areas/              # Áreas de responsabilidad continuas
├── 03-resources/          # Material de referencia por tema
   ├── programming/
   ├── security/
   ├── ai-engineering/
   ├── design/
   └── devops/
├── 04-archive/            # Proyectos completados, referencias antiguas
├── 05-signals/            # Ingesta de señales con puntuación
   ├── ai-tooling/
   ├── security/
   ├── systems/
   └── ...12 carpetas de dominio
├── 06-daily/              # Notas diarias (si se usan)
├── 07-templates/          # Plantillas de notas (excluidas del índice)
├── 08-attachments/        # Imágenes, PDFs (excluidos del índice)
├── .obsidian/             # Configuración de Obsidian (excluida del índice)
└── .indexignore            # Rutas a excluir del índice de recuperación

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 con contenido sensible que no quieras incluir en el índice de recuperación.

El archivo .indexignore

Crea un archivo .indexignore en la raíz del vault para excluir explícitamente rutas del índice de recuperación. La sintaxis coincide con .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 por completo las rutas coincidentes. Los archivos en rutas excluidas nunca se fragmentan, nunca se generan embeddings de ellos y nunca aparecen en los resultados de búsqueda.

Esquema de notas

Cada nota debe tener frontmatter YAML. El recuperador usa 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 usa en la visualización de resultados de búsqueda y como contexto de encabezado para BM25
  • type — Permite consultas filtradas por tipo (“mostrar solo MOCs” o “solo señales”)
  • tags — Se indexan en el contexto de encabezado de FTS5 con peso 0.3, proporcionando coincidencias por palabras clave incluso cuando el cuerpo usa terminología diferente

Campos opcionales pero valiosos:

  • domain — Permite consultas con alcance de 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 tu nota 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 (chunks) 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.

Malo 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 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ídela en subsecciones H2. El fragmentador se encarga del resto.

Qué no incluir en las notas

Contenido que degrada la calidad de recuperación:

  • Copias y pegas sin procesar de artículos completos sin anotación. El recuperador indexa las palabras clave del artículo original, diluyendo tu vault con contenido que no escribiste. En su lugar, agrega un resumen, extrae los puntos clave o enlaza a la URL de origen.
  • 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 el filtrado de credenciales, el enfoque más seguro es nunca pegar secretos en las notas. En su lugar, referiérelos por nombre (“el token API de Cloudflare en ~/.env”).
  • Contenido autogenerado sin curación. Si una herramienta genera una nota (transcripción de reunión, highlights de Readwise, importación RSS), revísala y anótala antes de que ingrese al vault 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 por 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 tu vault como una base de datos usando campos de frontmatter. Crea índices dinámicos: “todas las notas etiquetadas como security actualizadas en los últimos 30 días” o “todas las notas de proyecto con status active.” Dataview no ayuda directamente a la recuperación, pero te ayuda a identificar vacíos en la cobertura de tu 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. Asegura que cada nota nueva comience con el frontmatter correcto usando una plantilla que precargue 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

## Referencias

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

  • Incremento de encabezados: aplicar niveles secuenciales (no saltar de H1 a H3)
  • YAML title: que coincida 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 tu vault. Rastrea cambios a lo largo del tiempo, sincroniza entre máquinas y recupera notas eliminadas por accidente. Git también proporciona datos de mtime que el indexador usa para la detección incremental de cambios.

Plugins que ayudan a la indexación

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

Metadata Menu. Ofrece edición estructurada de frontmatter con autocompletado para valores de campos. Reduce errores tipográficos en los campos type, domain y tags. Unos 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 basura al ser dividido en chunks y convertido en embeddings. Excluye los archivos de Excalidraw del índice mediante .indexignore o filtrando 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. Excluye 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 usas notas diarias, escribe contenido sustancial en ellas o excluye la carpeta de notas diarias del índice.

Configuración de plugins que importa

File recovery → Activado. 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 dependes.

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

Default new file location → Carpeta designada. Dirige 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.

Wiki-link format → 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 embeddings: elección y configuración

El modelo de embeddings 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 generación de embeddings 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 conviene 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 un promedio ponderado de embeddings de tokens precalculados.5 La consecuencia práctica: la velocidad de generación de embeddings es entre 50 y 500 veces más rápida que la de modelos basados en transformers, ya que no hay cómputo 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).6 La brecha de calidad del 11% es la contrapartida de las ventajas en velocidad y simplicidad. Para fragmentos cortos de markdown (entre 200 y 400 palabras en promedio en un vault típico), la diferencia de calidad es menos pronunciada que en documentos largos, porque ambos modelos convergen en representaciones similares para textos breves 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 importar el módulo. Importar el módulo del embedder no tiene costo alguno cuando el recuperador opera en modo de respaldo BM25 (por ejemplo, cuando el entorno virtual de embeddings 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 el directorio 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 fragmentos a embed_batch() en lugar de generar embeddings 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
potion-multilingual-128M 256 ~500 MB 300x Vaults multilingües (101 idiomas)
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

Elige potion-base-32M cuando quieras mejor calidad que potion-base-8M sin salir de 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 un promedio MTEB de 52,46 (5% de mejora sobre potion-base-8M) manteniendo la misma salida de 256 dimensiones y la dependencia exclusiva de numpy.20 El archivo del modelo, 4 veces más grande, incrementa el uso de memoria, pero la velocidad de generación de embeddings sigue siendo órdenes de magnitud superior a la de los modelos transformer.

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

Elige potion-multilingual-128M cuando tu vault contenga notas en múltiples idiomas. Lanzado en mayo de 2025, este modelo de 101 idiomas es el modelo de embeddings estáticos con mejor rendimiento para tareas multilingües; genera embeddings para cualquier texto en cualquier idioma manteniendo la misma dependencia exclusiva de numpy que los demás modelos potion.24 El archivo del modelo, más grande (~500 MB), es la contrapartida de la capacidad multilingüe. Úsalo cuando tengas notas en japonés, chino, alemán u otros idiomas además del inglés.

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

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

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

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

Cuantización y reducción de dimensionalidad

Model2Vec v0.5.0+ permite cargar modelos con precisión reducida y menos dimensiones.20 Esto resulta útil para despliegues 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.22

Ajuste fino para embeddings específicos del vault

Model2Vec v0.4.0+ permite entrenar 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.22 Esto es relevante para vaults con vocabulario especializado (notas médicas, referencias legales, jerga de un dominio específico) donde los modelos potion predeterminados podrían 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, potion-base-8M produce una calidad de recuperación suficiente. El ajuste fino solo vale la pena 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 cambias el modelo de embeddings, el indexador detecta la discrepancia en la siguiente ejecución incremental y dispara 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 distintos 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 tras la primera descarga.

Discrepancia de dimensiones. Si cambias de modelo sin limpiar la base de datos, los vectores almacenados tienen una dimensión diferente a la de los nuevos embeddings. El indexador lo detecta mediante el hash del modelo y dispara 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 por discrepancia de dimensiones.

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, reduce 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 ranking 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 fallo específicos.

Tabla virtual FTS5

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

Modo content-sync. El parámetro content=chunks indica a FTS5 que haga referencia directa a la tabla chunks en lugar de almacenar una copia duplicada del texto. Esto reduce el almacenamiento a la mitad, pero implica 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)

Ranking BM25

BM25 clasifica los documentos según la frecuencia de términos, la frecuencia inversa de documentos y la normalización por longitud del 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 tu vault tiene encabezados descriptivos que predicen con fuerza la calidad del contenido, aumenta el peso de section. Si tus etiquetas son completas y precisas, aumenta el peso de heading_context.

Cuándo BM25 gana

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 BM25 falla

BM25 falla en consultas que utilizan terminología diferente a la del 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 la colisión de palabras clave a escala. En un vault de 15.000 archivos, una búsqueda de “configuration” coincide con cientos de notas porque prácticamente todas las notas de proyectos mencionan configuración. Los resultados son técnicamente correctos pero prácticamente inútiles: el ranking no puede determinar qué nota de “configuration” es relevante para la consulta actual.

Tokenizador FTS5

FTS5 usa el tokenizador unicode61 por defecto, que maneja texto ASCII y Unicode. Para vaults con contenido significativo en CJK (chino, japonés, coreano), considera 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 predeterminado unicode61 divide por límites de palabras, lo cual funciona mal para idiomas sin espacios entre palabras. El tokenizador trigram divide cada tres caracteres, lo que permite coincidencias de subcadenas a costa del tamaño del índice (aproximadamente 3 veces mayor).

Mantenimiento

FTS5 requiere sincronización explícita cuando la tabla subyacente chunks 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útalo después de inserciones masivas (reindexación completa), pero no después de actualizaciones incrementales individuales; para esas, usa 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 buscable, 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 se mapea 1:1 con la tabla chunks, permitiendo joins entre los resultados vectoriales y los metadatos de cada fragmento.

Pipeline de embeddings

El pipeline fluye desde la nota hasta el vector buscable:

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 fragmentos 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 una 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).

Paginación KNN con restricciones de distancia

A partir de sqlite-vec v0.1.7, las consultas KNN soportan restricciones WHERE distance < ?, lo que permite paginación basada en cursor a través de grandes conjuntos de resultados sin volver a escanear páginas anteriores.26

def _paginated_vector_search(self, query_vec, page_size=20, max_distance=None):
    """Paginate through KNN results using distance constraints."""
    packed = _serialize_vector(query_vec)
    constraint = f"AND distance < {max_distance}" if max_distance else ""

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

    # Use last result's distance as cursor for next page
    next_cursor = results[-1][1] if results else None
    return results, next_cursor

Esto reemplaza el patrón anterior de obtener un k grande y recortar en Python, reduciendo el uso de memoria para consultas exploratorias sobre vaults grandes.

Soporte de DELETE en tablas vec0

sqlite-vec v0.1.7 añadió soporte nativo de DELETE para tablas virtuales vec0.26 Anteriormente, eliminar vectores requería eliminar y recrear la tabla. Ahora la ruta de eliminación de archivos del indexador puede borrar vectores directamente:

# Before v0.1.7: required workaround (drop + recreate, or mark as inactive)
# After v0.1.7: direct DELETE works
db.execute("DELETE FROM chunk_vecs WHERE id = ?", [chunk_id])

Esto simplifica la reindexación incremental cuando se eliminan o mueven notas. El indexador ya no necesita mantener una tabla auxiliar de “IDs activos” ni reconstrucciones por lotes.

Cuándo la búsqueda vectorial gana

La búsqueda vectorial destaca 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 la búsqueda vectorial falla

La búsqueda vectorial tiene dificultades con identificadores exactos:

  • Consulta: _rrf_fuse → Devuelve notas sobre “algoritmos de fusión” y “combinación de rankings”, pero puede clasificar la definición real de la función por debajo de discusiones conceptuales
  • Consulta: PostToolUse → Devuelve notas sobre “hooks del ciclo de vida de herramientas” y “manejadores post-ejecución” 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 sistema de recuperación recurre a búsqueda exclusiva 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 sistema de recuperación verifica vec_available antes de intentar consultas vectoriales. Cuando está deshabilitado, todas las búsquedas usan exclusivamente BM25 y se omite el paso de fusión RRF.


Reciprocal Rank Fusion (RRF)

RRF combina dos listas ordenadas sin necesidad de calibrar 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 otras alternativas. Para una calculadora interactiva con rangos editables, escenarios preconfigurados y un explorador visual de arquitectura, consulta 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.3) - rank_i es el rango del documento (base 1) en la lista de resultados i - weight_i es un multiplicador opcional por lista (valor predeterminado 1.0)

Los documentos que ocupan buenas posiciones 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 solo posiciones de rango, que siempre son enteros comenzando en 1 independientemente del método de puntuación.

Los modelos de fusión aprendidos requieren datos de entrenamiento etiquetados — pares de relevancia consulta-documento. Para una base de conocimiento personal, estos datos de entrenamiento no existen. Necesitarías 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.3

Fusión en la práctica

Consulta: “how does the review aggregator handle disagreements”

BM25 clasifica 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 clasifica el mismo fragmento en la posición 1 (coincidencia semántica en 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 ocupan buenas posiciones en ambas listas suben a los primeros lugares. Los fragmentos que solo aparecen en una lista obtienen una puntuación de fuente única y caen por debajo de los resultados con rango dual. 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, prueba 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 clasificados frente a los de clasificación inferior:

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

Comienza con k=60. El artículo original de RRF encontró que este valor es robusto en diversos conjuntos de datos TREC. Ajusta solo después de medir casos de falla en tu 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), desempata de la siguiente manera:

  1. Prefiere fragmentos que aparecen en ambas listas sobre fragmentos que aparecen en solo una
  2. Entre fragmentos en ambas listas, prefiere el que tenga el rango combinado más bajo
  3. Entre fragmentos en solo una lista, prefiere el que tenga 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 del pipeline completo: 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 añaden resultados en orden de relevancia hasta agotar el presupuesto.

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 el ranking RRF ya ordena los resultados por relevancia.

Esquema de 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 provoca errores. El único fallo irrecuperable es la ausencia del archivo de base de datos.

Estadísticas de producción

Medidas 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. Durante 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 por mtime —detectaría casos donde un archivo fue tocado sin modificarse (por ejemplo, un git checkout que restaura el mtime original)—. No obstante, el hashing requiere leer cada archivo en cada ejecución incremental. Para 16.894 archivos, leer el contenido toma entre 2 y 3 segundos. Leer los mtimes del sistema de archivos toma menos de 100ms.

La compensación: la comparación por mtime ocasionalmente dispara la reindexación innecesaria de archivos sin cambios (falsos positivos), pero nunca omite cambios reales. Los falsos positivos cuestan unas pocas llamadas adicionales de embeddings por ejecución. La diferencia de velocidad (100ms vs 3 segundos) hace que mtime sea la opció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],
    )

La sentencia DELETE FROM chunk_vecs funciona de forma nativa a partir de sqlite-vec v0.1.7.26 Las versiones anteriores requerían soluciones alternativas (eliminar y recrear la tabla virtual, o mantener un conjunto externo de “IDs activos”). Si usas una versión anterior a la 0.1.7, actualiza antes de depender de eliminaciones directas.

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 eliminada. 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, para uso diario) y completo (lento, ocasional). Esta sección cubre cuándo usar cada uno, las garantías de idempotencia y la recuperación ante corrupción.

Reindexación incremental

Cuándo usarla: Para la indexación diaria después de editar notas. Es el modo predeterminado.

Qué hace: 1. Escanea el vault en busca de cambios en archivos (comparación de mtime) 2. Elimina los chunks de archivos borrados 3. Vuelve a dividir en chunks y generar embeddings de los archivos modificados 4. Inserta nuevos chunks para archivos nuevos 5. Sincroniza 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 usarla: - 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 una corrupción de base de datos (la verificación de integridad falla) - Cuando la indexación incremental produce resultados inesperados

Qué hace: 1. Elimina todos los datos existentes (chunks, vectores, entradas FTS5) 2. Escanea el vault completo 3. Divide todos los archivos en chunks 4. Genera embeddings de todos los chunks 5. Reconstruye 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 segunda ejecución de la indexación incremental sobre una base de datos ya actualizada no genera ningún cambio. Una segunda 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 una 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 siempre son 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 fundamental: nunca necesitas hacer respaldo de la base de datos.

El flag --incremental

Cuando el indexador se ejecuta con --incremental:

  1. Verificación del hash del modelo. Compara el hash del modelo almacenado con el del 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. Vuelve a dividir en chunks y generar embeddings de los archivos modificados en lotes de 64.
  5. Reporte de progreso. Muestra la cantidad de archivos procesados y el tiempo transcurrido.
  6. Apagado controlado. Maneja SIGINT finalizando el archivo en curso 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 entren 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 se dividirían en chunks, se convertirían en embeddings y se almacenarían en la base de datos. Una búsqueda de “autenticación” 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, comparando contra 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 de diseño clave:

  1. Filtrar antes de generar embeddings. El texto limpio es lo que se convierte en embedding. La representación vectorial nunca codifica patrones de credenciales. Una consulta sobre “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 “aquí había algo similar a una credencial” 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 depuración fina dentro de los archivos indexados. Ambos son necesarios:

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

Clasificación de datos

Para vaults con contenido diverso, considera 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 médicos No (.indexignore) N/A
Restringido Credenciales, claves privadas No (.indexignore) N/A

Arquitectura del servidor MCP

Los servidores 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 soporta 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 soportan 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. Resulta útil para acceso remoto, configuraciones con múltiples clientes o equipos donde la bóveda se encuentra en un servidor compartido.

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

Recomendación: Usa STDIO para bóvedas personales. Es más sencillo, más seguro (sin exposición de red) y el ciclo de vida del servidor lo gestiona la herramienta de IA. Usa 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 (prompts iniciados por el servidor hacia el usuario). La versión de noviembre de 2025 incorporó 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.1821 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 multiagente.21 Para servidores de bóvedas personales, STDIO sigue siendo la ruta más sencilla. El transporte Streamable HTTP y el descubrimiento .well-known benefician principalmente a despliegues HTTP empresariales con enrutamiento multitenant y balanceo de carga. Consulta el roadmap de MCP para actualizaciones que afecten tu elección de transporte.

Diseño de capacidades

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

search — La herramienta principal. Ejecuta 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 ruta. Útil cuando el agente quiere 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) se gestionan mediante hooks o skills separados, no a través del 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 ruta (../../etc/passwd) deben rechazarse.

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

  4. Respuestas con límite de tokens. Aplica 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 principal consumidor 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ñade 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, reinicia Claude Code. El servidor MCP se iniciará como un proceso hijo. Verifica 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 de forma automática.

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

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

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

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

El patrón obsidian_bridge.py

Un módulo puente proporciona una API de Python 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 adecuado y activa una reindexación incremental para que la nueva nota sea inmediatamente localizable.

Patrones de comandos personalizados

Los skills de Claude Code pueden encapsular operaciones de la bóveda en comandos con nombre. Los profesionales han construido bibliotecas de comandos específicos para Obsidian que tratan la bóveda tanto como fuente de lectura como destino de escritura.

Escaneo de señales. Un comando /scan-intel consulta fuentes externas, puntúa los hallazgos según intereses de investigación personales y escribe las señales que califican como notas de la bóveda con frontmatter:

/scan-intel --topics "agent infrastructure, security" --lookback 7d

El comando obtiene datos de fuentes configuradas (arXiv, HN, RSS), aplica un modelo de puntuación (relevancia, accionabilidad, profundidad, autoridad) y escribe las señales aprobadas en carpetas temáticas de la bóveda. La bóveda se convierte en el consumidor final de un pipeline de inteligencia automatizado.

Bitácora del capitán. Un comando /captains-log agrega la actividad diaria de git en todos los repositorios, escribe una entrada de diario estructurada en la bóveda e incluye decisiones tomadas, descubrimientos y temas abiertos:

/captains-log

El comando extrae el historial de commits de GitHub, agrupa por repositorio y formatea como una entrada de diario narrativa. Con el tiempo, los registros diarios crean un historial consultable de qué se entregó y por qué.

Captura en Obsidian. Un comando /obsidian-capture toma un hallazgo de la sesión actual de Claude Code y lo escribe directamente en la bóveda con los metadatos apropiados:

/obsidian-capture "SAST gates in agent loops increase security degradation"
  --folder AI-Tools --tags security,agents

El patrón se extiende a cualquier operación de la bóveda: crear MOCs, actualizar notas de estado de proyectos, vincular señales relacionadas o generar resúmenes semanales a partir de los registros diarios acumulados.

Ejemplos de la comunidad. Los profesionales están publicando sus bibliotecas de comandos. Un desarrollador compartió 22 comandos personalizados de Obsidian + Claude Code que cubren revisiones diarias, planificación de proyectos, captura de investigación y flujos de trabajo de contenido.1 Otro construyó un skill “Visual Explainer” que genera notas con diagramas en la bóveda a partir del análisis de código.2 Los comandos varían, pero la arquitectura es consistente: skills de Claude Code como interfaz, notas de la bóveda como capa de almacenamiento e infraestructura de recuperación como motor de consultas.

Gestión de la ventana de contexto

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

  • Limita el contexto inyectado a 1.500-2.000 tokens por consulta. Más que esto compite con la memoria de trabajo del agente.
  • Incluye atribución de fuente. Siempre incluye la ruta del archivo y el encabezado de sección para que el agente pueda referenciar la fuente.
  • Trunca 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 inyectes 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

Añade 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 en AGENTS.md

Codex CLI lee AGENTS.md para instrucciones a nivel de proyecto. Incluye orientación sobre la búsqueda en la bóveda:

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

Función Claude Code Codex CLI
Configuración 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, incluye instrucciones explícitas en AGENTS.md indicando al agente que busque en la bóveda 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 las herramientas más comunes.

Cursor

Agrega lo siguiente a .cursor/mcp.json en la raíz de tu 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 la bóveda:

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
Claudian (plugin de Obsidian) N/A (integrado) Claude Code CLI Configuración del plugin de Obsidian
Agent Client (plugin de Obsidian) N/A (integrado) ACP Configuración del plugin de Obsidian

Alternativa para herramientas sin MCP

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

El CLI genera texto estructurado que puede pegarse manualmente en la entrada de cualquier herramienta de IA. Es menos elegante que la integración MCP, pero funciona de forma universal.


Caché de prompts a partir de notas estructuradas

Las notas estructuradas en la bóveda pueden servir como bloques de contexto reutilizables que reducen el consumo 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, puedes pre-construir bloques de contexto a partir de notas bien estructuradas de la bóveda y almacenarlos 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 la bóveda.
  2. Detección de cambios en la bóveda. Cuando el indexador detecta cambios en archivos que contribuyeron a un bloque de contexto almacenado en caché, el bloque se invalida de inmediato.

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 según cada consulta. Este enfoque híbrido proporciona al agente una base de contexto frecuentemente necesario mientras preserva presupuesto para consultas específicas.

Antes y después del uso de tokens

Sin caché: Cada consulta relevante activa una búsqueda en la bóveda, 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 de la bóveda.

Con caché: Tres bloques de contexto pre-construidos 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 señal está en 2 líneas: 200 pasadas, 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

Pipeline de ingesta y clasificación 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 pipeline de puntuación que enruta señales a carpetas de dominio.

Fuentes

Las señales provienen de múltiples canales:

  • Feeds 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: Salidas significativas de herramientas de IA capturadas mediante hooks
  • Extensión para compartir en iOS: La app de iOS de Obsidian (actualizada a principios de 2026) incluye una extensión para compartir que guarda contenido desde Safari, redes sociales y otras apps directamente en la bóveda sin abrir Obsidian.31 Esto crea una vía de ingesta móvil de baja fricción — comparte un artículo desde Safari y llega como una nota de la bóveda lista para ser puntuada.
  • CLI de Obsidian: Los scripts de shell y hooks pueden crear notas mediante obsidian file create o agregar contenido a notas existentes mediante obsidian file append, habilitando pipelines de ingesta automatizados en escritorio.

Dimensiones de puntuación

Cada señal se puntú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 basándose en coincidencia de palabras clave y clasificación temática:

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 de grafos 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 antipatrones 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” “Consulta [[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 “resumen de autenticación” coincide con el MOC en sí, proporcionando al agente una lista curada de notas relacionadas.
  2. Expansión de contexto. Tras 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, dándole al agente un mapa del tema más amplio.

Recorrido del grafo para expansión de contexto

Una mejora futura para el recuperador: tras 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.

Antipatrones

Clústeres huérfanos. Grupos de notas que se enlazan entre sí pero no tienen conexiones con el resto del vault. El panel de grafo en Obsidian los hace visibles como islas desconectadas. Los clústeres huérfanos indican que faltan MOCs o enlaces entre dominios.

Proliferación de etiquetas. Usar etiquetas de forma inconsistente o crear demasiadas etiquetas granulares. Un vault con 500 etiquetas únicas en 5.000 notas promedia 1 nota por cada 10 etiquetas — las etiquetas no resultan útiles para filtrar. Consolida a 20-50 etiquetas de alto nivel que correspondan a tus carpetas de dominio.

Notas con muchos enlaces y poco contenido. Notas que consisten enteramente en wiki-links sin prosa. Estas notas se indexan mal porque el chunker no tiene texto para generar embeddings. Agrega 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]]. Reserva los wiki-links para relaciones intencionales y navegables donde hacer clic en el enlace proporcionaría contexto útil.


Recetas de flujos de trabajo para desarrolladores

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

Carga de contexto matutina

Comienza 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 tu proyecto activo, dándote un repaso rápido de dónde lo dejaste. Más efectivo que releer los mensajes de commit de ayer.

Captura de investigación durante la programación

Mientras implementas una función, captura ideas 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

La idea capturada se indexa de inmediato y queda disponible para futuras recuperaciones. Con el tiempo, estas microcapturas construyen un corpus de conocimiento específico de implementación.

Inicio de proyecto

Al comenzar un nuevo proyecto o función:

  1. Busca en el vault: “¿Qué sé sobre [tecnología/patrón]?”
  2. Revisa los 5 mejores resultados en busca de decisiones previas y problemas encontrados
  3. Verifica si existe un MOC para el dominio; si no, crea uno
  4. Busca modos de fallo: “problemas con [tecnología]”

Depuración con búsqueda en el vault

Cuando encuentras 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 — el vault recuerda lo que tú olvidas.

Preparación para revisión de código

Antes de revisar un PR:

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

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


Ajuste de 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, considera: - Aumentar el batch size de 64 a 128 para generar embeddings más rápido - Usar el modo WAL (predeterminado) para acceso concurrente - Ejecutar la reindexación completa fuera de horario

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 crítico cuando el servidor MCP maneja consultas mientras el indexador ejecuta una actualización incremental.

Pool de conexiones. El servidor MCP debería reutilizar conexiones a la base de datos en lugar de abrir una nueva conexión por consulta. Una sola conexión persistente con modo WAL soporta 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 de memoria. El pragma mmap_size indica a SQLite que use E/S con mapeo de 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, ejecuta:

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

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

Benchmarks de escalabilidad

Medidos en Apple M3 Pro, 36 GB RAM, NVMe SSD:

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 desactualizados u omite notas añadidas recientemente.

Causa: El indexador incremental no se ejecutó tras agregar notas, o el mtime de un archivo no se actualizó (por ejemplo, al sincronizar desde otra máquina con marcas de tiempo preservadas).

Solución: Ejecuta 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 sin sentido.

Causa: Los vectores antiguos (del modelo anterior) se están comparando con 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, limpia la base de datos manualmente y reindexa:

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 tras muchas actualizaciones pequeñas.

Solución: Reconstruye y optimiza:

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

Timeout de MCP

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

Causa: La primera consulta activa la carga del modelo (inicialización diferida), lo que toma entre 2 y 5 segundos. El timeout predeterminado de MCP en la herramienta de IA puede ser más corto.

Solución: Precalienta 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 en 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 a la vez.

Solución: Asegúrate de que solo un proceso (el indexador) escriba en la base de datos. El servidor MCP y los hooks solo deben leer. Si necesitas escrituras concurrentes, usa el modo WAL y establece 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á desactivada; el recuperador 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

Verifica que la extensión se cargue correctamente:

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

Problemas de memoria con bóvedas grandes

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

Causa: El tamaño del lote de embeddings es demasiado grande, o todos los contenidos de los archivos se cargan en memoria simultáneamente.

Solución: Reduce el tamaño del lote y procesa los archivos de forma incremental:

BATCH_SIZE = 32  # Reduce from 64

Asegúrate también de que el indexador procese los archivos de uno en uno (leyendo, segmentando y generando embeddings de 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. Exporta Apple Notes mediante la opción “Export All” (macOS) o usa una herramienta de migración como apple-notes-liberator
  2. Convierte las exportaciones HTML a markdown usando markdownify o pandoc
  3. Mueve los archivos convertidos a la carpeta 00-inbox/ de tu bóveda
  4. Revisa y añade frontmatter a cada nota
  5. Mueve las notas a las carpetas de dominio correspondientes

Desde Notion

  1. Exporta desde Notion: Settings → Export → Markdown & CSV
  2. Descomprime la exportación en la carpeta 00-inbox/ de tu bóveda
  3. Corrige los artefactos de markdown específicos de Notion:
  4. Notion usa - [ ] para listas de verificación — son markdown estándar
  5. Notion incluye tablas de propiedades como HTML — conviértelas a frontmatter YAML
  6. Notion embebe imágenes con rutas relativas — copia las imágenes a tu carpeta de adjuntos
  7. Añade frontmatter estándar (type, domain, tags)
  8. Reemplaza los enlaces de página de Notion con wiki-links de Obsidian

Desde Google Docs

  1. Usa Google Takeout para exportar todos los documentos
  2. Convierte los 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. Mueve los archivos a la bóveda, añade frontmatter y organízalos en carpetas

Desde markdown plano (sin Obsidian)

Si ya tienes un directorio de archivos markdown:

  1. Abre el directorio como una bóveda de Obsidian (Obsidian → Open Vault → Open folder)
  2. Añade .obsidian/ a .gitignore si el directorio tiene control de versiones
  3. Crea plantillas de frontmatter y aplícalas a los archivos existentes
  4. Comienza a enlazar notas con [[wiki-links]] a medida que leas y organices
  5. Ejecuta el indexador de inmediato — el sistema de recuperación funciona desde el primer día

Desde otro sistema de recuperación

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

  1. No intentes migrar los vectores. Diferentes modelos producen espacios vectoriales incompatibles. Ejecuta una reindexación completa con el nuevo modelo.
  2. Migra el contenido, no el índice. Los archivos de la bóveda son la fuente de verdad. El índice es un artefacto derivado.
  3. Verifica después de la migración. Ejecuta entre 10 y 20 consultas cuyas respuestas conozcas y verifica que los resultados coincidan con tus expectativas.

Registro de cambios

Fecha Cambio
2026-04-01 Añadida sección de Obsidian CLI (comandos v1.12 para flujos de trabajo con IA). Añadida sección de plugins de agentes (Claudian, Agent Client). Documentado el plugin principal Bases para organización de bóvedas. Actualizado el conteo de plugins a más de 2.500. Añadida extensión de compartir iOS como fuente de ingesta. Actualizada la matriz de compatibilidad con plugins de agentes integrados.
2026-03-30 MCPVault v0.11.0: herramienta list_all_tags, soporte para .base/.canvas, renombrado a @bitbonsai/mcpvault. Obsidian Desktop v1.12.7 incluye el binario de CLI para interacciones más rápidas con la terminal.
2026-03-23 Documentado sqlite-vec v0.1.7 estable: soporte DELETE para tablas vec0, restricciones de distancia KNN para paginación. Índice de vecinos más cercanos aproximado DiskANN anunciado para próxima versión.
2026-03-07 Añadido potion-multilingual-128M (101 idiomas, mayo 2025) a la comparación de modelos de embeddings. sqlite-vec en v0.1.7-alpha.10 (correcciones de CI/CD, sin cambios funcionales). Especificación MCP y técnicas de recuperación confirmadas como vigentes.
2026-03-03 Actualizada la evolución de la especificación MCP (noviembre 2025 lanzado: Streamable HTTP, .well-known, anotaciones de herramientas). Añadido soporte de fine-tuning de Model2Vec y tokenizador BPE/Unigram. Añadida tabla comparativa de servidores MCP de la comunidad. Actualizado 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 Versión inicial

Referencias


  1. Internet Vin, “22 commands I use with Obsidian and Claude Code,” March 2026, x.com/internetvin/status/2026461256677245131

  2. Nicopreme, “Visual Explainer” agent skill with slash commands, x.com/nicopreme/status/2023495040258261460

  3. 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 ordenadas. 

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

  5. 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 sentence transformers. 

  6. 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). 

  7. SQLite FTS5 Extension. FTS5 proporciona búsqueda de texto completo con ranking BM25 y pesos de columna configurables. 

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

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

  10. 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 QA de dominio abierto. 

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

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

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

  14. Gao, Y. et al. Retrieval-Augmented Generation for Large Language Models: A Survey. arXiv, 2024. Revisión de arquitecturas RAG y estrategias de chunking. 

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

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

  17. Obsidian Documentation. Documentación oficial de Obsidian. 

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

  19. 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 consultas medida con time.perf_counter()

  20. 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+. 

  21. 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, tentativamente a mediados de 2026, incluirá operaciones asíncronas, extensiones específicas de dominio y comunicación entre agentes. 

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

  23. Smart Connections for Obsidian. Smart Connections v4: embeddings de IA local-first, la búsqueda semántica funciona sin conexión tras la indexación inicial. 

  24. potion-multilingual-128M. Minish Lab, mayo 2025. Modelo de embeddings estáticos para 101 idiomas, el de mejor rendimiento entre los embeddings estáticos multilingües. Misma dependencia exclusiva de numpy que los demás modelos potion. 

  25. MCPVault v0.11.0. Marzo 2026. Nueva herramienta list_all_tags para escanear frontmatter y hashtags con conteos. Manejo mejorado de carpetas con punto, soporte para archivos .base y .canvas. Paquete renombrado a @bitbonsai/mcpvault en npm. 

  26. sqlite-vec v0.1.7 Release. 17 de marzo de 2026. Versión estable: soporte DELETE para tablas virtuales vec0, restricciones de distancia KNN para paginación, mejoras en pruebas de fuzzing. Indexación DiskANN de vecinos más cercanos aproximados anunciada para una versión futura. 

  27. Introduction to Bases. Plugin nativo de Obsidian introducido en v1.9.10. Vistas tipo base de datos (tablas, galerías, calendarios, tableros kanban) sobre archivos del vault utilizando propiedades de frontmatter como campos. Archivos guardados en formato .base

  28. Obsidian 1.12 Desktop Changelog. 27 de febrero de 2026. Introduce el CLI de Obsidian para automatización del vault desde la terminal. Los comandos incluyen búsqueda, notas diarias, plantillas, propiedades, plugins, tareas y herramientas de desarrollo. Documentación del CLI

  29. Claudian. Plugin de Obsidian que integra Claude Code como colaborador de IA dentro del vault. Ofrece chat en la barra lateral, prompts con contexto, soporte de visión, comandos slash y modos de permisos. 

  30. Agent Client. Plugin de Obsidian que proporciona una interfaz unificada para Claude Code, Codex CLI y Gemini CLI a través del Agent Client Protocol (ACP). Soporta menciones de notas, ejecución de shell y aprobación de acciones. 

  31. Obsidian iOS Changelog. Las actualizaciones de principios de 2026 incluyen la extensión para compartir (Share Extension) que permite guardar contenido de otras apps directamente en el vault, correcciones en los widgets de nota diaria y marcadores, y mejoras en la actualización del widget de vista de notas. 

VAULT obsidian.md INDEXED