Example vault location
#
Wichtigste Erkenntnisse
Context Engineering, nicht Notizen machen. Der Wert eines Obsidian-Vaults für KI liegt nicht in den Notizen selbst, sondern in der Abrufschicht, die sie abfragbar macht. Ein Vault mit 16.000 Dateien ohne Abrufmechanismus ist eine schreibgeschützte Datenbank. Ein Vault mit 200 Dateien mit hybrider Suche und MCP-Integration ist eine KI-Wissensbasis. Die Abrufinfrastruktur ist das Produkt. Die Notizen sind das Rohmaterial.
Hybrides Retrieval schlägt reine Stichwort- oder reine semantische Suche. BM25 erfasst exakte Bezeichner und Funktionsnamen. Vektorsuche erfasst Synonyme und konzeptuelle Übereinstimmungen über verschiedene Terminologien hinweg. Reciprocal Rank Fusion (RRF) vereint beide Ansätze, ohne dass eine Score-Kalibrierung erforderlich ist. Keine der beiden Methoden allein deckt beide Fehlermodi ab. Forschung zu MS MARCO Passage Ranking bestätigt dieses Muster: Hybrides Retrieval übertrifft konsistent jede einzelne Methode.1 Der Deep Dive zum hybriden Retriever behandelt die RRF-Mathematik, durchgerechnete Beispiele mit realen Zahlen, Fehlermodus-Analyse und einen interaktiven Fusion-Rechner.
MCP gibt KI-Tools direkten Vault-Zugriff. Model Context Protocol (MCP)-Server stellen den Retriever als Tool bereit, das Claude Code, Codex CLI, Cursor und andere KI-Tools direkt aufrufen können. Der Agent fragt den Vault ab, erhält bewertete Ergebnisse mit Quellenangabe und nutzt den Kontext, ohne ganze Dateien laden zu müssen. Der MCP-Server ist ein dünner Wrapper um die Abruf-Engine.
Local-first bedeutet keine API-Kosten und volle Privatsphäre. Der gesamte Stack läuft auf einer einzigen Maschine: SQLite für die Speicherung, Model2Vec für Embeddings, FTS5 für die Stichwortsuche, sqlite-vec für Vektor-KNN. Keine Cloud-Dienste, keine API-Aufrufe, keine Netzwerkabhängigkeit. Persönliche Notizen verlassen niemals den Rechner. Das vollständige Neu-Embedding von 49.746 Chunks würde bei OpenAI-API-Preisen etwa 0,30 $ kosten, doch die eigentlichen Kosten sind Latenz, Preisgabe der Privatsphäre und die Netzwerkabhängigkeit eines Systems, das offline funktionieren sollte.2
Inkrementelle Indexierung hält das System in unter 10 Sekunden aktuell. Der Vergleich der Dateiänderungszeiten erkennt Änderungen. Nur geänderte Dateien werden neu aufgeteilt und neu eingebettet. Eine vollständige Neuindexierung dauert auf Apple M-Serie-Hardware etwa vier Minuten. Inkrementelle Aktualisierungen bei den Bearbeitungen eines typischen Tages laufen in unter zehn Sekunden. Das System bleibt ohne manuelles Eingreifen aktuell.
Die Architektur skaliert von 200 bis über 20.000 Notizen. Das gleiche dreischichtige Design (Aufnahme, Abruf, Integration) funktioniert bei jeder Vault-Größe. Beginnen Sie mit einer reinen BM25-Suche über einen kleinen Vault. Fügen Sie Vektorsuche hinzu, wenn Stichwort-Kollisionen zum Problem werden. Fügen Sie RRF-Fusion hinzu, wenn Sie sowohl exakte als auch semantische Treffer benötigen. Jede Schicht ist unabhängig nutzbar und unabhängig entfernbar.
Wie Sie diesen Leitfaden nutzen
Dieser Leitfaden behandelt das vollständige System. Ihr Einstiegspunkt hängt davon ab, wo Sie stehen:
| Sie sind… | Beginnen Sie hier | Dann erkunden Sie |
|---|---|---|
| Neu bei Obsidian + KI | Warum Obsidian für KI-Infrastruktur, Schnellstart | Vault-Architektur, MCP-Server-Architektur |
| Bestehender Vault, KI-Zugriff gewünscht | MCP-Server-Architektur, Claude Code-Integration | Embedding-Modelle, Volltextsuche |
| Aufbau eines Abrufsystems | Die vollständige Abruf-Pipeline, Reciprocal Rank Fusion | Performance-Optimierung, Fehlerbehebung |
| Team- oder Unternehmenskontext | Entscheidungsrahmen, Knowledge-Graph-Muster | Entwickler-Workflow-Rezepte, Migrationsleitfaden |
Abschnitte mit der Kennzeichnung Contract enthalten Implementierungsdetails, Konfigurationsblöcke und Fehlermodi. Abschnitte mit der Kennzeichnung Narrative konzentrieren sich auf Konzepte, Architekturentscheidungen und die Begründung hinter Designentscheidungen. Abschnitte mit der Kennzeichnung Recipe bieten schrittweise Anleitungen.
Warum Obsidian für KI-Infrastruktur
Die These dieses Leitfadens: Obsidian-Vaults sind das beste Substrat für persönliche KI-Wissensbasen, weil sie lokal-zuerst, im Klartext, graphstrukturiert sind und Sie als Benutzer jede Schicht des Stacks kontrollieren.
Was Obsidian der KI bietet, was Alternativen nicht bieten
Klartext-Markdown-Dateien. Jede Notiz ist eine .md-Datei in Ihrem Dateisystem. Kein proprietäres Format, kein Datenbankexport, keine API erforderlich, um den Inhalt zu lesen. Jedes Tool, das Dateien lesen kann, kann Ihren Vault lesen. grep, ripgrep, Pythons pathlib, SQLite FTS5 — sie alle arbeiten direkt mit den Quelldateien. Wenn Sie ein Abrufsystem bauen, indexieren Sie Dateien, keine API-Antworten. Der Index ist immer konsistent mit der Quelle, weil die Quelle das Dateisystem ist.
Local-first-Architektur. Der Vault liegt auf Ihrem Rechner. Kein Server, keine Cloud-Sync-Abhängigkeit, keine API-Ratenbegrenzungen, keine Nutzungsbedingungen, die regeln, wie Sie Ihre eigenen Inhalte verarbeiten. Sie können Ihre Notizen einbetten, indexieren, aufteilen und durchsuchen, ohne einen externen Dienst zu benötigen. Das ist für KI-Infrastruktur relevant, weil die Abruf-Pipeline so schnell läuft, wie Ihre Festplatte es zulässt, nicht so schnell, wie ein API-Endpunkt antwortet. Es ist auch für den Datenschutz relevant: Persönliche Notizen mit Zugangsdaten, Gesundheitsdaten, Finanzinformationen und privaten Reflexionen verlassen niemals Ihren Rechner.
Graphstruktur durch Wiki-Links. Obsidians [[wiki-link]]-Syntax erzeugt einen gerichteten Graphen über Notizen hinweg. Eine Notiz über die OAuth-Implementierung verlinkt auf Notizen über Token-Rotation, Sitzungsverwaltung und API-Sicherheit. Die Graphstruktur kodiert von Menschen kuratierte Beziehungen zwischen Konzepten. Vektor-Embeddings erfassen semantische Ähnlichkeit, aber Wiki-Links erfassen beabsichtigte Verbindungen, die der Autor beim Nachdenken über das Thema hergestellt hat. Der Graph ist ein Signal, das Embeddings nicht replizieren können.
Plugin-Ökosystem. Obsidian verfügt über mehr als 1.800 Community-Plugins. Dataview fragt Ihren Vault wie eine Datenbank ab. Templater generiert Notizen aus Vorlagen mit JavaScript-Logik. Git-Integration synchronisiert Ihren Vault mit einem Repository. Linter erzwingt Formatierungskonsistenz. Diese Plugins fügen dem Vault Struktur hinzu, ohne das zugrunde liegende Klartext-Format zu ändern. Das Abrufsystem indexiert die Ausgabe dieser Plugins, nicht die Plugins selbst.
Über 5 Millionen Benutzer. Obsidian hat eine große aktive Community, die Vorlagen, Workflows, Plugins und Dokumentation erstellt. Wenn Sie auf ein Problem mit der Vault-Organisation oder Plugin-Konfiguration stoßen, hat wahrscheinlich bereits jemand eine Lösung dokumentiert. Die Community produziert auch Obsidian-nahe Tools: MCP-Server, Indexierungsskripte, Publishing-Pipelines und API-Wrapper.
Was ein Dateisystem allein nicht bietet
Ein Verzeichnis mit Markdown-Dateien hat den Klartext-Vorteil, aber es fehlen drei Dinge, die Obsidian hinzufügt:
-
Bidirektionale Links. Obsidian verfolgt Backlinks automatisch. Wenn Sie von Notiz A auf Notiz B verlinken, zeigt Notiz B an, dass Notiz A darauf verweist. Das Graph-Panel visualisiert Verbindungscluster. Dieses bidirektionale Bewusstsein sind Metadaten, die ein reines Dateisystem nicht bereitstellt.
-
Live-Vorschau mit Plugin-Rendering. Dataview-Abfragen, Mermaid-Diagramme und Callout-Blöcke werden in Echtzeit gerendert. Das Schreiberlebnis ist reichhaltiger als in einem Texteditor, während das Speicherformat reiner Klartext bleibt. Sie schreiben und organisieren in einer komfortablen Umgebung; das Abrufsystem indexiert das rohe Markdown.
-
Community-Infrastruktur. Plugin-Entdeckung, Theme-Marktplatz, Sync-Dienst (optional), Publish-Dienst (optional) und ein Dokumentations-Ökosystem. Sie können jede einzelne Funktion mit eigenständigen Tools nachbilden, aber Obsidian bündelt sie zu einem kohärenten Workflow.
Was Obsidian NICHT tut (und was Sie selbst bauen)
Obsidian enthält keine Abrufinfrastruktur. Es hat eine einfache Suche (Volltext, Dateiname, Tag), aber keine Embedding-Pipeline, keine Vektorsuche, kein Fusion-Ranking, keinen MCP-Server, keine Zugangsdatenfilterung, keine Chunking-Strategie und keine Integrationshaken für externe KI-Tools. Dieser Leitfaden behandelt die Infrastruktur, die Sie auf Obsidian aufbauen. Der Vault ist das Substrat. Die Abruf-Pipeline, der MCP-Server und die Integrationshaken sind die Infrastruktur.
Die hier beschriebene Architektur ist Markdown-first, nicht Obsidian-exklusiv. Wenn Sie Logseq, Foam, Dendron oder ein einfaches Verzeichnis mit Markdown-Dateien verwenden, funktioniert die Abruf-Pipeline identisch. Der Chunker liest .md-Dateien. Der Embedder verarbeitet Textzeichenketten. Der Indexer schreibt in SQLite. Keine dieser Komponenten hängt von Obsidian-spezifischen Funktionen ab. Obsidians Beitrag ist die Schreib- und Organisationsumgebung, die die Markdown-Dateien produziert, die der Retriever indexiert.
Schnellstart: Erster AI-verbundener Vault
Dieser Abschnitt verbindet einen Vault in fünf Minuten mit einem AI-Tool. Sie installieren Obsidian, erstellen einen Vault, installieren einen MCP-Server und führen Ihre erste Abfrage aus. Der Schnellstart verwendet einen Community-MCP-Server für sofortige Ergebnisse. Spätere Abschnitte behandeln den Aufbau einer maßgeschneiderten Retrieval-Pipeline für den Produktionseinsatz.
Voraussetzungen
- macOS, Linux oder Windows
- Node.js 18+ (für den MCP-Server)
- Claude Code, Codex CLI oder Cursor installiert
Schritt 1: Vault erstellen
Laden Sie Obsidian von obsidian.md herunter und erstellen Sie einen neuen Vault. Wählen Sie einen Speicherort, den Sie sich merken können — der MCP-Server benötigt den absoluten Pfad.
# Example vault location
~/Documents/knowledge-base/
Fügen Sie einige Notizen hinzu, damit der Retriever etwas zum Arbeiten hat. Schon 10–20 Notizen reichen aus, um Ergebnisse zu sehen. Jede Notiz sollte eine .md-Datei mit einem aussagekräftigen Titel und mindestens einem Absatz Inhalt sein.
Schritt 2: MCP-Server installieren
Der Community-Server obsidian-mcp bietet sofortigen Vault-Zugriff. Installieren Sie ihn:
npm install -g obsidian-mcp-server
Schritt 3: AI-Tool konfigurieren
Claude Code — fügen Sie Folgendes zu ~/.claude/settings.json hinzu:
{
"mcpServers": {
"obsidian": {
"command": "obsidian-mcp-server",
"args": ["--vault", "/absolute/path/to/your/vault"]
}
}
}
Codex CLI — fügen Sie Folgendes zu .codex/config.toml hinzu:
[mcp_servers.obsidian]
command = "obsidian-mcp-server"
args = ["--vault", "/absolute/path/to/your/vault"]
Cursor — fügen Sie Folgendes zu .cursor/mcp.json hinzu:
{
"mcpServers": {
"obsidian": {
"command": "obsidian-mcp-server",
"args": ["--vault", "/absolute/path/to/your/vault"]
}
}
}
Schritt 4: Erste Abfrage ausführen
Öffnen Sie Ihr AI-Tool und stellen Sie eine Frage, die Ihre Vault-Notizen beantworten können:
Search my Obsidian vault for notes about [topic you wrote about]
Das AI-Tool ruft den MCP-Server auf, der Ihren Vault durchsucht und passende Inhalte zurückgibt. Sie sollten Ergebnisse mit Dateipfaden und relevanten Auszügen sehen.
Was Sie gerade aufgebaut haben
Sie haben eine lokale Wissensbasis über ein Standardprotokoll mit einem AI-Tool verbunden. Der MCP-Server liest Ihre Vault-Dateien, führt eine einfache Suche durch und gibt Ergebnisse zurück. Dies ist die minimal funktionsfähige Version.
Was dieser Schnellstart NICHT bietet: - Hybrid Retrieval (BM25 + Vektorsuche + RRF-Fusion) - Embedding-basierte semantische Suche - Credential Filtering - Inkrementelle Indizierung - Hook-basierte automatische Kontexteinspeisung
Der Rest dieses Leitfadens behandelt den Aufbau jeder dieser Fähigkeiten. Der Schnellstart beweist das Konzept. Die vollständige Pipeline liefert Retrieval in Produktionsqualität.
Entscheidungsrahmen: Obsidian vs. Alternativen
Nicht jeder Anwendungsfall erfordert Obsidian. Dieser Abschnitt zeigt, wann Obsidian das richtige Fundament ist, wann es überdimensioniert ist und wann etwas anderes besser passt.
Entscheidungsbaum
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.
Vergleichsmatrix
| Kriterium | Obsidian | Notion | Apple Notes | Einfaches Dateisystem | CLAUDE.md |
|---|---|---|---|---|---|
| Lokal-first | Ja | Nein (Cloud) | Teilweise (iCloud) | Ja | Ja |
| Klartext | Ja (Markdown) | Nein (Blöcke) | Nein (proprietär) | Ja | Ja |
| Graphstruktur | Ja (wiki-links) | Teilweise (Erwähnungen) | Nein | Nein | Nein |
| AI-indizierbar | Direkter Dateizugriff | API erforderlich | Export erforderlich | Direkter Dateizugriff | Bereits im Kontext |
| Plugin-Ökosystem | 1.800+ Plugins | Integrationen | Keine | N/A | N/A |
| Offline-fähig | Vollständig | Nur gecachter Lesezugriff | Teilweise | Vollständig | Vollständig |
| Skaliert auf 10.000+ Notizen | Ja | Ja (mit API) | Verschlechtert sich | Ja | Nein (einzelne Datei) |
| Kosten | Kostenlos (Kern) | ab 10 $/Monat | Kostenlos | Kostenlos | Kostenlos |
Wann Obsidian überdimensioniert ist
- Einzelprojekt-Kontext. Wenn das AI-Tool nur Kontext über die aktuelle Codebasis benötigt, speichern Sie diesen in
CLAUDE.md,AGENTS.mdoder der Projektdokumentation. Diese Dateien gehören zum Repository und werden automatisch geladen. - Strukturierte Daten. Wenn der Inhalt aus Tabellen, Datensätzen oder Schemas besteht, verwenden Sie eine Datenbank. Obsidian-Notizen sind primär für Fließtext konzipiert. Dataview kann frontmatter-Felder abfragen, aber eine echte Datenbank bewältigt strukturierte Abfragen besser.
- Temporäre Recherche. Wenn die Notizen nach Projektende verworfen werden, ist ein Arbeitsverzeichnis mit Markdown-Dateien einfacher. Bauen Sie keine Retrieval-Infrastruktur für kurzlebige Inhalte auf.
Wann Obsidian die richtige Wahl ist
- Wissen, das sich über Monate oder Jahre ansammelt. Der Wert steigt mit wachsendem Korpus. Ein Vault mit 200 Notizen, der sechs Monate lang täglich abgefragt wird, bietet mehr Nutzen als ein Vault mit 5.000 Notizen, der einmal abgefragt wird.
- Mehrere Wissensgebiete in einem Korpus. Ein Vault mit Notizen zu Programmierung, Architektur, Sicherheit, Design und persönlichen Projekten profitiert von domänenübergreifendem Retrieval, das eine projektspezifische
CLAUDE.mdnicht bieten kann. - Datenschutzsensible Inhalte. Lokal-first bedeutet, dass die Retrieval-Pipeline niemals Inhalte an externe Dienste sendet. Der Vault enthält, was Sie hineinlegen — einschließlich Inhalte, die Sie nicht in einen Cloud-Dienst hochladen würden.
Mentales Modell: Drei Schichten
Das System besteht aus drei Schichten, die unabhängig voneinander arbeiten, sich aber in Kombination gegenseitig verstärken. Jede Schicht hat einen eigenen Zuständigkeitsbereich und einen eigenen Fehlermodus.
┌─────────────────────────────────────────────────────┐
│ INTEGRATION LAYER │
│ MCP servers, hooks, skills, context injection │
│ Concern: delivering context to AI tools │
│ Failure: wrong context, too much context, stale │
└──────────────────────┬──────────────────────────────┘
│ query + ranked results
┌──────────────────────┴──────────────────────────────┐
│ RETRIEVAL LAYER │
│ BM25, vector KNN, RRF fusion, token budget │
│ Concern: finding the right content for any query │
│ Failure: wrong ranking, missed results, slow queries │
└──────────────────────┬──────────────────────────────┘
│ chunked, embedded, indexed
┌──────────────────────┴──────────────────────────────┐
│ INTAKE LAYER │
│ Note creation, signal triage, vault organization │
│ Concern: what enters the vault and how it's stored │
│ Failure: noise, duplicates, missing structure │
└─────────────────────────────────────────────────────┘
Intake bestimmt, was in den Vault gelangt. Ohne Kuratierung sammelt der Vault Rauschen an: Screenshots von Tweets, kopierte Artikel ohne Anmerkungen, halbfertige Gedanken ohne Kontext. Die Intake-Schicht ist verantwortlich für die Qualitätskontrolle am Eintrittspunkt. Eine Bewertungs-Pipeline, eine Tagging-Konvention oder ein manueller Review-Prozess — jeder Mechanismus, der sicherstellt, dass der Vault Inhalte enthält, die es wert sind, abgerufen zu werden.
Retrieval macht den Vault abfragbar. Dies ist die Engine: Notizen in Sucheinheiten aufteilen (Chunking), Chunks in den Vektorraum einbetten (Embeddings), für Schlüsselwort- und semantische Suche indizieren, Ergebnisse mit RRF fusionieren. Die Retrieval-Schicht verwandelt ein Dateiverzeichnis in eine abfragbare Wissensbasis. Ohne diese Schicht ist der Vault durch manuelles Durchsuchen und einfache Suche navigierbar, aber nicht programmatisch für AI-Tools zugänglich.
Integration verbindet die Retrieval-Schicht mit AI-Tools. Ein MCP-Server stellt Retrieval als aufrufbares Tool bereit. Hooks speisen Kontext automatisch ein. Skills erfassen neues Wissen zurück in den Vault. Die Integrationsschicht ist die Schnittstelle zwischen der Wissensbasis und den AI-Agenten, die sie nutzen.
Die Schichten sind bewusst entkoppelt. Die Intake-Bewertungs-Pipeline weiß nichts über Embeddings. Der Retriever weiß nichts über Signal-Routing-Regeln. Der MCP-Server weiß nichts darüber, wie Notizen erstellt wurden. Diese Entkopplung bedeutet, dass Sie jede Schicht unabhängig verbessern können. Ersetzen Sie das Embedding-Modell, ohne die Intake-Pipeline zu ändern. Fügen Sie eine neue MCP-Fähigkeit hinzu, ohne den Retriever zu modifizieren. Ändern Sie die Signal-Bewertungsheuristiken, ohne den Index anzufassen.
Vault-Architektur für KI-Nutzung
Ein Vault, das für KI-gestütztes Retrieval optimiert ist, folgt anderen Konventionen als ein Vault, das für persönliches Durchsuchen optimiert ist. Dieser Abschnitt behandelt Ordnerstruktur, Notiz-Schema, Frontmatter-Konventionen und die spezifischen Muster, die die Retrieval-Qualität verbessern.
Ordnerstruktur
Verwenden Sie nummerierte Präfixe für Ordner der obersten Ebene, um eine vorhersehbare organisatorische Hierarchie zu schaffen. Die Nummern implizieren keine Priorität — sie gruppieren verwandte Bereiche und machen die Struktur übersichtlich.
vault/
├── 00-inbox/ # Unsorted captures, pending triage
├── 01-projects/ # Active project notes
├── 02-areas/ # Ongoing areas of responsibility
├── 03-resources/ # Reference material by topic
│ ├── programming/
│ ├── security/
│ ├── ai-engineering/
│ ├── design/
│ └── devops/
├── 04-archive/ # Completed projects, old references
├── 05-signals/ # Scored signal intake
│ ├── ai-tooling/
│ ├── security/
│ ├── systems/
│ └── ...12 domain folders
├── 06-daily/ # Daily notes (if used)
├── 07-templates/ # Note templates (excluded from index)
├── 08-attachments/ # Images, PDFs (excluded from index)
├── .obsidian/ # Obsidian config (excluded from index)
└── .indexignore # Paths to exclude from retrieval index
Ordner, die indexiert werden sollten: Alles, was Markdown-Prosa enthält — Projekte, Bereiche, Ressourcen, Signals, tägliche Notizen.
Ordner, die von der Indexierung ausgeschlossen werden sollten: Templates (sie enthalten Platzhaltervariablen, keine Inhalte), Anhänge (Binärdateien), Obsidian-Konfiguration und alle Ordner mit sensiblen Inhalten, die Sie nicht im Retrieval-Index haben möchten.
Die .indexignore-Datei
Erstellen Sie eine .indexignore-Datei im Stammverzeichnis des Vaults, um Pfade explizit vom Retrieval-Index auszuschließen. Die Syntax entspricht .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/
Der Indexer liest diese Datei vor dem Scannen und überspringt übereinstimmende Pfade vollständig. Dateien in ausgeschlossenen Pfaden werden niemals in Chunks aufgeteilt, niemals als Embeddings verarbeitet und erscheinen niemals in Suchergebnissen.
Notiz-Schema
Jede Notiz sollte YAML-Frontmatter haben. Der Retriever verwendet Frontmatter-Felder für Filterung und Kontextanreicherung:
---
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
---
Erforderliche Felder für das Retrieval:
title— Wird in der Suchergebnisanzeige und im Überschriftenkontext für BM25 verwendettype— Ermöglicht typgefilterte Abfragen („nur MOCs anzeigen” oder „nur Signals”)tags— Werden im FTS5-Überschriftenkontext mit 0,3-Gewichtung indexiert und liefern Keyword-Treffer, selbst wenn der Textkörper andere Terminologie verwendet
Optionale, aber wertvolle Felder:
domain— Ermöglicht domänenbezogene Abfragen („nur in Sicherheitsnotizen suchen”)source— Quellenangabe für erfasste Inhalte; der Retriever kann Quell-URLs in die Ergebnisse einbeziehenstatus— Ermöglicht das Ausschließen archivierter oder als Entwurf markierter Notizen aus der aktiven Suche
Chunking-Konventionen
Der Retriever teilt Inhalte an H2-Überschriftengrenzen (##) in Chunks auf. Das bedeutet, dass Ihre Notizstruktur die Retrieval-Granularität direkt beeinflusst:
Gut für das Retrieval:
## Token Rotation Strategy
The rotation interval depends on the threat model...
## Implementation with refresh_token
The OAuth 2.0 refresh token flow requires...
## Error Handling: Expired Tokens
When a token expires mid-request...
Drei H2-Abschnitte erzeugen drei unabhängig durchsuchbare Chunks. Jeder Chunk enthält genügend Kontext, damit das Embedding seine Bedeutung erfassen kann. Eine Abfrage zu „Behandlung abgelaufener Token” trifft gezielt den dritten Chunk.
Schlecht für das Retrieval:
# OAuth Notes
Token rotation depends on threat model. The OAuth 2.0 refresh
token flow requires storing the refresh token securely. When a
token expires mid-request, the client should retry after refresh.
The rotation interval is typically 15-30 minutes for access tokens
and 7-30 days for refresh tokens...
Ein langer Abschnitt ohne H2-Überschriften erzeugt einen einzigen großen Chunk. Das Embedding bildet den Durchschnitt über alle Themen im Abschnitt. Eine Abfrage zu einem beliebigen Teilthema trifft die gesamte Notiz gleichermaßen.
Faustregel: Wenn ein Abschnitt mehr als ein Konzept behandelt, teilen Sie ihn in H2-Unterabschnitte auf. Der Chunker erledigt den Rest.
Was nicht in Notizen gehört
Inhalte, die die Retrieval-Qualität verschlechtern:
- Unkommentierte Komplettkopien ganzer Artikel. Der Retriever indexiert die Keywords des Originalartikels und verwässert damit Ihr Vault mit Inhalten, die Sie nicht selbst geschrieben haben. Fügen Sie stattdessen eine Zusammenfassung hinzu, extrahieren Sie die wichtigsten Punkte oder verlinken Sie auf die Quell-URL.
- Screenshots ohne Textbeschreibung. Der Retriever indexiert Markdown-Text. Ein Bild ohne Alt-Text oder umgebende Beschreibung ist sowohl für BM25 als auch für die Vektorsuche unsichtbar.
- Zugangsdaten. API-Schlüssel, Token, Passwörter, Verbindungszeichenfolgen. Selbst mit Credential-Filterung ist der sicherste Ansatz, niemals Geheimnisse in Notizen einzufügen. Verweisen Sie stattdessen namentlich darauf („das Cloudflare API-Token in
~/.env”). - Automatisch generierte Inhalte ohne Kuration. Wenn ein Tool eine Notiz generiert (Besprechungstranskript, Readwise-Markierungen, RSS-Import), überprüfen und annotieren Sie diese, bevor sie in das permanente Vault aufgenommen wird. Unkuratierte Auto-Importe erhöhen das Volumen, ohne einen abrufbaren Mehrwert zu bieten.
Plugin-Ökosystem für KI-Workflows
Obsidian-Plugins, die die Vault-Qualität für KI-gestütztes Retrieval verbessern, fallen in drei Kategorien: strukturell (Konsistenz sicherstellen), abfragend (Metadaten zugänglich machen) und synchronisierend (das Vault aktuell halten).
Wesentliche Plugins
Dataview. Fragt Ihr Vault wie eine Datenbank ab und nutzt dabei Frontmatter-Felder. Erstellen Sie dynamische Indizes: „alle Notizen mit dem Tag security, die in den letzten 30 Tagen aktualisiert wurden” oder „alle Projektnotizen mit Status active.” Dataview hilft dem Retrieval nicht direkt, aber es hilft Ihnen, Lücken in der Abdeckung Ihres Vaults zu identifizieren und Notizen zu finden, die aktualisiert werden müssen.
TABLE type, domain, updated
FROM "03-resources"
WHERE status = "active"
SORT updated DESC
LIMIT 20
Templater. Erstellt Notizen aus Templates mit dynamischen Feldern. Stellen Sie sicher, dass jede neue Notiz mit korrektem Frontmatter beginnt, indem Sie ein Template verwenden, das die Felder created, type und domain vorausfüllt. Konsistentes Frontmatter verbessert die Retrieval-Filterung.
<%* /* 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
## Referenzen
Linter. Erzwingt Formatierungsregeln im gesamten Vault. Eine konsistente Überschriftenhierarchie (H1 für Titel, H2 für Abschnitte, H3 für Unterabschnitte) stellt sicher, dass der Chunker vorhersagbare Ergebnisse liefert. Linter-Regeln, die für das Retrieval relevant sind:
- Überschriftenabstufung: Sequenzielle Überschriftenebenen erzwingen (kein Sprung von H1 zu H3)
- YAML-Titel: Mit dem Dateinamen abgleichen
- Nachgestellte Leerzeichen: Entfernen (vermeidet FTS5-Tokenisierungsartefakte)
- Aufeinanderfolgende Leerzeilen: Auf 1 begrenzen (sauberere Chunks)
Git-Integration. Versionskontrolle für Ihren Vault. Verfolgen Sie Änderungen über die Zeit, synchronisieren Sie zwischen Geräten und stellen Sie versehentlich gelöschte Inhalte wieder her. Git liefert außerdem mtime-Daten, die der Indexer für die inkrementelle Änderungserkennung verwendet.
Plugins, die die Indexierung unterstützen
Smart Connections. Ein Obsidian-Plugin, das KI-gestützte semantische Suche direkt in Obsidian bereitstellt. Es erstellt einen eigenen Embedding-Index. Während das Retrieval-System in diesem Leitfaden extern zu Obsidian läuft (als Python-Pipeline), ist Smart Connections nützlich, um semantische Zusammenhänge beim Schreiben zu erkunden. Beide Systeme indexieren denselben Inhalt, dienen aber unterschiedlichen Zwecken: Smart Connections für die Entdeckung im Editor, der externe Retriever für die Integration mit KI-Tools.
Metadata Menu. Bietet strukturierte Frontmatter-Bearbeitung mit Autovervollständigung für Feldwerte. Reduziert Tippfehler in den Feldern type, domain und tags. Konsistente Metadaten verbessern die Genauigkeit der Retrieval-Filterung.
Plugins, die die Indexierung beeinträchtigen
Excalidraw. Speichert Zeichnungen als JSON, eingebettet in Markdown-Dateien. Das JSON ist syntaktisch gültiges Markdown, erzeugt aber beim Chunking und Embedding unbrauchbare Ergebnisse. Schließen Sie Excalidraw-Dateien über .indexignore oder per Dateierweiterungsfilter vom Index aus.
Kanban. Speichert den Board-Status als speziell formatiertes Markdown. Das Format ist für die Kanban-Darstellung konzipiert, nicht für Prosa-Retrieval. Der Chunker erzeugt Fragmente von Kartentiteln und Metadaten, die sich schlecht einbetten lassen. Schließen Sie Kanban-Boards vom Index aus.
Calendar. Erstellt tägliche Notizen mit minimalem Inhalt (oft nur eine Datumsüberschrift). Leere oder nahezu leere Notizen erzeugen Chunks von geringer Qualität. Wenn Sie tägliche Notizen verwenden, schreiben Sie substanzielle Inhalte hinein oder schließen Sie den Ordner für tägliche Notizen vom Index aus.
Plugin-Konfiguration, die relevant ist
Dateiwiederherstellung → Aktiviert. Schützt vor versehentlichem Löschen von Notizen. Nicht direkt mit dem Retrieval verbunden, aber entscheidend für eine Wissensdatenbank, auf die Sie sich verlassen.
Strikte Zeilenumbrüche → Deaktiviert. Markdown-standardkonforme Zeilenumbrüche (doppelter Zeilenumbruch für Absätze) erzeugen sauberere Chunks als Obsidians strikter Modus (einfacher Zeilenumbruch für <br>).
Standardspeicherort für neue Dateien → Festgelegter Ordner. Leiten Sie neue Dateien nach 00-inbox/ weiter, damit unkategorisierte Notizen die Domänenordner nicht verunreinigen. Die Inbox ist ein Zwischenspeicher; Dateien werden nach der Sichtung in Domänenordner verschoben.
Wiki-Link-Format → Kürzester Pfad wenn möglich. Kürzere Link-Ziele sind für den Retriever einfacher aufzulösen, wenn er die Linkstruktur indexiert.
Embedding-Modelle: Auswahl und Konfiguration
Das Embedding-Modell wandelt Textabschnitte in numerische Vektoren für die semantische Suche um. Die Modellwahl bestimmt die Abrufqualität, Indexgröße, Embedding-Geschwindigkeit und Laufzeitabhängigkeiten. Dieser Abschnitt erklärt, warum Model2Vec’s potion-base-8M die Standardwahl ist und wann Sie Alternativen in Betracht ziehen sollten.
Warum Model2Vec potion-base-8M
Modell: minishlab/potion-base-8M
Parameter: 7,6 Millionen
Dimensionen: 256
Größe: ~30 MB
Abhängigkeiten: model2vec (nur numpy, kein PyTorch)
Inferenz: Nur CPU, statische Wort-Embeddings (keine Attention-Schichten)
Model2Vec destilliert das Wissen eines Sentence Transformers in statische Token-Embeddings. Anstatt Attention-Schichten über die Eingabe laufen zu lassen (wie es BERT, MiniLM und andere Transformer-Modelle tun), erzeugt Model2Vec Vektoren durch gewichtete Mittelung vorberechneter Token-Embeddings.3 Die praktische Konsequenz: Die Embedding-Geschwindigkeit ist 50- bis 500-mal schneller als bei Transformer-basierten Modellen, da keine sequentielle Berechnung stattfindet.
Im MTEB-Benchmark-Suite erreicht potion-base-8M 89 % der Leistung von all-MiniLM-L6-v2 (50,03 vs. 56,09 im Durchschnitt).4 Die Qualitätslücke von 11 % ist der Kompromiss für die Vorteile bei Geschwindigkeit und Einfachheit. Bei kurzen Markdown-Abschnitten (durchschnittlich 200–400 Wörter in einem typischen Vault) ist der Qualitätsunterschied weniger ausgeprägt als bei längeren Dokumenten, da beide Modelle bei kurzem, fokussiertem Text zu ähnlichen Repräsentationen konvergieren.
Konfiguration
# 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]
Lazy Loading. Das Modell wird beim ersten Gebrauch geladen, nicht beim Importieren. Das Importieren des Embedder-Moduls kostet nichts, wenn der Retriever im BM25-only-Fallback-Modus arbeitet (z. B. wenn die Embedding-venv nicht installiert ist).
Isolierte virtuelle Umgebung. Das Modell läuft in einer dedizierten venv (z. B. ~/.claude/venvs/memory/), um Abhängigkeitskonflikte mit dem Rest der Toolchain zu vermeiden. Die Funktion _activate_venv() fügt die site-packages der venv zur Laufzeit zu sys.path hinzu.
# Create isolated venv
python3 -m venv ~/.claude/venvs/memory
~/.claude/venvs/memory/bin/pip install model2vec
Stapelverarbeitung. Der Embedder verarbeitet Texte in Stapeln von 64, um den Overhead von Model2Vec zu amortisieren. Der Indexer übergibt Abschnitte an embed_batch(), anstatt jeden Abschnitt einzeln einzubetten.
Wann Sie Alternativen wählen sollten
| Modell | Dim | Größe | Geschwindigkeit | Qualität (MTEB) | Geeignet für |
|---|---|---|---|---|---|
| potion-base-8M | 256 | 30 MB | 500x | 50,03 | Standard: lokal, schnell, kein GPU |
| potion-base-32M | 256 | 120 MB | 400x | 52,46 | Höhere Qualität, weiterhin statisch |
| potion-retrieval-32M | 256 | 120 MB | 400x | 36,35 (Retrieval) | Retrieval-optimiert, statisch |
| all-MiniLM-L6-v2 | 384 | 80 MB | 1x | 56,09 | Höhere Qualität, weiterhin lokal |
| nomic-embed-text-v1.5 | 768 | 270 MB | 0,5x | 62,28 | Beste lokale Qualität |
| text-embedding-3-small | 1536 | API | N/A | 62,30 | API-basiert, höchste Qualität |
Wählen Sie potion-base-32M, wenn Sie eine bessere Qualität als potion-base-8M wünschen, ohne die Familie der statischen Embeddings zu verlassen. Im Januar 2025 veröffentlicht, verwendet es ein größeres Vokabular, destilliert aus baai/bge-base-en-v1.5, und erreicht einen MTEB-Durchschnitt von 52,46 (5 % Verbesserung gegenüber potion-base-8M), bei gleichbleibender 256-dimensionaler Ausgabe und reiner numpy-Abhängigkeit.18 Die 4-mal größere Modelldatei erhöht den Speicherverbrauch, aber die Embedding-Geschwindigkeit bleibt um Größenordnungen schneller als bei Transformer-Modellen.
Wählen Sie potion-retrieval-32M, wenn Ihr Hauptanwendungsfall das Retrieval ist (was bei der Vault-Suche der Fall ist). Diese Variante ist ein Fine-Tuning von potion-base-32M speziell für Retrieval-Aufgaben und erreicht 36,35 in den MTEB-Retrieval-Benchmarks gegenüber 33,52 für das Basismodell.18 Der MTEB-Gesamtdurchschnitt sinkt auf 49,73, da das Fine-Tuning allgemeine Leistungsfähigkeit gegen Retrieval-spezifische Verbesserungen eintauscht.
Wählen Sie all-MiniLM-L6-v2, wenn die Abrufqualität wichtiger ist als Geschwindigkeit und Sie PyTorch installiert haben. Die 384-dimensionalen Vektoren vergrößern die SQLite-Datenbank um ~50 % im Vergleich zu 256-dim-Vektoren. Die Embedding-Geschwindigkeit sinkt von unter 1 Minute auf ~10 Minuten für eine vollständige Neuindexierung von 15.000 Dateien auf M-Series-Hardware.
Wählen Sie nomic-embed-text-v1.5, wenn Sie die bestmögliche lokale Abrufqualität benötigen und eine langsamere Indexierung akzeptieren. Die 768-dimensionalen Vektoren verdreifachen die Datenbankgröße annähernd. Erfordert PyTorch und eine moderne CPU oder GPU.
Wählen Sie text-embedding-3-small, wenn Netzwerklatenz und Datenschutz akzeptable Kompromisse darstellen. Die API liefert die qualitativ hochwertigsten Embeddings, führt aber eine Cloud-Abhängigkeit ein, verursacht Kosten pro Token (0,02 $/Million Tokens) und sendet Ihre Inhalte an die Server von OpenAI.
Bleiben Sie bei potion-base-8M in allen anderen Fällen. Der Geschwindigkeitsvorteil ist entscheidend für iteratives Indexieren (Neuindexierung während der Entwicklung), die reine numpy-Abhängigkeit vermeidet die Komplexität einer PyTorch-Installation, und die 256-dimensionalen Vektoren halten die Datenbank kompakt.
Quantisierung und Dimensionsreduktion
Model2Vec v0.5.0+ unterstützt das Laden von Modellen mit reduzierter Präzision und reduzierten Dimensionen.18 Dies ist nützlich für den Einsatz auf Hardware mit begrenzten Ressourcen oder zur Reduzierung der Datenbankgröße, ohne das Modell zu wechseln:
from model2vec import StaticModel
# Load with int8 quantization (25% of original size)
model = StaticModel.from_pretrained("minishlab/potion-base-8M", quantize=True)
# Load with reduced dimensions (e.g., 128 instead of 256)
model = StaticModel.from_pretrained("minishlab/potion-base-8M", dimensionality=128)
Quantisierte Modelle behalten nahezu identische Abrufqualität bei einem Bruchteil des Speicherbedarfs. Die Dimensionsreduktion folgt dem Matryoshka-Prinzip der Trunkierung — die ersten N Dimensionen tragen die meisten Informationen. Eine Reduktion von 256 auf 128 Dimensionen halbiert den Vektorspeicher bei minimalem Qualitätsverlust für das Retrieval kurzer Texte.
Modell-Hash-Tracking
Der Indexer speichert einen Hash, der aus dem Modellnamen und der Vokabulargröße abgeleitet wird. Wenn Sie das Embedding-Modell wechseln, erkennt der Indexer die Abweichung beim nächsten inkrementellen Durchlauf und löst automatisch eine vollständige Neuindexierung aus.
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]
Dies verhindert das Vermischen von Vektoren verschiedener Modelle in derselben Datenbank, was zu unsinnigen Cosine-Similarity-Werten führen würde.
Fehlermodi
Fehler beim Modell-Download. Der erste Durchlauf lädt das Modell von Hugging Face herunter. Wenn der Download fehlschlägt (Netzwerkproblem, Unternehmens-Firewall), fällt der Retriever auf den BM25-only-Modus zurück. Das Modell wird nach dem ersten Download lokal zwischengespeichert.
Dimensionskonflikt. Wenn Sie Modelle wechseln, ohne die Datenbank zu löschen, haben die gespeicherten Vektoren eine andere Dimension als neue Embeddings. Der Indexer erkennt dies über den Modell-Hash und löst eine vollständige Neuindexierung aus. Wenn die Hash-Prüfung fehlschlägt (benutzerdefiniertes Modell ohne korrekten Hash), gibt sqlite-vec bei KNN-Abfragen mit nicht übereinstimmenden Dimensionen einen Fehler aus.
Speicherdruck bei großen Vaults. Das Einbetten von 50.000+ Abschnitten in einem einzelnen Stapel kann erheblichen Speicher verbrauchen. Der Indexer verarbeitet in Stapeln von 64, um die Spitzenspeichernutzung zu begrenzen. Falls der Speicher weiterhin ein Problem darstellt, reduzieren Sie die Stapelgröße.
Volltextsuche mit FTS5
Die FTS5-Erweiterung von SQLite bietet Volltextsuche mit BM25-Ranking. FTS5 ist die Schlüsselwort-Suchkomponente der hybriden Retrieval-Pipeline. Dieser Abschnitt behandelt die FTS5-Konfiguration, die Stärken von BM25 und seine spezifischen Fehlermodi.
Virtuelle FTS5-Tabelle
CREATE VIRTUAL TABLE chunks_fts USING fts5(
chunk_text,
section,
heading_context,
content=chunks,
content_rowid=id
);
Content-Sync-Modus. Der Parameter content=chunks weist FTS5 an, direkt auf die Tabelle chunks zu verweisen, anstatt eine doppelte Kopie des Textes zu speichern. Dies halbiert den Speicherbedarf, bedeutet aber, dass FTS5 manuell synchronisiert werden muss, wenn Chunks eingefügt, aktualisiert oder gelöscht werden.
Spalten. Drei Spalten werden indiziert:
- chunk_text — Der Hauptinhalt jedes Chunks (BM25-Gewichtung: 1.0)
- section — Der H2-Überschriftentext (BM25-Gewichtung: 0.5)
- heading_context — Notiztitel, Tags und Metadaten (BM25-Gewichtung: 0.3)
BM25-Ranking
BM25 bewertet Dokumente anhand von Termfrequenz, inverser Dokumentfrequenz und Dokumentlängen-Normalisierung. Die Hilfsfunktion bm25() in FTS5 akzeptiert Gewichtungen pro Spalte:
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;
Die Spaltengewichtungen (1.0, 0.5, 0.3) bedeuten:
- Ein Schlüsselwort-Treffer in chunk_text trägt am meisten zur Bewertung bei
- Ein Treffer in section (Überschrift) trägt halb so viel bei
- Ein Treffer in heading_context (Titel, Tags) trägt 30 % bei
Diese Gewichtungen sind anpassbar. Wenn Ihr Vault aussagekräftige Überschriften hat, die die Inhaltsqualität zuverlässig vorhersagen, erhöhen Sie die Gewichtung für section. Wenn Ihre Tags umfassend und genau sind, erhöhen Sie die Gewichtung für heading_context.
Wann BM25 überlegen ist
BM25 glänzt bei Abfragen, die exakte Bezeichner enthalten:
- Funktionsnamen:
_rrf_fuse,embed_batch,get_stale_files - CLI-Flags:
--incremental,--vault,--model - Konfigurationsschlüssel:
bm25_weight,max_tokens,batch_size - Fehlermeldungen:
SQLITE_LOCKED,ConnectionRefusedError - Spezifische Fachbegriffe:
PostToolUse,PreToolUse,AGENTS.md
Für diese Abfragen findet BM25 den exakten Treffer sofort. Die Vektorsuche würde semantisch verwandte Inhalte zurückgeben, könnte aber den exakten Treffer niedriger bewerten als eine konzeptuelle Diskussion.
Wann BM25 versagt
BM25 versagt bei Abfragen, die eine andere Terminologie verwenden als der gespeicherte Inhalt:
- Abfrage: „how to handle authentication failures” → Der Vault enthält Notizen über „login error recovery” und „session expiration handling.” BM25 findet keine Treffer, weil sich die Schlüsselwörter unterscheiden.
- Abfrage: „what is the best way to manage state” → Der Vault enthält Notizen über „Redux store patterns” und „context providers.” BM25 findet nichts, weil „State Management” durch spezifische Technologienamen ausgedrückt wird.
BM25 versagt auch bei Schlüsselwort-Kollisionen in großem Maßstab. In einem Vault mit 15.000 Dateien liefert eine Suche nach „configuration” Hunderte von Treffern, weil nahezu jede Projektnotiz „Configuration” erwähnt. Die Ergebnisse sind technisch korrekt, aber praktisch nutzlos — das Ranking kann nicht bestimmen, welche „Configuration”-Notiz für die aktuelle Abfrage relevant ist.
FTS5-Tokenizer
FTS5 verwendet standardmäßig den unicode61-Tokenizer, der ASCII- und Unicode-Text verarbeitet. Für Vaults mit umfangreichem CJK-Inhalt (Chinesisch, Japanisch, Koreanisch) sollten Sie den trigram-Tokenizer in Betracht ziehen:
-- For CJK-heavy vaults
CREATE VIRTUAL TABLE chunks_fts USING fts5(
chunk_text, section, heading_context,
content=chunks, content_rowid=id,
tokenize='trigram'
);
Der Standard-Tokenizer unicode61 trennt an Wortgrenzen, was bei Sprachen ohne Leerzeichen zwischen Wörtern schlecht funktioniert. Der trigram-Tokenizer teilt alle drei Zeichen auf und ermöglicht so eine Teilstring-Suche — allerdings auf Kosten der Indexgröße (etwa 3× größer).
Wartung
FTS5 erfordert eine explizite Synchronisierung, wenn sich die zugrunde liegende Tabelle chunks ändert:
# After inserting chunks
cursor.execute("""
INSERT INTO chunks_fts(chunks_fts)
VALUES('rebuild')
""")
Der Befehl rebuild rekonstruiert den FTS5-Index aus der Inhaltstabelle. Führen Sie ihn nach Massen-Einfügungen (vollständige Neuindizierung) aus, aber nicht nach einzelnen inkrementellen Aktualisierungen — verwenden Sie dafür INSERT INTO chunks_fts(rowid, chunk_text, section, heading_context), um einzelne Zeilen zu synchronisieren.
Vektorsuche mit sqlite-vec
Die Erweiterung sqlite-vec bringt KNN-Vektorsuche (K-Nearest Neighbors) in SQLite. Dieser Abschnitt behandelt die sqlite-vec-Konfiguration, die Embedding-Pipeline von der Notiz zum durchsuchbaren Vektor und die spezifischen Abfragemuster.
Virtuelle sqlite-vec-Tabelle
CREATE VIRTUAL TABLE chunk_vecs USING vec0(
id INTEGER PRIMARY KEY,
embedding float[256]
);
Das Modul vec0 speichert 256-dimensionale Float-Vektoren als gepackte Binärdaten. Die Spalte id bildet eine 1:1-Zuordnung zur Tabelle chunks und ermöglicht Joins zwischen Vektorergebnissen und Chunk-Metadaten.
Embedding-Pipeline
Die Pipeline führt von der Notiz zum durchsuchbaren Vektor:
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
Vektor-Serialisierung
Das struct-Modul von Python serialisiert Float-Vektoren für die sqlite-vec-Speicherung:
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))
KNN-Abfrage
Eine Vektorsuchabfrage erzeugt ein Embedding der Eingabeabfrage und findet dann die K nächsten Chunks nach Kosinusdistanz:
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
Der MATCH-Operator in sqlite-vec führt eine approximative Nächste-Nachbarn-Suche durch. Der Parameter k steuert, wie viele Ergebnisse zurückgegeben werden. Die Spalte distance enthält die Kosinusdistanz (0 = identisch, 2 = entgegengesetzt).
Wann die Vektorsuche überlegen ist
Die Vektorsuche glänzt bei Abfragen, bei denen das Konzept wichtiger ist als die spezifischen Wörter:
- Abfrage: „how to handle authentication failures” → Findet Notizen über „login error recovery” (gleicher semantischer Raum, unterschiedliche Schlüsselwörter)
- Abfrage: „what patterns exist for caching” → Findet Notizen über „memoization,” „Redis TTL strategies” und „HTTP cache headers” (verwandte Konzepte, vielfältige Terminologie)
- Abfrage: „approaches to testing asynchronous code” → Findet Notizen über „pytest-asyncio fixtures,” „mock event loops” und „async test patterns” (gleiches Konzept, ausgedrückt durch Implementierungsdetails)
Wann die Vektorsuche versagt
Die Vektorsuche hat Schwierigkeiten mit exakten Bezeichnern:
- Abfrage:
_rrf_fuse→ Liefert Notizen über „fusion algorithms” und „rank merging”, kann aber die tatsächliche Funktionsdefinition niedriger bewerten als konzeptuelle Diskussionen - Abfrage:
PostToolUse→ Liefert Notizen über „tool lifecycle hooks” und „post-execution handlers” anstatt den spezifischen Hook-Namen
Die Vektorsuche hat auch Schwierigkeiten mit strukturierten Daten. JSON-Konfigurationsdateien, YAML-Blöcke und Code-Snippets erzeugen Embeddings, die eher strukturelle Muster als semantische Bedeutung erfassen. Eine JSON-Datei mit "review": true wird anders eingebettet als eine Prosadiskussion über Code-Reviews.
Graceful Degradation
Wenn sqlite-vec nicht geladen werden kann (fehlende Erweiterung, inkompatible Plattform, beschädigte Bibliothek), fällt der Retriever auf eine reine BM25-Suche zurück:
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
Der Retriever prüft vec_available, bevor er Vektorabfragen versucht. Wenn deaktiviert, verwenden alle Suchen ausschließlich BM25, und der RRF-Fusionsschritt wird übersprungen.
Reciprocal Rank Fusion (RRF)
RRF führt zwei Ranglisten zusammen, ohne dass eine Kalibrierung der Bewertungen erforderlich ist. Dieser Abschnitt behandelt den Algorithmus, eine nachvollzogene Beispielabfrage, die Abstimmung des k-Parameters und die Gründe, warum RRF gegenüber Alternativen bevorzugt wird. Für einen interaktiven Rechner mit bearbeitbaren Rängen, Szenario-Voreinstellungen und einem visuellen Architektur-Explorer siehe den Hybrid-Retriever-Deep-Dive.
Der Algorithmus
RRF weist jedem Dokument eine Bewertung zu, die ausschließlich auf seiner Rangposition in jeder Liste basiert:
score(d) = Σ (weight_i / (k + rank_i))
Dabei gilt:
- k ist eine Glättungskonstante (60, nach Cormack et al.1)
- rank_i ist der 1-basierte Rang des Dokuments in der Ergebnisliste i
- weight_i ist ein optionaler Multiplikator pro Liste (Standard 1.0)
Dokumente, die in mehreren Listen gut abschneiden, erhalten höhere fusionierte Bewertungen. Dokumente, die nur in einer Liste erscheinen, erhalten eine Bewertung aus dieser einzelnen Quelle.
Warum RRF gegenüber Alternativen
Gewichtete lineare Kombination erfordert die Kalibrierung von BM25-Bewertungen gegenüber Kosinus-Distanzen. BM25-Bewertungen sind unbegrenzt und skalieren mit der Korpusgröße. Kosinus-Distanzen sind begrenzt auf [0, 2]. Die Kombination erfordert eine Normalisierung, und die Normalisierungsparameter sind datensatzabhängig. RRF verwendet nur Rangpositionen, die unabhängig von der Bewertungsmethode immer ganzzahlig und bei 1 beginnend sind.
Gelernte Fusionsmodelle erfordern gelabelte Trainingsdaten — Abfrage-Dokument-Relevanzpaare. Für eine persönliche Wissensbasis existieren diese Trainingsdaten nicht. Sie müssten Hunderte von Abfrage-Dokument-Paaren manuell bewerten, um ein brauchbares Modell zu trainieren. RRF funktioniert ohne jegliche Trainingsdaten.
Condorcet-Wahlmethoden (Borda-Zählung, Schulze-Methode) sind theoretisch elegant, aber deutlich komplexer in Implementierung und Abstimmung. Das ursprüngliche RRF-Paper zeigte, dass RRF Condorcet-Methoden auf TREC-Evaluierungsdaten übertrifft.1
Fusion in der Praxis
Abfrage: „how does the review aggregator handle disagreements”
BM25 platziert review-aggregator.py auf Position 3 (exakte Schlüsselwort-Treffer für „review”, „aggregator”, „disagreements”), setzt aber zwei Konfigurationsdateien höher (sie enthalten „review” prominenter). Die Vektorsuche platziert denselben Chunk auf Position 1 (semantischer Treffer für Konfliktlösung). Nach der RRF-Fusion:
| Chunk | BM25 | Vec | Fused Score |
|---|---|---|---|
| review-aggregator.py „Disagreement Resolution” | #3 | #1 | 0.0323 |
| code-review-patterns.md „Multi-Reviewer” | #4 | #2 | 0.0317 |
| deliberation-config.json „Review Weights” | #1 | — | 0.0164 |
Chunks, die in beiden Listen gut abschneiden, steigen an die Spitze. Chunks, die nur in einer Liste erscheinen, erhalten eine Einzelquellen-Bewertung und fallen hinter doppelt gerankte Ergebnisse zurück. Die eigentliche Logik zur Konfliktlösung gewinnt, weil beide Methoden sie gefunden haben — BM25 über Schlüsselwörter, die Vektorsuche über Semantik.
Für die vollständige Schritt-für-Schritt-Nachverfolgung mit RRF-Mathematik pro Rang probieren Sie verschiedene k-Werte im interaktiven RRF-Rechner aus.
Implementierung
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
Abstimmung von k
Die k-Konstante steuert, wie viel Gewicht den höchstplatzierten Ergebnissen im Vergleich zu niedriger platzierten Ergebnissen gegeben wird:
- Niedriges k (z. B. 10): Höchstplatzierte Ergebnisse dominieren. Rang 1 erhält 1/11 = 0,091, Rang 10 erhält 1/20 = 0,050 (1,8-facher Unterschied). Geeignet, wenn Sie darauf vertrauen, dass die einzelnen Ranker das Top-Ergebnis richtig einordnen.
- Standard-k (60): Ausgewogen. Rang 1 erhält 1/61 = 0,0164, Rang 10 erhält 1/70 = 0,0143 (1,15-facher Unterschied). Rangdifferenzen werden komprimiert, wodurch das Erscheinen in mehreren Listen stärker gewichtet wird.
- Hohes k (z. B. 200): Das Erscheinen in beiden Listen ist deutlich wichtiger als die Rangposition. Rang 1 erhält 1/201, Rang 10 erhält 1/210 — nahezu identisch. Verwenden Sie dies, wenn die einzelnen Ranker verrauschte Rankings erzeugen, aber die listenübergreifende Übereinstimmung zuverlässig ist.
Beginnen Sie mit k=60. Das ursprüngliche RRF-Paper fand diesen Wert robust über verschiedene TREC-Datensätze hinweg. Optimieren Sie erst, nachdem Sie Fehlerfälle anhand Ihrer eigenen Abfrageverteilung gemessen haben.
Gleichstandsauflösung
Wenn zwei Chunks identische RRF-Bewertungen haben (selten, aber möglich bei gleichem Rang in einer Liste und keinem Erscheinen in der anderen), lösen Sie den Gleichstand wie folgt auf:
- Bevorzugen Sie Chunks, die in beiden Listen erscheinen, gegenüber Chunks, die nur in einer Liste erscheinen
- Unter Chunks in beiden Listen bevorzugen Sie denjenigen mit dem niedrigeren kombinierten Rang
- Unter Chunks in nur einer Liste bevorzugen Sie denjenigen mit dem niedrigeren Rang in dieser Liste
Die vollständige Retrieval-Pipeline
Dieser Abschnitt verfolgt eine Abfrage vom Eingang bis zum Ausgang durch die gesamte Pipeline: BM25-Suche, Vektorsuche, RRF-Fusion, Token-Budget-Kürzung und Kontextassemblierung.
Durchgängiger Ablauf
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
Gesamtlatenz: ~23ms für eine Datenbank mit 49.746 Chunks auf Apple M3 Pro Hardware.
Die Such-API
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
Token-Budget-Kürzung
Der Parameter max_tokens verhindert, dass der Retriever mehr Kontext zurückgibt, als das AI-Tool verwenden kann. Die Schätzung verwendet 4 Zeichen pro Token (eine vernünftige Annäherung für englische Prosa). Ergebnisse werden gierig gekürzt: Ergebnisse werden in Rangfolge hinzugefügt, bis das Budget erschöpft ist.
Dies ist eine konservative Strategie. Ein ausgefeilterer Ansatz würde Qualitätsbewertungen pro Ergebnis berücksichtigen und kürzere, qualitativ hochwertigere Ergebnisse gegenüber längeren, qualitativ schlechteren bevorzugen. Der gierige Ansatz ist einfacher und funktioniert in der Praxis gut, da die RRF-Rangfolge die Ergebnisse bereits nach Relevanz ordnet.
Datenbankschema (vollständig)
-- 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
);
Pfad zur graceful Degradation
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
Der Retriever prüft die verfügbaren Fähigkeiten bei der Initialisierung und passt seine Abfragestrategie entsprechend an. Eine fehlende Komponente verschlechtert die Qualität, verursacht aber keine Fehler. Der einzige harte Fehlerfall ist eine fehlende Datenbankdatei.
Produktionsstatistiken
Gemessen an einem Vault mit 16.894 Dateien, 49.746 Chunks, 83 MB SQLite-Datenbank, Apple M3 Pro:
| Metrik | Wert |
|---|---|
| Dateien gesamt | 16.894 |
| Chunks gesamt | 49.746 |
| Datenbankgröße | 83 MB |
| BM25-Abfragelatenz (p50) | 12ms |
| Vektor-Abfragelatenz (p50) | 8ms |
| RRF-Fusionslatenz | 3ms |
| End-to-End-Suchlatenz (p50) | 23ms |
| Vollständige Neuindizierung | ~4 Minuten |
| Inkrementelle Neuindizierung | <10 Sekunden |
| Embedding-Modell | potion-base-8M (256-dim) |
| BM25-Kandidatenpool | 30 |
| Vektor-Kandidatenpool | 30 |
| Standard-Ergebnislimit | 10 |
| Standard-Token-Budget | 4.000 Token |
Content-Hashing und Änderungserkennung
Der Indexer muss wissen, welche Dateien sich seit dem letzten Indexierungslauf geändert haben. Dieser Abschnitt behandelt den Mechanismus zur Änderungserkennung und die Hashing-Strategie.
Vergleich der Dateiänderungszeit
Der Indexer speichert mtime_ns (Dateiänderungszeit in Nanosekunden) für jeden Chunk in der Tabelle chunks. Bei einem inkrementellen Lauf führt der Indexer folgende Schritte durch:
- Durchsucht den Vault nach allen
.md-Dateien in erlaubten Ordnern - Liest die
mtime_nsfür jede Datei aus dem Dateisystem - Vergleicht mit der gespeicherten
mtime_nsin der Datenbank - Identifiziert drei Kategorien:
- Neue Dateien: Pfad existiert im Dateisystem, aber nicht in der Datenbank
- Geänderte Dateien: Pfad existiert in beiden, aber
mtime_nsunterscheidet sich - Gelöschte Dateien: Pfad existiert in der Datenbank, aber nicht im Dateisystem
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)
Warum mtime statt Content-Hash
Content-Hashing (SHA-256 des Dateiinhalts) wäre zuverlässiger als der mtime-Vergleich — es würde Fälle erkennen, in denen eine Datei berührt wurde, ohne sich zu ändern (z. B. wenn git checkout die ursprüngliche mtime wiederherstellt). Allerdings erfordert Hashing das Lesen jeder Datei bei jedem inkrementellen Lauf. Bei 16.894 Dateien dauert das Lesen der Dateiinhalte 2–3 Sekunden. Das Lesen der mtimes aus dem Dateisystem dauert <100ms.
Der Kompromiss: Der mtime-Vergleich löst gelegentlich eine unnötige Neuindizierung unveränderter Dateien aus (Falsch-Positive), verpasst aber niemals tatsächliche Änderungen. Falsch-Positive kosten ein paar zusätzliche Embedding-Aufrufe pro Lauf. Der Geschwindigkeitsunterschied (100ms gegenüber 3 Sekunden) macht mtime zur pragmatischen Wahl für ein System, das bei jeder AI-Interaktion ausgeführt wird.
Umgang mit Löschungen
Wenn eine Datei aus dem Vault gelöscht wird, entfernt der Indexer alle zugehörigen Chunks aus der Datenbank:
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],
)
FTS5 Content-Sync-Tabellen erfordern eine explizite Löschung über INSERT INTO chunks_fts(chunks_fts, rowid, ...) VALUES('delete', ?, ...) für jede entfernte Zeile. Der Indexer übernimmt dies als Teil des Dateientfernungsprozesses.
Inkrementelle vs. vollständige Neuindizierung
Der Indexer unterstützt zwei Modi: inkrementell (schnell, für den täglichen Gebrauch) und vollständig (langsam, gelegentlich). Dieser Abschnitt behandelt, wann Sie welchen Modus verwenden sollten, die Idempotenz-Garantien und die Wiederherstellung bei Beschädigung.
Inkrementelle Neuindizierung
Wann verwenden: Tägliche Indizierung nach dem Bearbeiten von Notizen. Der Standardmodus.
Funktionsweise: 1. Vault nach Dateiänderungen durchsuchen (mtime-Vergleich) 2. Chunks gelöschter Dateien entfernen 3. Geänderte Dateien neu aufteilen und neu einbetten 4. Neue Chunks für neue Dateien einfügen 5. FTS5-Index synchronisieren
Typische Dauer: <10 Sekunden für die Änderungen eines Tages bei einem Vault mit 16.000 Dateien.
python index_vault.py --incremental
Vollständige Neuindizierung
Wann verwenden: - Nach dem Wechsel des Embedding-Modells (Modell-Hash-Abweichung erkannt) - Nach einer Schema-Migration (neue Spalten, geänderte Indizes) - Nach einer Datenbankbeschädigung (Integritätsprüfung schlägt fehl) - Wenn die inkrementelle Indizierung unerwartete Ergebnisse liefert
Funktionsweise: 1. Alle vorhandenen Daten löschen (Chunks, Vektoren, FTS5-Einträge) 2. Gesamten Vault durchsuchen 3. Alle Dateien in Chunks aufteilen 4. Alle Chunks einbetten (Embeddings erzeugen) 5. FTS5-Index von Grund auf neu erstellen
Typische Dauer: ~4 Minuten für 16.894 Dateien auf Apple M3 Pro.
python index_vault.py --full
Idempotenz
Beide Modi sind idempotent: Die zweimalige Ausführung desselben Befehls liefert dasselbe Ergebnis. Der Indexer löscht vorhandene Chunks einer Datei, bevor er neue einfügt. Eine erneute inkrementelle Indizierung auf einer bereits aktuellen Datenbank erzeugt daher null Änderungen. Eine erneute vollständige Indizierung erzeugt eine identische Datenbank.
Wiederherstellung bei Beschädigung
Falls die SQLite-Datenbank beschädigt wird (Stromausfall während des Schreibvorgangs, Festplattenfehler, abgebrochener Prozess mitten in einer Transaktion):
# Check integrity
sqlite3 vectors.db "PRAGMA integrity_check;"
# If corruption detected, full reindex rebuilds from source files
python index_vault.py --full
Die Quelle der Wahrheit sind immer die Vault-Dateien, nicht die Datenbank. Die Datenbank ist ein abgeleitetes Artefakt, das jederzeit neu erstellt werden kann. Dies ist eine entscheidende Designeigenschaft: Sie müssen die Datenbank niemals sichern.
Das --incremental-Flag
Wenn der Indexer mit --incremental ausgeführt wird:
- Modell-Hash-Prüfung. Gespeicherten Modell-Hash mit dem aktuellen Modell vergleichen. Bei Abweichung automatisch in den vollständigen Neuindizierungsmodus wechseln und den Benutzer warnen.
- Datei-Scan. Erlaubte Ordner durchlaufen, Dateipfade und mtimes erfassen.
- Änderungserkennung. Mit den gespeicherten Daten vergleichen.
- Stapelverarbeitung. Geänderte Dateien in Stapeln von 64 neu aufteilen und neu einbetten.
- Fortschrittsmeldung. Anzahl der verarbeiteten Dateien und verstrichene Zeit ausgeben.
- Kontrolliertes Herunterfahren. SIGINT abfangen und die aktuelle Datei vor dem Stoppen zu Ende verarbeiten.
Zugangsdaten-Filterung und Datengrenzen
Persönliche Notizen enthalten Geheimnisse: API-Schlüssel, Bearer-Token, Datenbank-Verbindungszeichenfolgen, private Schlüssel, die während Debugging-Sitzungen eingefügt wurden. Der Zugangsdaten-Filter verhindert, dass diese in den Retrieval-Index gelangen.
Das Problem
Eine Notiz über das Debugging einer OAuth-Integration könnte Folgendes enthalten:
The token was: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
I used this curl command:
curl -H "Authorization: Bearer sk-ant-api03-abc123..."
Ohne Filterung würden sowohl das JWT als auch der API-Schlüssel in Chunks aufgeteilt, eingebettet und in der Datenbank gespeichert. Eine Suche nach „Authentifizierung” würde den Chunk mit den echten Geheimnissen zurückgeben. Schlimmer noch: Wenn der Retriever Ergebnisse über MCP an ein KI-Tool weitergibt, erscheinen die Geheimnisse im Kontextfenster der KI und möglicherweise in den Protokollen des Tools.
Musterbasierte Filterung
Der Zugangsdaten-Filter wird auf jedem Chunk vor der Speicherung ausgeführt und erkennt 25 herstellerspezifische Muster sowie generische Muster:
Herstellerspezifische Muster:
| Muster | Beispiel | Regex |
|---|---|---|
| OpenAI API-Schlüssel | sk-... |
sk-[a-zA-Z0-9_-]{20,} |
| Anthropic API-Schlüssel | sk-ant-api03-... |
sk-ant-api\d{2}-[a-zA-Z0-9_-]{20,} |
| GitHub PAT | ghp_... |
gh[ps]_[a-zA-Z0-9]{36,} |
| AWS Access Key | AKIA... |
AKIA[0-9A-Z]{16} |
| Stripe-Schlüssel | sk_live_... |
[sr]k_(live\|test)_[a-zA-Z0-9]{24,} |
| Cloudflare-Token | ... |
Verschiedene Muster |
Generische Muster:
| Muster | Erkennung |
|---|---|
| JWT-Token | eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+ |
| Bearer-Token | Bearer\s+[a-zA-Z0-9_\-\.]+ |
| Private Schlüssel | -----BEGIN (RSA\|EC\|OPENSSH) PRIVATE KEY----- |
| Base64 mit hoher Entropie | Zeichenketten mit >4,5 Bits/Zeichen Entropie, 40+ Zeichen |
| Passwortzuweisungen | password\s*[:=]\s*["'][^"']+["'] |
Filter-Implementierung
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
Zentrale Designentscheidungen:
-
Filterung vor dem Embedding. Der bereinigte Text wird eingebettet. Die Vektordarstellung kodiert niemals Zugangsdaten-Muster. Eine Abfrage nach „API-Schlüssel” gibt Notizen zurück, die die Verwaltung von API-Schlüsseln behandeln, nicht Notizen, die tatsächliche Schlüssel enthalten.
-
Ersetzen statt Entfernen. Der
[REDACTED:pattern-name]-Token bewahrt den semantischen Kontext des umgebenden Textes. Das Embedding erfasst, dass „hier etwas Zugangsdaten-ähnliches stand”, ohne die Zugangsdaten selbst zu kodieren. -
Muster protokollieren, nicht Werte. Der Filter protokolliert, welche Muster erkannt wurden (z. B. „2 Zugangsdaten aus oauth-debug.md bereinigt [jwt, bearer-token]”), jedoch niemals den Wert der Zugangsdaten selbst.
Pfadbasierter Ausschluss
Die .indexignore-Datei bietet einen grobkörnigen Ausschluss nach Pfad. Der Zugangsdaten-Filter bietet feinkörnige Bereinigung innerhalb indizierter Dateien. Beides ist notwendig:
.indexignorefür ganze Ordner, von denen Sie wissen, dass sie sensible Inhalte enthalten (Gesundheitsnotizen, Finanzdaten, Karrieredokumente)- Zugangsdaten-Filter für Geheimnisse, die versehentlich in ansonsten indizierbaren Inhalten eingebettet sind
Datenklassifizierung
Für Vaults mit vielfältigen Inhalten empfiehlt sich eine Klassifizierung der Notizen nach Vertraulichkeitsstufe:
| Stufe | Beispiele | Indizieren? | Filtern? |
|---|---|---|---|
| Öffentlich | Blog-Entwürfe, technische Notizen | Ja | Ja |
| Intern | Projektpläne, Architekturentscheidungen | Ja | Ja |
| Sensibel | Gehaltsdaten, Gesundheitsakten | Nein (.indexignore) | N/A |
| Eingeschränkt | Zugangsdaten, private Schlüssel | Nein (.indexignore) | N/A |
MCP-Server-Architektur
Das Model Context Protocol (MCP) stellt den Retriever als Tool bereit, das KI-Agenten aufrufen können. Dieser Abschnitt behandelt das Server-Design, den Funktionsumfang und die Berechtigungsgrenzen.
Protokollwahl: STDIO vs HTTP
MCP unterstützt zwei Transportmodi:
STDIO — Das KI-Tool startet den MCP-Server als Kindprozess und kommuniziert über stdin/stdout. Dies ist der Standardmodus für lokale Tools. Claude Code, Codex CLI und Cursor unterstützen alle STDIO-MCP-Server.
{
"mcpServers": {
"obsidian": {
"command": "python",
"args": ["/path/to/obsidian_mcp.py"],
"env": {
"VAULT_PATH": "/path/to/vault",
"DB_PATH": "/path/to/vectors.db"
}
}
}
}
HTTP — Der MCP-Server läuft als eigenständiger HTTP-Dienst. Nützlich für Fernzugriff, Multi-Client-Setups oder Team-Konfigurationen, bei denen der Vault auf einem gemeinsamen Server liegt.
{
"mcpServers": {
"obsidian": {
"url": "http://localhost:3333/mcp"
}
}
}
Empfehlung: Verwenden Sie STDIO für persönliche Vaults. Es ist einfacher, sicherer (keine Netzwerkexposition) und der Server-Lebenszyklus wird vom KI-Tool verwaltet. Verwenden Sie HTTP nur, wenn mehrere Tools oder mehrere Rechner gleichzeitigen Zugriff auf denselben Vault benötigen.
Entwicklung der MCP-Spezifikation. Die MCP-Spezifikation vom Juni 2025 führte OAuth-basierte Autorisierung, strukturierte Tool-Ausgaben (typisierte Rückgabe-Schemata) und Elicitation (serverinitiierte Benutzerabfragen) ein.16 Das nächste Spezifikations-Release (voraussichtlich Juni 2026) schlägt asynchrone Operationen für langwierige Aufgaben, zustandslose Anfrageverarbeitung als Standard-Transportmodus und Server-Erkennung über
.well-known-URLs vor.16 Für persönliche Vault-Server bleibt STDIO der einfachste Weg. Die Spezifikationsänderungen betreffen hauptsächlich Enterprise-HTTP-Deployments mit Multi-Tenant-Routing und Lastverteilung. Beobachten Sie die MCP-Roadmap für Aktualisierungen, die Ihre Transportwahl beeinflussen.
Capability-Design
Der MCP-Server sollte einen minimalen Satz an Tools bereitstellen:
search — Das primäre Tool. Führt Hybrid Retrieval durch und gibt sortierte Ergebnisse zurück.
{
"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 — Liest den vollständigen Inhalt einer bestimmten Notiz anhand des Pfads. Nützlich, wenn der Agent den vollständigen Kontext eines Suchergebnisses sehen möchte.
{
"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 — Listet Notizen auf, die einem Filter entsprechen (nach Ordner, Tag, Typ oder Datumsbereich). Nützlich zur Exploration, wenn der Agent keine spezifische Suchanfrage hat.
{
"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 — Ein Komfort-Tool, das eine Suche durchführt und die Ergebnisse als Kontextblock formatiert, der zur Einspeisung in eine Konversation geeignet ist.
{
"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 }
}
}
Berechtigungsgrenzen
Der MCP-Server sollte strikte Grenzen durchsetzen:
-
Nur Lesezugriff. Der Server liest den Vault und die Index-Datenbank. Er erstellt, ändert oder löscht keine Notizen. Schreiboperationen (Erfassung neuer Notizen) werden durch separate Hooks oder Skills gehandhabt, nicht durch den MCP-Server.
-
Vault-beschränkt. Der Server liest nur Dateien innerhalb des konfigurierten Vault-Pfads. Path-Traversal-Versuche (
../../etc/passwd) müssen abgelehnt werden. -
Gefilterte Ausgabe von Anmeldeinformationen. Auch wenn die Datenbank bereits vorgefilterte Inhalte enthält, wenden Sie Credential-Filterung auf die Ausgabe als Defense-in-Depth-Maßnahme an.
-
Token-limitierte Antworten. Erzwingen Sie
max_tokensbei allen Tool-Antworten, um zu verhindern, dass das KI-Tool übermäßig große Kontextblöcke empfängt.
Fehlerbehandlung
MCP-Tools sollten strukturierte Fehlermeldungen zurückgeben, die dem KI-Tool bei der Wiederherstellung helfen:
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,
}
Claude Code-Integration
Claude Code ist der primäre Konsument des Obsidian-Retrieval-Systems. Dieser Abschnitt behandelt die MCP-Konfiguration, Hook-Integration und das obsidian_bridge.py-Muster.
MCP-Konfiguration
Fügen Sie den Obsidian-MCP-Server zu ~/.claude/settings.json hinzu:
{
"mcpServers": {
"obsidian": {
"command": "python",
"args": ["/path/to/obsidian_mcp.py"],
"env": {
"VAULT_PATH": "/absolute/path/to/vault",
"DB_PATH": "/absolute/path/to/vectors.db"
}
}
}
}
Nach dem Hinzufügen der Konfiguration starten Sie Claude Code neu. Der MCP-Server wird als Kindprozess gestartet. Überprüfen Sie, ob er läuft:
> What tools do you have from the obsidian MCP server?
Claude Code sollte die verfügbaren Tools auflisten (obsidian_search, obsidian_read_note, etc.).
Hook-Integration
Hooks erweitern das Verhalten von Claude Code an definierten Lebenszyklus-Punkten. Zwei Hooks sind für die Obsidian-Integration relevant:
PreToolUse-Hook — Fragt den Vault ab, bevor der Agent einen Tool-Aufruf verarbeitet. Speist relevanten Kontext automatisch ein.
#!/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
PostToolUse-Hook — Erfasst bedeutsame Tool-Ausgaben und speichert sie zurück in den Vault für zukünftiges Retrieval.
#!/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
Das obsidian_bridge.py-Muster
Ein Bridge-Modul stellt eine Python API bereit, die Hooks und Skills aufrufen können:
# 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)
Der /capture-Skill
Ein Claude Code-Skill zum Erfassen von Erkenntnissen zurück in den Vault:
/capture "OAuth token rotation requires both access and refresh token invalidation"
--domain security
--tags oauth,tokens
Der Skill erstellt eine neue Notiz in 00-inbox/ mit korrektem Frontmatter und löst eine inkrementelle Neuindizierung aus, sodass die neue Notiz sofort durchsuchbar ist.
Kontextfenster-Verwaltung
Die Integration sollte das Kontextfenster von Claude Code berücksichtigen:
- Begrenzen Sie eingespeisten Kontext auf 1.500–2.000 Token pro Abfrage. Mehr als das konkurriert mit dem Arbeitsgedächtnis des Agenten.
- Quellenangabe einbeziehen. Geben Sie immer den Dateipfad und die Abschnittsüberschrift an, damit der Agent die Quelle referenzieren kann.
- Chunk-Text kürzen. Lange Chunks sollten mit
...gekürzt statt ganz weggelassen werden. Die ersten 300–500 Zeichen enthalten in der Regel die wesentlichen Informationen. - Nicht bei jedem Tool-Aufruf einspeisen. Der PreToolUse-Hook sollte Kontext selektiv basierend auf dem aufgerufenen Tool einspeisen. Leseoperationen benötigen keinen Vault-Kontext. Schreib- und Bearbeitungsoperationen profitieren davon.
Codex CLI-Integration
Codex CLI verbindet sich mit MCP-Servern über config.toml. Das Integrationsmuster unterscheidet sich von Claude Code in der Konfigurationssyntax und der Anweisungsbereitstellung.
MCP-Konfiguration
Fügen Sie Folgendes zu .codex/config.toml oder ~/.codex/config.toml hinzu:
[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"
AGENTS.md-Muster
Codex CLI liest AGENTS.md für Anweisungen auf Projektebene. Fügen Sie Hinweise zur Vault-Suche ein:
## Verfügbare Tools
### Obsidian Vault (MCP: obsidian)
Verwenden Sie das Tool `obsidian_search`, um relevanten Kontext aus der Wissensbasis zu finden.
Durchsuchen Sie den Vault, wenn Sie Folgendes benötigen:
- Hintergrundinformationen zu einem Konzept oder Muster
- Frühere Entscheidungen oder deren Begründung
- Referenzmaterial für die Implementierung
Beispielabfragen:
- "authentication patterns in FastAPI"
- "how does the review aggregator work"
- "sqlite-vec configuration"
Unterschiede zu Claude Code
| Funktion | Claude Code | Codex CLI |
|---|---|---|
| MCP-Konfiguration | settings.json |
config.toml |
| Hooks | ~/.claude/hooks/ |
Nicht unterstützt |
| Skills | ~/.claude/skills/ |
Nicht unterstützt |
| Anweisungsdatei | CLAUDE.md |
AGENTS.md |
| Genehmigungsmodi | --dangerously-skip-permissions |
suggest / auto-edit / full-auto |
Wesentlicher Unterschied: Codex CLI unterstützt keine Hooks. Das Muster zur automatischen Kontextinjektion (PreToolUse Hook) ist nicht verfügbar. Fügen Sie stattdessen explizite Anweisungen in AGENTS.md ein, die den Agenten auffordern, den Vault vor Arbeitsbeginn zu durchsuchen.
Cursor und andere Tools
Cursor und andere KI-Tools, die MCP unterstützen, können sich mit demselben Obsidian MCP-Server verbinden. Dieser Abschnitt behandelt die Konfiguration für gängige Tools.
Cursor
Fügen Sie Folgendes zu .cursor/mcp.json in Ihrem Projektstammverzeichnis hinzu:
{
"mcpServers": {
"obsidian": {
"command": "python",
"args": ["/path/to/obsidian_mcp.py"],
"env": {
"VAULT_PATH": "/absolute/path/to/vault",
"DB_PATH": "/absolute/path/to/vectors.db"
}
}
}
}
Die .cursorrules-Datei von Cursor kann Anweisungen zur Nutzung des Vaults enthalten:
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.
Kompatibilitätsmatrix
| Tool | MCP-Unterstützung | Transport | Konfigurationsort |
|---|---|---|---|
| Claude Code | Vollständig | STDIO | ~/.claude/settings.json |
| Codex CLI | Vollständig | STDIO | .codex/config.toml |
| Cursor | Vollständig | STDIO | .cursor/mcp.json |
| Windsurf | Vollständig | STDIO | .windsurf/mcp.json |
| Continue.dev | Teilweise | HTTP | ~/.continue/config.json |
| Zed | In Entwicklung | STDIO | Einstellungs-UI |
Fallback für Tools ohne MCP
Für Tools, die MCP nicht unterstützen, kann der Retriever als CLI eingebunden werden:
# 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
Die CLI gibt strukturierten Text aus, der manuell in die Eingabe eines beliebigen KI-Tools eingefügt werden kann. Das ist weniger elegant als eine MCP-Integration, funktioniert aber universell.
Prompt Caching aus strukturierten Notizen
Strukturierte Notizen im Vault können als wiederverwendbare Kontextblöcke dienen, die den Token-Verbrauch über KI-Interaktionen hinweg reduzieren. Dieser Abschnitt behandelt das Design von Cache-Schlüsseln und die Verwaltung des Token-Budgets.
Das Muster
Anstatt bei jeder Interaktion nach Kontext zu suchen, erstellen Sie vorab Kontextblöcke aus gut strukturierten Vault-Notizen und cachen Sie diese:
# 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
},
}
Cache-Invalidierung
Die Cache-Invalidierung basiert auf zwei Signalen:
- TTL-Ablauf. Jeder Kontextblock hat eine Gültigkeitsdauer (Time-to-Live). Wenn die TTL abläuft, wird der Block durch erneutes Abfragen des Vaults neu erstellt.
- Vault-Änderungserkennung. Wenn der Indexer Änderungen an Dateien erkennt, die zu einem gecachten Kontextblock beigetragen haben, wird der Block sofort invalidiert.
Verwaltung des Token-Budgets
Eine Sitzung beginnt mit einem Gesamtbudget für den Kontext. Gecachte Blöcke verbrauchen einen Teil dieses Budgets:
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)
Die gecachten Blöcke werden zu Sitzungsbeginn geladen. Dynamische Suchergebnisse füllen das verbleibende Budget auf Basis einzelner Abfragen. Dieser hybride Ansatz gibt dem Agenten eine Grundlage an häufig benötigtem Kontext und bewahrt gleichzeitig Budget für spezifische Abfragen.
Vorher/Nachher: Token-Verbrauch
Ohne Caching: Jede relevante Abfrage löst eine Vault-Suche aus, die 1.500–2.000 Token an Kontext zurückgibt. Bei 10 Abfragen in einer Sitzung verbraucht der Agent 15.000–20.000 Token an Vault-Kontext.
Mit Caching: Drei vorab erstellte Kontextblöcke verbrauchen insgesamt 4.500 Token. Zusätzliche Suchen fügen 1.500–2.000 Token pro einzigartiger Abfrage hinzu. Bei 10 Abfragen, von denen 6 durch gecachte Blöcke abgedeckt werden, verbraucht der Agent 4.500 + (4 × 1.500) = 10.500 Token — etwa die Hälfte des Verbrauchs ohne Caching.
PostToolUse Hooks zur Kontextkomprimierung
Tool-Ausgaben können umfangreich sein: Stack Traces, Dateilisten, Testergebnisse. Ein PostToolUse Hook kann diese Ausgaben komprimieren, bevor sie Platz im Kontextfenster belegen.
Das Problem
Ein Bash-Tool-Aufruf, der Tests ausführt, könnte Folgendes zurückgeben:
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
Die vollständige Ausgabe umfasst 5.000 Token, aber das Wesentliche steckt in 2 Zeilen: 200 bestanden, 1 fehlgeschlagen.
Hook-Implementierung
#!/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
Verhinderung rekursiver Auslösung
Ein Komprimierungs-Hook, der Ausgaben erzeugt, könnte sich selbst auslösen, wenn er nicht geschützt ist:
# Guard against recursive invocation
if [ -n "$COMPRESS_HOOK_ACTIVE" ]; then
exit 0
fi
export COMPRESS_HOOK_ACTIVE=1
Komprimierungsheuristiken
| Ausgabetyp | Erkennung | Komprimierungsstrategie |
|---|---|---|
| Testergebnisse | Schlüsselwörter PASSED / FAILED |
Bestanden/Fehlgeschlagen zählen, nur Fehler anzeigen |
| Dateilisten | ls oder find im Befehl |
Auf die ersten 20 Einträge kürzen + Anzahl |
| Stack Traces | Schlüsselwort Traceback |
Ersten und letzten Frame + Fehlermeldung beibehalten |
| Git-Status | modified: / new file: |
Anzahl nach Status zusammenfassen |
| Build-Ausgabe | warning: / error: |
Info-Zeilen entfernen, Warnungen/Fehler beibehalten |
Pipeline für Signalaufnahme und Triage
Die Aufnahmeschicht bestimmt, was in den Vault gelangt. Ohne Kuratierung sammelt der Vault Rauschen an. Dieser Abschnitt behandelt die Bewertungs-Pipeline, die Signale in Domänenordner weiterleitet.
Quellen
Signale stammen aus mehreren Kanälen:
- RSS-Feeds: Technische Blogs, Sicherheitshinweise, Release Notes
- Lesezeichen: Browser-Lesezeichen, gespeichert über Obsidian Web Clipper oder Bookmarklet
- Newsletter: Wichtige Auszüge aus E-Mail-Newslettern
- Manuelle Erfassung: Notizen, die beim Lesen, in Gesprächen oder bei der Recherche erstellt wurden
- Tool-Ausgaben: Bedeutende Ausgaben von KI-Tools, die über Hooks erfasst wurden
Bewertungsdimensionen
Jedes Signal wird in vier Dimensionen bewertet (jeweils 0,0 bis 1,0):
| Dimension | Frage | Niedrige Bewertung (0,0–0,3) | Hohe Bewertung (0,7–1,0) |
|---|---|---|---|
| Relevanz | Bezieht sich dies auf meine aktiven Domänen? | Tangential, außerhalb des Umfangs | Direkt relevant für aktuelle Arbeit |
| Umsetzbarkeit | Kann ich diese Information nutzen? | Reine Theorie, keine Anwendung | Spezifische Technik oder Muster, die ich anwenden kann |
| Tiefe | Wie substanziell ist der Inhalt? | Schlagzeilen, oberflächliche Zusammenfassung | Detaillierte Analyse mit Beispielen |
| Autorität | Wie glaubwürdig ist die Quelle? | Anonymer Blog, nicht verifiziert | Primärquelle, peer-reviewed, anerkannter Experte |
Zusammengesetzte Bewertung und Weiterleitung
composite = (relevance * 0.35) + (actionability * 0.25) +
(depth * 0.25) + (authority * 0.15)
| Bewertungsbereich | Aktion |
|---|---|
| 0,55+ | Automatische Weiterleitung in Domänenordner |
| 0,40 – 0,55 | In Warteschlange für manuelle Überprüfung |
| < 0,40 | Verwerfen (nicht speichern) |
Domänen-Weiterleitung
Signale mit einer Bewertung über 0,55 werden basierend auf Schlüsselwortabgleich und Themenklassifikation in einen von 12 Domänenordnern weitergeleitet:
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
Produktionsstatistiken
Über 14 Monate Betrieb:
| Metrik | Wert |
|---|---|
| Verarbeitete Signale gesamt | 7.771 |
| Automatisch weitergeleitet (>0,55) | 4.832 (62 %) |
| Zur Überprüfung eingereiht (0,40–0,55) | 1.543 (20 %) |
| Verworfen (<0,40) | 1.396 (18 %) |
| Aktive Domänenordner | 12 |
| Durchschnittliche Signale pro Tag | ~18 |
Knowledge-Graph-Muster
Der wiki-link-Graph von Obsidian kodiert Beziehungen zwischen Notizen. Dieser Abschnitt behandelt Link-Semantik, Graph-Traversierung zur Kontexterweiterung und Anti-Muster, die die Graph-Qualität verschlechtern.
Backlink-Semantik
Jeder wiki-link erzeugt eine gerichtete Kante im Graphen. Obsidian verfolgt sowohl Vorwärts-Links als auch Backlinks:
- Vorwärts-Link: Notiz A enthält
[[Note B]]→ A verlinkt auf B - Backlink: Notiz B zeigt, dass Notiz A auf sie verweist
Der Graph kodiert verschiedene Arten von Beziehungen, je nach Kontext:
| Link-Muster | Semantik | Beispiel |
|---|---|---|
| Inline-Link | „Steht in Beziehung zu” | “See [[OAuth Token Rotation]] for details” |
| Header-Link | „Hat Unterthema” | ”## Related\n- [[Token Rotation]]\n- [[Session Management]]” |
| Tag-artiger Link | „Ist kategorisiert als” | ”[[type/reference]]” |
| MOC-Link | „Ist Teil von” | Eine Maps of Content-Notiz, die verwandte Notizen auflistet |
Maps of Content (MOCs)
MOCs sind Indexnotizen, die verwandte Notizen in einer navigierbaren Struktur organisieren:
---
title: "Authentication & Security MOC"
type: moc
domain: security
---
## Core Concepts
- [[OAuth 2.0 Overview]]
- [[JWT Token Anatomy]]
- [[Session Management Patterns]]
## Implementation Patterns
- [[OAuth Token Rotation]]
- [[Refresh Token Security]]
- [[PKCE Flow Implementation]]
## Failure Modes
- [[Token Expiry Handling]]
- [[Session Fixation Prevention]]
- [[CSRF Defense Strategies]]
MOCs verbessern die Suche auf zwei Arten:
- Direkter Treffer. Eine Suche nach „authentication overview” findet die MOC selbst und liefert dem Agenten eine kuratierte Liste verwandter Notizen.
- Kontexterweiterung. Nachdem eine bestimmte Notiz gefunden wurde, kann der Retriever prüfen, ob die Notiz in MOCs vorkommt, und die Struktur der MOC in die Ergebnisse einbeziehen, um dem Agenten eine Übersicht über das breitere Thema zu geben.
Graph-Traversierung zur Kontexterweiterung
Eine zukünftige Erweiterung des Retrievers: Nach dem Finden der besten Ergebnisse den Kontext durch Verfolgen von Links erweitern:
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)
Dies ist im aktuellen Retriever nicht implementiert, stellt aber eine natürliche Erweiterung der Graphstruktur dar.
Anti-Muster
Verwaiste Cluster. Gruppen von Notizen, die untereinander verlinkt sind, aber keine Verbindungen zum Rest des Vaults haben. Das Graph-Panel in Obsidian macht diese als getrennte Inseln sichtbar. Verwaiste Cluster weisen auf fehlende MOCs oder fehlende domänenübergreifende Links hin.
Tag-Wildwuchs. Inkonsistente Verwendung von Tags oder die Erstellung zu vieler feingranularer Tags. Ein Vault mit 500 einzigartigen Tags über 5.000 Notizen hat durchschnittlich 1 Notiz pro 10 Tags — die Tags sind zur Filterung nicht nützlich. Konsolidieren Sie auf 20–50 übergeordnete Tags, die Ihren Domänenordnern entsprechen.
Link-lastige, inhaltsarme Notizen. Notizen, die ausschließlich aus wiki-links ohne Prosatext bestehen. Diese Notizen werden schlecht indiziert, da der Chunker keinen Text zum Einbetten hat. Fügen Sie mindestens einen Absatz Kontext hinzu, der erklärt, warum die verlinkten Notizen zusammenhängen.
Bidirektionale Links für alles. Nicht jede Erwähnung muss ein wiki-link sein. Die beiläufige Erwähnung von „OAuth” erfordert keinen [[OAuth 2.0 Overview]]-Link. Reservieren Sie wiki-links für bewusste, navigierbare Beziehungen, bei denen ein Klick auf den Link nützlichen Kontext liefern würde.
Entwickler-Workflow-Rezepte
Praktische Workflows, die Vault-Suche mit täglichen Entwicklungsaufgaben kombinieren.
Morgendlicher Kontextaufbau
Starten Sie den Tag mit dem Laden relevanten Kontexts:
Search my vault for notes about [current project] updated in the last week
Der Retriever liefert aktuelle Notizen zu Ihrem aktiven Projekt und bietet Ihnen eine schnelle Auffrischung, wo Sie aufgehört haben. Effektiver als das erneute Lesen der gestrigen Commit-Nachrichten.
Recherche-Erfassung während des Programmierens
Erfassen Sie beim Implementieren einer Funktion Erkenntnisse, ohne den Editor zu verlassen:
/capture "FastAPI dependency injection with async generators requires yield,
not return. The generator is the dependency lifecycle."
--domain programming
--tags fastapi,dependency-injection
Die erfasste Erkenntnis wird sofort indiziert und steht für zukünftige Suchen zur Verfügung. Über Monate hinweg bauen diese Mikro-Erfassungen einen Korpus implementierungsspezifischen Wissens auf.
Projekt-Kickoff
Beim Start eines neuen Projekts oder Features:
- Durchsuchen Sie den Vault: „Was weiß ich über [Technologie/Muster]?”
- Überprüfen Sie die Top-5-Ergebnisse auf frühere Entscheidungen und Fallstricke
- Prüfen Sie, ob eine MOC für die Domäne existiert; falls nicht, erstellen Sie eine
- Suchen Sie nach Fehlermodi: „Probleme mit [Technologie]”
Debugging mit Vault-Suche
Bei einem Fehler oder unerwartetem Verhalten:
Search my vault for [error message or symptom]
Frühere Debugging-Notizen enthalten oft die Grundursache und die Lösung. Dies ist besonders wertvoll bei wiederkehrenden Problemen über Projekte hinweg — der Vault erinnert sich an das, was Sie vergessen.
Vorbereitung auf Code Reviews
Vor der Überprüfung eines PR:
Search my vault for patterns and conventions about [module being changed]
Der Vault liefert frühere Entscheidungen, architektonische Einschränkungen und Kodierungsstandards, die für den zu überprüfenden Code relevant sind. Das Review wird durch institutionelles Wissen informiert, nicht nur durch den Diff.
Performance-Tuning
Dieser Abschnitt behandelt Optimierungsstrategien für verschiedene Vault-Größen und Nutzungsmuster.
Verwaltung der Indexgröße
| Vault-Größe | Chunks | DB-Größe | Vollständige Neuindizierung | Inkrementell |
|---|---|---|---|---|
| 500 Notizen | ~1.500 | 3 MB | 15 Sekunden | <1 Sekunde |
| 2.000 Notizen | ~6.000 | 12 MB | 45 Sekunden | 2 Sekunden |
| 5.000 Notizen | ~15.000 | 30 MB | 2 Minuten | 4 Sekunden |
| 15.000 Notizen | ~50.000 | 83 MB | 4 Minuten | <10 Sekunden |
| 50.000 Notizen | ~150.000 | 250 MB | 15 Minuten | 30 Sekunden |
Ab 50.000+ Notizen sollten Sie Folgendes in Betracht ziehen: - Erhöhen Sie die Batch-Größe von 64 auf 128 für schnelleres Embedding - Verwenden Sie den WAL-Modus (Standard) für gleichzeitigen Zugriff - Führen Sie die vollständige Neuindizierung außerhalb der Hauptnutzungszeiten durch
Abfrageoptimierung
WAL-Modus. Der Write-Ahead-Logging-Modus von SQLite ermöglicht gleichzeitige Lesezugriffe, während der Indexer schreibt:
db.execute("PRAGMA journal_mode=WAL")
Dies ist entscheidend, wenn der MCP-Server Abfragen verarbeitet, während der Indexer ein inkrementelles Update durchführt.
Connection-Pooling. Der MCP-Server sollte Datenbankverbindungen wiederverwenden, anstatt für jede Abfrage eine neue Verbindung zu öffnen. Eine einzelne langlebige Verbindung mit WAL-Modus unterstützt gleichzeitige Lesezugriffe.
# 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
Memory-Mapped I/O. Das mmap_size-Pragma weist SQLite an, Memory-Mapped I/O für die Datenbankdatei zu verwenden. Bei einer 83 MB großen Datenbank eliminiert das Mapping der gesamten Datei in den Arbeitsspeicher die meisten Festplattenzugriffe.
FTS5-Optimierung. Führen Sie nach einer vollständigen Neuindizierung Folgendes aus:
INSERT INTO chunks_fts(chunks_fts) VALUES('optimize');
Dies führt die internen B-Tree-Segmente von FTS5 zusammen und reduziert die Abfragelatenz für nachfolgende Suchen.
Skalierungs-Benchmarks
Gemessen auf Apple M3 Pro, 36 GB RAM, NVMe SSD:
| Operation | 500 Notizen | 5K Notizen | 15K Notizen | 50K Notizen |
|---|---|---|---|---|
| BM25-Abfrage | 2ms | 5ms | 12ms | 25ms |
| Vektor-Abfrage | 1ms | 3ms | 8ms | 20ms |
| RRF-Fusion | <1ms | <1ms | 3ms | 5ms |
| Vollständige Suche | 3ms | 8ms | 23ms | 50ms |
Alle Benchmarks umfassen Datenbankzugriff, Abfrageausführung und Ergebnisformatierung. Die Netzwerklatenz für die MCP STDIO-Kommunikation fügt 1–2 ms hinzu.
Fehlerbehebung
Index-Drift
Symptom: Die Suche liefert veraltete Ergebnisse oder übersieht kürzlich hinzugefügte Notizen.
Ursache: Der inkrementelle Indexer wurde nach dem Hinzufügen von Notizen nicht ausgeführt, oder die mtime einer Datei wurde nicht aktualisiert (z. B. bei Synchronisation von einem anderen Gerät mit beibehaltenen Zeitstempeln).
Lösung: Führen Sie eine vollständige Neuindizierung durch: python index_vault.py --full
Wechsel des Embedding-Modells
Symptom: Nach dem Wechsel des Embedding-Modells liefert die Vektorsuche unsinnige Ergebnisse.
Ursache: Alte Vektoren (vom vorherigen Modell) werden mit neuen Abfragevektoren verglichen. Die Dimensionen oder die Semantik des Vektorraums sind inkompatibel.
Lösung: Der Indexer sollte die Abweichung des Modell-Hashes erkennen und automatisch eine vollständige Neuindizierung auslösen. Falls dies nicht geschieht, löschen Sie die Datenbank manuell und indizieren Sie neu:
rm vectors.db
python index_vault.py --full
FTS5-Wartung
Symptom: FTS5-Abfragen liefern nach vielen inkrementellen Updates falsche oder unvollständige Ergebnisse.
Ursache: Interne FTS5-Segmente können nach vielen kleinen Updates fragmentiert werden.
Lösung: Neuaufbau und Optimierung:
INSERT INTO chunks_fts(chunks_fts) VALUES('rebuild');
INSERT INTO chunks_fts(chunks_fts) VALUES('optimize');
MCP-Timeout
Symptom: Das AI-Tool meldet, dass der MCP-Server ein Timeout hatte.
Ursache: Die erste Abfrage löst das Laden des Modells aus (Lazy Initialization), was 2–5 Sekunden dauert. Das Standard-MCP-Timeout des AI-Tools kann kürzer sein.
Lösung: Wärmen Sie das Modell beim Serverstart vor:
# In MCP server initialization
retriever = HybridRetriever(db_path, vault_path)
retriever.search("warmup", limit=1) # Trigger model load
SQLite-Dateisperren
Symptom: SQLITE_BUSY- oder SQLITE_LOCKED-Fehler.
Ursache: Mehrere Prozesse schreiben gleichzeitig in die Datenbank. Der WAL-Modus erlaubt gleichzeitige Lesezugriffe, aber nur einen Schreiber.
Lösung: Stellen Sie sicher, dass nur ein Prozess (der Indexer) in die Datenbank schreibt. Der MCP-Server und Hooks sollten nur lesen. Wenn Sie gleichzeitige Schreibzugriffe benötigen, verwenden Sie den WAL-Modus und setzen Sie ein Busy-Timeout:
db.execute("PRAGMA busy_timeout=5000") # Wait up to 5 seconds
sqlite-vec wird nicht geladen
Symptom: Die Vektorsuche ist deaktiviert; der Retriever läuft im reinen BM25-Modus.
Ursache: Die sqlite-vec-Erweiterung ist nicht installiert, wird im Bibliothekspfad nicht gefunden oder ist mit der SQLite-Version inkompatibel.
Lösung:
# Install via pip
pip install sqlite-vec
# Or compile from source
git clone https://github.com/asg017/sqlite-vec
cd sqlite-vec && make
Überprüfen Sie, ob die Erweiterung geladen wird:
import sqlite3
db = sqlite3.connect(":memory:")
db.enable_load_extension(True)
db.load_extension("vec0")
print("sqlite-vec loaded successfully")
Speicherprobleme bei großen Vaults
Symptom: Out-of-Memory-Fehler während der vollständigen Neuindizierung eines großen Vaults (50.000+ Notizen).
Ursache: Die Embedding-Batch-Größe ist zu groß, oder alle Dateiinhalte werden gleichzeitig in den Arbeitsspeicher geladen.
Lösung: Reduzieren Sie die Batch-Größe und verarbeiten Sie Dateien inkrementell:
BATCH_SIZE = 32 # Reduce from 64
Stellen Sie außerdem sicher, dass der Indexer Dateien einzeln verarbeitet (Lesen, Chunking und Embedding jeder Datei vor dem Übergang zur nächsten), anstatt alle Dateien in den Arbeitsspeicher zu laden.
Migrationsleitfaden
Von Apple Notes
- Exportieren Sie Apple Notes über die Option „Alle exportieren” (macOS) oder verwenden Sie ein Migrationstool wie
apple-notes-liberator - Konvertieren Sie HTML-Exporte zu Markdown mit
markdownifyoderpandoc - Verschieben Sie die konvertierten Dateien in den
00-inbox/-Ordner Ihres Vaults - Überprüfen Sie jede Notiz und fügen Sie frontmatter hinzu
- Verschieben Sie die Notizen in die entsprechenden Domänen-Ordner
Von Notion
- Exportieren Sie aus Notion: Einstellungen → Exportieren → Markdown & CSV
- Entpacken Sie den Export in den
00-inbox/-Ordner Ihres Vaults - Beheben Sie Notion-spezifische Markdown-Artefakte:
- Notion verwendet
- [ ]für Checklisten — dies ist Standard-Markdown - Notion fügt Eigenschaftstabellen als HTML ein — konvertieren Sie diese zu YAML-frontmatter
- Notion bettet Bilder als relative Pfade ein — kopieren Sie die Bilder in Ihren Anhänge-Ordner
- Fügen Sie Standard-frontmatter hinzu (
type,domain,tags) - Ersetzen Sie Notion-Seitenlinks durch Obsidian wiki-links
Von Google Docs
- Verwenden Sie Google Takeout, um alle Dokumente zu exportieren
- Konvertieren Sie
.docx-Dateien zu Markdown:pandoc -f docx -t markdown input.docx -o output.md - Stapelkonvertierung:
for f in *.docx; do pandoc -f docx -t markdown "$f" -o "${f%.docx}.md"; done - Verschieben Sie die Dateien in den Vault, fügen Sie frontmatter hinzu und organisieren Sie sie in Ordnern
Von reinem Markdown (ohne Obsidian)
Wenn Sie bereits ein Verzeichnis mit Markdown-Dateien haben:
- Öffnen Sie das Verzeichnis als Obsidian-Vault (Obsidian → Vault öffnen → Ordner öffnen)
- Fügen Sie
.obsidian/zu.gitignorehinzu, falls das Verzeichnis versionskontrolliert ist - Erstellen Sie frontmatter-Vorlagen und wenden Sie diese auf bestehende Dateien an
- Beginnen Sie, Notizen mit
[[wiki-links]]zu verknüpfen, während Sie lesen und organisieren - Führen Sie den Indexer sofort aus — das Retrieval-System funktioniert ab dem ersten Tag
Von einem anderen Retrieval-System
Wenn Sie von einem anderen Embedding-/Suchsystem migrieren:
- Versuchen Sie nicht, Vektoren zu migrieren. Verschiedene Modelle erzeugen inkompatible Vektorräume. Führen Sie eine vollständige Neuindizierung mit dem neuen Modell durch.
- Migrieren Sie den Inhalt, nicht den Index. Die Vault-Dateien sind die Quelle der Wahrheit. Der Index ist ein abgeleitetes Artefakt.
- Überprüfen Sie nach der Migration. Führen Sie 10–20 Abfragen durch, deren Antworten Sie kennen, und prüfen Sie, ob die Ergebnisse Ihren Erwartungen entsprechen.
Changelog
| Datum | Änderung |
|---|---|
| 02.03.2026 | potion-base-32M und potion-retrieval-32M zum Modellvergleich hinzugefügt. Abschnitt zu Quantisierung/Dimensionsreduktion hinzugefügt. Hinweis zur Weiterentwicklung der MCP-Spezifikation hinzugefügt. |
| 01.03.2026 | Erstveröffentlichung |
Referenzen
-
Cormack, G.V., Clarke, C.L.A., and Buettcher, S. Reciprocal Rank Fusion outperforms Condorcet and individual Rank Learning Methods. SIGIR, 2009. Führt RRF mit k=60 als parameterfreie Methode zur Kombination von Ranglisten ein. ↩↩↩
-
OpenAI Embeddings Pricing. text-embedding-3-small: 0,02 $ pro Million Tokens. Geschätzte Vault-Kosten pro vollständiger Neuindizierung: ca. 0,30 $. ↩
-
van Dongen, T. et al. Model2Vec: Turn any Sentence Transformer into a Small Fast Model. arXiv, 2025. Beschreibt den Destillationsansatz zur Erzeugung statischer Embeddings aus Sentence Transformers. ↩
-
MTEB: Massive Text Embedding Benchmark. potion-base-8M erreicht einen Durchschnitt von 50,03 gegenüber 56,09 für all-MiniLM-L6-v2 (89 % Beibehaltung). ↩
-
SQLite FTS5 Extension. FTS5 bietet Volltextsuche mit BM25-Ranking und konfigurierbaren Spaltengewichtungen. ↩
-
sqlite-vec: A vector search SQLite extension. Stellt virtuelle
vec0-Tabellen für KNN-Vektorsuche innerhalb von SQLite bereit. ↩ -
Robertson, S. and 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. Dichte Repräsentationen übertreffen BM25 um 9–19 % bei Open-Domain-Frage-Antwort-Aufgaben. ↩
-
Reimers, N. and Gurevych, I. Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks. EMNLP, 2019. Grundlegende Arbeit zur dichten semantischen Ähnlichkeit. ↩
-
Luan, Y. et al. Sparse, Dense, and Attentional Representations for Text Retrieval. TACL, 2021. Hybrid Retrieval übertrifft konsistent Einzelmodalitäts-Ansätze auf MS MARCO. ↩
-
SQLite Write-Ahead Logging. WAL-Modus für gleichzeitige Lesezugriffe bei einem einzelnen Schreibvorgang. ↩
-
Gao, Y. et al. Retrieval-Augmented Generation for Large Language Models: A Survey. arXiv, 2024. Überblick über RAG-Architekturen und Chunking-Strategien. ↩
-
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. Offizielle Dokumentation für Obsidian. ↩
-
Model Context Protocol Specification. Der MCP-Standard zur Verbindung von KI-Werkzeugen mit Datenquellen. ↩↩
-
Produktionsdaten des Autors. 16.894 Dateien, 49.746 Chunks, 83,56 MB SQLite-Datenbank, 7.771 Signale über 14 Monate verarbeitet. Abfragelatenz gemessen mittels
time.perf_counter(). ↩ -
Model2Vec Potion Models. Minish Lab, 2025. Potion-base-32M (MTEB 52,46), potion-retrieval-32M (MTEB Retrieval 36,35) und Quantisierungs-/Dimensionsreduktionsfunktionen ab v0.5.0+. ↩↩↩