# Index und Suche — SQLite mit FTS5 und sqlite-vec Entscheidung Andreas, 2026-09-04 (spine/core#8): kein Datenbankdienst. Der Index ist eine **abgeleitete Sicht** auf die Markdown-Dateien der Ablage und liegt als eine SQLite-Datei neben ihr. Er darf jederzeit gelöscht und neu gebaut werden — die Wahrheit sind die Dateien. Code: `image/km/index.py` (bauen), `image/km/search.py` (suchen). Abhängigkeiten: `requests`, `sqlite-vec` (`image/requirements.txt`). ## Was drin ist | Tabelle | Inhalt | |---|---| | `docs` | eine Zeile je Datei: Pfad, Namespace, Projekt, Klassifikation, Typ (`vorgang`, `doku`, …), Zustand, Titel, Zeitstempel, SHA-256, ob eingebettet | | `chunks` | Absatz-Chunks je Datei (max. 600 Zeichen, 60 Überlappung — dasselbe Chunking wie der Wissens-MCP); der Titel ist Chunk 0 | | `chunks_fts` | FTS5 über die Chunks, Tokenizer `unicode61` **ohne** Diakritika-Entfernung (ä bleibt ä) | | `chunks_vec` | sqlite-vec, `bge-m3` mit 1024 Dimensionen, rowid = Chunk-ID | | `verweise` | Kanten aus dem Frontmatter (`verweise:`), beide Richtungen abfragbar | Die Filterspalten sind der Punkt, an dem die Zugriffsregel greift: Namespace, Projekt, Klassifikation, Typ, Zustand sind Spalten in `docs`, und die Suche filtert **vor** dem Ranking. Wer eine Klassifikation nicht sehen darf, bekommt `0 Treffer` — nicht „vorhanden, aber gesperrt" (spine/core#18). ## Bauen ```bash # Volltext (Sekunden) — nach jedem Ingest image/km/index.py --ablage /srv/km/ablage # dazu Vektoren fuer neue und geaenderte Dateien image/km/index.py --ablage /srv/km/ablage --embed # nur ausstehende Vektoren nachziehen, z. B. nachts image/km/index.py --ablage /srv/km/ablage --embed-only [--embed-limit 200] ``` Inkrementell über den SHA-256 je Datei: unveränderte Dateien werden nicht angefasst, geänderte komplett neu zerlegt (alte Chunks und Vektoren raus, neue rein), verschwundene entfernt. Der Volltext ist sofort da; die Vektoren folgen, sortiert nach `aktualisiert` absteigend — das Neueste zuerst. ## Suchen ```bash search.py "worktree prune" # hybrid: Volltext + Vektor, RRF search.py --mode fts "live/live#2181" # exakt search.py --mode vec "warum braucht der bus keine persistenz mehr" search.py --ns 42i --typ vorgang --zustand open "i18n" search.py --classification lan --classification public "…" # Sichtkreis search.py --json "…" ``` Volltext führend, Vektor ergänzend (spine/core#1). Hybrid fusioniert beide Ranglisten per Reciprocal Rank Fusion; ausgegeben wird ein Treffer je Datei mit dem besten Chunk als Anriss. Die FTS-Anfrage macht aus jedem Wort einen Präfix (`worktree*`), Zeichenketten mit Sonderzeichen (`live/live#2181`) werden wörtlich gesucht. ## Gemessen am 2026-09-04 (Mac, Ablage mit 3424 Vorgängen aus elf Repos) | Schritt | Menge | Dauer | |---|---|---| | Volltext-Index, Erstbau | 2935 Dateien, 61.141 Chunks | 3,1 s | | Volltext, inkrementell | 491 neue Dateien | 0,3 s | | Vektoren | 1756 Chunks (30 Dateien) | 550 s → **0,31 s je Chunk** | | Vektoren, hochgerechnet | 64.392 Chunks | **~5,5 h** | | FTS-Suche | — | unter 50 ms | | Vektorsuche | Embedding der Anfrage + Nachbarn | ~1 s, davon fast alles das Embedding | Die Vektorzeit ist der Rechner, nicht die Leitung: `/api/embed` mit 32 Texten im Batch liefert 0,31 s je Text, einzeln 1,0 s — die Ollama-Instanz auf dem s18-CT rechnet auf der CPU (`OLLAMA_VULKAN=0`, siehe `info: lan/infra/wissens-mcp.md`). Die Erstbefüllung fällt einmal an; danach kostet ein geändertes Ticket Sekunden. Wer es schneller braucht, hängt ein GPU-Embedding ans Gateway — das ist eine Betriebsfrage, keine am Index. **Dateigröße:** 62 MB für den Volltext über 61.000 Chunks; mit Vektoren (1024 × 4 Byte je Chunk) kommen rund 260 MB dazu. ## Was fehlt - **Deutsche Wortformen.** `unicode61` stemmt nicht; „Sperrung" findet „gesperrt" nur über die Vektorsuche. Falls das nicht reicht: Trigramm- Tokenizer, keine neue Datenbank. - **Kanten zu Doku-Seiten.** `verweise` kennt heute nur Vorgang→Vorgang. - **Der Dienst.** Heute zwei Skripte; `read`/`search`/`summary` als HTTP-API und MCP-Endpunkt kommen darüber (Reihenfolge in `image/README.md`).