Example vault location
#
Conclusiones Clave
Ingeniería de contexto, no toma de notas. El valor de un vault de Obsidian para la IA no son las notas en sí mismas, sino la capa de recuperación que las hace consultables. Un vault de 16.000 archivos sin recuperación es una base de datos de solo escritura. Un vault de 200 archivos con búsqueda híbrida e integración MCP es una base de conocimiento para IA. La infraestructura de recuperación es el producto. Las notas son la materia prima.
La recuperación híbrida supera a la búsqueda puramente por palabras clave o puramente semántica. BM25 captura identificadores exactos y nombres de funciones. La búsqueda vectorial captura sinónimos y coincidencias conceptuales entre terminología diferente. Reciprocal Rank Fusion (RRF) combina ambos métodos sin requerir calibración de puntuaciones. Ningún método por sí solo cubre ambos modos de fallo. La investigación sobre MS MARCO passage ranking confirma el patrón: la recuperación híbrida supera consistentemente a cualquiera de los métodos de forma aislada.1 El análisis detallado del recuperador híbrido cubre las matemáticas de RRF, ejemplos prácticos con números reales, análisis de modos de fallo y una calculadora interactiva de fusión.
MCP otorga a las herramientas de IA acceso directo al vault. Los servidores de Model Context Protocol (MCP) exponen el recuperador como una herramienta que Claude Code, Codex CLI, Cursor y otras herramientas de IA pueden invocar directamente. El agente consulta el vault, recibe resultados clasificados con atribución de fuentes y utiliza el contexto sin cargar archivos completos. El servidor MCP es un envoltorio ligero alrededor del motor de recuperación.
Local-first significa cero costos de API y privacidad total. Toda la pila tecnológica se ejecuta en una sola máquina: SQLite para almacenamiento, Model2Vec para embeddings, FTS5 para búsqueda por palabras clave, sqlite-vec para KNN vectorial. Sin servicios en la nube, sin llamadas a API, sin dependencia de red. Las notas personales nunca salen de la máquina. El re-embedding completo de 49.746 fragmentos costaría aproximadamente $0,30 a precios de API de OpenAI, pero los costos reales son la latencia, la exposición de privacidad y la dependencia de red para un sistema que debería funcionar sin conexión.2
La indexación incremental mantiene el sistema actualizado en menos de 10 segundos. La comparación del tiempo de modificación de archivos detecta los cambios. Solo los archivos modificados se re-fragmentan y re-embeben. Una reindexación completa toma aproximadamente cuatro minutos en hardware Apple M-series. Las actualizaciones incrementales de las ediciones de un día típico se ejecutan en menos de diez segundos. El sistema se mantiene actualizado sin intervención manual.
La arquitectura escala de 200 a más de 20.000 notas. El mismo diseño de tres capas (ingesta, recuperación, integración) funciona con cualquier tamaño de vault. Comience con búsqueda solo por BM25 sobre un vault pequeño. Agregue búsqueda vectorial cuando las colisiones de palabras clave se conviertan en un problema. Agregue fusión RRF cuando necesite coincidencias tanto exactas como semánticas. Cada capa es independientemente útil e independientemente removible.
Cómo Usar Esta Guía
Esta guía cubre el sistema completo. Su punto de partida depende de dónde se encuentre:
| Usted es… | Comience aquí | Luego explore |
|---|---|---|
| Nuevo en Obsidian + IA | ¿Por qué Obsidian para infraestructura de IA?, Inicio rápido | Arquitectura del vault, Arquitectura del servidor MCP |
| Vault existente, desea acceso de IA | Arquitectura del servidor MCP, Integración con Claude Code | Modelos de embedding, Búsqueda de texto completo |
| Construyendo un sistema de recuperación | El pipeline de recuperación completo, Reciprocal Rank Fusion | Optimización de rendimiento, Solución de problemas |
| Contexto de equipo o empresa | Marco de decisión, Patrones de grafo de conocimiento | Recetas de flujo de trabajo para desarrolladores, Guía de migración |
Las secciones marcadas como Contrato incluyen detalles de implementación, bloques de configuración y modos de fallo. Las secciones marcadas como Narrativa se enfocan en conceptos, decisiones de arquitectura y el razonamiento detrás de las decisiones de diseño. Las secciones marcadas como Receta proporcionan flujos de trabajo paso a paso.
¿Por Qué Obsidian para Infraestructura de IA?
La tesis de esta guía: Los vaults de Obsidian son el mejor sustrato para bases de conocimiento personales de IA porque son local-first, en texto plano, con estructura de grafo, y el usuario controla cada capa de la pila tecnológica.
Lo que Obsidian ofrece a la IA que las alternativas no ofrecen
Archivos markdown en texto plano. Cada nota es un archivo .md en su sistema de archivos. Sin formato propietario, sin exportación de base de datos, sin API requerida para leer el contenido. Cualquier herramienta que lea archivos puede leer su vault. grep, ripgrep, pathlib de Python, SQLite FTS5 — todos funcionan directamente sobre los archivos fuente. Cuando construye un sistema de recuperación, está indexando archivos, no respuestas de API. El índice siempre es consistente con la origen porque el origen es el sistema de archivos.
Arquitectura local-first. El vault reside en su máquina. Sin servidor, sin dependencia de sincronización en la nube, sin límites de tasa de API, sin términos de servicio que regulen cómo procesa su propio contenido. Puede embeber, indexar, fragmentar y buscar en sus notas sin ningún servicio externo. Esto importa para la infraestructura de IA porque el pipeline de recuperación se ejecuta tan rápido como su disco lo permita, no tan rápido como responda un endpoint de API. También importa para la privacidad: las notas personales que contienen credenciales, datos de salud, información financiera y reflexiones privadas nunca salen de su máquina.
Estructura de grafo a través de wiki-links. La sintaxis [[wiki-link]] de Obsidian crea un grafo dirigido a través de las notas. Una nota sobre la implementación de OAuth enlaza a notas sobre rotación de tokens, gestión de sesiones y seguridad de API. La estructura de grafo codifica relaciones curadas por humanos entre conceptos. Los embeddings vectoriales capturan similitud semántica, pero los wiki-links capturan conexiones intencionales que el autor realizó mientras reflexionaba sobre el tema. El grafo es una señal que los embeddings no pueden replicar.
Ecosistema de plugins. Obsidian cuenta con más de 1.800 plugins comunitarios. Dataview consulta su vault como una base de datos. Templater genera notas a partir de plantillas con lógica de JavaScript. La integración con Git sincroniza su vault con un repositorio. Linter impone consistencia de formato. Estos plugins agregan estructura al vault sin cambiar el formato subyacente de texto plano. El sistema de recuperación indexa la salida de estos plugins, no los plugins en sí mismos.
Más de 5 millones de usuarios. Obsidian tiene una comunidad activa y amplia que produce plantillas, flujos de trabajo, plugins y documentación. Cuando encuentra un problema con la organización del vault o la configuración de plugins, es probable que alguien haya documentado una solución. La comunidad también produce herramientas adyacentes a Obsidian: servidores MCP, scripts de indexación, pipelines de publicación y envoltorios de API.
Lo que un sistema de archivos por sí solo no le ofrece
Un directorio de archivos markdown tiene la ventaja del texto plano, pero carece de tres cosas que Obsidian agrega:
-
Enlaces bidireccionales. Obsidian rastrea backlinks automáticamente. Cuando enlaza desde la Nota A hacia la Nota B, la Nota B muestra que la Nota A la referencia. El panel de grafo visualiza grupos de conexiones. Esta conciencia bidireccional es metadata que un sistema de archivos sin procesar no proporciona.
-
Vista previa en vivo con renderizado de plugins. Las consultas de Dataview, los diagramas Mermaid y los bloques de llamada se renderizan en tiempo real. La experiencia de escritura es más rica que la de un editor de texto, mientras que el formato de almacenamiento permanece en texto plano. Usted escribe y organiza en un entorno enriquecido; el sistema de recuperación indexa el markdown sin procesar.
-
Infraestructura comunitaria. Descubrimiento de plugins, mercado de temas, servicio de sincronización (opcional), servicio de publicación (opcional) y un ecosistema de documentación. Puede replicar cualquier función individual con herramientas independientes, pero Obsidian las empaqueta en un flujo de trabajo coherente.
Lo que Obsidian NO hace (y lo que usted construye)
Obsidian no incluye infraestructura de recuperación. Tiene búsqueda básica (texto completo, nombre de archivo, etiqueta) pero no pipeline de embedding, no búsqueda vectorial, no clasificación por fusión, no servidor MCP, no filtrado de credenciales, no estrategia de fragmentación y no hooks de integración para herramientas de IA externas. Esta guía cubre la infraestructura que usted construye sobre Obsidian. El vault es el sustrato. El pipeline de recuperación, el servidor MCP y los hooks de integración son la infraestructura.
La arquitectura descrita aquí es markdown-first, no exclusiva de Obsidian. Si utiliza Logseq, Foam, Dendron o un directorio simple de archivos markdown, el pipeline de recuperación funciona de manera idéntica. El fragmentador lee archivos .md. El embebedor procesa cadenas de texto. El indexador escribe en SQLite. Ninguno de estos componentes depende de funciones específicas de Obsidian. La contribución de Obsidian es el entorno de escritura y organización que produce los archivos markdown que el recuperador indexa.
Inicio rápido: Su primer vault conectado a IA
Esta sección le permite conectar un vault a una herramienta de IA en cinco minutos. Instalará Obsidian, creará un vault, instalará un servidor MCP y ejecutará su primera consulta. El inicio rápido utiliza un servidor MCP de la comunidad para obtener resultados inmediatos. Las secciones posteriores cubren la construcción de un pipeline de recuperación personalizado para uso en producción.
Requisitos previos
- macOS, Linux o Windows
- Node.js 18+ (para el servidor MCP)
- Claude Code, Codex CLI o Cursor instalado
Paso 1: Crear un vault
Descargue Obsidian desde obsidian.md y cree un nuevo vault. Elija una ubicación que pueda recordar — el servidor MCP necesita la ruta absoluta.
# Example vault location
~/Documents/knowledge-base/
Agregue algunas notas para darle al recuperador contenido con el cual 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
El servidor comunitario obsidian-mcp proporciona acceso inmediato al vault. Instálelo:
npm install -g obsidian-mcp-server
Paso 3: Configurar su herramienta de IA
Claude Code — agregue a ~/.claude/settings.json:
{
"mcpServers": {
"obsidian": {
"command": "obsidian-mcp-server",
"args": ["--vault", "/absolute/path/to/your/vault"]
}
}
}
Codex CLI — agregue a .codex/config.toml:
[mcp_servers.obsidian]
command = "obsidian-mcp-server"
args = ["--vault", "/absolute/path/to/your/vault"]
Cursor — agregue a .cursor/mcp.json:
{
"mcpServers": {
"obsidian": {
"command": "obsidian-mcp-server",
"args": ["--vault", "/absolute/path/to/your/vault"]
}
}
}
Paso 4: Ejecutar su primera consulta
Abra su herramienta de IA y haga una pregunta que las notas de su 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 su vault y devuelve contenido coincidente. Debería ver resultados con rutas de archivos y extractos relevantes.
Lo que acaba de construir
Conectó una base de conocimiento local a una herramienta de IA a través de un protocolo estándar. El servidor MCP lee los archivos de su vault, realiza una búsqueda básica y devuelve resultados. Esta es la versión mínima viable.
Lo que este inicio rápido NO le proporciona: - Recuperación híbrida (BM25 + búsqueda vectorial + fusión RRF) - Búsqueda semántica basada en embeddings - Filtrado de credenciales - Indexación incremental - Inyección automática de contexto basada en hooks
El resto de esta guía cubre la construcción de cada una de estas capacidades. El inicio rápido demuestra el concepto. El pipeline completo ofrece recuperación con calidad de producción.
Marco de decisión: Obsidian vs alternativas
No todos los casos de uso necesitan Obsidian. Esta sección mapea cuándo Obsidian es el sustrato adecuado, cuándo es excesivo, y cuándo algo diferente se adapta mejor.
Árbol de decisión
START: What is your primary content type?
│
├─ Structured data (tables, records, schemas)
│ → Use a database. SQLite, PostgreSQL, or a spreadsheet.
│ → Obsidian is for prose, not tabular data.
│
├─ Ephemeral context (current project, temporary notes)
│ → Use CLAUDE.md / AGENTS.md in the project repo.
│ → These travel with the code and reset per project.
│
├─ Team wiki (shared documentation, onboarding)
│ → Evaluate Notion, Confluence, or a shared git repo.
│ → Obsidian vaults are personal-first. Team sync is possible
│ but not native.
│
└─ Growing personal knowledge corpus
│
├─ < 50 notes
│ → A folder of markdown files + grep is sufficient.
│ → Obsidian adds value mainly through the link graph,
│ which needs density to be useful.
│
├─ 50 - 500 notes
│ → Obsidian adds value. Wiki-links create a navigable graph.
│ → BM25-only search (FTS5) is sufficient at this scale.
│ → Skip vector search and RRF until keyword collisions appear.
│
├─ 500 - 5,000 notes
│ → Full hybrid retrieval becomes valuable. Keyword collisions
│ increase. Semantic search catches queries that BM25 misses.
│ → Add vector search + RRF fusion at this scale.
│
└─ 5,000+ notes
→ Full pipeline is essential. BM25-only returns too much noise.
→ Credential filtering becomes critical (more notes = more
accidentally pasted secrets).
→ Incremental indexing matters (full reindex takes minutes).
→ MCP integration pays dividends on every AI interaction.
Matriz de comparación
| Criterio | Obsidian | Notion | Apple Notes | Sistema de archivos | CLAUDE.md |
|---|---|---|---|---|---|
| Local-first | Sí | No (nube) | Parcial (iCloud) | Sí | Sí |
| Texto plano | Sí (markdown) | No (bloques) | No (propietario) | Sí | Sí |
| Estructura de grafo | Sí (wiki-links) | Parcial (menciones) | No | No | No |
| Indexable por IA | Acceso directo a archivos | Se requiere API | Se requiere exportación | Acceso directo a archivos | Ya en contexto |
| Ecosistema de plugins | Más de 1.800 plugins | Integraciones | Ninguno | N/A | N/A |
| Funciona sin conexión | Completo | Solo lectura en caché | Parcial | Completo | Completo |
| Escala a más de 10K notas | Sí | Sí (con API) | Se degrada | Sí | No (archivo único) |
| Costo | Gratuito (núcleo) | $10/mes+ | Gratuito | Gratuito | Gratuito |
Cuándo Obsidian es excesivo
- Contexto de un solo proyecto. Si la IA solo necesita contexto sobre el código base actual, colóquelo en
CLAUDE.md,AGENTS.mdo en la 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, use una base de datos. Las notas de Obsidian son principalmente prosa. Dataview puede consultar campos de frontmatter, pero una base de datos real maneja mejor las consultas estructuradas.
- Investigación temporal. Si las notas se descartarán después de que termine el proyecto, un directorio temporal con archivos markdown es más simple. No construya infraestructura de recuperación para contenido efímero.
Cuándo Obsidian es la elección correcta
- Conocimiento que se acumula durante meses o años. El valor se compone a medida que el corpus crece. Un vault de 200 notas consultado diariamente durante seis meses proporciona más valor que un vault 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 que un
CLAUDE.mdespecífico de proyecto no puede proporcionar. - 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 usted decida poner en él, incluido contenido que no subiría a un servicio en la nube.
Modelo mental: Tres capas
El sistema tiene tres capas que operan de forma independiente pero se potencian cuando se combinan. Cada capa tiene una responsabilidad diferente y un modo de fallo diferente.
┌─────────────────────────────────────────────────────┐
│ INTEGRATION LAYER │
│ MCP servers, hooks, skills, context injection │
│ Concern: delivering context to AI tools │
│ Failure: wrong context, too much context, stale │
└──────────────────────┬──────────────────────────────┘
│ query + ranked results
┌──────────────────────┴──────────────────────────────┐
│ RETRIEVAL LAYER │
│ BM25, vector KNN, RRF fusion, token budget │
│ Concern: finding the right content for any query │
│ Failure: wrong ranking, missed results, slow queries │
└──────────────────────┬──────────────────────────────┘
│ chunked, embedded, indexed
┌──────────────────────┴──────────────────────────────┐
│ INTAKE LAYER │
│ Note creation, signal triage, vault organization │
│ Concern: what enters the vault and how it's stored │
│ Failure: noise, duplicates, missing structure │
└─────────────────────────────────────────────────────┘
Ingesta determina qué entra 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: dividir notas en unidades de búsqueda (chunking), convertir fragmentos en espacio vectorial (embeddings), 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 las reglas de enrutamiento de señales. El servidor MCP no sabe nada sobre cómo se crearon las notas. Este desacoplamiento significa que puede mejorar cualquier capa de forma independiente. Reemplace el modelo de embeddings sin cambiar el pipeline de ingesta. Agregue una nueva capacidad MCP sin modificar el recuperador. Cambie las heurísticas de puntuación de señales sin tocar el índice.
Arquitectura del Vault para Consumo por IA
Un vault optimizado para recuperación por IA sigue convenciones diferentes 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
Utilice prefijos numéricos para las carpetas de nivel superior con el fin de crear una jerarquía organizacional predecible. Los números no implican prioridad — agrupan dominios relacionados y hacen que la estructura sea fácil de recorrer.
vault/
├── 00-inbox/ # Capturas sin clasificar, pendientes de triaje
├── 01-projects/ # Notas de proyectos activos
├── 02-areas/ # Áreas de responsabilidad en curso
├── 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 domain folders
├── 06-daily/ # Notas diarias (si se utilizan)
├── 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 que contenga contenido sensible que no desee incluir en el índice de recuperación.
El archivo .indexignore
Cree un archivo .indexignore en la raíz 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 utiliza los campos de frontmatter para filtrado y enriquecimiento de contexto:
---
title: "OAuth Token Rotation Patterns"
type: note # note | signal | project | moc | daily
domain: security # primary domain for routing
tags:
- authentication
- oauth
- token-management
created: 2026-01-15
updated: 2026-02-28
source: "" # URL if captured from external source
status: active # active | archived | draft
---
Campos requeridos para la recuperación:
title— Se utiliza en la visualización de resultados de búsqueda y en el contexto de encabezados para BM25type— Permite consultas filtradas por tipo (“mostrar solo MOCs” o “solo señales”)tags— Se indexan en el contexto de encabezados de FTS5 con peso de 0,3, proporcionando coincidencias por palabras clave incluso cuando el cuerpo utiliza terminología diferente
Campos opcionales pero valiosos:
domain— Permite consultas con alcance de dominio (“buscar solo en notas de seguridad”)source— Atribución para contenido capturado; el recuperador puede incluir URLs de origen en los resultadosstatus— Permite excluir notas archivadas o en borrador de la búsqueda activa
Convenciones de Fragmentación (Chunking)
El recuperador fragmenta en los límites de encabezados H2 (##). Esto significa que la estructura de sus notas afecta directamente la granularidad de la recuperación:
Bueno para la recuperación:
## Token Rotation Strategy
The rotation interval depends on the threat model...
## Implementation with refresh_token
The OAuth 2.0 refresh token flow requires...
## Error Handling: Expired Tokens
When a token expires mid-request...
Tres secciones H2 producen tres fragmentos buscables de forma independiente. Cada fragmento tiene suficiente contexto para que el embedding capture su significado. Una consulta sobre “manejo de tokens expirados” coincide específicamente con el tercer fragmento.
Deficiente para la recuperación:
# OAuth Notes
Token rotation depends on threat model. The OAuth 2.0 refresh
token flow requires storing the refresh token securely. When a
token expires mid-request, the client should retry after refresh.
The rotation interval is typically 15-30 minutes for access tokens
and 7-30 days for refresh tokens...
Una sección larga sin encabezados H2 produce un solo fragmento grande. El embedding promedia todos los temas de la sección. Una consulta sobre cualquier subtema coincide con la nota completa de forma equivalente.
Regla general: Si una sección cubre más de un concepto, divídala en subsecciones H2. El fragmentador se encarga del resto.
Qué No Incluir en las Notas
Contenido que degrada la calidad de recuperación:
- Copias sin procesar de artículos completos sin anotación. El recuperador indexa las palabras clave del artículo original, diluyendo su vault con contenido que usted no escribió. En su lugar, agregue un resumen, extraiga los puntos clave o incluya un enlace a la URL de origen.
- Capturas de pantalla sin descripción textual. El recuperador indexa texto markdown. Una imagen sin texto alternativo o descripción circundante es invisible tanto para BM25 como para la búsqueda vectorial.
- Cadenas de credenciales. Claves API, tokens, contraseñas, cadenas de conexión. Incluso con filtrado de credenciales, el enfoque más seguro es nunca pegar secretos en las notas. En su lugar, haga referencia a ellos por nombre (“el token API de Cloudflare en
~/.env”). - Contenido generado automáticamente sin curación. Si una herramienta genera una nota (transcripción de reunión, resaltados de Readwise, importación RSS), revísela y anótela 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 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 su vault como una base de datos utilizando campos de frontmatter. Cree índices dinámicos: “todas las notas etiquetadas con security actualizadas en los últimos 30 días” o “todas las notas de proyecto con status active.” Dataview no ayuda directamente a la recuperación, pero le ayuda a identificar vacíos en la cobertura de su vault y encontrar notas que necesitan actualización.
TABLE type, domain, updated
FROM "03-resources"
WHERE status = "active"
SORT updated DESC
LIMIT 20
Templater. Crea notas a partir de plantillas con campos dinámicos. Asegúrese de que cada nueva nota comience con el frontmatter correcto utilizando una plantilla que pre-llene los campos created, type y domain. Un frontmatter consistente mejora el filtrado en la recuperación.
<%* /* New Resource Note Template */ %>
---
title: "<% tp.file.cursor() %>"
type: note
domain: <% tp.system.suggester(["programming", "security", "ai-engineering", "design", "devops"], ["programming", "security", "ai-engineering", "design", "devops"]) %>
tags: []
created: <% tp.date.now("YYYY-MM-DD") %>
updated: <% tp.date.now("YYYY-MM-DD") %>
source: ""
status: active
---
## Key Points
## Details
## 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 de encabezado secuenciales (sin saltar de H1 a H3)
- YAML title: coincidir con el nombre del archivo
- Espacios finales: eliminar (evita artefactos de tokenización en FTS5)
- Líneas en blanco consecutivas: limitar a 1 (chunks más limpios)
Integración con Git. Control de versiones para su vault. Rastree cambios a lo largo del tiempo, sincronice entre máquinas y recupérese de eliminaciones accidentales. Git también proporciona datos de mtime que el indexador usa para la detección incremental de cambios.
Plugins que ayudan a la indexación
Smart Connections. Un plugin de Obsidian que proporciona búsqueda semántica impulsada por IA dentro del propio Obsidian. Crea su propio índice de embeddings. Si bien el sistema de recuperación en esta guía es externo a Obsidian (se ejecuta como un pipeline Python), Smart Connections es útil para explorar relaciones semánticas mientras escribe. Los dos sistemas indexan el mismo contenido pero sirven para casos de uso diferentes: Smart Connections para descubrimiento dentro del editor, el retriever externo para integración con herramientas de IA.
Metadata Menu. Proporciona edición estructurada de frontmatter con autocompletado para valores de campos. Reduce errores tipográficos en los campos type, domain y tags. Los 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 cuando se divide en chunks y se genera embeddings. Excluya los archivos de Excalidraw del índice mediante .indexignore o filtre por extensión de archivo.
Kanban. Almacena el estado del tablero como markdown con formato especial. El formato está diseñado para la renderización de Kanban, no para la recuperación de prosa. El chunker produce fragmentos de títulos de tarjetas y metadatos que no generan buenos embeddings. Excluya los tableros Kanban del índice.
Calendar. Crea notas diarias con contenido mínimo (a menudo solo un encabezado de fecha). Las notas vacías o casi vacías producen chunks de baja calidad. Si usa notas diarias, escriba contenido sustancial en ellas o excluya la carpeta de notas diarias del índice.
Configuración de plugins que importa
File recovery → Habilitado. Protege contra la eliminación accidental de notas. No está directamente relacionado con la recuperación, pero es crítico para una base de conocimiento de la que depende.
Strict line breaks → Deshabilitado. Los saltos de línea estándar de markdown (doble salto de línea para párrafo) producen chunks más limpios que el modo estricto de Obsidian (salto de línea simple para <br>).
Default new file location → Carpeta designada. Dirija los archivos nuevos a 00-inbox/ para que las notas sin categorizar no contaminen las carpetas de dominio. La bandeja de entrada es un área de clasificación; los archivos se mueven a carpetas de dominio después del triaje.
Formato de wiki-link → Ruta más corta cuando sea posible. Los destinos de enlace más cortos son más fáciles de resolver para el retriever al indexar la estructura de enlaces.
Modelos de embeddings: selecció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 elegir alternativas.
Por qué Model2Vec potion-base-8M
Modelo: minishlab/potion-base-8M
Parámetros: 7,6 millones
Dimensiones: 256
Tamaño: ~30 MB
Dependencias: model2vec (solo numpy, sin PyTorch)
Inferencia: solo CPU, embeddings de palabras estáticos (sin capas de atención)
Model2Vec destila el conocimiento de un sentence transformer en embeddings de tokens estáticos. En lugar de ejecutar capas de atención sobre la entrada (como hacen BERT, MiniLM y otros modelos transformer), Model2Vec produce vectores mediante el promedio ponderado de embeddings de tokens precalculados.3 La consecuencia práctica: la velocidad de generación de embeddings es 50-500x más rápida que los modelos basados en transformers porque no hay computación secuencial.
En el benchmark MTEB, potion-base-8M alcanza el 89% del rendimiento de all-MiniLM-L6-v2 (50,03 vs 56,09 en promedio).4 La brecha de calidad del 11% es la compensación por las ventajas de velocidad y simplicidad. Para chunks cortos de markdown (promedio de 200-400 palabras en un vault típico), la diferencia de calidad es menos pronunciada que en documentos más largos porque ambos modelos convergen en representaciones similares para texto corto y enfocado.
Configuración
# embedder.py
DEFAULT_MODEL = "minishlab/potion-base-8M"
EMBEDDING_DIM = 256
class Model2VecEmbedder:
def __init__(self, model_name=DEFAULT_MODEL):
self._model_name = model_name
self._model = None
def _ensure_model(self):
if self._model is not None:
return
_activate_venv() # Add isolated venv to sys.path
from model2vec import StaticModel
self._model = StaticModel.from_pretrained(self._model_name)
def embed_batch(self, texts):
self._ensure_model()
vecs = self._model.encode(texts)
return [v.tolist() for v in vecs]
Carga diferida. El modelo se carga en el primer uso, no al momento de importar. Importar el módulo del embedder no tiene costo cuando el retriever opera en modo de respaldo BM25-only (por ejemplo, cuando el venv 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 los site-packages del venv a sys.path en tiempo de ejecución.
# Create isolated venv
python3 -m venv ~/.claude/venvs/memory
~/.claude/venvs/memory/bin/pip install model2vec
Procesamiento por lotes. El embedder procesa textos en lotes de 64 para amortizar la sobrecarga de Model2Vec. El indexador alimenta chunks a embed_batch() en lugar de generar embeddings de un chunk 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 |
| 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, mayor calidad |
Elija all-MiniLM-L6-v2 cuando la calidad de recuperación importa más que la velocidad y tiene PyTorch instalado. Los vectores de 384 dimensiones aumentan el tamaño de la base de datos SQLite en aproximadamente un 50% comparado con vectores de 256 dimensiones. La velocidad de generación de embeddings baja de <1 minuto a ~10 minutos para una reindexación completa de 15.000 archivos en hardware de la serie M.
Elija nomic-embed-text-v1.5 cuando necesite la mejor calidad de recuperación local posible y acepte una indexación más lenta. Los vectores de 768 dimensiones aproximadamente triplican el tamaño de la base de datos. Requiere PyTorch y un CPU moderno o GPU.
Elija text-embedding-3-small cuando la latencia de red y la privacidad sean compensaciones aceptables. La API produce los embeddings de mayor calidad pero introduce una dependencia en la nube, costo por token ($0,02/millón de tokens) y envía su contenido a los servidores de OpenAI.
Quédese 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 instalación de PyTorch, y los vectores de 256 dimensiones mantienen la base de datos compacta.
Seguimiento del hash del modelo
El indexador almacena un hash derivado del nombre del modelo y el tamaño del vocabulario. Si cambia el modelo de embeddings, el indexador detecta la discrepancia en la siguiente ejecución incremental y activa una reindexación completa automáticamente.
def _compute_model_hash(self):
"""Hash model name + vocab size for compatibility tracking."""
key = f"{self._model_name}:{self._model.vocab_size}"
return hashlib.sha256(key.encode()).hexdigest()[:16]
Esto evita mezclar vectores de diferentes modelos en la misma base de datos, lo que produciría puntuaciones de cosine similarity sin sentido.
Modos de fallo
Fallo en la descarga del modelo. La primera ejecución descarga el modelo de Hugging Face. Si la descarga falla (problema de red, firewall corporativo), el retriever recurre al modo BM25-only. El modelo se almacena en caché localmente después de la primera descarga.
Discrepancia de dimensiones. Si cambia de modelo sin limpiar la base de datos, los vectores almacenados tienen una dimensión diferente a los nuevos embeddings. El indexador detecta esto mediante el hash del modelo y activa una reindexación completa. Si la verificación del hash falla (modelo personalizado sin hash adecuado), sqlite-vec generará un error en las consultas KNN con dimensiones no coincidentes.
Presión de memoria en vaults grandes. Generar embeddings de más de 50.000 chunks en un solo lote puede consumir una cantidad significativa de memoria. El indexador procesa en lotes de 64 para limitar el uso máximo de memoria. Si la memoria sigue siendo un problema, reduzca el tamaño del lote.
Búsqueda de texto completo con FTS5
La extensión FTS5 de SQLite proporciona búsqueda de texto completo con clasificación BM25. FTS5 es el componente de búsqueda por palabras clave del pipeline de recuperación híbrida (hybrid retrieval). Esta sección cubre la configuración de FTS5, cuándo BM25 sobresale y sus modos de fallo específicos.
Tabla virtual FTS5
CREATE VIRTUAL TABLE chunks_fts USING fts5(
chunk_text,
section,
heading_context,
content=chunks,
content_rowid=id
);
Modo de sincronización de contenido. El parámetro content=chunks indica a FTS5 que haga referencia 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)
Clasificación 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 su vault tiene encabezados descriptivos que predicen con precisión la calidad del contenido, aumente el peso de section. Si sus etiquetas son exhaustivas y precisas, aumente 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 gran escala. En un vault de 15.000 archivos, una búsqueda de “configuration” coincide con cientos de notas porque prácticamente cada nota de proyecto menciona la configuración. Los resultados son técnicamente correctos pero prácticamente inútiles — la clasificación no puede determinar qué nota de “configuration” es relevante para la consulta actual.
Tokenizador FTS5
FTS5 utiliza el tokenizador unicode61 de forma predeterminada, que maneja texto ASCII y Unicode. Para vaults con contenido significativo en CJK (chino, japonés, coreano), considere el tokenizador trigram:
-- For CJK-heavy vaults
CREATE VIRTUAL TABLE chunks_fts USING fts5(
chunk_text, section, heading_context,
content=chunks, content_rowid=id,
tokenize='trigram'
);
El tokenizador unicode61 predeterminado divide en límites de palabras, lo cual funciona mal para idiomas sin espacios entre palabras. El tokenizador trigram divide cada tres caracteres, habilitando la búsqueda de subcadenas a costa del tamaño del índice (aproximadamente 3 veces más grande).
Mantenimiento
FTS5 requiere sincronización explícita cuando la tabla 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útelo después de inserciones masivas (reindexación completa), pero no después de actualizaciones incrementales individuales — para esas, utilice 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 la 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 de punto flotante 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 los chunks.
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 de punto flotante para el almacenamiento en sqlite-vec:
import struct
def _serialize_vector(vec):
"""Pack float list into binary for sqlite-vec."""
return struct.pack(f"{len(vec)}f", *vec)
def _deserialize_vector(blob, dim=256):
"""Unpack binary blob to float list."""
return list(struct.unpack(f"{dim}f", blob))
Consulta KNN
Una consulta de búsqueda vectorial genera el embedding de la consulta de entrada y luego encuentra los K chunks más cercanos por distancia coseno:
def _vector_search(self, query_text, limit=30):
query_vec = self.embedder.embed_batch([query_text])[0]
packed = _serialize_vector(query_vec)
results = self.db.execute("""
SELECT
cv.id,
cv.distance,
c.file_path,
c.section,
c.chunk_text
FROM chunk_vecs cv
JOIN chunks c ON cv.id = c.id
WHERE embedding MATCH ?
AND k = ?
ORDER BY distance
""", [packed, limit]).fetchall()
return results
El operador MATCH en sqlite-vec realiza una búsqueda aproximada de vecinos más cercanos. El parámetro k controla cuántos resultados se devuelven. La columna distance contiene la distancia coseno (0 = idéntico, 2 = opuesto).
Cuándo la búsqueda vectorial gana
La búsqueda vectorial sobresale en consultas donde el concepto importa más que las palabras específicas:
- Consulta: “how to handle authentication failures” → Encuentra notas sobre “login error recovery” (mismo espacio semántico, diferentes palabras clave)
- 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 “fusion algorithms” y “rank merging” pero puede clasificar la definición real de la función por debajo de discusiones conceptuales - Consulta:
PostToolUse→ Devuelve notas sobre “tool lifecycle hooks” y “post-execution handlers” en lugar del nombre específico del hook
La búsqueda vectorial también tiene dificultades con datos estructurados. Los archivos de configuración JSON, los bloques YAML y los fragmentos de código producen embeddings que capturan patrones estructurales en lugar de significado semántico. Un archivo JSON con "review": true genera un embedding diferente al de una discusión en prosa sobre revisión de código.
Degradación gradual
Si sqlite-vec no puede cargarse (extensión faltante, plataforma incompatible, biblioteca corrupta), el recuperador recurre a la búsqueda solo con BM25:
class VectorIndex:
def __init__(self, db_path):
self.db = sqlite3.connect(db_path)
self._vec_available = False
try:
self.db.enable_load_extension(True)
self.db.load_extension("vec0")
self._vec_available = True
except Exception:
pass # BM25-only mode
@property
def vec_available(self):
return self._vec_available
El recuperador verifica vec_available antes de intentar consultas vectoriales. Cuando está deshabilitado, todas las búsquedas utilizan solo BM25 y 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 seguimiento detallado de una consulta, el ajuste del parámetro k y las razones por las que se elige RRF sobre otras alternativas. Para una calculadora interactiva con rangos editables, escenarios predefinidos y un explorador visual de arquitectura, consulte el análisis profundo del hybrid retriever.
El Algoritmo
RRF asigna a cada documento una puntuación basada únicamente en su posición en cada lista:
score(d) = Σ (weight_i / (k + rank_i))
Donde:
- k es una constante de suavizado (60, siguiendo a Cormack et al.1)
- rank_i es la posición 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 obtienen 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 otras alternativas
La combinación lineal ponderada requiere calibrar las puntuaciones de BM25 contra las distancias de cosine similarity. Las puntuaciones de BM25 no tienen límite superior y escalan con el tamaño del corpus. Las distancias de coseno están acotadas en [0, 2]. Combinarlas requiere normalización, y los parámetros de normalización dependen del conjunto de datos. RRF utiliza únicamente posiciones de rango, que siempre son enteros comenzando en 1 independientemente del método de puntuación.
Los modelos de fusión entrenados requieren datos de entrenamiento etiquetados — pares de relevancia consulta-documento. Para una base de conocimiento personal, estos datos de entrenamiento no existen. Sería necesario evaluar manualmente cientos de pares consulta-documento para entrenar un modelo útil. RRF funciona sin ningún dato de entrenamiento.
Los métodos de votación Condorcet (Borda count, método Schulze) son teóricamente elegantes pero más complejos de implementar y ajustar. El artículo original de RRF demostró que RRF supera a los métodos Condorcet en datos de evaluación TREC.1
Fusión en la Práctica
Consulta: “how does the review aggregator handle disagreements”
BM25 posiciona review-aggregator.py en la posición 3 (coincidencias exactas de palabras clave en “review”, “aggregator”, “disagreements”) pero coloca dos archivos de configuración más arriba (coinciden con “review” de forma más prominente). La búsqueda vectorial posiciona el mismo fragmento en la posición 1 (coincidencia semántica en resolución de conflictos). Después de la fusión con RRF:
| Chunk | BM25 | Vec | Fused Score |
|---|---|---|---|
| review-aggregator.py “Disagreement Resolution” | #3 | #1 | 0.0323 |
| code-review-patterns.md “Multi-Reviewer” | #4 | #2 | 0.0317 |
| deliberation-config.json “Review Weights” | #1 | — | 0.0164 |
Los fragmentos que obtienen buenas posiciones en ambas listas ascienden al primer lugar. Los fragmentos que solo aparecen en una lista obtienen una puntuación de fuente única y caen por debajo de los resultados con posición en ambas listas. 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 seguimiento completo paso a paso con las matemáticas de RRF por rango, pruebe diferentes valores de k en la calculadora interactiva de RRF.
Implementación
RRF_K = 60
def _rrf_fuse(self, bm25_results, vec_results,
bm25_weight=1.0, vec_weight=1.0):
"""Fuse BM25 and vector results using Reciprocal Rank Fusion."""
scores = {}
for rank, r in enumerate(bm25_results, start=1):
cid = r["id"]
if cid not in scores:
scores[cid] = {
"rrf_score": 0.0,
"file_path": r["file_path"],
"section": r["section"],
"chunk_text": r["chunk_text"],
"bm25_rank": None,
"vec_rank": None,
}
scores[cid]["rrf_score"] += bm25_weight / (self._rrf_k + rank)
scores[cid]["bm25_rank"] = rank
for rank, r in enumerate(vec_results, start=1):
cid = r["id"]
if cid not in scores:
scores[cid] = {
"rrf_score": 0.0,
"file_path": r["file_path"],
"section": r["section"],
"chunk_text": r["chunk_text"],
"bm25_rank": None,
"vec_rank": None,
}
scores[cid]["rrf_score"] += vec_weight / (self._rrf_k + rank)
scores[cid]["vec_rank"] = rank
fused = sorted(
scores.values(),
key=lambda x: x["rrf_score"],
reverse=True,
)
return fused
Ajuste de k
La constante k controla cuánto peso se otorga a los resultados mejor posicionados en comparación con los de posiciones más bajas:
- k bajo (por ejemplo, 10): Los resultados mejor posicionados dominan. La posición 1 obtiene 1/11 = 0,091, la posición 10 obtiene 1/20 = 0,050 (diferencia de 1,8x). Adecuado cuando confía en que los clasificadores individuales aciertan con el resultado principal.
- k predeterminado (60): Equilibrado. La posición 1 obtiene 1/61 = 0,0164, la posición 10 obtiene 1/70 = 0,0143 (diferencia de 1,15x). Las diferencias de rango se comprimen, otorgando más peso a aparecer en múltiples listas.
- k alto (por ejemplo, 200): Aparecer en ambas listas importa mucho más que la posición en el rango. La posición 1 obtiene 1/201, la posición 10 obtiene 1/210 — casi idénticas. Utilice este valor cuando los clasificadores individuales producen posiciones ruidosas pero la concordancia entre listas es confiable.
Comience con k=60. El artículo original de RRF encontró que este valor es robusto en diversos conjuntos de datos TREC. Ajuste únicamente después de medir casos de fallo en su propia distribución de consultas.
Desempate
Cuando dos fragmentos tienen puntuaciones RRF idénticas (poco frecuente pero posible cuando tienen la misma posición en una lista y no aparecen en la otra), resuelva los empates de la siguiente manera:
- Prefiera fragmentos que aparecen en ambas listas sobre fragmentos que aparecen en una sola
- Entre fragmentos presentes en ambas listas, prefiera el que tenga la menor posición combinada
- Entre fragmentos presentes en una sola lista, prefiera el que tenga la menor posición en esa lista
El pipeline completo de recuperación
Esta sección traza una consulta desde la entrada hasta la salida a través de todo el pipeline: búsqueda BM25, búsqueda vectorial, fusión RRF, truncamiento por presupuesto de tokens y ensamblaje de contexto.
Flujo de extremo a extremo
User query: "PostToolUse hook for context compression"
│
├─ BM25 Search (FTS5)
│ → MATCH "PostToolUse hook context compression"
│ → Top 30 results ranked by BM25 score
│ → 12ms
│
├─ Vector Search (sqlite-vec)
│ → Embed query with Model2Vec
│ → KNN k=30 on chunk_vecs
│ → Top 30 results ranked by cosine distance
│ → 8ms
│
└─ RRF Fusion
→ Merge 60 candidates (may overlap)
→ Score by rank position
→ Top 10 results
→ 3ms
│
└─ Token Budget
→ Truncate to max_tokens (default 4000)
→ Estimate at 4 chars per token
→ Return results with metadata
→ <1ms
Latencia total: ~23ms para una base de datos de 49.746 chunks en hardware Apple M3 Pro.
El API de búsqueda
class HybridRetriever:
def search(self, query, limit=10, max_tokens=4000,
bm25_weight=1.0, vec_weight=1.0):
"""
Search the vault using hybrid BM25 + vector retrieval.
Args:
query: Search query text
limit: Maximum results to return
max_tokens: Token budget for total result text
bm25_weight: Weight for BM25 results in RRF
vec_weight: Weight for vector results in RRF
Returns:
List of SearchResult with file_path, section,
chunk_text, rrf_score, bm25_rank, vec_rank
"""
# BM25 search
bm25_results = self._bm25_search(query, limit=30)
# Vector search (if available)
if self.index.vec_available:
vec_results = self._vector_search(query, limit=30)
fused = self._rrf_fuse(
bm25_results, vec_results,
bm25_weight, vec_weight,
)
else:
fused = bm25_results # BM25-only fallback
# Token budget truncation
results = []
token_count = 0
for r in fused[:limit]:
chunk_tokens = len(r["chunk_text"]) // 4
if token_count + chunk_tokens > max_tokens:
break
results.append(r)
token_count += chunk_tokens
return results
Truncamiento por presupuesto de tokens
El parámetro max_tokens evita que el recuperador devuelva más contexto del que la herramienta de IA puede utilizar. La estimación usa 4 caracteres por token (una aproximación razonable para prosa en inglés). Los resultados se truncan de forma voraz: se agregan resultados en orden de clasificación hasta 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 la clasificación RRF ya ordena los resultados por relevancia.
Esquema de la base de datos (completo)
-- Chunk content and metadata
CREATE TABLE chunks (
id INTEGER PRIMARY KEY,
file_path TEXT NOT NULL,
section TEXT NOT NULL,
chunk_text TEXT NOT NULL,
heading_context TEXT DEFAULT '',
mtime_ns INTEGER NOT NULL,
embedded_at REAL NOT NULL
);
CREATE INDEX idx_chunks_file ON chunks(file_path);
CREATE INDEX idx_chunks_mtime ON chunks(mtime_ns);
-- FTS5 for BM25 search (content-synced to chunks table)
CREATE VIRTUAL TABLE chunks_fts USING fts5(
chunk_text, section, heading_context,
content=chunks, content_rowid=id
);
-- sqlite-vec for vector KNN search
CREATE VIRTUAL TABLE chunk_vecs USING vec0(
id INTEGER PRIMARY KEY,
embedding float[256]
);
-- Model metadata for compatibility tracking
CREATE TABLE model_meta (
key TEXT PRIMARY KEY,
value TEXT
);
Ruta de degradación gradual
Full pipeline: BM25 + Vector + RRF → Best results
No sqlite-vec: BM25 only → Good results (no semantic)
No model download: BM25 only → Good results (no semantic)
No FTS5: Vector only → Decent results (no keyword)
No database: Error → Prompt user to run indexer
El recuperador verifica las capacidades disponibles durante la inicialización y adapta su estrategia de consulta. Un componente faltante degrada la calidad pero no causa errores. La única falla 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, 83 MB de base de datos SQLite, 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 predeterminado | 10 |
| Presupuesto de tokens predeterminado | 4.000 tokens |
Hashing de contenido y detección de cambios
El indexador necesita saber qué archivos han cambiado desde la última ejecución del índice. Esta sección cubre el mecanismo de detección de cambios y la estrategia de hashing.
Comparación del tiempo de modificación de archivos
El indexador almacena mtime_ns (tiempo de modificación del archivo en nanosegundos) para cada chunk en la tabla chunks. En una ejecución incremental, el indexador:
- Escanea el vault en busca de todos los archivos
.mden las carpetas permitidas - Lee el
mtime_nsde cada archivo desde el sistema de archivos - Compara con el
mtime_nsalmacenado en la base de datos - Identifica tres categorías:
- Archivos nuevos: la ruta existe en el sistema de archivos pero no en la base de datos
- Archivos modificados: la ruta existe en ambos pero el
mtime_nsdifiere - Archivos eliminados: la ruta existe en la base de datos pero no en el sistema de archivos
def get_stale_files(self, vault_mtimes):
"""Find files whose mtime changed or are new."""
stored = dict(self.db.execute(
"SELECT DISTINCT file_path, mtime_ns FROM chunks"
).fetchall())
stale = []
for path, mtime in vault_mtimes.items():
if path not in stored or stored[path] != mtime:
stale.append(path)
return stale
def get_deleted_files(self, vault_paths):
"""Find files in database that no longer exist in vault."""
stored_paths = set(r[0] for r in self.db.execute(
"SELECT DISTINCT file_path FROM chunks"
).fetchall())
return stored_paths - set(vault_paths)
¿Por qué mtime y no hash de contenido?
El hashing de contenido (SHA-256 del contenido del archivo) sería más confiable que la comparación de mtime — detectaría casos en los que un archivo fue tocado sin cambiar (por ejemplo, git checkout restaurando el mtime original). Sin embargo, el hashing requiere leer cada archivo en cada ejecución incremental. Para 16.894 archivos, leer el contenido toma 2-3 segundos. Leer los mtimes desde el sistema de archivos toma <100ms.
La compensación: la comparación de mtime ocasionalmente provoca la reindexación innecesaria de archivos sin cambios (falsos positivos) pero nunca omite cambios reales. Los falsos positivos cuestan 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],
)
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, 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: 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 chunks de archivos borrados 3. Vuelve a dividir en chunks y generar embeddings de 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 corrupción de la 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. Construye el índice FTS5 desde cero
Duración típica: ~4 minutos para 16.894 archivos en Apple M3 Pro.
python index_vault.py --full
Idempotencia
Ambos modos son idempotentes: ejecutar el mismo comando dos veces produce el mismo resultado. El indexador elimina los chunks existentes de un archivo antes de insertar los nuevos, por lo que una re-ejecución de la indexación incremental en una base de datos ya actualizada produce cero cambios. Una re-ejecución de la indexación completa produce una base de datos idéntica.
Recuperación ante corrupción
Si la base de datos SQLite se corrompe (pérdida de energía durante una 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 son siempre los archivos del vault, no la base de datos. La base de datos es un artefacto derivado que puede reconstruirse en cualquier momento. Esta es una propiedad de diseño crítica: nunca necesita respaldar la base de datos.
El flag --incremental
Cuando el indexador se ejecuta con --incremental:
- Verificación del hash del modelo. Compara el hash del modelo almacenado con el modelo actual. Si difieren, cambia automáticamente al modo de reindexación completa y advierte al usuario.
- Escaneo de archivos. Recorre las carpetas permitidas, recopila rutas de archivos y mtimes.
- Detección de cambios. Compara contra los datos almacenados.
- Procesamiento por lotes. Vuelve a dividir en chunks y generar embeddings de archivos modificados en lotes de 64.
- Informe de progreso. Imprime la cantidad de archivos procesados y el tiempo transcurrido.
- Cierre ordenado. Maneja SIGINT terminando el archivo actual antes de detenerse.
Filtrado de credenciales y límites de datos
Las notas personales contienen secretos: claves API, tokens bearer, cadenas de conexión a bases de datos, claves privadas pegadas durante sesiones de depuración. El filtro de credenciales evita que estos ingresen al índice de recuperación.
El problema
Una nota sobre la depuración de una integración con OAuth podría contener:
The token was: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
I used this curl command:
curl -H "Authorization: Bearer sk-ant-api03-abc123..."
Sin filtrado, tanto el JWT como la clave API serían divididos en chunks, convertidos en embeddings y almacenados en la base de datos. Una búsqueda de “authentication” devolvería el chunk que contiene secretos reales. Peor aún, si el recuperador alimenta los resultados a una herramienta de IA a través de MCP, los secretos aparecen en la ventana de contexto de la IA y potencialmente en los registros de la herramienta.
Filtrado basado en patrones
El filtro de credenciales se ejecuta en cada chunk antes del almacenamiento, detectando 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,} |
| Access Key de 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:
-
Filtrar antes de generar embeddings. El texto limpio es lo que se convierte en embeddings. La representación vectorial nunca codifica patrones de credenciales. Una consulta por “clave API” devuelve notas que discuten la gestión de claves API, no notas que contienen claves reales.
-
Reemplazar, no eliminar. El token
[REDACTED:pattern-name]preserva el contexto semántico del texto circundante. El embedding captura que “algo similar a una credencial estaba aquí” sin codificar la credencial en sí. -
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 de grano grueso por ruta. El filtro de credenciales proporciona limpieza de grano fino dentro de los archivos indexados. Ambos son necesarios:
.indexignorepara carpetas completas que se sabe que contienen contenido sensible (notas de salud, registros financieros, documentos de carrera profesional)- Filtro de credenciales para secretos incrustados accidentalmente en contenido que de otro modo sería indexable
Clasificación de datos
Para vaults que contienen contenido diverso, considere clasificar las notas por sensibilidad:
| Nivel | Ejemplos | ¿Indexar? | ¿Filtrar? |
|---|---|---|---|
| Público | Borradores de blog, notas técnicas | Sí | Sí |
| Interno | Planes de proyecto, decisiones de arquitectura | Sí | Sí |
| Sensible | Datos salariales, registros de salud | No (.indexignore) | N/A |
| Restringido | Credenciales, claves privadas | No (.indexignore) | N/A |
Arquitectura del servidor MCP
Model Context Protocol (MCP) expone el retriever 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 genera 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. Útil para acceso remoto, configuraciones multi-cliente o configuraciones de equipo donde el vault se encuentra en un servidor compartido.
{
"mcpServers": {
"obsidian": {
"url": "http://localhost:3333/mcp"
}
}
}
Recomendación: Use STDIO para vaults personales. Es más simple, más seguro (sin exposición de red), y el ciclo de vida del servidor es gestionado por la herramienta de IA. Use HTTP únicamente cuando múltiples herramientas o múltiples máquinas necesiten acceso concurrente al mismo vault.
Diseño de capacidades
El servidor MCP debe exponer un conjunto mínimo de herramientas:
search — La herramienta principal. Ejecuta la recuperación híbrida y devuelve resultados clasificados.
{
"name": "obsidian_search",
"description": "Search the Obsidian vault using hybrid BM25 + vector retrieval",
"parameters": {
"query": { "type": "string", "description": "Search query" },
"limit": { "type": "integer", "default": 5 },
"max_tokens": { "type": "integer", "default": 2000 }
}
}
read_note — Lee el contenido completo de una nota específica por ruta. Útil cuando el agente desea ver el contexto completo de un resultado de búsqueda.
{
"name": "obsidian_read_note",
"description": "Read the full content of a note by file path",
"parameters": {
"file_path": { "type": "string", "description": "Relative path within vault" }
}
}
list_notes — Lista notas que coinciden 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:
-
Solo lectura. El servidor lee el vault y la base de datos del índice. No crea, modifica ni elimina notas. Las operaciones de escritura (captura de nuevas notas) son gestionadas por hooks o skills independientes, no por el servidor MCP.
-
Alcance limitado al vault. El servidor solo lee archivos dentro de la ruta de vault configurada. Los intentos de path traversal (
../../etc/passwd) deben ser rechazados. -
Filtrado de credenciales en la salida. Incluso si la base de datos contiene contenido prefiltrado, aplique el filtrado de credenciales en la salida como medida de defensa en profundidad.
-
Respuestas limitadas por tokens. Aplique
max_tokensen todas las respuestas de herramientas para evitar que la herramienta de IA reciba bloques de contexto excesivamente grandes.
Manejo de errores
Las herramientas MCP deben devolver mensajes de error estructurados que ayuden a la herramienta de IA a recuperarse:
def search(self, query, limit=5, max_tokens=2000):
if not self.db_path.exists():
return {
"error": "Index database not found. Run the indexer first.",
"suggestion": "python index_vault.py --full"
}
results = self.retriever.search(query, limit, max_tokens)
if not results:
return {
"results": [],
"message": f"No results found for '{query}'. Try broader terms."
}
return {
"results": [
{
"file_path": r["file_path"],
"section": r["section"],
"text": r["chunk_text"],
"score": round(r["rrf_score"], 4),
}
for r in results
],
"count": len(results),
"query": query,
}
Integración con Claude Code
Claude Code es el consumidor principal del sistema de recuperación de Obsidian. Esta sección cubre la configuración de MCP, la integración con hooks y el patrón obsidian_bridge.py.
Configuración de MCP
Agregue 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 agregar la configuración, reinicie Claude Code. El servidor MCP se iniciará como un proceso hijo. Verifique que está en ejecución:
> What tools do you have from the obsidian MCP server?
Claude Code debería listar las herramientas disponibles (obsidian_search, obsidian_read_note, etc.).
Integración con hooks
Los hooks extienden el comportamiento de Claude Code en puntos definidos del ciclo de vida. Dos hooks son relevantes para la integración con Obsidian:
Hook PreToolUse — Consulta el vault antes de que el agente procese una llamada a herramienta. Inyecta contexto relevante automáticamente.
#!/bin/bash
# ~/.claude/hooks/pre-tool-use/obsidian-context.sh
# Automatically inject vault context before tool execution
TOOL_NAME="$1"
PROMPT="$2"
# Only inject context for code-related tools
case "$TOOL_NAME" in
Edit|Write|Bash)
# Query the vault
CONTEXT=$(python /path/to/retriever.py search "$PROMPT" --limit 3 --max-tokens 1500)
if [ -n "$CONTEXT" ]; then
echo "---"
echo "Relevant vault context:"
echo "$CONTEXT"
echo "---"
fi
;;
esac
Hook PostToolUse — Captura salidas significativas de herramientas de vuelta al vault para 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 Python API que los hooks y skills pueden invocar:
# obsidian_bridge.py
from retriever import HybridRetriever
_retriever = None
def get_retriever():
global _retriever
if _retriever is None:
_retriever = HybridRetriever(
db_path="/path/to/vectors.db",
vault_path="/path/to/vault",
)
return _retriever
def search_vault(query, limit=5, max_tokens=2000):
"""Search vault and return formatted context."""
retriever = get_retriever()
results = retriever.search(query, limit, max_tokens)
if not results:
return ""
lines = ["## Vault Context\n"]
for r in results:
lines.append(f"**{r['file_path']}** — {r['section']}")
lines.append(f"> {r['chunk_text'][:500]}")
lines.append("")
return "\n".join(lines)
El skill /capture
Un skill de Claude Code para capturar conocimientos de vuelta al vault:
/capture "OAuth token rotation requires both access and refresh token invalidation"
--domain security
--tags oauth,tokens
El skill crea una nueva nota en 00-inbox/ con frontmatter apropiado y activa una reindexación incremental para que la nueva nota sea inmediatamente buscable.
Gestión de la ventana de contexto
La integración debe ser consciente de la ventana de contexto de Claude Code:
- Limite el contexto inyectado a 1.500-2.000 tokens por consulta. Más que esto compite con la memoria de trabajo del agente.
- Incluya atribución de fuente. Siempre incluya la ruta del archivo y el encabezado de sección para que el agente pueda referenciar la fuente.
- Trunque el texto de los fragmentos. Los fragmentos largos deben ser truncados con
...en lugar de omitidos por completo. Los primeros 300-500 caracteres generalmente contienen la información clave. - No inyecte en cada llamada a herramienta. El hook PreToolUse debe inyectar contexto selectivamente según la herramienta que se está invocando. Las operaciones de lectura no necesitan contexto del vault. Las operaciones de escritura y edición se benefician de él.
Integración con Codex CLI
Codex CLI se conecta a servidores MCP a través de config.toml. El patrón de integración difiere de Claude Code en la sintaxis de configuración y la entrega de instrucciones.
Configuración de MCP
Agregue a .codex/config.toml o ~/.codex/config.toml:
[mcp_servers.obsidian]
command = "python"
args = ["/path/to/obsidian_mcp.py"]
[mcp_servers.obsidian.env]
VAULT_PATH = "/absolute/path/to/vault"
DB_PATH = "/absolute/path/to/vectors.db"
Patrones de AGENTS.md
Codex CLI lee AGENTS.md para instrucciones a nivel de proyecto. Incluya orientación sobre la búsqueda en el vault:
## Herramientas disponibles
### Vault de Obsidian (MCP: obsidian)
Use la herramienta `obsidian_search` para encontrar contexto relevante de la base de conocimiento.
Busque en el vault cuando necesite:
- Información de fondo sobre un concepto o patrón
- Decisiones previas o su justificación
- Material de referencia para la implementación
Consultas de ejemplo:
- "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 de MCP | settings.json |
config.toml |
| Hooks | ~/.claude/hooks/ |
No soportado |
| Skills | ~/.claude/skills/ |
No soportado |
| Archivo de instrucciones | CLAUDE.md |
AGENTS.md |
| Modos de aprobación | --dangerously-skip-permissions |
suggest / auto-edit / full-auto |
Diferencia clave: Codex CLI no soporta hooks. El patrón de inyección automática de contexto (hook PreToolUse) no está disponible. En su lugar, incluya instrucciones explícitas en AGENTS.md indicando al agente que busque en el vault antes de comenzar a trabajar.
Cursor y otras herramientas
Cursor y otras herramientas de IA que soportan MCP pueden conectarse al mismo servidor MCP de Obsidian. Esta sección cubre la configuración para herramientas comunes.
Cursor
Agregue lo siguiente a .cursor/mcp.json en la raíz de su proyecto:
{
"mcpServers": {
"obsidian": {
"command": "python",
"args": ["/path/to/obsidian_mcp.py"],
"env": {
"VAULT_PATH": "/absolute/path/to/vault",
"DB_PATH": "/absolute/path/to/vectors.db"
}
}
}
}
El archivo .cursorrules de Cursor puede incluir instrucciones para usar el vault:
When working on implementation tasks, search the Obsidian vault
for relevant context before writing code. Use the obsidian_search
tool with descriptive queries about the concept you're implementing.
Matriz de compatibilidad
| Herramienta | Soporte MCP | Transporte | Ubicación de configuración |
|---|---|---|---|
| Claude Code | Completo | STDIO | ~/.claude/settings.json |
| Codex CLI | Completo | STDIO | .codex/config.toml |
| Cursor | Completo | STDIO | .cursor/mcp.json |
| Windsurf | Completo | STDIO | .windsurf/mcp.json |
| Continue.dev | Parcial | HTTP | ~/.continue/config.json |
| Zed | En desarrollo | STDIO | Interfaz de configuración |
Alternativa para herramientas sin MCP
Para herramientas que no soportan MCP, el retriever puede encapsularse 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 produce texto estructurado que puede pegarse manualmente en la entrada de cualquier herramienta de IA. Esto es menos elegante que la integración con MCP, pero funciona universalmente.
Caché de prompts a partir de notas estructuradas
Las notas estructuradas en el vault 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, construya previamente bloques de contexto a partir de notas bien estructuradas del vault y almacénelos en caché:
# cache_keys.py
CONTEXT_BLOCKS = {
"auth-patterns": {
"vault_query": "authentication patterns implementation",
"max_tokens": 1500,
"ttl_hours": 24, # Rebuild daily
},
"api-conventions": {
"vault_query": "API design conventions REST patterns",
"max_tokens": 1000,
"ttl_hours": 168, # Rebuild weekly
},
"project-architecture": {
"vault_query": "current project architecture decisions",
"max_tokens": 2000,
"ttl_hours": 12, # Rebuild twice daily
},
}
Invalidación de caché
La invalidación de caché se basa en dos señales:
- Expiración del TTL. Cada bloque de contexto tiene un tiempo de vida (time-to-live). Cuando el TTL expira, el bloque se reconstruye volviendo a consultar el vault.
- Detección de cambios en el vault. Cuando el indexador detecta cambios en archivos que contribuyeron a un bloque de contexto en caché, el bloque se invalida inmediatamente.
Gestión del presupuesto de tokens
Una sesión comienza con un presupuesto total de contexto. Los bloques en caché consumen parte de ese presupuesto:
Total context budget: 8,000 tokens
├─ System prompt: 1,500 tokens
├─ Cached blocks: 3,000 tokens (pre-loaded)
├─ Dynamic search: 2,000 tokens (on-demand)
└─ Conversation: 1,500 tokens (remaining)
Los bloques en caché se cargan al inicio de la sesión. Los resultados de búsqueda dinámica llenan el presupuesto restante por consulta. Este enfoque híbrido proporciona al agente una base de contexto frecuentemente necesario mientras preserva presupuesto para consultas específicas.
Comparación del uso de tokens: antes y después
Sin caché: Cada consulta relevante activa una búsqueda en el vault, devolviendo entre 1.500 y 2.000 tokens de contexto. A lo largo de 10 consultas en una sesión, el agente consume entre 15.000 y 20.000 tokens de contexto del vault.
Con caché: Tres bloques de contexto preconstruidos consumen 4.500 tokens en total. Las búsquedas adicionales agregan entre 1.500 y 2.000 tokens por consulta única. A lo largo de 10 consultas donde 6 están cubiertas por bloques en caché, el agente consume 4.500 + (4 × 1.500) = 10.500 tokens — aproximadamente la mitad del uso sin caché.
Hooks PostToolUse para compresión de contexto
Las salidas de herramientas pueden ser extensas: trazas de pila, listados de archivos, resultados de pruebas. Un hook PostToolUse puede comprimir estas salidas antes de que consuman espacio en la ventana de contexto.
El problema
Una llamada a la herramienta Bash que ejecuta pruebas podría devolver:
PASSED tests/test_auth.py::test_login_success
PASSED tests/test_auth.py::test_login_failure
PASSED tests/test_auth.py::test_token_refresh
PASSED tests/test_auth.py::test_session_expiry
... (200 more lines)
FAILED tests/test_api.py::test_rate_limit_exceeded
La salida completa ocupa 5.000 tokens, pero la información relevante está en 2 líneas: 200 aprobadas, 1 fallida.
Implementación del hook
#!/bin/bash
# ~/.claude/hooks/post-tool-use/compress-output.sh
# Compress verbose tool outputs to preserve context window
TOOL_NAME="$1"
OUTPUT="$2"
OUTPUT_LEN=${#OUTPUT}
# Only compress large outputs
if [ "$OUTPUT_LEN" -lt 2000 ]; then
exit 0 # Pass through unchanged
fi
case "$TOOL_NAME" in
Bash)
# Compress test output
if echo "$OUTPUT" | grep -q "PASSED\|FAILED"; then
PASSED=$(echo "$OUTPUT" | grep -c "PASSED")
FAILED=$(echo "$OUTPUT" | grep -c "FAILED")
FAILURES=$(echo "$OUTPUT" | grep "FAILED")
echo "Tests: $PASSED passed, $FAILED failed"
if [ "$FAILED" -gt 0 ]; then
echo "Failures:"
echo "$FAILURES"
fi
fi
;;
esac
Prevención de activación recursiva
Un hook de compresión que emite salida podría activarse a sí mismo si no se protege:
# Guard against recursive invocation
if [ -n "$COMPRESS_HOOK_ACTIVE" ]; then
exit 0
fi
export COMPRESS_HOOK_ACTIVE=1
Heurísticas de compresión
| Tipo de salida | Detección | Estrategia de compresión |
|---|---|---|
| Resultados de pruebas | Palabras clave PASSED / FAILED |
Contar aprobadas/fallidas, mostrar solo las fallidas |
| Listados de archivos | ls o find en el comando |
Truncar a las primeras 20 entradas + total |
| Trazas de pila | Palabra clave Traceback |
Conservar primer y último frame + mensaje de error |
| Estado de Git | modified: / new file: |
Resumir conteos por estado |
| Salida de compilación | warning: / error: |
Eliminar líneas informativas, conservar advertencias/errores |
Flujo de Ingesta y Triaje de Señales
La capa de ingesta determina qué entra al vault. Sin curación, el vault acumula ruido. Esta sección cubre el flujo de puntuación que enruta señales a las carpetas de dominio.
Fuentes
Las señales provienen de múltiples canales:
- Fuentes RSS: Blogs técnicos, avisos de seguridad, notas de versión
- Marcadores: Marcadores del navegador guardados mediante Obsidian Web Clipper o bookmarklet
- Boletines: Extractos clave de boletines por correo electrónico
- Captura manual: Notas escritas durante lectura, conversaciones o investigación
- Salida de herramientas: Resultados significativos de herramientas de IA capturados mediante hooks
Dimensiones de Puntuación
Cada señal se 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 del alcance | Directamente relevante al trabajo activo |
| Accionabilidad | ¿Puedo usar esta información? | Pura teoría, 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 de temas:
05-signals/
├── ai-tooling/ # Claude, LLMs, AI development tools
├── security/ # Vulnerabilities, auth, cryptography
├── systems/ # Architecture, distributed systems
├── programming/ # Languages, patterns, algorithms
├── web/ # Frontend, backends, APIs
├── data/ # Databases, data engineering
├── devops/ # CI/CD, containers, infrastructure
├── design/ # UI/UX, product design
├── mobile/ # iOS, Android, cross-platform
├── career/ # Industry trends, hiring, growth
├── research/ # Academic papers, whitepapers
└── other/ # Signals that don't fit a domain
Estadísticas de Producción
A lo largo de 14 meses de operación:
| Métrica | Valor |
|---|---|
| Total de señales procesadas | 7.771 |
| Enrutadas automáticamente (>0.55) | 4.832 (62%) |
| En cola para revisión (0.40-0.55) | 1.543 (20%) |
| Descartadas (<0.40) | 1.396 (18%) |
| Carpetas de dominio activas | 12 |
| Promedio de señales por día | ~18 |
Patrones de Grafo de Conocimiento
El grafo de wiki-link de Obsidian codifica 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.
Semántica de Backlinks
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
[[Note B]]→ A enlaza a B - Backlink: La Nota B muestra que la Nota A la referencia
El grafo codifica diferentes tipos de relaciones dependiendo del contexto:
| Patrón de enlace | Semántica | Ejemplo |
|---|---|---|
| Enlace en línea | “Está relacionado con” | “Consulte [[OAuth Token Rotation]] para más detalles” |
| Enlace de encabezado | “Tiene subtema” | ”## Relacionado\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:
- Coincidencia directa. Una búsqueda de “descripción general de autenticación” coincide con el propio MOC, proporcionando al agente una lista curada de notas relacionadas.
- Expansión de contexto. Después de encontrar una nota específica, el recuperador puede verificar si la nota aparece en algún MOC e incluir la estructura del MOC en los resultados, dando al agente un mapa del tema más amplio.
Recorrido del Grafo para Expansión de Contexto
Una mejora futura para el recuperador: después de encontrar los mejores resultados, expandir el contexto siguiendo los enlaces:
def expand_context(results, depth=1):
"""Follow wiki-links from top results to find related context."""
expanded = set()
for result in results:
# Parse wiki-links from chunk text
links = extract_wiki_links(result["chunk_text"])
for link_target in links:
# Resolve link to file path
target_path = resolve_wiki_link(link_target)
if target_path and target_path not in expanded:
expanded.add(target_path)
# Include target's most relevant chunk
target_chunks = get_chunks_for_file(target_path)
# ... rank and include best chunk
return results + list(expanded_results)
Esto no está implementado en el recuperador actual, pero representa una extensión natural de la estructura del grafo.
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 MOCs faltantes o enlaces entre dominios faltantes.
Proliferación de etiquetas. Usar etiquetas de forma inconsistente o crear demasiadas etiquetas de granularidad fina. Un vault con 500 etiquetas únicas en 5.000 notas promedia 1 nota por cada 10 etiquetas — las etiquetas no son útiles para filtrar. Consolide a 20-50 etiquetas de alto nivel que se correspondan con sus carpetas de dominio.
Notas con muchos enlaces y poco contenido. Notas que consisten enteramente de wiki-links sin prosa. Estas notas se indexan mal porque el chunker no tiene texto para generar embeddings. Agregue al menos un párrafo de contexto explicando por qué las notas enlazadas están relacionadas.
Enlaces bidireccionales para todo. No toda referencia necesita ser un wiki-link. Mencionar “OAuth” de pasada no requiere [[OAuth 2.0 Overview]]. Reserve los wiki-links para relaciones intencionales y navegables donde hacer clic en el enlace proporcionaría contexto útil.
Recetas de Flujo de Trabajo para Desarrolladores
Flujos de trabajo prácticos que combinan la recuperación del vault con tareas de desarrollo diarias.
Carga de Contexto Matutina
Comience el día cargando contexto relevante:
Search my vault for notes about [current project] updated in the last week
El recuperador devuelve notas recientes sobre su proyecto activo, dándole un repaso rápido de dónde lo dejó. Más efectivo que releer los mensajes de commit del día anterior.
Captura de Investigación Durante la Programación
Mientras implementa una función, capture 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 inmediatamente y queda disponible para recuperación futura. Con el paso de los meses, estas micro-capturas construyen un corpus de conocimiento específico de implementación.
Inicio de Proyecto
Al comenzar un nuevo proyecto o función:
- Busque en el vault: “¿Qué sé sobre [tecnología/patrón]?”
- Revise los 5 mejores resultados en busca de decisiones previas y problemas encontrados
- Verifique si existe un MOC para el dominio; si no, cree uno
- Busque modos de fallo: “problemas con [tecnología]”
Depuración con Búsqueda en el Vault
Cuando encuentre un error o comportamiento inesperado:
Search my vault for [error message or symptom]
Las notas de depuración previas frecuentemente contienen la causa raíz y la solución. Esto es particularmente valioso para problemas recurrentes entre proyectos — el vault recuerda lo que usted olvida.
Preparación para Revisión de Código
Antes de revisar un PR:
Search my vault for patterns and conventions about [module being changed]
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 fundamenta en conocimiento institucional, no solo en el diff.
Optimización del rendimiento
Esta sección cubre estrategias de optimización para diferentes tamaños de vault y patrones de uso.
Gestión del tamaño del índice
| Tamaño del vault | Chunks | Tamaño de BD | Reindexación completa | Incremental |
|---|---|---|---|---|
| 500 notas | ~1.500 | 3 MB | 15 segundos | <1 segundo |
| 2.000 notas | ~6.000 | 12 MB | 45 segundos | 2 segundos |
| 5.000 notas | ~15.000 | 30 MB | 2 minutos | 4 segundos |
| 15.000 notas | ~50.000 | 83 MB | 4 minutos | <10 segundos |
| 50.000 notas | ~150.000 | 250 MB | 15 minutos | 30 segundos |
Con más de 50.000 notas, considere: - Aumentar el batch size de 64 a 128 para un embedding más rápido - Usar el modo WAL (predeterminado) para acceso concurrente - Ejecutar la reindexación completa fuera de horarios de uso intensivo
Optimización de consultas
Modo WAL. El modo Write-Ahead Logging de SQLite permite lecturas concurrentes mientras el indexador escribe:
db.execute("PRAGMA journal_mode=WAL")
Esto es fundamental cuando el servidor MCP maneja consultas mientras el indexador ejecuta una actualización incremental.
Pool de conexiones. El servidor MCP debe reutilizar conexiones a la base de datos en lugar de abrir una nueva conexión por cada consulta. Una única conexión de larga duración 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 en memoria. El pragma mmap_size indica a SQLite que use E/S con mapeo en memoria para el archivo de base de datos. Para una base de datos de 83 MB, mapear el archivo completo en memoria elimina la mayoría de las lecturas de disco.
Optimización de FTS5. Después de una reindexación completa, ejecute:
INSERT INTO chunks_fts(chunks_fts) VALUES('optimize');
Esto fusiona los segmentos internos del b-tree de FTS5, reduciendo la latencia de consulta para búsquedas posteriores.
Benchmarks de escalabilidad
Medido en Apple M3 Pro, 36 GB de RAM, SSD NVMe:
| Operación | 500 notas | 5K notas | 15K notas | 50K notas |
|---|---|---|---|---|
| Consulta BM25 | 2ms | 5ms | 12ms | 25ms |
| Consulta vectorial | 1ms | 3ms | 8ms | 20ms |
| Fusión RRF | <1ms | <1ms | 3ms | 5ms |
| Búsqueda completa | 3ms | 8ms | 23ms | 50ms |
Todos los benchmarks incluyen acceso a la base de datos, ejecución de consultas y formateo de resultados. La latencia de red para la comunicación MCP STDIO agrega 1-2ms.
Solución de problemas
Desincronización del índice
Síntoma: La búsqueda devuelve resultados desactualizados o no encuentra notas agregadas recientemente.
Causa: El indexador incremental no se ejecutó después de agregar notas, o el mtime de un archivo no se actualizó (p. ej., sincronizado desde otra máquina con marcas de tiempo preservadas).
Solución: Ejecute una reindexación completa: python index_vault.py --full
Cambio de modelo de embedding
Síntoma: Después de cambiar el modelo de embedding, la búsqueda vectorial devuelve resultados sin sentido.
Causa: Los vectores antiguos (del modelo anterior) se están comparando contra los nuevos vectores de consulta. Las dimensiones o la semántica del espacio vectorial son incompatibles.
Solución: El indexador debería detectar la discrepancia en el hash del modelo y activar una reindexación completa automáticamente. Si no lo hace, elimine manualmente la base de datos y reindexe:
rm vectors.db
python index_vault.py --full
Mantenimiento de FTS5
Síntoma: Las consultas FTS5 devuelven resultados incorrectos o incompletos después de muchas actualizaciones incrementales.
Causa: Los segmentos internos de FTS5 pueden fragmentarse después de muchas actualizaciones pequeñas.
Solución: Reconstruya y optimice:
INSERT INTO chunks_fts(chunks_fts) VALUES('rebuild');
INSERT INTO chunks_fts(chunks_fts) VALUES('optimize');
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 cual toma de 2 a 5 segundos. El timeout predeterminado de MCP en la herramienta de IA puede ser menor.
Solución: Precaliente el modelo al iniciar el servidor:
# In MCP server initialization
retriever = HybridRetriever(db_path, vault_path)
retriever.search("warmup", limit=1) # Trigger model load
Bloqueos de archivo SQLite
Síntoma: Errores SQLITE_BUSY o SQLITE_LOCKED.
Causa: Múltiples procesos escribiendo en la base de datos simultáneamente. El modo WAL permite lecturas concurrentes pero solo un escritor.
Solución: Asegúrese de que solo un proceso (el indexador) escriba en la base de datos. El servidor MCP y los hooks solo deben leer. Si necesita escrituras concurrentes, use el modo WAL y establezca un timeout de espera:
db.execute("PRAGMA busy_timeout=5000") # Wait up to 5 seconds
sqlite-vec no se carga
Síntoma: La búsqueda vectorial está deshabilitada; el retriever opera en modo solo BM25.
Causa: La extensión sqlite-vec no está instalada, no se encuentra en la ruta de la biblioteca, o es incompatible con la versión de SQLite.
Solución:
# Install via pip
pip install sqlite-vec
# Or compile from source
git clone https://github.com/asg017/sqlite-vec
cd sqlite-vec && make
Verifique que la extensión se carga correctamente:
import sqlite3
db = sqlite3.connect(":memory:")
db.enable_load_extension(True)
db.load_extension("vec0")
print("sqlite-vec loaded successfully")
Problemas de memoria en vaults grandes
Síntoma: Errores de memoria insuficiente durante la reindexación completa de un vault grande (más de 50.000 notas).
Causa: El batch size de embedding es demasiado grande, o todos los contenidos de archivos se cargan en memoria simultáneamente.
Solución: Reduzca el batch size y procese los archivos de forma incremental:
BATCH_SIZE = 32 # Reduce from 64
También asegúrese de que el indexador procese los archivos uno por uno (leyendo, dividiendo en chunks y generando embeddings 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
- Exporte Apple Notes mediante la opción “Exportar todo” (macOS) o use una herramienta de migración como
apple-notes-liberator - Convierta las exportaciones HTML a markdown usando
markdownifyopandoc - Mueva los archivos convertidos a la carpeta
00-inbox/de su vault - Revise y agregue frontmatter a cada nota
- Mueva las notas a las carpetas de dominio correspondientes
Desde Notion
- Exporte desde Notion: Configuración → Exportar → Markdown & CSV
- Descomprima la exportación en la carpeta
00-inbox/de su vault - Corrija los artefactos de markdown específicos de Notion:
- Notion usa
- [ ]para listas de verificación — esto es markdown estándar - Notion incluye tablas de propiedades como HTML — conviértalas a frontmatter YAML
- Notion inserta imágenes como rutas relativas — copie las imágenes a su carpeta de adjuntos
- Agregue frontmatter estándar (
type,domain,tags) - Reemplace los enlaces de página de Notion con wiki-links de Obsidian
Desde Google Docs
- Use Google Takeout para exportar todos los documentos
- Convierta los archivos
.docxa markdown:pandoc -f docx -t markdown input.docx -o output.md - Conversión por lotes:
for f in *.docx; do pandoc -f docx -t markdown "$f" -o "${f%.docx}.md"; done - Mueva al vault, agregue frontmatter y organice en carpetas
Desde markdown plano (sin Obsidian)
Si ya tiene un directorio de archivos markdown:
- Abra el directorio como un vault de Obsidian (Obsidian → Abrir Vault → Abrir carpeta)
- Agregue
.obsidian/a.gitignoresi el directorio tiene control de versiones - Cree plantillas de frontmatter y aplíquelas a los archivos existentes
- Comience a enlazar notas con
[[wiki-links]]a medida que lee y organiza - Ejecute el indexador inmediatamente — el sistema de recuperación funciona desde el primer día
Desde otro sistema de recuperación
Si está migrando desde un sistema diferente de embeddings o búsqueda:
- No intente migrar los vectores. Diferentes modelos producen espacios vectoriales incompatibles. Ejecute una reindexación completa con el nuevo modelo.
- Migre el contenido, no el índice. Los archivos del vault son la fuente de verdad. El índice es un artefacto derivado.
- Verifique después de la migración. Ejecute entre 10 y 20 consultas cuyas respuestas conozca y verifique que los resultados coincidan con sus expectativas.
Registro de cambios
| Fecha | Cambio |
|---|---|
| 2026-03-01 | Versión inicial |
Referencias
-
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 un método libre de parámetros para combinar listas ordenadas por relevancia. ↩↩↩
-
OpenAI Embeddings Pricing. text-embedding-3-small: $0,02 por millón de tokens. Costo estimado del vault por reindexación completa: ~$0,30. ↩
-
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. ↩
-
MTEB: Massive Text Embedding Benchmark. potion-base-8M obtiene un promedio de 50,03 frente a 56,09 de all-MiniLM-L6-v2 (89% de retención). ↩
-
SQLite FTS5 Extension. FTS5 proporciona búsqueda de texto completo con clasificación BM25 y pesos de columna configurables. ↩
-
sqlite-vec: A vector search SQLite extension. Proporciona tablas virtuales
vec0para búsqueda vectorial KNN dentro de SQLite. ↩ -
Robertson, S. y Zaragoza, H. The Probabilistic Relevance Framework: BM25 and Beyond. Foundations and Trends in Information Retrieval, 2009. ↩
-
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 preguntas y respuestas de dominio abierto. ↩
-
Reimers, N. y Gurevych, I. Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks. EMNLP, 2019. Trabajo fundacional sobre similitud semántica densa. ↩
-
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. ↩
-
SQLite Write-Ahead Logging. Modo WAL para lecturas concurrentes con un solo escritor. ↩
-
Gao, Y. et al. Retrieval-Augmented Generation for Large Language Models: A Survey. arXiv, 2024. Revisión de arquitecturas RAG y estrategias de fragmentación (chunking). ↩
-
Thakur, N. et al. BEIR: A Heterogeneous Benchmark for Zero-shot Evaluation of Information Retrieval Models. NeurIPS, 2021. ↩
-
Model2Vec: Distill a Small Fast Model from any Sentence Transformer. Minish Lab, 2024. ↩
-
Obsidian Documentation. Documentación oficial de Obsidian. ↩
-
Model Context Protocol Specification. El estándar MCP para conectar herramientas de IA con fuentes de datos. ↩
-
Datos de producción del autor. 16.894 archivos, 49.746 fragmentos (chunks), 83,56 MB de base de datos SQLite, 7.771 señales procesadas a lo largo de 14 meses. Latencia de consulta medida mediante
time.perf_counter(). ↩