Example vault location
#
Kluczowe wnioski
Inżynieria kontekstu, nie robienie notatek. Wartość skarbca Obsidian dla AI nie tkwi w samych notatkach, lecz w warstwie wyszukiwania, która czyni je możliwymi do odpytywania. Skarbiec zawierający 16 000 plików bez wyszukiwania to baza danych, do której tylko się zapisuje. Skarbiec z 200 plikami wyposażony w wyszukiwanie hybrydowe i integrację z MCP to baza wiedzy AI. Infrastruktura wyszukiwania jest produktem. Notatki to surowiec.
Wyszukiwanie hybrydowe przewyższa czyste wyszukiwanie słów kluczowych lub czyste wyszukiwanie semantyczne. BM25 wyłapuje dokładne identyfikatory i nazwy funkcji. Wyszukiwanie wektorowe wyłapuje synonimy i dopasowania koncepcyjne w różnej terminologii. Reciprocal Rank Fusion (RRF) łączy oba podejścia bez konieczności kalibracji wyników. Żadna z metod samodzielnie nie pokrywa obu trybów awaryjnych. Badania nad rankingiem fragmentów MS MARCO potwierdzają ten wzorzec: wyszukiwanie hybrydowe konsekwentnie przewyższa każdą z metod stosowaną osobno.1 Szczegółowe omówienie retrievera hybrydowego zawiera matematykę RRF, przykłady z rzeczywistymi liczbami, analizę trybów awaryjnych oraz interaktywny kalkulator fuzji.
MCP zapewnia narzędziom AI bezpośredni dostęp do skarbca. Serwery Model Context Protocol (MCP) udostępniają retriever jako narzędzie, które Claude Code, Codex CLI, Cursor i inne narzędzia AI mogą wywoływać bezpośrednio. Agent odpytuje skarbiec, otrzymuje wyniki z rankingiem i atrybucją źródła, a następnie wykorzystuje kontekst bez ładowania całych plików. Serwer MCP to cienka warstwa opakowująca silnik wyszukiwania.
Podejście local-first oznacza zerowe koszty API i pełną prywatność. Cały stos działa na jednej maszynie: SQLite do przechowywania danych, Model2Vec do embeddingów, FTS5 do wyszukiwania słów kluczowych, sqlite-vec do wektorowego KNN. Żadnych usług chmurowych, żadnych wywołań API, żadnej zależności od sieci. Prywatne notatki nigdy nie opuszczają maszyny. Pełne ponowne generowanie embeddingów dla 49 746 fragmentów kosztowałoby około 0,30 USD po cenach API OpenAI, ale rzeczywistymi kosztami są opóźnienia, narażenie prywatności i zależność od sieci w systemie, który powinien działać offline.2
Inkrementalne indeksowanie utrzymuje system na bieżąco w mniej niż 10 sekund. Porównanie czasu modyfikacji plików wykrywa zmiany. Tylko zmodyfikowane pliki są ponownie dzielone na fragmenty i ponownie embeddowane. Pełne reindeksowanie zajmuje około czterech minut na sprzęcie Apple z serii M. Inkrementalne aktualizacje po typowym dniu edycji wykonują się w mniej niż dziesięć sekund. System pozostaje aktualny bez ręcznej interwencji.
Architektura skaluje się od 200 do ponad 20 000 notatek. Ten sam trzywarstwowy projekt (pozyskiwanie, wyszukiwanie, integracja) działa przy dowolnym rozmiarze skarbca. Można zacząć od wyszukiwania wyłącznie BM25 w małym skarbcu. Wyszukiwanie wektorowe dodaje się, gdy kolizje słów kluczowych stają się problemem. Fuzję RRF dodaje się, gdy potrzebne są zarówno dokładne, jak i semantyczne dopasowania. Każda warstwa jest niezależnie użyteczna i niezależnie usuwalna.
Jak korzystać z tego przewodnika
Ten przewodnik obejmuje kompletny system. Punkt startowy zależy od aktualnej sytuacji:
| Opis sytuacji | Zacznij tutaj | Następnie eksploruj |
|---|---|---|
| Nowy w Obsidian + AI | Dlaczego Obsidian jako infrastruktura AI, Szybki start | Architektura skarbca, Architektura serwera MCP |
| Istniejący skarbiec, potrzebny dostęp AI | Architektura serwera MCP, Integracja z Claude Code | Modele embeddingów, Wyszukiwanie pełnotekstowe z FTS5 |
| Budowanie systemu wyszukiwania | Kompletny pipeline wyszukiwania, Reciprocal Rank Fusion | Optymalizacja wydajności, Rozwiązywanie problemów |
| Kontekst zespołowy lub korporacyjny | Framework decyzyjny, Wzorce grafu wiedzy | Przepisy dla programistów, Przewodnik migracji |
Sekcje oznaczone jako Kontrakt zawierają szczegóły implementacji, bloki konfiguracyjne i tryby awaryjne. Sekcje oznaczone jako Narracja koncentrują się na koncepcjach, decyzjach architektonicznych i uzasadnieniu wyborów projektowych. Sekcje oznaczone jako Przepis dostarczają procedury krok po kroku.
Dlaczego Obsidian jako infrastruktura AI
Teza tego przewodnika: Skarbce Obsidian są najlepszym podłożem dla osobistych baz wiedzy AI, ponieważ są local-first, oparte na czystym tekście, mają strukturę grafową, a użytkownik kontroluje każdą warstwę stosu.
Co Obsidian daje AI, a alternatywy nie
Pliki markdown w czystym tekście. Każda notatka to plik .md w systemie plików. Brak formatu własnościowego, brak eksportu z bazy danych, brak wymagań dotyczących API do odczytania treści. Każde narzędzie odczytujące pliki może odczytać skarbiec. grep, ripgrep, pathlib z Python, SQLite FTS5 — wszystkie działają bezpośrednio na plikach źródłowych. Podczas budowania systemu wyszukiwania indeksowane są pliki, a nie odpowiedzi API. Indeks jest zawsze spójny ze źródłem, ponieważ źródłem jest system plików.
Architektura local-first. Skarbiec znajduje się na lokalnej maszynie. Brak serwera, brak zależności od synchronizacji w chmurze, brak limitów API, brak warunków korzystania z usługi regulujących przetwarzanie własnych treści. Można generować embeddingi, indeksować, dzielić na fragmenty i przeszukiwać notatki bez żadnej zewnętrznej usługi. Ma to znaczenie dla infrastruktury AI, ponieważ pipeline wyszukiwania działa tak szybko, jak pozwala dysk, a nie tak szybko, jak odpowiada endpoint API. Ma to również znaczenie dla prywatności: prywatne notatki zawierające dane uwierzytelniające, dane medyczne, informacje finansowe i osobiste refleksje nigdy nie opuszczają maszyny.
Struktura grafowa dzięki wiki-linkom. Składnia [[wiki-link]] w Obsidian tworzy graf skierowany pomiędzy notatkami. Notatka o implementacji OAuth zawiera odnośniki do notatek o rotacji tokenów, zarządzaniu sesjami i bezpieczeństwie API. Struktura grafowa koduje relacje między koncepcjami kuratorowane przez człowieka. Embeddingi wektorowe wychwytują podobieństwo semantyczne, ale wiki-linki wychwytują celowe połączenia, które autor stworzył podczas myślenia o danym temacie. Graf jest sygnałem, którego embeddingi nie są w stanie odtworzyć.
Ekosystem wtyczek. Obsidian dysponuje ponad 1800 wtyczkami społecznościowymi. Dataview odpytuje skarbiec jak bazę danych. Templater generuje notatki z szablonów z logiką JavaScript. Integracja z Git synchronizuje skarbiec z repozytorium. Linter wymusza spójność formatowania. Wtyczki te dodają strukturę do skarbca bez zmiany bazowego formatu czystego tekstu. System wyszukiwania indeksuje wyniki działania tych wtyczek, a nie same wtyczki.
Ponad 5 milionów użytkowników. Obsidian posiada dużą, aktywną społeczność tworzącą szablony, przepływy pracy, wtyczki i dokumentację. W przypadku napotkania problemu z organizacją skarbca lub konfiguracją wtyczki istnieje duże prawdopodobieństwo, że ktoś już udokumentował rozwiązanie. Społeczność tworzy również narzędzia powiązane z Obsidian: serwery MCP, skrypty indeksujące, pipeline publikacyjne i wrappery API.
Czego sam system plików nie zapewnia
Katalog plików markdown ma przewagę czystego tekstu, ale brakuje mu trzech rzeczy, które dodaje Obsidian:
-
Linki dwukierunkowe. Obsidian automatycznie śledzi backlinki. Gdy notatka A zawiera odnośnik do notatki B, notatka B pokazuje, że notatka A się do niej odwołuje. Panel grafu wizualizuje klastry połączeń. Ta dwukierunkowa świadomość to metadane, których surowy system plików nie zapewnia.
-
Podgląd na żywo z renderowaniem wtyczek. Zapytania Dataview, diagramy Mermaid i bloki callout renderują się w czasie rzeczywistym. Doświadczenie pisania jest bogatsze niż w edytorze tekstu, a format przechowywania pozostaje czystym tekstem. Pisanie i organizacja odbywają się w bogatym środowisku; system wyszukiwania indeksuje surowy markdown.
-
Infrastruktura społecznościowa. Odkrywanie wtyczek, marketplace motywów, usługa synchronizacji (opcjonalna), usługa publikacji (opcjonalna) oraz ekosystem dokumentacji. Każdą pojedynczą funkcję można odtworzyć za pomocą samodzielnych narzędzi, ale Obsidian łączy je w spójny przepływ pracy.
Czego Obsidian NIE robi (i co trzeba zbudować)
Obsidian nie zawiera infrastruktury wyszukiwania. Posiada podstawowe wyszukiwanie (pełnotekstowe, po nazwie pliku, po tagach), ale brak w nim pipeline embeddingów, wyszukiwania wektorowego, rankingu fuzyjnego, serwera MCP, filtrowania danych uwierzytelniających, strategii dzielenia na fragmenty oraz hooków integracyjnych dla zewnętrznych narzędzi AI. Ten przewodnik obejmuje infrastrukturę budowaną na wierzchu Obsidian. Skarbiec jest podłożem. Pipeline wyszukiwania, serwer MCP i hooki integracyjne to infrastruktura.
Opisana tutaj architektura jest markdown-first, a nie ekskluzywna dla Obsidian. W przypadku korzystania z Logseq, Foam, Dendron lub zwykłego katalogu plików markdown, pipeline wyszukiwania działa identycznie. Chunker odczytuje pliki .md. Embedder przetwarza ciągi tekstowe. Indekser zapisuje do SQLite. Żaden z tych komponentów nie zależy od funkcji specyficznych dla Obsidian. Wkładem Obsidian jest środowisko pisania i organizacji, które tworzy pliki markdown indeksowane przez retriever.
Szybki start: pierwszy vault połączony z AI
Ta sekcja pozwala połączyć vault z narzędziem AI w pięć minut. Obejmuje instalację Obsidian, utworzenie vault, instalację serwera MCP oraz uruchomienie pierwszego zapytania. Szybki start wykorzystuje społecznościowy serwer MCP w celu uzyskania natychmiastowych wyników. Dalsze sekcje opisują budowę własnego pipeline’u wyszukiwania do zastosowań produkcyjnych.
Wymagania wstępne
- macOS, Linux lub Windows
- Node.js 18+ (do serwera MCP)
- Zainstalowany Claude Code, Codex CLI lub Cursor
Krok 1: Utworzenie vault
Należy pobrać Obsidian ze strony obsidian.md i utworzyć nowy vault. Warto wybrać lokalizację, którą łatwo zapamiętać — serwer MCP potrzebuje ścieżki bezwzględnej.
# Example vault location
~/Documents/knowledge-base/
Następnie warto dodać kilka notatek, aby moduł wyszukiwania miał z czym pracować. Nawet 10–20 notatek wystarczy, aby zobaczyć rezultaty. Każda notatka powinna być plikiem .md z sensownym tytułem i co najmniej jednym akapitem treści.
Krok 2: Instalacja serwera MCP
Kilka społecznościowych serwerów MCP zapewnia natychmiastowy dostęp do vault. Ekosystem znacząco się rozwinął w latach 2025–2026:
| Serwer | Autor | Transport | Wymaga wtyczki | Kluczowa funkcja |
|---|---|---|---|---|
| obsidian-mcp-server | StevenStavrakis | STDIO | Nie | Lekki, oparty na plikach |
| mcp-obsidian | MarkusPfundstein | STDIO | Local REST API | Pełny CRUD vault przez REST |
| obsidian-mcp-tools | jacksteamdev | STDIO | Tak (wtyczka) | Wyszukiwanie semantyczne + Templater |
| obsidian-claude-code-mcp | iansinnott | WebSocket | Tak (wtyczka) | Automatyczne wykrywanie dla Claude Code |
| obsidian-mcp-server | cyanheads | STDIO | Local REST API | Zarządzanie tagami i frontmatter |
Do szybkiego startu najprostszą opcją jest serwer oparty na plikach, który bezpośrednio odczytuje pliki .md:
npm install -g obsidian-mcp-server
Krok 3: Konfiguracja narzędzia AI
Claude Code — należy dodać do ~/.claude/settings.json:
{
"mcpServers": {
"obsidian": {
"command": "obsidian-mcp-server",
"args": ["--vault", "/absolute/path/to/your/vault"]
}
}
}
Codex CLI — należy dodać do .codex/config.toml:
[mcp_servers.obsidian]
command = "obsidian-mcp-server"
args = ["--vault", "/absolute/path/to/your/vault"]
Cursor — należy dodać do .cursor/mcp.json:
{
"mcpServers": {
"obsidian": {
"command": "obsidian-mcp-server",
"args": ["--vault", "/absolute/path/to/your/vault"]
}
}
}
Krok 4: Uruchomienie pierwszego zapytania
Należy otworzyć narzędzie AI i zadać pytanie, na które notatki w vault mogą odpowiedzieć:
Search my Obsidian vault for notes about [topic you wrote about]
Narzędzie AI wywołuje serwer MCP, który przeszukuje vault i zwraca pasujące treści. Powinny pojawić się wyniki ze ścieżkami plików i odpowiednimi fragmentami.
Co właśnie powstało
Lokalna baza wiedzy została połączona z narzędziem AI za pośrednictwem standardowego protokołu. Serwer MCP odczytuje pliki vault, wykonuje podstawowe wyszukiwanie i zwraca wyniki. To minimalna działająca wersja.
Czego ten szybki start NIE zapewnia: - Wyszukiwanie hybrydowe (BM25 + wyszukiwanie wektorowe + fuzja RRF) - Wyszukiwanie semantyczne oparte na embeddings - Filtrowanie poświadczeń - Indeksowanie przyrostowe - Automatyczne wstrzykiwanie kontekstu oparte na hookach
Dalsza część przewodnika opisuje budowę każdej z tych funkcji. Szybki start potwierdza koncepcję. Pełny pipeline zapewnia wyszukiwanie o jakości produkcyjnej.
Framework decyzyjny: Obsidian vs alternatywy
Nie każdy przypadek użycia wymaga Obsidian. Ta sekcja pokazuje, kiedy Obsidian jest właściwym podłożem, kiedy to przesada, a kiedy lepiej sprawdza się coś innego.
Drzewo decyzyjne
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.
Macierz porównawcza
| Kryterium | Obsidian | Notion | Apple Notes | Zwykły system plików | CLAUDE.md |
|---|---|---|---|---|---|
| Lokalność | Tak | Nie (chmura) | Częściowo (iCloud) | Tak | Tak |
| Czysty tekst | Tak (markdown) | Nie (bloki) | Nie (format własnościowy) | Tak | Tak |
| Struktura grafowa | Tak (wiki-links) | Częściowo (wzmianki) | Nie | Nie | Nie |
| Indeksowalność przez AI | Bezpośredni dostęp do plików | Wymaga API | Wymaga eksportu | Bezpośredni dostęp do plików | Już w kontekście |
| Ekosystem wtyczek | 1800+ wtyczek | Integracje | Brak | Nie dotyczy | Nie dotyczy |
| Praca offline | Pełna | Tylko odczyt z pamięci podręcznej | Częściowa | Pełna | Pełna |
| Skalowalność do 10 000+ notatek | Tak | Tak (z API) | Pogarsza się | Tak | Nie (pojedynczy plik) |
| Koszt | Bezpłatny (rdzeń) | Od 10 $/mies. | Bezpłatny | Bezpłatny | Bezpłatny |
Kiedy Obsidian to przesada
- Kontekst pojedynczego projektu. Jeśli AI potrzebuje kontekstu wyłącznie o bieżącej bazie kodu, należy umieścić go w
CLAUDE.md,AGENTS.mdlub dokumentacji na poziomie projektu. Te pliki są powiązane z repozytorium i ładowane automatycznie. - Dane strukturalne. Jeśli treść to tabele, rekordy lub schematy, lepiej użyć bazy danych. Notatki Obsidian są przeznaczone przede wszystkim do prozy. Dataview umożliwia odpytywanie pól frontmatter, ale prawdziwa baza danych obsługuje zapytania strukturalne znacznie lepiej.
- Tymczasowe badania. Jeśli notatki zostaną usunięte po zakończeniu projektu, prostszy jest katalog roboczy z plikami markdown. Nie warto budować infrastruktury wyszukiwania dla treści efemerycznych.
Kiedy Obsidian to właściwy wybór
- Gromadzenie wiedzy przez miesiące lub lata. Wartość rośnie wraz z rozwojem korpusu. Vault z 200 notatkami odpytywany codziennie przez sześć miesięcy daje większą wartość niż vault z 5000 notatek odpytany raz.
- Wiele dziedzin w jednym korpusie. Vault zawierający notatki o programowaniu, architekturze, bezpieczeństwie, designie i projektach osobistych korzysta z wyszukiwania międzydomenowego, którego nie zapewni
CLAUDE.mdna poziomie projektu. - Treści wrażliwe pod kątem prywatności. Podejście local-first oznacza, że pipeline wyszukiwania nigdy nie wysyła treści do zewnętrznych serwisów. Vault zawiera dokładnie to, co się w nim umieści, włącznie z treściami, których nie chciałoby się przesyłać do usługi chmurowej.
Model mentalny: trzy warstwy
System składa się z trzech warstw, które działają niezależnie, ale wzajemnie się wzmacniają. Każda warstwa ma inne zadanie i inny tryb awarii.
┌─────────────────────────────────────────────────────┐
│ 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 (przyjmowanie) określa, co trafia do skarbca. Bez kuracji skarbiec gromadzi szum: zrzuty ekranu tweetów, skopiowane artykuły bez adnotacji, niedokończone myśli bez kontekstu. Warstwa intake odpowiada za kontrolę jakości w punkcie wejścia. Pipeline oceniający, konwencja tagowania lub ręczny proces przeglądu — dowolny mechanizm zapewniający, że skarbiec zawiera treści warte wyszukiwania.
Retrieval (wyszukiwanie) czyni skarbiec przeszukiwalnym. To silnik systemu: dzielenie notatek na jednostki wyszukiwania (chunking), osadzanie fragmentów w przestrzeni wektorowej (embeddings), indeksowanie pod kątem wyszukiwania słów kluczowych i semantycznego, łączenie wyników za pomocą RRF. Warstwa retrieval przekształca katalog plików w przeszukiwalną bazę wiedzy. Bez tej warstwy skarbiec jest dostępny jedynie przez ręczne przeglądanie i podstawowe wyszukiwanie, ale nie jest programistycznie dostępny dla narzędzi AI.
Integration (integracja) łączy warstwę retrieval z narzędziami AI. Serwer MCP udostępnia wyszukiwanie jako wywoływalne narzędzie. Hooki automatycznie wstrzykują kontekst. Skille zapisują nową wiedzę z powrotem do skarbca. Warstwa integracji stanowi interfejs między bazą wiedzy a agentami AI, którzy z niej korzystają.
Warstwy są celowo od siebie oddzielone. Pipeline oceniający intake nie wie nic o embeddingach. Retriever nie wie nic o regułach routingu sygnałów. Serwer MCP nie wie nic o tym, jak powstały notatki. Ta separacja oznacza, że każdą warstwę można ulepszać niezależnie. Można wymienić model embeddingów bez zmiany pipeline’u intake. Dodać nową funkcję MCP bez modyfikowania retrievera. Zmienić heurystyki oceniania sygnałów bez dotykania indeksu.
Architektura skarbca pod kątem konsumpcji przez AI
Skarbiec zoptymalizowany pod wyszukiwanie AI stosuje inne konwencje niż skarbiec zoptymalizowany pod osobiste przeglądanie. Ta sekcja obejmuje strukturę folderów, schemat notatek, konwencje frontmatter oraz konkretne wzorce poprawiające jakość wyszukiwania.
Struktura folderów
Należy używać numerowanych prefiksów dla folderów najwyższego poziomu, aby stworzyć przewidywalną hierarchię organizacyjną. Numery nie oznaczają priorytetu — grupują powiązane domeny i czynią strukturę łatwą do przeglądania.
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
Foldery, które powinny być indeksowane: Wszystko, co zawiera tekst markdown — projekty, obszary, zasoby, sygnały, notatki dzienne.
Foldery, które powinny być wyłączone z indeksowania: Szablony (zawierają zmienne zastępcze, nie treść), załączniki (pliki binarne), konfiguracja Obsidian oraz wszelkie foldery zawierające wrażliwe treści, które nie powinny trafiać do indeksu wyszukiwania.
Plik .indexignore
Należy utworzyć plik .indexignore w katalogu głównym skarbca, aby jawnie wyłączyć ścieżki z indeksu wyszukiwania. Składnia jest zgodna z .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/
Indekser odczytuje ten plik przed skanowaniem i całkowicie pomija pasujące ścieżki. Pliki w wyłączonych ścieżkach nigdy nie są dzielone na fragmenty, nigdy nie są osadzane jako embeddingi i nigdy nie pojawiają się w wynikach wyszukiwania.
Schemat notatki
Każda notatka powinna mieć frontmatter YAML. Retriever wykorzystuje pola frontmatter do filtrowania i wzbogacania kontekstu:
---
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
---
Pola wymagane do wyszukiwania:
title— Używane w wyświetlaniu wyników wyszukiwania i kontekście nagłówków dla BM25type— Umożliwia zapytania filtrowane po typie („pokaż tylko MOC” lub „tylko sygnały”)tags— Indeksowane w kontekście nagłówków FTS5 z wagą 0.3, zapewniając dopasowania słów kluczowych nawet gdy treść używa innej terminologii
Pola opcjonalne, ale wartościowe:
domain— Umożliwia zapytania ograniczone do domeny („szukaj tylko w notatkach o bezpieczeństwie”)source— Atrybucja dla przechwyconych treści; retriever może dołączać źródłowe adresy URL do wynikówstatus— Pozwala wykluczać zarchiwizowane lub szkicowe notatki z aktywnego wyszukiwania
Konwencje dzielenia na fragmenty (chunking)
Retriever dzieli tekst na fragmenty na granicach nagłówków H2 (##). Oznacza to, że struktura notatki bezpośrednio wpływa na granularność wyszukiwania:
Dobre dla wyszukiwania:
## 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...
Trzy sekcje H2 dają trzy niezależnie przeszukiwalne fragmenty. Każdy fragment ma wystarczający kontekst, aby embedding uchwycił jego znaczenie. Zapytanie o „obsługę wygasłych tokenów” dopasowuje się konkretnie do trzeciego fragmentu.
Złe dla wyszukiwania:
# 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...
Jedna długa sekcja bez nagłówków H2 daje jeden duży fragment. Embedding uśrednia się po wszystkich tematach w sekcji. Zapytanie o dowolny podtemat dopasowuje się do całej notatki jednakowo.
Zasada ogólna: Jeśli sekcja obejmuje więcej niż jedno zagadnienie, należy podzielić ją na podsekcje H2. Chunker zajmie się resztą.
Czego nie umieszczać w notatkach
Treści obniżające jakość wyszukiwania:
- Surowe kopie całych artykułów bez adnotacji. Retriever indeksuje słowa kluczowe oryginalnego artykułu, rozmywając skarbiec treściami, których nie napisaliśmy. Lepiej dodać streszczenie, wyodrębnić kluczowe punkty lub podlinkować źródłowy URL.
- Zrzuty ekranu bez opisu tekstowego. Retriever indeksuje tekst markdown. Obraz bez tekstu alternatywnego lub otaczającego opisu jest niewidoczny zarówno dla BM25, jak i wyszukiwania wektorowego.
- Ciągi poświadczeń. Klucze API, tokeny, hasła, ciągi połączeń. Nawet przy filtrowaniu poświadczeń najbezpieczniejszym podejściem jest nigdy nie wklejać sekretów do notatek. Zamiast tego należy odwoływać się do nich po nazwie („token API Cloudflare w
~/.env”). - Automatycznie generowane treści bez kuracji. Jeśli narzędzie generuje notatkę (transkrypcja spotkania, wyróżnienia z Readwise, import RSS), należy ją przejrzeć i opatrzyć adnotacjami przed wprowadzeniem do stałego skarbca. Niekurowane automatyczne importy zwiększają objętość bez dodawania wartości wyszukiwawczej.
Ekosystem wtyczek dla przepływów pracy z AI
Wtyczki Obsidian poprawiające jakość skarbca pod kątem wyszukiwania AI dzielą się na trzy kategorie: strukturalne (wymuszają spójność), zapytaniowe (eksponują metadane) i synchronizacyjne (utrzymują skarbiec w aktualnym stanie).
Niezbędne wtyczki
Dataview. Umożliwia odpytywanie skarbca jak bazy danych z wykorzystaniem pól frontmatter. Pozwala tworzyć dynamiczne indeksy: „wszystkie notatki z tagiem security zaktualizowane w ciągu ostatnich 30 dni” lub „wszystkie notatki projektowe ze statusem active.” Dataview nie wspomaga bezpośrednio wyszukiwania, ale pomaga identyfikować luki w pokryciu skarbca i znajdować notatki wymagające aktualizacji.
TABLE type, domain, updated
FROM "03-resources"
WHERE status = "active"
SORT updated DESC
LIMIT 20
Templater. Tworzy notatki z szablonów z dynamicznymi polami. Zapewnia, że każda nowa notatka zaczyna się od prawidłowego frontmatter poprzez użycie szablonu, który wstępnie wypełnia pola created, type i domain. Spójny frontmatter poprawia filtrowanie wyników wyszukiwania.
<%* /* New Resource Note Template */ %>
---
title: "<% tp.file.cursor() %>"
type: note
domain: <% tp.system.suggester(["programming", "security", "ai-engineering", "design", "devops"], ["programming", "security", "ai-engineering", "design", "devops"]) %>
tags: []
created: <% tp.date.now("YYYY-MM-DD") %>
updated: <% tp.date.now("YYYY-MM-DD") %>
source: ""
status: active
---
## Key Points
## Details
## References
Linter. Wymusza reguły formatowania w całym skarbcu. Spójna hierarchia nagłówków (H1 dla tytułu, H2 dla sekcji, H3 dla podsekcji) zapewnia, że chunker generuje przewidywalne wyniki. Reguły Lintera istotne dla wyszukiwania:
- Inkrementacja nagłówków: wymuszanie sekwencyjnych poziomów nagłówków (bez przeskakiwania z H1 do H3)
- YAML title: dopasowanie do nazwy pliku
- Końcowe spacje: usuwanie (zapobiega artefaktom tokenizacji FTS5)
- Kolejne puste linie: ograniczenie do 1 (czystsze fragmenty)
Integracja z Git. Kontrola wersji dla skarbca. Umożliwia śledzenie zmian w czasie, synchronizację między maszynami i odzyskiwanie po przypadkowym usunięciu. Git dostarcza również dane mtime, które indekser wykorzystuje do przyrostowego wykrywania zmian.
Wtyczki wspierające indeksowanie
Smart Connections. Wtyczka Obsidian zapewniająca semantyczne wyszukiwanie wspierane przez AI bezpośrednio w Obsidian. Smart Connections v4 domyślnie tworzy lokalne embeddingi — po zaindeksowaniu skarbca semantyczne powiązania i wyszukiwanie działają całkowicie offline bez wywołań API.21 Choć system wyszukiwania opisany w tym przewodniku jest zewnętrzny względem Obsidian (działa jako pipeline Python), Smart Connections jest przydatny do eksplorowania semantycznych powiązań podczas pisania. Oba systemy indeksują tę samą treść, ale służą różnym celom: Smart Connections do odkrywania powiązań w edytorze, zewnętrzny retriever do integracji narzędzi AI przez MCP.
Metadata Menu. Zapewnia strukturalną edycję frontmatter z autouzupełnianiem wartości pól. Redukuje literówki w polach type, domain i tags. Spójne metadane poprawiają dokładność filtrowania wyników wyszukiwania.
Wtyczki pogarszające indeksowanie
Excalidraw. Przechowuje rysunki jako JSON osadzone w plikach markdown. JSON jest składniowo poprawnym markdownem, ale po podziale na fragmenty i wygenerowaniu embeddingów daje bezużyteczne wyniki. Pliki Excalidraw należy wykluczyć z indeksu za pomocą .indexignore lub filtrowania po rozszerzeniu pliku.
Kanban. Przechowuje stan tablicy jako specjalnie sformatowany markdown. Format jest zaprojektowany pod renderowanie Kanban, nie pod wyszukiwanie tekstu. Chunker generuje fragmenty tytułów kart i metadanych, które nie tworzą dobrych embeddingów. Tablice Kanban należy wykluczyć z indeksu.
Calendar. Tworzy dzienne notatki z minimalną treścią (często jedynie nagłówek z datą). Puste lub prawie puste notatki generują fragmenty niskiej jakości. Jeśli korzysta się z dziennych notatek, należy umieszczać w nich merytoryczną treść lub wykluczyć folder dziennych notatek z indeksu.
Konfiguracja wtyczek istotna dla indeksowania
Odzyskiwanie plików → Włączone. Chroni przed przypadkowym usunięciem notatek. Nie jest bezpośrednio związane z wyszukiwaniem, ale ma kluczowe znaczenie dla bazy wiedzy, na której się polega.
Ścisłe łamanie wierszy → Wyłączone. Standardowe łamanie wierszy w markdown (podwójny znak nowej linii dla akapitu) generuje czystsze fragmenty niż tryb ścisły Obsidian (pojedynczy znak nowej linii dla <br>).
Domyślna lokalizacja nowych plików → Wyznaczony folder. Kierowanie nowych plików do 00-inbox/, aby nieskategoryzowane notatki nie zaśmiecały folderów domenowych. Skrzynka odbiorcza pełni rolę bufora — pliki trafiają do folderów domenowych po wstępnym przeglądzie.
Format wiki-link → Najkrótsza ścieżka, gdy to możliwe. Krótsze cele linków są łatwiejsze do rozwiązania przez retriever podczas indeksowania struktury powiązań.
Modele embeddingów: wybór i konfiguracja
Model embeddingów przekształca fragmenty tekstu w wektory numeryczne na potrzeby wyszukiwania semantycznego. Wybór modelu determinuje jakość wyszukiwania, rozmiar indeksu, szybkość generowania embeddingów oraz wymagane zależności. Ta sekcja wyjaśnia, dlaczego Model2Vec potion-base-8M jest domyślnym wyborem i kiedy warto rozważyć alternatywy.
Dlaczego Model2Vec potion-base-8M
Model: minishlab/potion-base-8M
Parametry: 7,6 miliona
Wymiary: 256
Rozmiar: ~30 MB
Zależności: model2vec (tylko numpy, bez PyTorch)
Wnioskowanie: wyłącznie CPU, statyczne embeddingi słów (brak warstw atencji)
Model2Vec destyluje wiedzę z transformera zdaniowego do statycznych embeddingów tokenów. Zamiast uruchamiać warstwy atencji na danych wejściowych (jak robią to BERT, MiniLM i inne modele transformerowe), Model2Vec generuje wektory poprzez średnią ważoną wstępnie obliczonych embeddingów tokenów.3 Praktyczna konsekwencja: szybkość generowania embeddingów jest 50-500x większa niż w modelach opartych na transformerach, ponieważ nie występują obliczenia sekwencyjne.
W zestawie benchmarków MTEB potion-base-8M osiąga 89% wydajności all-MiniLM-L6-v2 (50,03 vs 56,09 średnio).4 11-procentowa różnica jakości to kompromis na rzecz szybkości i prostoty. W przypadku krótkich fragmentów markdown (średnio 200-400 słów w typowym sejfie) różnica jakościowa jest mniej widoczna niż przy dłuższych dokumentach, ponieważ oba modele generują zbliżone reprezentacje dla krótkich, skoncentrowanych tekstów.
Konfiguracja
# 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]
Leniwe ładowanie. Model jest ładowany przy pierwszym użyciu, nie podczas importu. Zaimportowanie modułu embeddera nie generuje żadnych kosztów, gdy retriever działa w trybie awaryjnym BM25 (np. gdy środowisko wirtualne z embeddingami nie jest zainstalowane).
Izolowane środowisko wirtualne. Model działa w dedykowanym venv (np. ~/.claude/venvs/memory/), aby uniknąć konfliktów zależności z pozostałymi narzędziami. Funkcja _activate_venv() dodaje site-packages środowiska wirtualnego do sys.path w czasie wykonywania.
# Create isolated venv
python3 -m venv ~/.claude/venvs/memory
~/.claude/venvs/memory/bin/pip install model2vec
Przetwarzanie wsadowe. Embedder przetwarza teksty w partiach po 64, aby zamortyzować narzut Model2Vec. Indekser przekazuje fragmenty do embed_batch() zamiast generować embedding dla każdego fragmentu osobno.
Kiedy wybrać alternatywy
| Model | Wym. | Rozmiar | Szybkość | Jakość (MTEB) | Najlepszy do |
|---|---|---|---|---|---|
| potion-base-8M | 256 | 30 MB | 500x | 50,03 | Domyślny: lokalny, szybki, bez GPU |
| potion-base-32M | 256 | 120 MB | 400x | 52,46 | Wyższa jakość, nadal statyczny |
| potion-retrieval-32M | 256 | 120 MB | 400x | 36,35 (retrieval) | Zoptymalizowany pod retrieval, statyczny |
| all-MiniLM-L6-v2 | 384 | 80 MB | 1x | 56,09 | Wyższa jakość, nadal lokalny |
| nomic-embed-text-v1.5 | 768 | 270 MB | 0,5x | 62,28 | Najlepsza jakość lokalna |
| text-embedding-3-small | 1536 | API | N/A | 62,30 | Oparty na API, najwyższa jakość |
Wybierz potion-base-32M, gdy zależy Ci na lepszej jakości niż potion-base-8M, ale nie chcesz rezygnować z rodziny statycznych embeddingów. Wydany w styczniu 2025, wykorzystuje większy słownik zdestylowany z baai/bge-base-en-v1.5, osiągając 52,46 średnio w MTEB (5% poprawy względem potion-base-8M) przy zachowaniu tego samego 256-wymiarowego wyjścia i zależności wyłącznie od numpy.18 Czterokrotnie większy plik modelu zwiększa zużycie pamięci, ale szybkość generowania embeddingów pozostaje o rzędy wielkości wyższa niż w modelach transformerowych.
Wybierz potion-retrieval-32M, gdy głównym zastosowaniem jest wyszukiwanie (a właśnie tym jest przeszukiwanie sejfu). Ten wariant jest dostrojony na podstawie potion-base-32M specjalnie pod kątem zadań wyszukiwania, osiągając 36,35 w benchmarkach retrieval MTEB w porównaniu z 33,52 dla modelu bazowego.18 Ogólna średnia MTEB spada do 49,73, ponieważ dostrajanie zamienia wydajność ogólnego przeznaczenia na zyski specyficzne dla wyszukiwania.
Wybierz all-MiniLM-L6-v2, gdy jakość wyszukiwania jest ważniejsza niż szybkość i masz zainstalowany PyTorch. 384-wymiarowe wektory zwiększają rozmiar bazy SQLite o ~50% w porównaniu z wektorami 256-wymiarowymi. Szybkość generowania embeddingów spada z poniżej 1 minuty do ~10 minut przy pełnym reindeksowaniu 15 000 plików na sprzęcie z serii M.
Wybierz nomic-embed-text-v1.5, gdy potrzebujesz najlepszej możliwej jakości wyszukiwania lokalnego i akceptujesz wolniejsze indeksowanie. 768-wymiarowe wektory mniej więcej trzykrotnie zwiększają rozmiar bazy danych. Wymaga PyTorch oraz nowoczesnego procesora lub GPU.
Wybierz text-embedding-3-small, gdy opóźnienie sieciowe i prywatność są akceptowalnymi kompromisami. API generuje embeddingi o najwyższej jakości, ale wprowadza zależność od chmury, koszt za token (0,02 USD/milion tokenów) i przesyła treści na serwery OpenAI.
Pozostań przy potion-base-8M we wszystkich pozostałych przypadkach. Przewaga szybkości jest kluczowa dla iteracyjnego indeksowania (reindeksacja podczas rozwoju), zależność wyłącznie od numpy eliminuje złożoność instalacji PyTorch, a 256-wymiarowe wektory utrzymują kompaktowy rozmiar bazy danych.
Kwantyzacja i redukcja wymiarowości
Model2Vec w wersji 0.5.0 i nowszych obsługuje ładowanie modeli z obniżoną precyzją i liczbą wymiarów.18 Jest to przydatne przy wdrożeniach na ograniczonym sprzęcie lub redukcji rozmiaru bazy danych bez zmiany modelu:
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)
Skwantyzowane modele zachowują niemal identyczną jakość wyszukiwania przy ułamku pierwotnego zużycia pamięci. Redukcja wymiarowości opiera się na obcinaniu w stylu Matrioszki — pierwsze N wymiarów niesie najwięcej informacji. Redukcja z 256 do 128 wymiarów zmniejsza o połowę przestrzeń dyskową wektorów przy minimalnej utracie jakości wyszukiwania krótkich tekstów.
Od maja 2025 Model2Vec obsługuje również tokenizery BPE i Unigram (oprócz WordPiece), co poszerza zestaw transformerów zdaniowych, które można destylować do modeli statycznych.20
Dostrajanie embeddingów specyficznych dla sejfu
Model2Vec w wersji 0.4.0 i nowszych umożliwia trenowanie niestandardowych modeli klasyfikacyjnych na bazie statycznych embeddingów, a wersja 0.7.0 dodaje kwantyzację słownika i konfigurowalny pooling dla destylacji.20 Ma to znaczenie dla sejfów ze specjalistycznym słownictwem (notatki medyczne, odniesienia prawne, żargon branżowy), gdzie domyślne modele potion mogą nie uchwycić niuansów semantycznych:
from model2vec import StaticModel
from model2vec.train import train_model
# Fine-tune on vault-specific data
model = StaticModel.from_pretrained("minishlab/potion-base-8M")
trained_model = train_model(model, train_texts, train_labels)
trained_model.save_pretrained("./vault-embeddings")
W przypadku większości sejfów domyślny potion-base-8M zapewnia wystarczającą jakość wyszukiwania. Dostrajanie jest opłacalne tylko wtedy, gdy wyszukiwanie systematycznie pomija powiązania specyficzne dla danej dziedziny, których model ogólnego przeznaczenia nie jest w stanie uchwycić.
Śledzenie hasza modelu
Indekser przechowuje hasz obliczony na podstawie nazwy modelu i rozmiaru słownika. Jeśli zmienisz model embeddingów, indekser wykryje niezgodność przy następnym uruchomieniu przyrostowym i automatycznie uruchomi pełne reindeksowanie.
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]
Zapobiega to mieszaniu wektorów z różnych modeli w tej samej bazie danych, co prowadziłoby do bezsensownych wyników cosine similarity.
Typowe problemy
Błąd pobierania modelu. Przy pierwszym uruchomieniu model jest pobierany z Hugging Face. Jeśli pobieranie się nie powiedzie (problem z siecią, zapora korporacyjna), retriever przełącza się w tryb wyłącznie BM25. Model jest buforowany lokalnie po pierwszym pobraniu.
Niezgodność wymiarów. Jeśli zmienisz model bez wyczyszczenia bazy danych, przechowywane wektory mają inny wymiar niż nowe embeddingi. Indekser wykrywa to za pomocą hasza modelu i uruchamia pełne reindeksowanie. Jeśli sprawdzanie hasza zawiedzie (niestandardowy model bez prawidłowego hasza), sqlite-vec zgłosi błąd przy zapytaniach KNN z niezgodnymi wymiarami.
Obciążenie pamięci przy dużych sejfach. Generowanie embeddingów dla ponad 50 000 fragmentów w jednej partii może zużyć znaczną ilość pamięci. Indekser przetwarza dane w partiach po 64, aby ograniczyć szczytowe zużycie pamięci. Jeśli pamięć nadal stanowi problem, należy zmniejszyć rozmiar partii.
Wyszukiwanie pełnotekstowe z FTS5
Rozszerzenie FTS5 w SQLite zapewnia wyszukiwanie pełnotekstowe z rankingiem BM25. FTS5 jest komponentem wyszukiwania słów kluczowych w potoku hybrydowego wyszukiwania (hybrid retrieval). Ta sekcja opisuje konfigurację FTS5, sytuacje, w których BM25 sprawdza się najlepiej, oraz jego specyficzne ograniczenia.
Wirtualna tabela FTS5
CREATE VIRTUAL TABLE chunks_fts USING fts5(
chunk_text,
section,
heading_context,
content=chunks,
content_rowid=id
);
Tryb synchronizacji z tabelą źródłową. Parametr content=chunks informuje FTS5, że ma odwoływać się bezpośrednio do tabeli chunks, zamiast przechowywać duplikat tekstu. Zmniejsza to wymagania dotyczące pamięci o połowę, ale oznacza, że FTS5 wymaga ręcznej synchronizacji przy wstawianiu, aktualizowaniu lub usuwaniu fragmentów.
Kolumny. Indeksowane są trzy kolumny:
- chunk_text — Główna treść każdego fragmentu (waga BM25: 1.0)
- section — Tekst nagłówka H2 (waga BM25: 0.5)
- heading_context — Tytuł notatki, tagi i metadane (waga BM25: 0.3)
Ranking BM25
BM25 ocenia dokumenty na podstawie częstotliwości terminu, odwrotnej częstotliwości dokumentowej oraz normalizacji długości dokumentu. Funkcja pomocnicza bm25() w FTS5 przyjmuje wagi dla poszczególnych kolumn:
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;
Wagi kolumn (1.0, 0.5, 0.3) oznaczają:
- Dopasowanie słowa kluczowego w chunk_text ma największy wpływ na wynik
- Dopasowanie w section (nagłówek) ma o połowę mniejszy wpływ
- Dopasowanie w heading_context (tytuł, tagi) ma 30% wpływu
Wagi te można dostosować. Jeśli w skarbcu (vault) nagłówki dobrze opisują treść, warto zwiększyć wagę section. Jeśli tagi są kompleksowe i dokładne, warto zwiększyć wagę heading_context.
Kiedy BM25 wygrywa
BM25 sprawdza się doskonale w zapytaniach zawierających dokładne identyfikatory:
- Nazwy funkcji:
_rrf_fuse,embed_batch,get_stale_files - CLI flagi:
--incremental,--vault,--model - Klucze konfiguracyjne:
bm25_weight,max_tokens,batch_size - Komunikaty błędów:
SQLITE_LOCKED,ConnectionRefusedError - Specyficzne terminy techniczne:
PostToolUse,PreToolUse,AGENTS.md
W przypadku takich zapytań BM25 natychmiast znajduje dokładne dopasowanie. Wyszukiwanie wektorowe zwróciłoby semantycznie powiązane treści, ale mogłoby umieścić dokładne dopasowanie niżej w rankingu niż dyskusję koncepcyjną.
Kiedy BM25 zawodzi
BM25 zawodzi w przypadku zapytań używających innej terminologii niż przechowywana treść:
- Zapytanie: „how to handle authentication failures” → Skarbiec zawiera notatki o „login error recovery” i „session expiration handling”. BM25 nie znajduje dopasowania, ponieważ słowa kluczowe się różnią.
- Zapytanie: „what is the best way to manage state” → Skarbiec zawiera notatki o „Redux store patterns” i „context providers”. BM25 nie znajduje dopasowania, ponieważ „zarządzanie stanem” wyrażone jest poprzez nazwy konkretnych technologii.
BM25 zawodzi również przy kolizji słów kluczowych na dużą skalę. W skarbcu zawierającym 15 000 plików wyszukiwanie słowa „configuration” zwraca setki notatek, ponieważ niemal każda notatka projektowa wspomina o konfiguracji. Wyniki są technicznie poprawne, ale praktycznie bezużyteczne — ranking nie jest w stanie określić, która notatka o „konfiguracji” jest istotna dla bieżącego zapytania.
Tokenizer FTS5
FTS5 domyślnie używa tokenizera unicode61, który obsługuje tekst ASCII i Unicode. W przypadku skarbców z dużą ilością treści w językach CJK (chiński, japoński, koreański) warto rozważyć tokenizer trigram:
-- For CJK-heavy vaults
CREATE VIRTUAL TABLE chunks_fts USING fts5(
chunk_text, section, heading_context,
content=chunks, content_rowid=id,
tokenize='trigram'
);
Domyślny tokenizer unicode61 dzieli tekst na granicach wyrazów, co działa źle w przypadku języków bez spacji między słowami. Tokenizer trigram dzieli na trzyznakowe sekwencje, umożliwiając dopasowanie podciągów kosztem rozmiaru indeksu (około 3 razy większy).
Konserwacja
FTS5 wymaga jawnej synchronizacji przy zmianach w bazowej tabeli chunks:
# After inserting chunks
cursor.execute("""
INSERT INTO chunks_fts(chunks_fts)
VALUES('rebuild')
""")
Polecenie rebuild rekonstruuje indeks FTS5 na podstawie tabeli źródłowej. Należy je uruchamiać po masowym wstawianiu danych (pełna reindeksacja), ale nie po pojedynczych aktualizacjach przyrostowych — w takim przypadku należy użyć INSERT INTO chunks_fts(rowid, chunk_text, section, heading_context) do synchronizacji pojedynczych wierszy.
Wyszukiwanie wektorowe z sqlite-vec
Rozszerzenie sqlite-vec wprowadza wyszukiwanie wektorowe KNN (K-Nearest Neighbors) do SQLite. Ta sekcja opisuje konfigurację sqlite-vec, potok osadzania (embedding pipeline) od notatki do przeszukiwalnego wektora oraz konkretne wzorce zapytań.
Wirtualna tabela sqlite-vec
CREATE VIRTUAL TABLE chunk_vecs USING vec0(
id INTEGER PRIMARY KEY,
embedding float[256]
);
Moduł vec0 przechowuje 256-wymiarowe wektory zmiennoprzecinkowe jako spakowane dane binarne. Kolumna id mapuje się 1:1 na tabelę chunks, umożliwiając złączenia między wynikami wektorowymi a metadanymi fragmentów.
Potok osadzania
Potok prowadzi od notatki do przeszukiwalnego wektora:
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
Serializacja wektorów
Moduł struct w Python serializuje wektory zmiennoprzecinkowe do formatu przechowywanego przez sqlite-vec:
import struct
def _serialize_vector(vec):
"""Pack float list into binary for sqlite-vec."""
return struct.pack(f"{len(vec)}f", *vec)
def _deserialize_vector(blob, dim=256):
"""Unpack binary blob to float list."""
return list(struct.unpack(f"{dim}f", blob))
Zapytanie KNN
Zapytanie wyszukiwania wektorowego osadza tekst zapytania, a następnie znajduje K najbliższych fragmentów na podstawie odległości cosinusowej:
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
Operator MATCH w sqlite-vec wykonuje przybliżone wyszukiwanie najbliższego sąsiada. Parametr k określa liczbę zwracanych wyników. Kolumna distance zawiera odległość cosinusową (0 = identyczne, 2 = przeciwne).
Kiedy wyszukiwanie wektorowe wygrywa
Wyszukiwanie wektorowe sprawdza się doskonale w zapytaniach, w których liczy się koncepcja, a nie konkretne słowa:
- Zapytanie: „how to handle authentication failures” → Znajduje notatki o „login error recovery” (ta sama przestrzeń semantyczna, inne słowa kluczowe)
- Zapytanie: „what patterns exist for caching” → Znajduje notatki o „memoization”, „Redis TTL strategies” i „HTTP cache headers” (powiązane koncepcje, różnorodna terminologia)
- Zapytanie: „approaches to testing asynchronous code” → Znajduje notatki o „pytest-asyncio fixtures”, „mock event loops” i „async test patterns” (ta sama koncepcja wyrażona poprzez szczegóły implementacyjne)
Kiedy wyszukiwanie wektorowe zawodzi
Wyszukiwanie wektorowe radzi sobie słabo z dokładnymi identyfikatorami:
- Zapytanie:
_rrf_fuse→ Zwraca notatki o „fusion algorithms” i „rank merging”, ale może umieścić faktyczną definicję funkcji niżej w rankingu niż dyskusje koncepcyjne - Zapytanie:
PostToolUse→ Zwraca notatki o „tool lifecycle hooks” i „post-execution handlers” zamiast konkretnej nazwy hooka
Wyszukiwanie wektorowe radzi sobie również słabo z danymi strukturalnymi. Pliki konfiguracyjne JSON, bloki YAML i fragmenty kodu generują osadzenia (embeddings), które wychwytują wzorce strukturalne, a nie znaczenie semantyczne. Plik JSON z "review": true jest osadzany inaczej niż prozaiczny opis przeglądu kodu.
Łagodna degradacja
Jeśli sqlite-vec nie załaduje się poprawnie (brak rozszerzenia, niekompatybilna platforma, uszkodzona biblioteka), mechanizm wyszukiwania przełącza się na tryb wyłącznie BM25:
class VectorIndex:
def __init__(self, db_path):
self.db = sqlite3.connect(db_path)
self._vec_available = False
try:
self.db.enable_load_extension(True)
self.db.load_extension("vec0")
self._vec_available = True
except Exception:
pass # BM25-only mode
@property
def vec_available(self):
return self._vec_available
Mechanizm wyszukiwania sprawdza vec_available przed próbą wykonania zapytań wektorowych. W przypadku niedostępności rozszerzenia wszystkie wyszukiwania korzystają wyłącznie z BM25, a krok fuzji RRF jest pomijany.
Reciprocal Rank Fusion (RRF)
RRF łączy dwie uszeregowane listy bez konieczności kalibracji wyników. Ta sekcja obejmuje algorytm, śledzenie przykładowego zapytania, dostrajanie parametru k oraz powody wyboru RRF zamiast alternatywnych metod. Interaktywny kalkulator z edytowalnymi rangami, predefiniowanymi scenariuszami i wizualnym eksploratorem architektury znajduje się w szczegółowym omówieniu hybrid retrieval.
Algorytm
RRF przypisuje każdemu dokumentowi wynik oparty wyłącznie na jego pozycji rankingowej w każdej liście:
score(d) = Σ (weight_i / (k + rank_i))
Gdzie:
- k to stała wygładzająca (60, zgodnie z Cormack i in.1)
- rank_i to pozycja dokumentu (liczona od 1) na liście wyników i
- weight_i to opcjonalny mnożnik dla danej listy (domyślnie 1.0)
Dokumenty, które zajmują wysokie pozycje w wielu listach, otrzymują wyższe wyniki po fuzji. Dokumenty pojawiające się tylko na jednej liście otrzymują wynik wyłącznie z tego jednego źródła.
Dlaczego RRF zamiast alternatyw
Ważona kombinacja liniowa wymaga kalibracji wyników BM25 względem odległości cosinusowych. Wyniki BM25 są nieograniczone i skalują się z rozmiarem korpusu. Odległości cosinusowe są ograniczone do przedziału [0, 2]. Ich łączenie wymaga normalizacji, a parametry normalizacji zależą od zbioru danych. RRF wykorzystuje wyłącznie pozycje rankingowe, które zawsze są liczbami całkowitymi zaczynającymi się od 1, niezależnie od metody punktacji.
Wyuczone modele fuzji wymagają oznakowanych danych treningowych — par zapytanie-dokument z oceną trafności. W przypadku osobistej bazy wiedzy takie dane treningowe nie istnieją. Konieczne byłoby ręczne ocenienie setek par zapytanie-dokument, aby wytrenować użyteczny model. RRF działa bez jakichkolwiek danych treningowych.
Metody głosowania Condorceta (metoda Bordy, metoda Schulzego) są teoretycznie eleganckie, ale bardziej złożone w implementacji i dostrajaniu. Oryginalna praca o RRF wykazała, że RRF przewyższa metody Condorceta na danych ewaluacyjnych TREC.1
Fuzja w praktyce
Zapytanie: „how does the review aggregator handle disagreements”
BM25 umieszcza review-aggregator.py na pozycji 3 (dokładne dopasowania słów kluczowych „review”, „aggregator”, „disagreements”), ale wyżej plasuje dwa pliki konfiguracyjne (częściej zawierają słowo „review”). Wyszukiwanie wektorowe umieszcza ten sam fragment na pozycji 1 (dopasowanie semantyczne dotyczące rozwiązywania konfliktów). Po fuzji RRF:
| Fragment | BM25 | Vec | Wynik fuzji |
|---|---|---|---|
| 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 |
Fragmenty zajmujące wysokie pozycje w obu listach trafiają na szczyt. Fragmenty pojawiające się tylko na jednej liście otrzymują wynik z jednego źródła i spadają poniżej wyników o podwójnym rankingu. Właściwa logika rozwiązywania sporów wygrywa, ponieważ obie metody ją znalazły — BM25 przez słowa kluczowe, wyszukiwanie wektorowe przez semantykę.
Pełne śledzenie krok po kroku z obliczeniami RRF dla każdej rangi oraz możliwością wypróbowania różnych wartości k znajduje się w interaktywnym kalkulatorze RRF.
Implementacja
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
Dostrajanie k
Stała k kontroluje, jak duża waga jest przypisywana wynikom z czołowych pozycji w porównaniu z wynikami z niższych pozycji:
- Niższe k (np. 10): Dominują wyniki z czołowych pozycji. Ranga 1 daje wynik 1/11 = 0,091, ranga 10 daje wynik 1/20 = 0,050 (różnica 1,8x). Dobre rozwiązanie, gdy poszczególne metody rankingowe trafnie wskazują najlepszy wynik.
- Domyślne k (60): Zrównoważone. Ranga 1 daje wynik 1/61 = 0,0164, ranga 10 daje wynik 1/70 = 0,0143 (różnica 1,15x). Różnice rang są skompresowane, co nadaje większą wagę obecności na wielu listach.
- Wyższe k (np. 200): Obecność na obu listach ma znacznie większe znaczenie niż pozycja rankingowa. Ranga 1 daje wynik 1/201, ranga 10 daje wynik 1/210 — niemal identyczne. Stosowane, gdy poszczególne metody rankingowe generują zaszumione rankingi, ale zgodność między listami jest wiarygodna.
Należy zacząć od k=60. Oryginalna praca o RRF wykazała, że ta wartość jest odporna na zróżnicowanych zbiorach danych TREC. Dostrajanie powinno nastąpić dopiero po zmierzeniu przypadków niepowodzeń na własnym rozkładzie zapytań.
Rozstrzyganie remisów
Gdy dwa fragmenty mają identyczne wyniki RRF (rzadkie, ale możliwe przy tej samej randze na jednej liście i braku obecności na drugiej), remisy rozstrzyga się następująco:
- Preferowane są fragmenty obecne na obu listach nad fragmentami obecnymi tylko na jednej
- Wśród fragmentów na obu listach preferowany jest ten z niższą łączną rangą
- Wśród fragmentów na tylko jednej liście preferowany jest ten z niższą rangą na tej liście
Kompletny potok wyszukiwania
Ta sekcja opisuje ścieżkę zapytania od wejścia do wyjścia przez cały potok: wyszukiwanie BM25, wyszukiwanie wektorowe, fuzja RRF, obcinanie budżetu tokenów i składanie kontekstu.
Przepływ od początku do końca
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
Całkowite opóźnienie: ~23ms dla bazy danych zawierającej 49 746 fragmentów na sprzęcie Apple M3 Pro.
API wyszukiwania
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
Obcinanie budżetu tokenów
Parametr max_tokens zapobiega zwracaniu przez retriever większej ilości kontekstu, niż narzędzie AI jest w stanie przetworzyć. Szacunek wykorzystuje 4 znaki na token (rozsądne przybliżenie dla prozy w języku angielskim). Wyniki są obcinane zachłannie: dodawane w kolejności rankingowej do momentu wyczerpania budżetu.
Jest to strategia konserwatywna. Bardziej zaawansowane podejście uwzględniałoby oceny jakości poszczególnych wyników i preferowałoby krótsze, wyższej jakości wyniki nad dłuższymi, niższej jakości. Podejście zachłanne jest prostsze i sprawdza się dobrze w praktyce, ponieważ ranking RRF już porządkuje wyniki według trafności.
Schemat bazy danych (kompletny)
-- 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
);
Ścieżka łagodnej degradacji
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
Retriever sprawdza dostępne możliwości podczas inicjalizacji i dostosowuje strategię zapytań. Brak komponentu obniża jakość, ale nie powoduje błędów. Jedynym krytycznym błędem jest brak pliku bazy danych.
Statystyki produkcyjne
Pomiary wykonane na skarbcu (vault) zawierającym 16 894 pliki, 49 746 fragmentów, bazę danych SQLite o rozmiarze 83 MB, na Apple M3 Pro:
| Metryka | Wartość |
|---|---|
| Łączna liczba plików | 16 894 |
| Łączna liczba fragmentów | 49 746 |
| Rozmiar bazy danych | 83 MB |
| Opóźnienie zapytania BM25 (p50) | 12ms |
| Opóźnienie zapytania wektorowego (p50) | 8ms |
| Opóźnienie fuzji RRF | 3ms |
| Opóźnienie wyszukiwania od początku do końca (p50) | 23ms |
| Czas pełnej reindeksacji | ~4 minuty |
| Czas przyrostowej reindeksacji | <10 sekund |
| Model embeddingów | potion-base-8M (256-dim) |
| Pula kandydatów BM25 | 30 |
| Pula kandydatów wektorowych | 30 |
| Domyślny limit wyników | 10 |
| Domyślny budżet tokenów | 4 000 tokenów |
Haszowanie treści i wykrywanie zmian
Indekser musi wiedzieć, które pliki zmieniły się od ostatniego uruchomienia indeksowania. Ta sekcja opisuje mechanizm wykrywania zmian oraz strategię haszowania.
Porównanie czasu modyfikacji plików
Indekser przechowuje mtime_ns (czas modyfikacji pliku w nanosekundach) dla każdego fragmentu w tabeli chunks. Podczas przyrostowego uruchomienia indekser:
- Skanuje skarbiec (vault) w poszukiwaniu wszystkich plików
.mdw dozwolonych folderach - Odczytuje
mtime_nskażdego pliku z systemu plików - Porównuje z zapisanym
mtime_nsw bazie danych - Identyfikuje trzy kategorie:
- Nowe pliki: ścieżka istnieje w systemie plików, ale nie w bazie danych
- Zmienione pliki: ścieżka istnieje w obu, ale
mtime_nssię różni - Usunięte pliki: ścieżka istnieje w bazie danych, ale nie w systemie plików
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)
Dlaczego mtime, a nie hasz treści
Haszowanie treści (SHA-256 zawartości pliku) byłoby bardziej niezawodne niż porównywanie mtime — wykrywałoby przypadki, gdy plik został „dotknięty” bez zmiany (np. git checkout przywracający oryginalny mtime). Jednak haszowanie wymaga odczytu każdego pliku przy każdym przyrostowym uruchomieniu. Dla 16 894 plików odczyt treści zajmuje 2–3 sekundy. Odczyt mtime z systemu plików zajmuje <100ms.
Kompromis: porównywanie mtime czasami powoduje niepotrzebne ponowne indeksowanie niezmienionych plików (fałszywe pozytywy), ale nigdy nie pomija rzeczywistych zmian. Fałszywe pozytywy kosztują kilka dodatkowych wywołań embeddingów na uruchomienie. Różnica w szybkości (100ms vs 3 sekundy) czyni mtime pragmatycznym wyborem dla systemu uruchamianego przy każdej interakcji z AI.
Obsługa usunięć
Gdy plik zostaje usunięty ze skarbca (vault), indekser usuwa wszystkie jego fragmenty z bazy danych:
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],
)
Tabele FTS5 zsynchronizowane z treścią wymagają jawnego usunięcia za pomocą INSERT INTO chunks_fts(chunks_fts, rowid, ...) VALUES('delete', ?, ...) dla każdego usuwanego wiersza. Indekser obsługuje to w ramach procesu usuwania pliku.
Indeksowanie przyrostowe a pełna reindeksacja
Indekser obsługuje dwa tryby: przyrostowy (szybki, do codziennego użytku) oraz pełny (wolniejszy, okazjonalny). Ta sekcja opisuje, kiedy stosować każdy z nich, gwarancje idempotentności oraz odzyskiwanie po uszkodzeniu bazy.
Indeksowanie przyrostowe
Kiedy stosować: Codzienne indeksowanie po edycji notatek. Tryb domyślny.
Jak działa: 1. Skanuje sejf w poszukiwaniu zmian w plikach (porównanie mtime) 2. Usuwa fragmenty (chunks) dla skasowanych plików 3. Ponownie dzieli i generuje embeddings dla zmienionych plików 4. Wstawia nowe fragmenty dla nowych plików 5. Synchronizuje indeks FTS5
Typowy czas trwania: <10 sekund dla dziennych edycji w sejfie liczącym 16 000 plików.
python index_vault.py --incremental
Pełna reindeksacja
Kiedy stosować: - Po zmianie modelu embeddingów (wykryto niezgodność hasza modelu) - Po migracji schematu (nowe kolumny, zmienione indeksy) - Po uszkodzeniu bazy danych (test integralności kończy się błędem) - Gdy indeksowanie przyrostowe daje nieoczekiwane wyniki
Jak działa: 1. Usuwa wszystkie istniejące dane (fragmenty, wektory, wpisy FTS5) 2. Skanuje cały sejf 3. Dzieli wszystkie pliki na fragmenty 4. Generuje embeddings dla wszystkich fragmentów 5. Buduje indeks FTS5 od podstaw
Typowy czas trwania: ~4 minuty dla 16 894 plików na Apple M3 Pro.
python index_vault.py --full
Idempotentność
Oba tryby są idempotentne: dwukrotne uruchomienie tego samego polecenia daje identyczny wynik. Indekser usuwa istniejące fragmenty dla danego pliku przed wstawieniem nowych, więc ponowne uruchomienie indeksowania przyrostowego na aktualnej bazie danych nie wprowadza żadnych zmian. Ponowne uruchomienie pełnej reindeksacji tworzy identyczną bazę danych.
Odzyskiwanie po uszkodzeniu
Jeśli baza danych SQLite ulegnie uszkodzeniu (utrata zasilania podczas zapisu, błąd dysku, przerwanie procesu w trakcie transakcji):
# Check integrity
sqlite3 vectors.db "PRAGMA integrity_check;"
# If corruption detected, full reindex rebuilds from source files
python index_vault.py --full
Źródłem prawdy są zawsze pliki w sejfie, nie baza danych. Baza danych jest artefaktem pochodnym, który można odbudować w dowolnym momencie. To kluczowa właściwość projektu: nigdy nie trzeba tworzyć kopii zapasowej bazy danych.
Flaga --incremental
Gdy indekser uruchamiany jest z flagą --incremental:
- Sprawdzenie hasza modelu. Porównuje zapisany hasz modelu z aktualnym. Jeśli różnią się, automatycznie przełącza na tryb pełnej reindeksacji i ostrzega użytkownika.
- Skanowanie plików. Przechodzi przez dozwolone foldery, zbierając ścieżki plików i znaczniki czasu mtime.
- Wykrywanie zmian. Porównuje ze zapisanymi danymi.
- Przetwarzanie wsadowe. Ponownie dzieli i generuje embeddings dla zmienionych plików w partiach po 64.
- Raportowanie postępu. Wyświetla liczbę przetworzonych plików i czas, który upłynął.
- Łagodne zamknięcie. Obsługuje SIGINT, kończąc przetwarzanie bieżącego pliku przed zatrzymaniem.
Filtrowanie poświadczeń i granice danych
Osobiste notatki zawierają tajne dane: klucze API, tokeny bearer, ciągi połączeń z bazami danych, klucze prywatne wklejone podczas sesji debugowania. Filtr poświadczeń zapobiega ich przedostawaniu się do indeksu wyszukiwania.
Problem
Notatka o debugowaniu integracji z OAuth może zawierać:
The token was: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
I used this curl command:
curl -H "Authorization: Bearer sk-ant-api03-abc123..."
Bez filtrowania zarówno JWT, jak i klucz API zostałyby podzielone na fragmenty, przekształcone w embeddings i zapisane w bazie danych. Wyszukiwanie hasła „uwierzytelnianie” zwróciłoby fragment zawierający prawdziwe tajne dane. Co gorsza, jeśli moduł wyszukiwania przekazuje wyniki do narzędzia AI przez MCP, tajne dane pojawiają się w oknie kontekstu AI i potencjalnie w logach narzędzia.
Filtrowanie oparte na wzorcach
Filtr poświadczeń uruchamiany jest na każdym fragmencie przed zapisem, dopasowując 25 wzorców specyficznych dla dostawców oraz wzorce ogólne:
Wzorce specyficzne dla dostawców:
| Wzorzec | Przykład | Regex |
|---|---|---|
| Klucz API OpenAI | sk-... |
sk-[a-zA-Z0-9_-]{20,} |
| Klucz API Anthropic | sk-ant-api03-... |
sk-ant-api\d{2}-[a-zA-Z0-9_-]{20,} |
| PAT GitHub | ghp_... |
gh[ps]_[a-zA-Z0-9]{36,} |
| Klucz dostępu AWS | AKIA... |
AKIA[0-9A-Z]{16} |
| Klucz Stripe | sk_live_... |
[sr]k_(live\|test)_[a-zA-Z0-9]{24,} |
| Token Cloudflare | ... |
Różne wzorce |
Wzorce ogólne:
| Wzorzec | Wykrywanie |
|---|---|
| Tokeny JWT | eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+ |
| Tokeny Bearer | Bearer\s+[a-zA-Z0-9_\-\.]+ |
| Klucze prywatne | -----BEGIN (RSA\|EC\|OPENSSH) PRIVATE KEY----- |
| Ciągi base64 o wysokiej entropii | Ciągi z entropią >4,5 bita/znak, ponad 40 znaków |
| Przypisania haseł | password\s*[:=]\s*["'][^"']+["'] |
Implementacja filtra
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
Kluczowe decyzje projektowe:
-
Filtrowanie przed generowaniem embeddingów. Oczyszczony tekst jest tym, co podlega osadzeniu (embedding). Reprezentacja wektorowa nigdy nie koduje wzorców poświadczeń. Zapytanie o „klucz API” zwraca notatki omawiające zarządzanie kluczami API, a nie notatki zawierające rzeczywiste klucze.
-
Zastępowanie zamiast usuwania. Token
[REDACTED:pattern-name]zachowuje kontekst semantyczny otaczającego tekstu. Embedding oddaje fakt, że „w tym miejscu znajdowało się coś przypominającego poświadczenie”, bez kodowania samego poświadczenia. -
Logowanie wzorców, nie wartości. Filtr rejestruje, które wzorce zostały dopasowane (np. „Wyczyszczono 2 poświadczenia z oauth-debug.md [jwt, bearer-token]”), ale nigdy nie loguje wartości poświadczenia.
Wykluczanie na podstawie ścieżek
Plik .indexignore zapewnia wykluczanie ogólne na poziomie ścieżek. Filtr poświadczeń zapewnia precyzyjne oczyszczanie wewnątrz indeksowanych plików. Oba mechanizmy są niezbędne:
.indexignoredla całych folderów, o których wiadomo, że zawierają treści wrażliwe (notatki zdrowotne, dokumenty finansowe, dokumenty kadrowe)- Filtr poświadczeń dla tajnych danych przypadkowo osadzonych w treściach, które poza tym powinny być indeksowane
Klasyfikacja danych
W przypadku sejfów zawierających zróżnicowane treści warto rozważyć klasyfikację notatek według poziomu wrażliwości:
| Poziom | Przykłady | Indeksować? | Filtrować? |
|---|---|---|---|
| Publiczny | Szkice blogów, notatki techniczne | Tak | Tak |
| Wewnętrzny | Plany projektów, decyzje architektoniczne | Tak | Tak |
| Wrażliwy | Dane o wynagrodzeniach, dokumentacja medyczna | Nie (.indexignore) | Nie dotyczy |
| Zastrzeżony | Poświadczenia, klucze prywatne | Nie (.indexignore) | Nie dotyczy |
Architektura serwera MCP
Model Context Protocol (MCP) udostępnia system wyszukiwania jako narzędzie, które agenci AI mogą wywoływać. Ta sekcja obejmuje projekt serwera, zakres funkcjonalności oraz granice uprawnień.
Wybór protokołu: STDIO vs HTTP
MCP obsługuje dwa tryby transportu:
STDIO — Narzędzie AI uruchamia serwer MCP jako proces potomny i komunikuje się przez stdin/stdout. Jest to standardowy tryb dla narzędzi lokalnych. Claude Code, Codex CLI oraz Cursor obsługują serwery MCP w trybie STDIO.
{
"mcpServers": {
"obsidian": {
"command": "python",
"args": ["/path/to/obsidian_mcp.py"],
"env": {
"VAULT_PATH": "/path/to/vault",
"DB_PATH": "/path/to/vectors.db"
}
}
}
}
HTTP — Serwer MCP działa jako samodzielna usługa HTTP. Przydatny w przypadku zdalnego dostępu, konfiguracji z wieloma klientami lub ustawień zespołowych, w których skarbiec (vault) znajduje się na serwerze współdzielonym.
{
"mcpServers": {
"obsidian": {
"url": "http://localhost:3333/mcp"
}
}
}
Zalecenie: Dla osobistych skarbców (vaults) należy używać STDIO. Jest prostsze, bezpieczniejsze (brak ekspozycji sieciowej), a cykl życia serwera jest zarządzany przez narzędzie AI. HTTP należy stosować wyłącznie wtedy, gdy wiele narzędzi lub wiele maszyn potrzebuje jednoczesnego dostępu do tego samego skarbca.
Ewolucja specyfikacji MCP. Specyfikacja MCP z czerwca 2025 dodała autoryzację OAuth 2.1, strukturyzowane wyniki narzędzi (typowane schematy zwracanych wartości) oraz elicytację (monity inicjowane przez serwer kierowane do użytkownika). Wydanie z listopada 2025 wprowadziło Streamable HTTP jako pełnoprawny tryb transportu, odkrywanie URL
.well-knowndo automatycznego przeglądania możliwości serwera, strukturyzowane adnotacje narzędzi deklarujące, czy narzędzie jest tylko do odczytu, czy modyfikujące, oraz system standaryzacji warstw SDK.1619 Następne wydanie specyfikacji (wstępnie planowane na połowę 2026) proponuje operacje asynchroniczne dla długotrwałych zadań, rozszerzenia protokołu specyficzne dla branż takich jak ochrona zdrowia i finanse oraz standardy komunikacji agent-agent dla przepływów pracy z wieloma agentami.19 Dla osobistych serwerów skarbców STDIO pozostaje najprostszą ścieżką. Transport Streamable HTTP i odkrywanie.well-knownprzynoszą korzyści głównie w przypadku wdrożeń HTTP w środowiskach korporacyjnych z routingiem wielodostępowym i równoważeniem obciążenia. Należy śledzić plan rozwoju MCP pod kątem aktualizacji wpływających na wybór transportu.
Projektowanie funkcjonalności
Serwer MCP powinien udostępniać minimalny zestaw narzędzi:
search — Główne narzędzie. Wykonuje wyszukiwanie hybrydowe i zwraca uszeregowane wyniki.
{
"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 — Odczytuje pełną treść konkretnej notatki według ścieżki. Przydatne, gdy agent chce zobaczyć pełny kontekst wyniku wyszukiwania.
{
"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 — Wyświetla listę notatek pasujących do filtra (według folderu, tagu, typu lub zakresu dat). Przydatne do eksploracji, gdy agent nie ma konkretnego zapytania.
{
"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 — Narzędzie pomocnicze, które wykonuje wyszukiwanie i formatuje wyniki jako blok kontekstu odpowiedni do wstrzyknięcia do konwersacji.
{
"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 }
}
}
Granice uprawnień
Serwer MCP powinien egzekwować ścisłe granice:
-
Tylko do odczytu. Serwer odczytuje skarbiec i bazę danych indeksu. Nie tworzy, nie modyfikuje ani nie usuwa notatek. Operacje zapisu (przechwytywanie nowych notatek) są obsługiwane przez oddzielne hooki lub umiejętności (skills), a nie przez serwer MCP.
-
Zakres ograniczony do skarbca. Serwer odczytuje wyłącznie pliki w ramach skonfigurowanej ścieżki skarbca. Próby przejścia ścieżki (
../../etc/passwd) muszą być odrzucane. -
Filtrowanie poświadczeń na wyjściu. Nawet jeśli baza danych zawiera wstępnie przefiltrowaną treść, należy stosować filtrowanie poświadczeń na wyjściu jako dodatkową warstwę ochrony (defense-in-depth).
-
Odpowiedzi ograniczone tokenami. Należy wymuszać
max_tokensna wszystkich odpowiedziach narzędzi, aby zapobiec otrzymywaniu przez narzędzie AI nadmiernie dużych bloków kontekstu.
Obsługa błędów
Narzędzia MCP powinny zwracać strukturyzowane komunikaty o błędach, które pomagają narzędziu AI w odzyskaniu sprawności:
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,
}
Integracja z Claude Code
Claude Code jest głównym konsumentem systemu wyszukiwania Obsidian. Ta sekcja obejmuje konfigurację MCP, integrację z hookami oraz wzorzec obsidian_bridge.py.
Konfiguracja MCP
Należy dodać serwer MCP Obsidian do ~/.claude/settings.json:
{
"mcpServers": {
"obsidian": {
"command": "python",
"args": ["/path/to/obsidian_mcp.py"],
"env": {
"VAULT_PATH": "/absolute/path/to/vault",
"DB_PATH": "/absolute/path/to/vectors.db"
}
}
}
}
Po dodaniu konfiguracji należy ponownie uruchomić Claude Code. Serwer MCP zostanie uruchomiony jako proces potomny. Aby zweryfikować jego działanie:
> What tools do you have from the obsidian MCP server?
Claude Code powinien wyświetlić listę dostępnych narzędzi (obsidian_search, obsidian_read_note itp.).
Integracja z hookami
Hooki rozszerzają zachowanie Claude Code w określonych punktach cyklu życia. Dwa hooki są istotne dla integracji z Obsidian:
Hook PreToolUse — Odpytuje skarbiec przed przetworzeniem wywołania narzędzia przez agenta. Automatycznie wstrzykuje odpowiedni kontekst.
#!/bin/bash
# ~/.claude/hooks/pre-tool-use/obsidian-context.sh
# Automatically inject vault context before tool execution
TOOL_NAME="$1"
PROMPT="$2"
# Only inject context for code-related tools
case "$TOOL_NAME" in
Edit|Write|Bash)
# Query the vault
CONTEXT=$(python /path/to/retriever.py search "$PROMPT" --limit 3 --max-tokens 1500)
if [ -n "$CONTEXT" ]; then
echo "---"
echo "Relevant vault context:"
echo "$CONTEXT"
echo "---"
fi
;;
esac
Hook PostToolUse — Przechwytuje istotne wyniki narzędzi z powrotem do skarbca w celu przyszłego wyszukiwania.
#!/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
Wzorzec obsidian_bridge.py
Moduł pomostowy udostępnia Python API, które hooki i umiejętności (skills) mogą wywoływać:
# 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)
Umiejętność /capture
Umiejętność (skill) Claude Code do przechwytywania spostrzeżeń z powrotem do skarbca:
/capture "OAuth token rotation requires both access and refresh token invalidation"
--domain security
--tags oauth,tokens
Umiejętność tworzy nową notatkę w 00-inbox/ z odpowiednim frontmatter i uruchamia przyrostowe reindeksowanie, dzięki czemu nowa notatka jest natychmiast dostępna w wyszukiwaniu.
Zarządzanie oknem kontekstu
Integracja powinna uwzględniać okno kontekstu Claude Code:
- Należy ograniczyć wstrzykiwany kontekst do 1500–2000 tokenów na zapytanie. Więcej konkuruje z pamięcią roboczą agenta.
- Należy dołączać atrybucję źródła. Zawsze należy podawać ścieżkę pliku i nagłówek sekcji, aby agent mógł odwoływać się do źródła.
- Należy obcinać tekst fragmentów. Długie fragmenty powinny być obcinane ze znakiem
...zamiast pomijane całkowicie. Pierwsze 300–500 znaków zazwyczaj zawiera kluczowe informacje. - Nie należy wstrzykiwać kontekstu przy każdym wywołaniu narzędzia. Hook PreToolUse powinien selektywnie wstrzykiwać kontekst w zależności od wywoływanego narzędzia. Operacje odczytu nie potrzebują kontekstu ze skarbca. Operacje zapisu i edycji z niego korzystają.
Integracja z Codex CLI
Codex CLI łączy się z serwerami MCP poprzez config.toml. Wzorzec integracji różni się od Claude Code składnią konfiguracji i sposobem dostarczania instrukcji.
Konfiguracja MCP
Należy dodać do .codex/config.toml lub ~/.codex/config.toml:
[mcp_servers.obsidian]
command = "python"
args = ["/path/to/obsidian_mcp.py"]
[mcp_servers.obsidian.env]
VAULT_PATH = "/absolute/path/to/vault"
DB_PATH = "/absolute/path/to/vectors.db"
Wzorce AGENTS.md
Codex CLI odczytuje AGENTS.md w celu pobrania instrukcji na poziomie projektu. Należy uwzględnić wskazówki dotyczące przeszukiwania skarbca:
## Available Tools
### Obsidian Vault (MCP: obsidian)
Use the `obsidian_search` tool to find relevant context from the knowledge base.
Search the vault when you need:
- Background on a concept or pattern
- Prior decisions or rationale
- Reference material for implementation
Example queries:
- "authentication patterns in FastAPI"
- "how does the review aggregator work"
- "sqlite-vec configuration"
Różnice w porównaniu z Claude Code
| Funkcja | Claude Code | Codex CLI |
|---|---|---|
| Konfiguracja MCP | settings.json |
config.toml |
| Hooki | ~/.claude/hooks/ |
Nieobsługiwane |
| Umiejętności | ~/.claude/skills/ |
Nieobsługiwane |
| Plik instrukcji | CLAUDE.md |
AGENTS.md |
| Tryby zatwierdzania | --dangerously-skip-permissions |
suggest / auto-edit / full-auto |
Kluczowa różnica: Codex CLI nie obsługuje hooków. Wzorzec automatycznego wstrzykiwania kontekstu (hook PreToolUse) nie jest dostępny. Zamiast tego należy umieścić w AGENTS.md jawne instrukcje nakazujące agentowi przeszukanie skarbca przed rozpoczęciem pracy.
Cursor i inne narzędzia
Cursor oraz inne narzędzia AI obsługujące MCP mogą łączyć się z tym samym serwerem Obsidian MCP. Ta sekcja opisuje konfigurację dla popularnych narzędzi.
Cursor
Należy dodać do .cursor/mcp.json w katalogu głównym projektu:
{
"mcpServers": {
"obsidian": {
"command": "python",
"args": ["/path/to/obsidian_mcp.py"],
"env": {
"VAULT_PATH": "/absolute/path/to/vault",
"DB_PATH": "/absolute/path/to/vectors.db"
}
}
}
}
Plik .cursorrules w Cursor może zawierać instrukcje dotyczące korzystania ze skarbca:
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.
Macierz kompatybilności
| Narzędzie | Obsługa MCP | Transport | Lokalizacja konfiguracji |
|---|---|---|---|
| Claude Code | Pełna | STDIO | ~/.claude/settings.json |
| Codex CLI | Pełna | STDIO | .codex/config.toml |
| Cursor | Pełna | STDIO | .cursor/mcp.json |
| Windsurf | Pełna | STDIO | .windsurf/mcp.json |
| Continue.dev | Częściowa | HTTP | ~/.continue/config.json |
| Zed | W trakcie | STDIO | Interfejs ustawień |
Rozwiązanie zastępcze dla narzędzi bez MCP
W przypadku narzędzi nieobsługujących MCP moduł wyszukiwania można opakować jako CLI:
# Search from command line
python retriever_cli.py search "query text" --limit 5
# Output formatted for copy-paste into any tool
python retriever_cli.py context "query text" --format markdown
CLI generuje ustrukturyzowany tekst, który można ręcznie wkleić do dowolnego narzędzia AI. Jest to mniej eleganckie niż integracja przez MCP, ale działa uniwersalnie.
Buforowanie promptów ze strukturyzowanych notatek
Strukturyzowane notatki w skarbcu mogą służyć jako bloki kontekstu wielokrotnego użytku, zmniejszające zużycie tokenów w interakcjach z AI. Ta sekcja opisuje projektowanie kluczy pamięci podręcznej i zarządzanie budżetem tokenów.
Wzorzec
Zamiast wyszukiwać kontekst przy każdej interakcji, można wstępnie zbudować bloki kontekstu z dobrze ustrukturyzowanych notatek w skarbcu i je buforować:
# 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
},
}
Unieważnianie pamięci podręcznej
Unieważnianie pamięci podręcznej opiera się na dwóch sygnałach:
- Wygaśnięcie TTL. Każdy blok kontekstu ma czas życia (TTL). Po jego wygaśnięciu blok jest odbudowywany poprzez ponowne zapytanie do skarbca.
- Wykrywanie zmian w skarbcu. Gdy indekser wykryje zmiany w plikach, które przyczyniły się do utworzenia buforowanego bloku kontekstu, blok jest natychmiast unieważniany.
Zarządzanie budżetem tokenów
Sesja rozpoczyna się z całkowitym budżetem kontekstu. Buforowane bloki zużywają część tego budżetu:
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)
Buforowane bloki ładują się na początku sesji. Dynamiczne wyniki wyszukiwania wypełniają pozostały budżet dla poszczególnych zapytań. To hybrydowe podejście zapewnia agentowi bazowy zestaw często potrzebnego kontekstu, jednocześnie zachowując budżet na konkretne zapytania.
Porównanie zużycia tokenów
Bez buforowania: Każde trafne zapytanie uruchamia wyszukiwanie w skarbcu, zwracając 1500–2000 tokenów kontekstu. Przy 10 zapytaniach w sesji agent zużywa 15 000–20 000 tokenów kontekstu ze skarbca.
Z buforowaniem: Trzy wstępnie zbudowane bloki kontekstu zużywają łącznie 4500 tokenów. Dodatkowe wyszukiwania dodają 1500–2000 tokenów na unikalne zapytanie. Przy 10 zapytaniach, z których 6 jest pokrytych przez buforowane bloki, agent zużywa 4500 + (4 × 1500) = 10 500 tokenów — mniej więcej połowę zużycia bez buforowania.
Hooki PostToolUse do kompresji kontekstu
Dane wyjściowe narzędzi mogą być obszerne: ślady stosu, listy plików, wyniki testów. Hook PostToolUse może skompresować te dane przed zajęciem miejsca w oknie kontekstu.
Problem
Wywołanie narzędzia Bash uruchamiające testy może zwrócić:
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
Pełne dane wyjściowe to 5000 tokenów, ale istotna informacja mieści się w 2 liniach: 200 zaliczonych, 1 niezaliczony.
Implementacja hooka
#!/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
Zapobieganie rekurencyjnemu wywoływaniu
Hook kompresji generujący dane wyjściowe może wywołać sam siebie, jeśli nie zostanie zabezpieczony:
# Guard against recursive invocation
if [ -n "$COMPRESS_HOOK_ACTIVE" ]; then
exit 0
fi
export COMPRESS_HOOK_ACTIVE=1
Heurystyki kompresji
| Typ danych wyjściowych | Wykrywanie | Strategia kompresji |
|---|---|---|
| Wyniki testów | Słowa kluczowe PASSED / FAILED |
Zliczanie zaliczonych/niezaliczonych, wyświetlanie tylko błędów |
| Listy plików | ls lub find w poleceniu |
Obcięcie do pierwszych 20 pozycji + liczba |
| Ślady stosu | Słowo kluczowe Traceback |
Zachowanie pierwszej i ostatniej ramki + komunikat błędu |
| Status Git | modified: / new file: |
Podsumowanie liczby według statusu |
| Dane wyjściowe kompilacji | warning: / error: |
Usunięcie linii informacyjnych, zachowanie ostrzeżeń/błędów |
Pipeline przyjmowania i segregacji sygnałów
Warstwa przyjmowania określa, co trafia do vault. Bez kuracji vault gromadzi szum informacyjny. Ta sekcja opisuje pipeline oceniania, który kieruje sygnały do folderów domenowych.
Źródła
Sygnały napływają z wielu kanałów:
- Kanały RSS: Blogi techniczne, biuletyny bezpieczeństwa, informacje o wydaniach
- Zakładki: Zakładki przeglądarki zapisane za pomocą Obsidian Web Clipper lub bookmarkletu
- Newslettery: Kluczowe fragmenty z newsletterów e-mailowych
- Ręczne przechwytywanie: Notatki sporządzone podczas czytania, rozmów lub badań
- Wyniki narzędzi: Istotne wyniki narzędzi AI przechwycone za pomocą hooków
Wymiary oceny
Każdy sygnał jest oceniany w czterech wymiarach (od 0,0 do 1,0 każdy):
| Wymiar | Pytanie | Niska ocena (0,0-0,3) | Wysoka ocena (0,7-1,0) |
|---|---|---|---|
| Trafność | Czy dotyczy to moich aktywnych domen? | Poboczne, poza zakresem | Bezpośrednio związane z bieżącą pracą |
| Wykonalność | Czy mogę wykorzystać tę informację? | Czysta teoria, brak zastosowania | Konkretna technika lub wzorzec do zastosowania |
| Głębokość | Jak merytoryczna jest treść? | Nagłówki, powierzchowne podsumowanie | Szczegółowa analiza z przykładami |
| Autorytet | Jak wiarygodne jest źródło? | Anonimowy blog, niezweryfikowane | Źródło pierwotne, recenzowane, uznany ekspert |
Ocena złożona i routing
composite = (relevance * 0.35) + (actionability * 0.25) +
(depth * 0.25) + (authority * 0.15)
| Zakres oceny | Działanie |
|---|---|
| 0,55+ | Automatyczne przekierowanie do folderu domenowego |
| 0,40 - 0,55 | Kolejka do ręcznego przeglądu |
| < 0,40 | Odrzucenie (nie przechowuj) |
Routing domenowy
Sygnały z oceną powyżej 0,55 są kierowane do jednego z 12 folderów domenowych na podstawie dopasowania słów kluczowych i klasyfikacji tematycznej:
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
Statystyki produkcyjne
Dane z 14 miesięcy działania:
| Metryka | Wartość |
|---|---|
| Łączna liczba przetworzonych sygnałów | 7 771 |
| Automatycznie przekierowane (>0,55) | 4 832 (62%) |
| W kolejce do przeglądu (0,40-0,55) | 1 543 (20%) |
| Odrzucone (<0,40) | 1 396 (18%) |
| Aktywne foldery domenowe | 12 |
| Średnia liczba sygnałów dziennie | ~18 |
Wzorce grafu wiedzy
Graf wiki-link w Obsidian koduje relacje między notatkami. Ta sekcja opisuje semantykę linków, przechodzenie grafu w celu rozszerzania kontekstu oraz antywzorce obniżające jakość grafu.
Semantyka backlinków
Każdy wiki-link tworzy skierowaną krawędź w grafie. Obsidian śledzi zarówno linki wychodzące, jak i backlinki:
- Link wychodzący: Notatka A zawiera
[[Note B]]→ A linkuje do B - Backlink: Notatka B pokazuje, że notatka A się do niej odwołuje
Graf koduje różne typy relacji w zależności od kontekstu:
| Wzorzec linku | Semantyka | Przykład |
|---|---|---|
| Link inline | „Jest powiązany z” | „Zobacz [[OAuth Token Rotation]] po szczegóły” |
| Link w nagłówku | „Ma podtemat” | „## Powiązane\n- [[Token Rotation]]\n- [[Session Management]]” |
| Link tagowy | „Jest skategoryzowany jako” | „[[type/reference]]” |
| Link MOC | „Jest częścią” | Notatka Maps of Content zawierająca listę powiązanych notatek |
Maps of Content (MOC)
MOC to notatki indeksowe organizujące powiązane notatki w nawigacyjną strukturę:
---
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]]
MOC wspierają wyszukiwanie na dwa sposoby:
- Bezpośrednie dopasowanie. Wyszukiwanie „authentication overview” trafia w sam MOC, dostarczając agentowi wyselekcjonowaną listę powiązanych notatek.
- Rozszerzanie kontekstu. Po znalezieniu konkretnej notatki retriever może sprawdzić, czy notatka pojawia się w jakichś MOC, i dołączyć strukturę MOC do wyników, dając agentowi mapę szerszego tematu.
Przechodzenie grafu w celu rozszerzania kontekstu
Przyszłe rozszerzenie retrievera: po znalezieniu najlepszych wyników rozszerzenie kontekstu poprzez podążanie za linkami:
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)
Nie jest to zaimplementowane w obecnym retrieverze, ale stanowi naturalną rozbudowę struktury grafowej.
Antywzorce
Klastry osierocone. Grupy notatek, które linkują do siebie nawzajem, ale nie mają połączeń z resztą vault. Panel grafu w Obsidian uwidacznia je jako odizolowane wyspy. Klastry osierocone wskazują na brakujące MOC lub brakujące linki między domenami.
Rozrost tagów. Niespójne używanie tagów lub tworzenie zbyt wielu szczegółowych tagów. Vault z 500 unikalnymi tagami na 5 000 notatek daje średnio 1 notatkę na 10 tagów — tagi nie są przydatne do filtrowania. Należy skonsolidować je do 20-50 tagów wysokiego poziomu odpowiadających folderom domenowym.
Notatki bogate w linki, ubogie w treść. Notatki składające się wyłącznie z wiki-linków bez tekstu opisowego. Takie notatki indeksują się słabo, ponieważ chunker nie ma tekstu do wygenerowania embeddingów. Należy dodać co najmniej akapit kontekstu wyjaśniający, dlaczego powiązane notatki są ze sobą związane.
Linki dwukierunkowe do wszystkiego. Nie każde odniesienie wymaga wiki-linku. Wzmianka o „OAuth” mimochodem nie wymaga [[OAuth 2.0 Overview]]. Wiki-linki należy rezerwować dla celowych, nawigacyjnych relacji, w których kliknięcie linku dostarczyłoby przydatny kontekst.
Przepisy na workflow deweloperski
Praktyczne przepływy pracy łączące wyszukiwanie w vault z codziennymi zadaniami programistycznymi.
Poranny ładunek kontekstu
Rozpoczęcie dnia od załadowania odpowiedniego kontekstu:
Search my vault for notes about [current project] updated in the last week
Retriever zwraca ostatnie notatki dotyczące aktywnego projektu, zapewniając szybkie przypomnienie, na czym skończono. Skuteczniejsze niż ponowne czytanie wczorajszych komunikatów commitów.
Przechwytywanie wiedzy podczas kodowania
Podczas implementacji funkcji — zapisywanie spostrzeżeń bez opuszczania edytora:
/capture "FastAPI dependency injection with async generators requires yield,
not return. The generator is the dependency lifecycle."
--domain programming
--tags fastapi,dependency-injection
Przechwycone spostrzeżenie jest natychmiast indeksowane i dostępne do przyszłego wyszukiwania. W ciągu miesięcy te mikroprzechwycenia budują korpus wiedzy specyficznej dla implementacji.
Rozpoczęcie projektu
Przy rozpoczynaniu nowego projektu lub funkcji:
- Przeszukaj vault: „Co wiem o [technologii/wzorcu]?”
- Przejrzyj 5 najlepszych wyników pod kątem wcześniejszych decyzji i pułapek
- Sprawdź, czy istnieje MOC dla danej domeny; jeśli nie — utwórz go
- Wyszukaj tryby awarii: „problemy z [technologią]”
Debugowanie z wyszukiwaniem w vault
Przy napotkaniu błędu lub nieoczekiwanego zachowania:
Search my vault for [error message or symptom]
Wcześniejsze notatki z debugowania często zawierają przyczynę źródłową i rozwiązanie. Jest to szczególnie wartościowe w przypadku powtarzających się problemów w różnych projektach — vault pamięta to, co się zapomina.
Przygotowanie do code review
Przed przeglądem PR:
Search my vault for patterns and conventions about [module being changed]
Vault zwraca wcześniejsze decyzje, ograniczenia architektoniczne i standardy kodowania istotne dla recenzowanego kodu. Przegląd jest oparty na wiedzy instytucjonalnej, nie tylko na diffie.
Optymalizacja wydajności
Ta sekcja obejmuje strategie optymalizacji dla różnych rozmiarów sejfów i wzorców użytkowania.
Zarządzanie rozmiarem indeksu
| Rozmiar sejfu | Fragmenty | Rozmiar bazy | Pełna reindeksacja | Przyrostowa |
|---|---|---|---|---|
| 500 notatek | ~1 500 | 3 MB | 15 sekund | <1 sekunda |
| 2 000 notatek | ~6 000 | 12 MB | 45 sekund | 2 sekundy |
| 5 000 notatek | ~15 000 | 30 MB | 2 minuty | 4 sekundy |
| 15 000 notatek | ~50 000 | 83 MB | 4 minuty | <10 sekund |
| 50 000 notatek | ~150 000 | 250 MB | 15 minut | 30 sekund |
Przy 50 000+ notatek warto rozważyć: - Zwiększenie batch size z 64 do 128 w celu szybszego generowania embeddings - Użycie trybu WAL (domyślnego) dla współbieżnego dostępu - Uruchamianie pełnej reindeksacji poza godzinami pracy
Optymalizacja zapytań
Tryb WAL. Tryb Write-Ahead Logging w SQLite umożliwia współbieżne odczyty podczas zapisu przez indekser:
db.execute("PRAGMA journal_mode=WAL")
Jest to kluczowe, gdy serwer MCP obsługuje zapytania podczas przyrostowej aktualizacji wykonywanej przez indekser.
Pula połączeń. Serwer MCP powinien ponownie wykorzystywać połączenia z bazą danych zamiast otwierać nowe połączenie dla każdego zapytania. Jedno długotrwałe połączenie z trybem WAL obsługuje współbieżne odczyty.
# 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
We/wy z mapowaniem pamięci. Pragma mmap_size informuje SQLite o konieczności użycia we/wy z mapowaniem pamięci dla pliku bazy danych. Dla bazy o rozmiarze 83 MB zmapowanie całego pliku do pamięci eliminuje większość odczytów z dysku.
Optymalizacja FTS5. Po pełnej reindeksacji należy uruchomić:
INSERT INTO chunks_fts(chunks_fts) VALUES('optimize');
To scala wewnętrzne segmenty b-tree FTS5, zmniejszając opóźnienie zapytań przy kolejnych wyszukiwaniach.
Testy wydajności przy skalowaniu
Pomiary na Apple M3 Pro, 36 GB RAM, NVMe SSD:
| Operacja | 500 notatek | 5 tys. notatek | 15 tys. notatek | 50 tys. notatek |
|---|---|---|---|---|
| Zapytanie BM25 | 2ms | 5ms | 12ms | 25ms |
| Zapytanie wektorowe | 1ms | 3ms | 8ms | 20ms |
| Fuzja RRF | <1ms | <1ms | 3ms | 5ms |
| Pełne wyszukiwanie | 3ms | 8ms | 23ms | 50ms |
Wszystkie testy wydajności obejmują dostęp do bazy danych, wykonanie zapytania i formatowanie wyników. Opóźnienie sieciowe komunikacji MCP STDIO dodaje 1–2ms.
Rozwiązywanie problemów
Rozbieżność indeksu
Objaw: Wyszukiwanie zwraca nieaktualne wyniki lub pomija niedawno dodane notatki.
Przyczyna: Przyrostowy indekser nie został uruchomiony po dodaniu notatek lub znacznik czasu modyfikacji pliku (mtime) nie został zaktualizowany (np. plik zsynchronizowany z innego urządzenia z zachowanymi znacznikami czasu).
Rozwiązanie: Uruchomienie pełnej reindeksacji: python index_vault.py --full
Zmiana modelu embeddings
Objaw: Po zmianie modelu embeddings wyszukiwanie wektorowe zwraca bezsensowne wyniki.
Przyczyna: Stare wektory (z poprzedniego modelu) są porównywane z nowymi wektorami zapytania. Wymiary lub semantyka przestrzeni wektorowej są niekompatybilne.
Rozwiązanie: Indekser powinien wykryć niezgodność hasza modelu i automatycznie uruchomić pełną reindeksację. Jeśli tego nie zrobi, należy ręcznie wyczyścić bazę danych i przeprowadzić reindeksację:
rm vectors.db
python index_vault.py --full
Konserwacja FTS5
Objaw: Zapytania FTS5 zwracają nieprawidłowe lub niekompletne wyniki po wielu przyrostowych aktualizacjach.
Przyczyna: Wewnętrzne segmenty FTS5 mogą ulec fragmentacji po wielu małych aktualizacjach.
Rozwiązanie: Przebudowa i optymalizacja:
INSERT INTO chunks_fts(chunks_fts) VALUES('rebuild');
INSERT INTO chunks_fts(chunks_fts) VALUES('optimize');
Przekroczenie limitu czasu MCP
Objaw: Narzędzie AI zgłasza przekroczenie limitu czasu serwera MCP.
Przyczyna: Pierwsze zapytanie wyzwala ładowanie modelu (leniwa inicjalizacja), co zajmuje 2–5 sekund. Domyślny limit czasu MCP w narzędziu AI może być krótszy.
Rozwiązanie: Wstępne załadowanie modelu przy uruchamianiu serwera:
# In MCP server initialization
retriever = HybridRetriever(db_path, vault_path)
retriever.search("warmup", limit=1) # Trigger model load
Blokady pliku SQLite
Objaw: Błędy SQLITE_BUSY lub SQLITE_LOCKED.
Przyczyna: Wiele procesów jednocześnie zapisuje do bazy danych. Tryb WAL umożliwia współbieżne odczyty, ale pozwala na jednoczesny zapis tylko jednemu procesowi.
Rozwiązanie: Należy zapewnić, że tylko jeden proces (indekser) zapisuje do bazy danych. Serwer MCP i hooki powinny wykonywać wyłącznie odczyty. Jeśli wymagane są współbieżne zapisy, należy użyć trybu WAL i ustawić limit oczekiwania:
db.execute("PRAGMA busy_timeout=5000") # Wait up to 5 seconds
Brak ładowania sqlite-vec
Objaw: Wyszukiwanie wektorowe jest wyłączone; mechanizm wyszukiwania działa wyłącznie w trybie BM25.
Przyczyna: Rozszerzenie sqlite-vec nie jest zainstalowane, nie znajduje się w ścieżce bibliotek lub jest niekompatybilne z wersją SQLite.
Rozwiązanie:
# Install via pip
pip install sqlite-vec
# Or compile from source
git clone https://github.com/asg017/sqlite-vec
cd sqlite-vec && make
Weryfikacja ładowania rozszerzenia:
import sqlite3
db = sqlite3.connect(":memory:")
db.enable_load_extension(True)
db.load_extension("vec0")
print("sqlite-vec loaded successfully")
Problemy z pamięcią przy dużych sejfach
Objaw: Błędy braku pamięci podczas pełnej reindeksacji dużego sejfu (50 000+ notatek).
Przyczyna: Zbyt duży batch size lub jednoczesne wczytanie zawartości wszystkich plików do pamięci.
Rozwiązanie: Zmniejszenie batch size i przyrostowe przetwarzanie plików:
BATCH_SIZE = 32 # Reduce from 64
Należy również upewnić się, że indekser przetwarza pliki pojedynczo (odczyt, dzielenie na fragmenty i generowanie embeddings dla każdego pliku przed przejściem do następnego) zamiast wczytywania wszystkich plików do pamięci.
Przewodnik migracji
Z Apple Notes
- Eksport Apple Notes za pomocą opcji „Eksportuj wszystko” (macOS) lub narzędzia migracyjnego, np.
apple-notes-liberator - Konwersja eksportów HTML do markdown za pomocą
markdownifylubpandoc - Przeniesienie skonwertowanych plików do folderu
00-inbox/w sejfie - Przegląd i dodanie frontmatter do każdej notatki
- Przeniesienie notatek do odpowiednich folderów domenowych
Z Notion
- Eksport z Notion: Ustawienia → Eksport → Markdown i CSV
- Rozpakowanie eksportu do folderu
00-inbox/w sejfie - Naprawa artefaktów markdown specyficznych dla Notion:
- Notion używa
- [ ]dla list kontrolnych — jest to standardowy markdown - Notion dołącza tabele właściwości jako HTML — konwersja do YAML frontmatter
- Notion osadza obrazy jako ścieżki względne — skopiowanie obrazów do folderu załączników
- Dodanie standardowego frontmatter (
type,domain,tags) - Zastąpienie linków do stron Notion linkami wiki-link w Obsidian
Z Google Docs
- Użycie Google Takeout do eksportu wszystkich dokumentów
- Konwersja plików
.docxdo markdown:pandoc -f docx -t markdown input.docx -o output.md - Konwersja wsadowa:
for f in *.docx; do pandoc -f docx -t markdown "$f" -o "${f%.docx}.md"; done - Przeniesienie do sejfu, dodanie frontmatter, organizacja w foldery
Z czystego markdown (bez Obsidian)
Jeśli istnieje już katalog z plikami markdown:
- Otwarcie katalogu jako sejfu Obsidian (Obsidian → Otwórz sejf → Otwórz folder)
- Dodanie
.obsidian/do.gitignore, jeśli katalog jest wersjonowany - Utworzenie szablonów frontmatter i zastosowanie ich do istniejących plików
- Rozpoczęcie łączenia notatek za pomocą
[[wiki-links]]podczas czytania i organizowania - Natychmiastowe uruchomienie indeksera — system wyszukiwania działa od pierwszego dnia
Z innego systemu wyszukiwania
W przypadku migracji z innego systemu embeddings/wyszukiwania:
- Nie należy próbować migrować wektorów. Różne modele generują niekompatybilne przestrzenie wektorowe. Należy przeprowadzić pełną reindeksację z nowym modelem.
- Migracja treści, nie indeksu. Pliki sejfu są źródłem prawdy. Indeks jest artefaktem pochodnym.
- Weryfikacja po migracji. Uruchomienie 10–20 zapytań, na które znana jest odpowiedź, i sprawdzenie, czy wyniki odpowiadają oczekiwaniom.
Dziennik zmian
| Data | Zmiana |
|---|---|
| 2026-03-03 | Aktualizacja ewolucji specyfikacji MCP (listopad 2025: Streamable HTTP, .well-known, adnotacje narzędzi). Dodanie fine-tuningu Model2Vec i obsługi tokenizera BPE/Unigram. Dodanie tabeli porównawczej społecznościowych serwerów MCP. Aktualizacja Smart Connections do v4. |
| 2026-03-02 | Dodanie potion-base-32M i potion-retrieval-32M do porównania modeli. Dodanie sekcji o kwantyzacji/redukcji wymiarów. Dodanie uwagi o ewolucji specyfikacji MCP. |
| 2026-03-01 | Wydanie początkowe |
Bibliografia
-
Cormack, G.V., Clarke, C.L.A. i Buettcher, S. Reciprocal Rank Fusion outperforms Condorcet and individual Rank Learning Methods. SIGIR, 2009. Wprowadza RRF z parametrem k=60 jako metodę niewymagającą parametrów do łączenia rankingowych list wyników. ↩↩↩
-
OpenAI Embeddings Pricing. text-embedding-3-small: 0,02 $ za milion tokenów. Szacowany koszt pełnej reindeksacji skarbca: ~0,30 $. ↩
-
van Dongen, T. i in. Model2Vec: Turn any Sentence Transformer into a Small Fast Model. arXiv, 2025. Opisuje podejście destylacyjne generujące statyczne embeddingi z transformerów zdaniowych. ↩
-
MTEB: Massive Text Embedding Benchmark. potion-base-8M uzyskuje średni wynik 50,03 wobec 56,09 dla all-MiniLM-L6-v2 (89% retencji). ↩
-
SQLite FTS5 Extension. FTS5 zapewnia wyszukiwanie pełnotekstowe z rankingiem BM25 i konfigurowalnymi wagami kolumn. ↩
-
sqlite-vec: A vector search SQLite extension. Udostępnia wirtualne tabele
vec0do wyszukiwania wektorowego KNN w SQLite. ↩ -
Robertson, S. i Zaragoza, H. The Probabilistic Relevance Framework: BM25 and Beyond. Foundations and Trends in Information Retrieval, 2009. ↩
-
Karpukhin, V. i in. Dense Passage Retrieval for Open-Domain Question Answering. EMNLP, 2020. Gęste reprezentacje przewyższają BM25 o 9–19% w zadaniach otwartego wyszukiwania odpowiedzi. ↩
-
Reimers, N. i Gurevych, I. Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks. EMNLP, 2019. Fundamentalna praca nad gęstym podobieństwem semantycznym. ↩
-
Luan, Y. i in. Sparse, Dense, and Attentional Representations for Text Retrieval. TACL, 2021. Wyszukiwanie hybrydowe (hybrid retrieval) konsekwentnie przewyższa podejścia jednomodalne na zbiorze MS MARCO. ↩
-
SQLite Write-Ahead Logging. Tryb WAL umożliwiający równoczesne odczyty przy jednym procesie zapisującym. ↩
-
Gao, Y. i in. Retrieval-Augmented Generation for Large Language Models: A Survey. arXiv, 2024. Przegląd architektur RAG i strategii podziału na fragmenty (chunking). ↩
-
Thakur, N. i in. 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. Oficjalna dokumentacja Obsidian. ↩
-
Model Context Protocol Specification. Standard MCP do łączenia narzędzi AI ze źródłami danych. ↩
-
Dane produkcyjne autora. 16 894 plików, 49 746 fragmentów, baza danych SQLite o wielkości 83,56 MB, 7 771 sygnałów przetworzonych w ciągu 14 miesięcy. Opóźnienie zapytań mierzone za pomocą
time.perf_counter(). ↩ -
Model2Vec Potion Models. Minish Lab, 2025. Potion-base-32M (MTEB 52,46), potion-retrieval-32M (MTEB retrieval 36,35) oraz funkcje kwantyzacji i redukcji wymiarowości w wersji v0.5.0+. ↩↩↩
-
Update on the Next MCP Protocol Release. Wydanie z listopada 2025 wprowadziło transport Streamable HTTP, odkrywanie przez adres .well-known, strukturalne adnotacje narzędzi oraz standaryzację poziomów SDK. Następne wydanie planowane wstępnie na połowę 2026 roku z operacjami asynchronicznymi, rozszerzeniami domenowymi oraz komunikacją agent-agent. ↩↩
-
Model2Vec Releases. v0.4.0 (luty 2025): wsparcie dla trenowania/dostrajania. v0.5.0 (kwiecień 2025): przepisany backend, kwantyzacja, redukcja wymiarowości. v0.7.0 (październik 2025): kwantyzacja słownika, obsługa tokenizerów BPE/Unigram. ↩↩
-
Smart Connections for Obsidian. Smart Connections v4: lokalne embeddingi AI z priorytetem offline, wyszukiwanie semantyczne działa bez połączenia z internetem po początkowym indeksowaniu. ↩