Przykładowa lokalizacja sejfu
# Obsidian jako infrastruktura AI: kompletna dokumentacja techniczna
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 dostępnymi do odpytywania. Skarbiec z 16 000 plików bez systemu wyszukiwania to baza danych tylko do zapisu. Skarbiec z 200 plików z wyszukiwaniem hybrydowym i integracją MCP to baza wiedzy AI. Infrastruktura wyszukiwania jest produktem. Notatki to surowiec.
Wyszukiwanie hybrydowe przewyższa czysto słownikowe lub czysto semantyczne. BM25 wychwytuje dokładne identyfikatory i nazwy funkcji. Wyszukiwanie wektorowe wychwytuje 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 osobno nie pokrywa obu trybów awarii. Badania na MS MARCO passage ranking potwierdzają ten wzorzec: wyszukiwanie hybrydowe konsekwentnie przewyższa każdą z metod stosowaną w izolacji.3 Szczegółowa analiza hybrydowego retrievera omawia matematykę RRF, przykłady obliczeniowe z rzeczywistymi liczbami, analizę trybów awarii 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 rankingowe z atrybucją źródeł i 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łownikowego, 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 w 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.4
Indeksowanie przyrostowe utrzymuje aktualność systemu w czasie poniżej 10 sekund. Porównanie czasu modyfikacji plików wykrywa zmiany. Tylko zmodyfikowane pliki są ponownie dzielone na fragmenty i ponownie osadzane wektorowo. Pełna reindeksacja na sprzęcie Apple z serii M zajmuje około czterech minut. Przyrostowe aktualizacje przy typowych edycjach dnia trwają poniżej dziesięciu sekund. System pozostaje aktualny bez ręcznej interwencji.
Architektura skaluje się od 200 do ponad 20 000 notatek. Ten sam trójwarstwowy projekt (przyjmowanie, wyszukiwanie, integracja) działa przy dowolnym rozmiarze skarbca. Można zacząć od wyszukiwania wyłącznie BM25 w małym skarbcu. Wyszukiwanie wektorowe warto dodać, 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
Niniejszy przewodnik obejmuje kompletny system. Punkt wejścia zależy od aktualnej sytuacji:
| Opis sytuacji | Zacznij tutaj | Następnie przejdź do |
|---|---|---|
| Początkujący 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 |
| Budowa systemu wyszukiwania | Kompletny pipeline wyszukiwania, Reciprocal Rank Fusion | Optymalizacja wydajności, Rozwiązywanie problemów |
| Kontekst zespołowy lub korporacyjny | Ramowy model decyzji, Wzorce grafów wiedzy | Przepisy dla programistów, Przewodnik migracji |
Sekcje oznaczone Kontrakt zawierają szczegóły implementacyjne, bloki konfiguracji i tryby awarii. Sekcje oznaczone Narracja skupiają się na koncepcjach, decyzjach architektonicznych i uzasadnieniu wyborów projektowych. Sekcje oznaczone Przepis zawierają instrukcje krok po kroku.
Dlaczego Obsidian jako infrastruktura AI
Teza tego przewodnika: Skarbce Obsidian stanowią najlepszy substrat dla osobistych baz wiedzy AI, ponieważ działają lokalnie, opierają się 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. Żadnego zastrzeżonego formatu, żadnego eksportu bazy danych, żadnego API wymaganego do odczytania treści. Każde narzędzie potrafiące czytać pliki może odczytać skarbiec. grep, ripgrep, Python’s pathlib, SQLite FTS5 — wszystkie działają bezpośrednio na plikach źródłowych. Budując system wyszukiwania, indeksuje się 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 maszynie użytkownika. Żadnego serwera, żadnej zależności od synchronizacji chmurowej, żadnych limitów szybkości API, żadnych warunków użytkowania regulujących sposób przetwarzania własnych treści. Można osadzać wektorowo, indeksować, dzielić na fragmenty i przeszukiwać notatki bez jakiejkolwiek usługi zewnętrznej. 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: notatki osobiste zawierające dane uwierzytelniające, dane zdrowotne, informacje finansowe i prywatne refleksje nigdy nie opuszczają maszyny.
Struktura grafowa dzięki wiki-linkom. Składnia [[wiki-link]] Obsidian tworzy graf skierowany pomiędzy notatkami. Notatka o implementacji OAuth odsyła do notatek o rotacji tokenów, zarządzaniu sesjami i bezpieczeństwie API. Struktura grafowa koduje relacje między koncepcjami wyselekcjonowane przez człowieka. Embeddingi wektorowe wychwytują podobieństwo semantyczne, ale wiki-linki wychwytują celowe powiązania, które autor utworzył w trakcie myślenia o danym temacie. Graf to sygnał, którego embeddingi nie są w stanie odtworzyć.
Ekosystem wtyczek. Obsidian posiada ponad 2500 wtyczek społecznościowych (stan na marzec 2026, wzrost z ponad 1800 w połowie 2025 roku). 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. Wbudowana wtyczka Bases (wprowadzona w wersji 1.9.10) dodaje widoki podobne do baz danych — tabele, galerie, kalendarze i tablice kanban — ponad plikami skarbca, wykorzystując właściwości frontmatter jako pola, zapisywane jako pliki .base.27 Wtyczki te dodają strukturę do skarbca bez zmiany bazowego formatu czystego tekstu. System wyszukiwania indeksuje wynik działania tych wtyczek, nie same wtyczki.
Ponad 5 milionów użytkowników. Obsidian ma dużą aktywną społeczność tworzącą szablony, przepływy pracy, wtyczki i dokumentację. W przypadku napotkania problemu z organizacją skarbca lub konfiguracją wtyczki, z dużym prawdopodobieństwem ktoś już udokumentował rozwiązanie. Społeczność tworzy również narzędzia powiązane z Obsidian: serwery MCP, skrypty indeksujące, pipeline’y publikacji i wrappery API.
Czego sam system plików nie zapewnia
Katalog z plikami markdown ma przewagę czystego tekstu, ale brakuje mu trzech rzeczy, które dodaje Obsidian:
-
Linki dwukierunkowe. Obsidian automatycznie śledzi backlinki. Gdy notatka A odsyła do notatki B, notatka B pokazuje, że notatka A się do niej odwołuje. Panel grafowy wizualizuje klastry powiązań. 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 objaśnień renderują się w czasie rzeczywistym. Doświadczenie pisania jest bogatsze niż w edytorze tekstu, podczas gdy 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) i 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 należy zbudować samodzielnie)
Obsidian nie zawiera infrastruktury wyszukiwania. Posiada podstawowe wyszukiwanie (pełnotekstowe, po nazwie pliku, po tagu), ale nie oferuje pipeline’u embeddingów, wyszukiwania wektorowego, rankingu fuzyjnego, serwera MCP, filtrowania danych uwierzytelniających, strategii podziału na fragmenty ani hooków integracyjnych dla zewnętrznych narzędzi AI. Niniejszy przewodnik obejmuje infrastrukturę budowaną na bazie Obsidian. Skarbiec jest substratem. Pipeline wyszukiwania, serwer MCP i hooki integracyjne stanowią infrastrukturę.
Opisana tu architektura jest zorientowana na markdown, nie wyłączna dla Obsidian. W przypadku korzystania z Logseq, Foam, Dendron lub zwykłego katalogu z plikami 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 produkuje pliki markdown indeksowane przez retriever.
Szybki start: pierwszy sejf połączony z AI
Ta sekcja pozwala połączyć sejf z narzędziem AI w pięć minut. Obejmuje instalację Obsidian, utworzenie sejfu, instalację serwera MCP oraz uruchomienie pierwszego zapytania. Szybki start wykorzystuje społecznościowy serwer MCP dla natychmiastowych rezultatów. Kolejne sekcje opisują budowę własnego pipeline’u wyszukiwania do zastosowań produkcyjnych.
Wymagania wstępne
- macOS, Linux lub Windows
- Node.js 18+ (do serwera MCP)
- Obsidian 1.12+ (do integracji z CLI; wcześniejsze wersje działają w konfiguracjach opartych wyłącznie na MCP)
- Zainstalowany Claude Code, Codex CLI lub Cursor
Krok 1: Utworzenie sejfu
Należy pobrać Obsidian ze strony obsidian.md i utworzyć nowy sejf. Warto wybrać łatwą do zapamiętania lokalizację — serwer MCP wymaga ścieżki bezwzględnej.
# Example vault location
~/Documents/knowledge-base/
Warto dodać kilka notatek, aby wyszukiwarka miała z czym pracować. Nawet 10–20 notatek wystarczy, by 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 sejfu. Ekosystem znacząco się rozwinął w latach 2025–2026. Wśród istotnych aktualizacji warto wyróżnić MCPVault v0.11.0 (marzec 2026), który dodał list_all_tags do skanowania frontmatter i hashtagów z licznikami, ulepszył obsługę folderów z kropką oraz wsparcie dla plików .base i .canvas.25 Pakiet został również przemianowany na @bitbonsai/mcpvault w npm.
| Serwer | Autor | Transport | Wymaga wtyczki | Kluczowa funkcja |
|---|---|---|---|---|
| obsidian-mcp-server | StevenStavrakis | STDIO | Nie | Lekki, oparty na plikach |
| mcp-obsidian | MarkusPfundstein | STDIO | Lokalny REST API | Pełne CRUD sejfu 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 | Lokalny 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 sejfie mogą odpowiedzieć:
Search my Obsidian vault for notes about [topic you wrote about]
Narzędzie AI wywołuje serwer MCP, który przeszukuje sejf i zwraca pasujące treści. W wynikach powinny pojawić się ścieżki plików oraz odpowiednie fragmenty.
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 sejfu, 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 embeddingach - Filtrowanie poświadczeń - Indeksowanie przyrostowe - Automatyczne wstrzykiwanie kontekstu za pomocą hooków
Dalsza część przewodnika opisuje budowę każdej z tych funkcji. Szybki start potwierdza koncepcję. Pełny pipeline zapewnia wyszukiwanie o jakości produkcyjnej.
Obsidian CLI dla przepływów pracy z AI
Obsidian 1.12 (luty 2026) wprowadził wbudowany interfejs wiersza poleceń, otwierając nową płaszczyznę integracji dla przepływów pracy z AI.28 CLI działa jako zdalny kontroler GUI Obsidian — aplikacja musi być uruchomiona (lub uruchomi się automatycznie przy pierwszym poleceniu). Aktywacja: Ustawienia > Ogólne > Interfejs wiersza poleceń.
Dlaczego CLI ma znaczenie dla infrastruktury AI
CLI zapewnia programistyczny dostęp do natywnych operacji Obsidian, które wcześniej wymagały GUI lub APIów wtyczek. Kluczowe możliwości dla przepływów pracy z AI:
- Wyszukiwanie ze skryptów i hooków.
obsidian search "query"orazobsidian search:context "query"uruchamiają przeszukiwanie sejfu z dowolnego skryptu powłoki, hooka lub pipeline’u automatyzacji. Wariantsearch:contextzwraca pasujące wiersze z otaczającym kontekstem, co jest przydatne do zasilania wyników w promptach AI. - Automatyzacja notatek dziennych.
obsidian dailyotwiera lub tworzy dzisiejszą notatkę dzienną. W połączeniu ze skryptami powłoki umożliwia to zautomatyzowane przepływy codziennych podsumowań — hook może dołączać wygenerowane przez AI streszczenia do notatki dziennej. - Tworzenie notatek z szablonów.
obsidian template listiobsidian template creategenerują notatki z szablonów Templater lub wbudowanych, umożliwiając agentom AI tworzenie ustrukturyzowanych wpisów w sejfie bez bezpośredniego pisania plików markdown. - Zarządzanie właściwościami.
obsidian property setiobsidian property getodczytują i zapisują właściwości frontmatter, umożliwiając aktualizację metadanych ze skryptów bez parsowania YAML. - Sterowanie wtyczkami.
obsidian plugin enable/disable/listzarządza wtyczkami programistycznie — przydatne do przełączania wtyczek indeksujących podczas operacji wsadowych. - Zarządzanie zadaniami.
obsidian task list/add/completezapewnia ustrukturyzowany dostęp do zadań, przydatny dla agentów AI zarządzających elementami pracy w sejfie.
CLI a MCP w kontekście dostępu AI
CLI i serwery MCP pełnią różne role i są komplementarne, nie konkurencyjne:
| Aspekt | Obsidian CLI | Serwer MCP |
|---|---|---|
| Wywołujący | Skrypty powłoki, hooki, zadania cron | Agenci AI (Claude Code, Codex, Cursor) |
| Protokół | Proces POSIX (stdin/stdout/stderr) | MCP (JSON-RPC przez STDIO lub HTTP) |
| Mocna strona | Natywne operacje Obsidian (szablony, wtyczki, właściwości) | Niestandardowe wyszukiwanie (embeddingi, BM25, fuzja RRF) |
| Ograniczenie | Brak wyszukiwania wektorowego, brak pipeline’u embeddingów | Brak dostępu do wewnętrznych operacji Obsidian |
| Najlepsze do | Skrypty automatyzacji, pipeline’y importu, akcje hooków | Zapytania agentów AI w czasie rzeczywistym podczas sesji |
Zalecenie: CLI najlepiej sprawdza się w automatyzacji importu (tworzenie notatek, zarządzanie właściwościami, natywne wyszukiwanie Obsidian), natomiast MCP — w wyszukiwaniu (hybrydowe wyszukiwanie z embeddingami). Hook PreToolUse może wywołać obsidian search:context jako szybkie wstępne sprawdzenie przed przejściem do pełnego retrievera MCP dla rankingowanych wyników.
Przykład: hook importu oparty na CLI
#!/bin/bash
# Hook: append today's signals to daily note via CLI
DATE=$(date +%Y-%m-%d)
SUMMARY="$1"
obsidian daily # ensure daily note exists
obsidian file append "Daily Notes/${DATE}.md" "## AI Summary\n${SUMMARY}"
Wtyczki agentów dla Obsidian
Rosnąca kategoria wtyczek Obsidian osadza agentów AI bezpośrednio w interfejsie sejfu, stanowiąc alternatywę dla konfiguracji zewnętrznego serwera MCP. Wtyczki te uruchamiają agenta AI w panelu bocznym Obsidian, zamiast łączyć się z zewnętrznego narzędzia.
Claudian
Claudian osadza Claude Code jako współpracownika AI w sejfie. Katalog sejfu staje się katalogiem roboczym Claude, zapewniając pełne możliwości agentowe: odczyt/zapis plików, wyszukiwanie, polecenia bash i wieloetapowe przepływy pracy.29
Kluczowe funkcje dla infrastruktury AI:
- Prompty uwzględniające kontekst. Automatycznie dołącza aktywną notatkę, obsługuje wzmianki plików @nazwaNotatki, wykluczanie na podstawie tagów oraz zaznaczenie z edytora jako kontekst.
- Obsługa obrazów. Analiza obrazów przez przeciągnij-i-upuść, wklejenie lub ścieżkę pliku — przydatne do przetwarzania zrzutów ekranu i diagramów przechwyconych w sejfie.
- Polecenia slash. Tworzenie wielokrotnie używalnych szablonów promptów wywoływanych przez /polecenie, umożliwiających standaryzowane operacje na sejfie.
- Tryby uprawnień. YOLO (automatyczna akceptacja), Safe (akceptacja każdej akcji) i Plan (tylko planowanie) z listą blokowania zabezpieczeń i ograniczeniem do sejfu.
Agent Client
Agent Client wprowadza Claude Code, Codex CLI i Gemini CLI do ujednoliconego panelu bocznego Obsidian za pośrednictwem Agent Client Protocol (ACP).30
Kluczowe funkcje:
- Przełączanie między agentami. Rozmowa z Claude Code, Codex lub Gemini CLI z jednego panelu, z możliwością przełączania się między agentami w razie potrzeby.
- Wzmianki notatek. Użycie @nazwaNotatki do dołączenia treści notatki w promptach, podobnie jak w Claudian, ale niezależnie od agenta.
- Wykonywanie poleceń powłoki. Uruchamianie poleceń terminala bezpośrednio w czacie — skrypty budowania, polecenia git czy dowolne operacje terminala bez opuszczania konwersacji.
- Akceptacja akcji. Szczegółowa kontrola nad odczytami plików, edycjami i wykonywaniem poleceń.
Kiedy używać wtyczek agentów, a kiedy zewnętrznego MCP
| Scenariusz | Wtyczka agenta | Zewnętrzny MCP |
|---|---|---|
| Pisanie i edycja notatek sejfu z pomocą AI | Lepsze — agent widzi kontekst edytora | Działa, ale bez świadomości edytora |
| Rozwój kodu w wielu repozytoriach | Ograniczone — zakres sejfu | Lepsze — zakres projektu z pełnym dostępem do systemu plików |
| Wyszukiwanie w dużym zindeksowanym korpusie | Tylko podstawowe wyszukiwanie | Pełny pipeline hybrydowego wyszukiwania |
| Szybkie pytania do sejfu podczas tworzenia notatek | Idealne — bez przełączania kontekstu | Wymaga przejścia do terminala |
Zalecenie: Wtyczki agentów sprawdzają się w przepływach pracy skoncentrowanych na sejfie (pisanie, organizacja, podsumowywanie notatek). Zewnętrzne serwery MCP są lepsze dla przepływów deweloperskich, gdzie agent AI potrzebuje pełnego pipeline’u wyszukiwania i dostępu do baz kodu poza sejfem. Oba podejścia mogą współistnieć — Claudian wewnątrz Obsidian do pracy z notatkami i Claude Code z MCP zewnętrznie do prac deweloperskich.
Schemat decyzyjny: Obsidian a alternatywy
Nie każdy przypadek użycia wymaga Obsidian. Ta sekcja pokazuje, kiedy Obsidian jest właściwym fundamentem, kiedy to przesada, a kiedy lepiej sprawdzi się inne rozwiązanie.
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ść danych | 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 | 2500+ wtyczek | Integracje | Brak | Nie dotyczy | Nie dotyczy |
| Praca offline | Pełna | Tylko odczyt z pamięci podręcznej | Częściowo | Pełna | Pełna |
| Skalowalność do 10 000+ notatek | Tak | Tak (z API) | Pogarsza się | Tak | Nie (pojedynczy plik) |
| Koszt | Bezpłatny (rdzeń) | 10 USD/mies.+ | Bezpłatny | Bezpłatny | Bezpłatny |
Kiedy Obsidian to przesada
- Kontekst jednego projektu. Jeśli AI potrzebuje kontekstu wyłącznie o bieżącym kodzie, wystarczy umieścić go w
CLAUDE.md,AGENTS.mdlub dokumentacji na poziomie projektu. Pliki te są częścią repozytorium i ładowane automatycznie. - Dane ustrukturyzowane. Jeśli treść to tabele, rekordy lub schematy, należy użyć bazy danych. Notatki w Obsidian są zorientowane na tekst ciągły. Dataview potrafi odpytywać pola frontmatter, ale prawdziwa baza danych radzi sobie z zapytaniami strukturalnymi znacznie lepiej.
- Tymczasowe badania. Jeśli notatki zostaną usunięte po zakończeniu projektu, prostszy będzie katalog roboczy z plikami markdown. Nie warto budować infrastruktury wyszukiwania dla treści efemerycznych.
Kiedy Obsidian to właściwy wybór
- Wiedza gromadzona przez miesiące lub lata. Wartość rośnie wraz z powiększaniem się korpusu. Skarbiec (vault) liczący 200 notatek, odpytywany codziennie przez sześć miesięcy, przynosi więcej korzyści niż skarbiec z 5000 notatek odpytywany jednorazowo.
- Wiele dziedzin w jednym korpusie. Skarbiec zawierający notatki z programowania, architektury, bezpieczeństwa, projektowania i projektów osobistych czerpie korzyści z wyszukiwania międzydomenowego, którego nie zapewni
CLAUDE.mdograniczony do jednego projektu. - Treści wrażliwe pod względem prywatności. Lokalność danych oznacza, że pipeline wyszukiwania nigdy nie przesyła treści do zewnętrznych usług. Skarbiec zawiera dokładnie to, co się w nim umieści — łącznie z treściami, których nie należałoby przesyłać do usługi chmurowej.
Model mentalny: trzy warstwy
System składa się z trzech warstw, które działają niezależnie, lecz w połączeniu wzajemnie się wzmacniają. Każda warstwa odpowiada za inny aspekt i ma odmienny 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 selekcji skarbiec gromadzi szum: zrzuty ekranu tweetów, skopiowane artykuły bez adnotacji, niedokończone myśli pozbawione kontekstu. Warstwa intake odpowiada za kontrolę jakości w punkcie wejścia. Pipeline oceniający, konwencja tagowania lub ręczny proces weryfikacji — dowolny mechanizm zapewniający, że skarbiec zawiera treści warte wyszukiwania.
Retrieval (wyszukiwanie) sprawia, że skarbiec staje się odpytywalny. 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 oraz fuzja wyników za pomocą RRF. Warstwa retrieval przekształca katalog plików w odpytywalną bazę wiedzy. Bez tej warstwy skarbiec jest dostępny jedynie przez ręczne przeglądanie i podstawowe wyszukiwanie, lecz nie programistycznie 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 przechwytują 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 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 powstawały notatki. To rozdzielenie oznacza, że każdą warstwę można usprawniać niezależnie. Można wymienić model embeddingów bez zmiany pipeline’u intake. Można dodać nową funkcję MCP bez modyfikacji retrievera. Można zmienić heurystyki oceny sygnałów bez ingerencji w indeks.
Architektura vault do wykorzystania przez AI
Vault zoptymalizowany pod kątem wyszukiwania przez AI wymaga innych konwencji niż vault zoptymalizowany do osobistego przeglądania. Ta sekcja obejmuje strukturę folderów, schemat notatek, konwencje frontmatter oraz konkretne wzorce poprawiające jakość wyszukiwania.
Struktura folderów
Warto stosować numerowane prefiksy dla folderów najwyższego poziomu, tworząc 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 treść w formacie markdown — projekty, obszary odpowiedzialności, zasoby, sygnały, notatki dzienne.
Foldery, które należy wykluczyć z indeksowania: szablony (zawierają zmienne zastępcze, nie treść), załączniki (pliki binarne), konfiguracja Obsidian oraz wszelkie foldery zawierające wrażliwe dane, których nie powinno być w indeksie wyszukiwania.
Plik .indexignore
Należy utworzyć plik .indexignore w katalogu głównym vault, aby jawnie wykluczyć ścieżki z indeksu wyszukiwania. Składnia odpowiada .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 wykluczonych ścieżkach nigdy nie są dzielone na fragmenty, nigdy nie otrzymują embeddings i nigdy nie pojawiają się w wynikach wyszukiwania.
Schemat notatek
Każda notatka powinna zawierać YAML frontmatter. Moduł wyszukiwania 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
---
Wymagane pola dla 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ące 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; moduł wyszukiwania może dołączać adresy URL źródeł do wynikówstatus— pozwala wykluczać zarchiwizowane lub szkicowe notatki z aktywnego wyszukiwania
Konwencje podziału na fragmenty
Moduł wyszukiwania dzieli treść na fragmenty (chunking) na granicach nagłówków H2 (##). Oznacza to, że struktura notatki bezpośrednio wpływa na granularność wyszukiwania:
Korzystne 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 zawiera wystarczający kontekst, aby embedding uchwycił jego znaczenie. Zapytanie o „obsługę wygasłych tokenów” dopasowuje się konkretnie do trzeciego fragmentu.
Niekorzystne 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 ją podzielić na podsekcje H2. Mechanizm podziału na fragmenty zajmie się resztą.
Czego nie umieszczać w notatkach
Treści obniżające jakość wyszukiwania:
- Surowe kopie całych artykułów bez adnotacji. Moduł wyszukiwania indeksuje słowa kluczowe oryginalnego artykułu, rozmywając vault treścią, której nie napisaliśmy. Zamiast tego warto dodać podsumowanie, wyodrębnić kluczowe punkty lub zamieścić odnośnik do źródłowego URL.
- Zrzuty ekranu bez opisu tekstowego. Moduł wyszukiwania indeksuje tekst markdown. Obraz bez tekstu alternatywnego lub otaczającego opisu jest niewidoczny zarówno dla BM25, jak i wyszukiwania wektorowego.
- Ciągi uwierzytelniające. 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 dla Cloudflare w
~/.env”). - Treści generowane automatycznie bez kuracji. Jeśli narzędzie generuje notatkę (transkrypcja spotkania, wyróżnienia z Readwise, import RSS), należy ją przejrzeć i opatrzyć adnotacjami przed włączeniem do stałego vault. 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ść vault pod kątem wyszukiwania AI dzielą się na trzy kategorie: strukturalne (wymuszają spójność), zapytaniowe (udostępniają metadane) i synchronizacyjne (utrzymują vault w aktualnym stanie).
Niezbędne wtyczki
Dataview. Umożliwia odpytywanie vault jak bazy danych z wykorzystaniem pól frontmatter. Pozwala tworzyć dynamiczne indeksy: „wszystkie notatki z tagiem security zaktualizowane w ostatnich 30 dniach” lub „wszystkie notatki projektowe ze statusem active.” Dataview nie wspomaga bezpośrednio wyszukiwania, ale pomaga identyfikować luki w pokryciu vault 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 poprawnego frontmatter, wykorzystując szablon z wstępnie wypełnionymi polami created, type i domain. Spójny frontmatter poprawia filtrowanie w wyszukiwaniu.
<%* /* 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
## Źródła i odniesienia
Linter. Wymusza reguły formatowania w całym sejfie. Spójna hierarchia nagłówków (H1 dla tytułu, H2 dla sekcji, H3 dla podsekcji) sprawia, ż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 sejfu. Umożliwia śledzenie zmian w czasie, synchronizację między urządzeniami i odzyskiwanie przypadkowo usuniętych plików. 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 sejfu semantyczne powiązania i wyszukiwanie działają całkowicie offline, bez wywołań API.23 Choć system wyszukiwania opisany w tym przewodniku jest zewnętrzny wobec Obsidian (działa jako potok Python), Smart Connections przydaje się do eksploracji semantycznych powiązań podczas pisania. Oba systemy indeksują tę samą treść, lecz służą różnym celom: Smart Connections do odkrywania powiązań w edytorze, zewnętrzny retriever do integracji z narzędziami AI przez MCP.
Metadata Menu. Zapewnia edycję strukturalnego 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 osadzony w plikach markdown. JSON jest składniowo poprawnym markdownem, ale po podziale na fragmenty i wygenerowaniu embeddingów daje bezużyteczne wyniki. Należy wykluczyć pliki Excalidraw 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 prozy. Chunker generuje fragmenty tytułów kart i metadanych, które nie dają dobrych embeddingów. Należy wykluczyć tablice Kanban z indeksu.
Calendar. Tworzy notatki dzienne z minimalną treścią (często tylko nagłówek z datą). Puste lub prawie puste notatki generują niskojakościowe fragmenty. Jeśli korzysta się z notatek dziennych, warto umieszczać w nich treści merytoryczne lub wykluczyć folder notatek dziennych z indeksu.
Konfiguracja wtyczek, która ma znaczenie
File recovery → 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.
Strict line breaks → Wyłączone. Standardowe łamanie wierszy w markdown (podwójna nowa linia dla akapitu) generuje czystsze fragmenty niż tryb ścisły Obsidian (pojedyncza nowa linia jako <br>).
Default new file location → Wyznaczony folder. Przekierowanie nowych plików do 00-inbox/ zapobiega zanieczyszczaniu folderów domenowych przez nieskategoryzowane notatki. Skrzynka odbiorcza pełni rolę strefy tymczasowej; pliki trafiają do folderów domenowych po segregacji.
Wiki-link format → Najkrótsza ścieżka, gdy to możliwe. Krótsze cele linków ułatwiają retrieverowi rozwiązywanie struktury powiązań podczas indeksowania.
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 zależności środowiskowe. W tej sekcji wyjaśniono, 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)
Inferencja: wyłącznie CPU, statyczne embeddingi słów (brak warstw atencji)
Model2Vec destyluje wiedzę transformera zdaniowego do statycznych embeddingów tokenów. Zamiast uruchamiać warstwy atencji na wejściu (jak robią to BERT, MiniLM i inne modele transformerowe), Model2Vec generuje wektory poprzez średnią ważoną wstępnie obliczonych embeddingów tokenów.5 Praktyczna konsekwencja: szybkość generowania embeddingów jest 50–500 razy wyższa 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 (średnia 50,03 wobec 56,09).6 11-procentowa różnica jakości to kompromis za przewagę w szybkości i prostocie. W przypadku krótkich fragmentów markdown (średnio 200–400 słów w typowym sejfie) różnica jakościowa jest mniej wyraźna niż w dłuższych dokumentach, ponieważ oba modele zbiegają do podobnych reprezentacji 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. Import modułu embeddera nie wiąże się z żadnym kosztem, gdy retriever działa w trybie awaryjnym wyłącznie z BM25 (np. gdy środowisko wirtualne embeddingów nie jest zainstalowane).
Izolowane środowisko wirtualne. Model działa w dedykowanym venv (np. ~/.claude/venvs/memory/), aby uniknąć konfliktów zależności z resztą narzędzi. Funkcja _activate_venv() dodaje site-packages tego środowiska 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ć embeddingi pojedynczo.
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 |
| potion-multilingual-128M | 256 | ~500 MB | 300x | — | Wielojęzyczne sejfy (101 języków) |
| 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 na lepszej jakości niż potion-base-8M bez opuszczania rodziny statycznych embeddingów. Wydany w styczniu 2025, wykorzystuje większy słownik zdestylowany z baai/bge-base-en-v1.5, osiągając średnią MTEB 52,46 (5% poprawy wobec potion-base-8M), zachowując ten sam 256-wymiarowy wynik i zależność wyłącznie od numpy.20 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 takim właśnie 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 wobec 33,52 dla modelu bazowego.20 Ogólna średnia MTEB spada do 49,73, ponieważ dostrajanie poświęca wydajność ogólną na rzecz zysków specyficznych dla wyszukiwania.
Wybierz potion-multilingual-128M, gdy sejf zawiera notatki w wielu językach. Wydany w maju 2025, ten 101-językowy model jest najlepiej działającym statycznym modelem embeddingów do zadań wielojęzycznych — generuje embeddingi dla dowolnego tekstu w dowolnym języku, zachowując tę samą zależność wyłącznie od numpy co inne modele potion.24 Większy plik modelu (~500 MB) to kompromis za możliwości międzyjęzykowe. Warto go użyć, gdy notatki są prowadzone po japońsku, chińsku, niemiecku lub w innych językach obok angielskiego.
Wybierz all-MiniLM-L6-v2, gdy jakość wyszukiwania jest ważniejsza od szybkości i PyTorch jest zainstalowany. Wektory 384-wymiarowe 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łnej reindeksacji 15 000 plików na sprzęcie z procesorami serii M.
Wybierz nomic-embed-text-v1.5, gdy potrzebna jest najlepsza możliwa lokalna jakość wyszukiwania przy akceptacji wolniejszego indeksowania. Wektory 768-wymiarowe mniej więcej potrajają rozmiar bazy. Wymaga PyTorch oraz nowoczesnego procesora lub GPU.
Wybierz text-embedding-3-small, gdy opóźnienia sieciowe i kwestie prywatności stanowią akceptowalny kompromis. API generuje embeddingi najwyższej jakości, ale wprowadza zależność od chmury, koszt per token (0,02 USD za milion tokenów) i wysyła treści na serwery OpenAI.
Zostań przy potion-base-8M we wszystkich pozostałych przypadkach. Przewaga szybkości jest kluczowa dla iteracyjnego indeksowania (reindeksacja podczas prac deweloperskich), zależność wyłącznie od numpy eliminuje złożoność instalacji PyTorch, a wektory 256-wymiarowe utrzymują kompaktowy rozmiar bazy danych.
Kwantyzacja i redukcja wymiarowości
Model2Vec w wersji 0.5.0+ obsługuje ładowanie modeli ze zredukowaną precyzją i liczbą wymiarów.20 Jest to przydatne przy wdrożeniach na sprzęcie o ograniczonych zasobach lub redukcji rozmiaru bazy 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)
Modele skwantyzowane zachowują niemal identyczną jakość wyszukiwania przy ułamku pierwotnego zużycia pamięci. Redukcja wymiarowości opiera się na metodzie truncation w stylu Matrioszki — pierwszych N wymiarów niesie najwięcej informacji. Redukcja z 256 do 128 wymiarów zmniejsza o połowę przestrzeń wektorową przy minimalnej utracie jakości w wyszukiwaniu krótkich tekstów.
Od maja 2025 Model2Vec obsługuje również tokenizery BPE i Unigram (oprócz WordPiece), co rozszerza zestaw transformerów zdaniowych, które można destylować do modeli statycznych.22
Dostrajanie embeddingów specyficznych dla sejfu
Model2Vec w wersji 0.4.0+ obsługuje trenowanie własnych modeli klasyfikacyjnych na bazie statycznych embeddingów, a wersja 0.7.0 dodaje kwantyzację słownika i konfigurowalny pooling do destylacji.22 Jest to istotne w przypadku 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 większości sejfów domyślny potion-base-8M zapewnia wystarczającą jakość wyszukiwania. Dostrajanie ma sens 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 wyprowadzony z nazwy modelu i rozmiaru słownika. Jeśli model embeddingów zostanie zmieniony, indekser wykryje niezgodność przy następnym uruchomieniu przyrostowym i automatycznie uruchomi pełną reindeksację.
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.
Tryby awarii
Błąd pobierania modelu. Pierwsze uruchomienie pobiera model z Hugging Face. Jeśli pobieranie się nie powiedzie (problem z siecią, firmowy firewall), retriever przełącza się na tryb wyłącznie BM25. Po pierwszym pobraniu model jest buforowany lokalnie.
Niezgodność wymiarów. Zmiana modelu bez wyczyszczenia bazy powoduje, że przechowywane wektory mają inny wymiar niż nowe embeddingi. Indekser wykrywa to za pomocą hasza modelu i uruchamia pełną reindeksację. Jeśli weryfikacja hasza zawiedzie (własny model bez prawidłowego hasza), sqlite-vec zgłosi błąd przy zapytaniach KNN z niedopasowanymi wymiarami.
Obciążenie pamięci w 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 stanowi komponent wyszukiwania słów kluczowych w potoku hybrid retrieval. W tej sekcji omówiono konfigurację FTS5, sytuacje, w których BM25 sprawdza się najlepiej, oraz jego konkretne tryby awarii.
Wirtualna tabela FTS5
CREATE VIRTUAL TABLE chunks_fts USING fts5(
chunk_text,
section,
heading_context,
content=chunks,
content_rowid=id
);
Tryb synchronizacji treści. 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 konieczność ręcznej synchronizacji FTS5 przy każdym wstawieniu, aktualizacji lub usunięciu 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 klasyfikuje dokumenty na podstawie częstości występowania terminów, odwrotnej częstości dokumentowej oraz normalizacji długości dokumentu. Funkcja pomocnicza bm25() w FTS5 przyjmuje wagi 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) stanowi 30% wpływu
Wagi te można dostosować. Jeśli skarbiec zawiera opisowe nagłówki, które dobrze przewidują jakość treści, warto zwiększyć wagę section. Jeśli tagi są kompletne 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 - Flagi CLI:
--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 treści semantycznie powiązane, ale mogłoby uszeregować dokładne dopasowanie niżej niż ogólną dyskusję koncepcyjną.
Kiedy BM25 zawodzi
BM25 zawodzi w zapytaniach, które używają innej terminologii niż ta zawarta w przechowywanych treściach:
- 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 trafia, ponieważ „zarządzanie stanem” jest wyrażone przez konkretne nazwy technologii.
BM25 zawodzi również w przypadku kolizji słów kluczowych na dużą skalę. W skarbcu liczącym 15 000 plików wyszukiwanie „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 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 źle sprawdza się w językach bez spacji między wyrazami. Tokenizer trigram dzieli tekst co trzy znaki, umożliwiając wyszukiwanie podciągów kosztem rozmiaru indeksu (około 3 razy większego).
Konserwacja
FTS5 wymaga jawnej synchronizacji, gdy zmienia się bazowa tabela chunks:
# After inserting chunks
cursor.execute("""
INSERT INTO chunks_fts(chunks_fts)
VALUES('rebuild')
""")
Polecenie rebuild rekonstruuje indeks FTS5 na podstawie tabeli treści. Należy je uruchamiać po masowych wstawieniach (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 poszczególnych wierszy.
Wyszukiwanie wektorowe z sqlite-vec
Rozszerzenie sqlite-vec wprowadza wyszukiwanie wektorowe KNN (K-Nearest Neighbors) do SQLite. Ta sekcja obejmuje konfigurację sqlite-vec, potok przetwarzania embeddings od notatki do przeszukiwalnego wektora oraz konkretne wzorce zapytań.
Tabela wirtualna 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 do tabeli chunks, umożliwiając złączenia między wynikami wektorowymi a metadanymi fragmentów.
Potok przetwarzania embeddings
Potok przebiega 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 przechowywania w 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 najpierw koduje zapytanie wejściowe, a następnie znajduje K najbliższych fragmentów według odległości kosinusowej:
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 kontroluje liczbę zwracanych wyników. Kolumna distance zawiera odległość kosinusową (0 = identyczne, 2 = przeciwne).
Paginacja KNN z ograniczeniami odległości
Od wersji sqlite-vec v0.1.7 zapytania KNN obsługują ograniczenia WHERE distance < ?, co umożliwia paginację opartą na kursorze przez duże zbiory wyników bez ponownego skanowania wcześniejszych stron.26
def _paginated_vector_search(self, query_vec, page_size=20, max_distance=None):
"""Paginate through KNN results using distance constraints."""
packed = _serialize_vector(query_vec)
constraint = f"AND distance < {max_distance}" if max_distance else ""
results = self.db.execute(f"""
SELECT cv.id, cv.distance, c.file_path, c.chunk_text
FROM chunk_vecs cv
JOIN chunks c ON cv.id = c.id
WHERE embedding MATCH ?
AND k = ?
{constraint}
ORDER BY distance
""", [packed, page_size]).fetchall()
# Use last result's distance as cursor for next page
next_cursor = results[-1][1] if results else None
return results, next_cursor
Podejście to zastępuje wcześniejszy wzorzec pobierania dużej wartości k i przycinania wyników w Python, redukując zużycie pamięci przy eksploracyjnych zapytaniach w dużych skarbcach notatek.
Obsługa DELETE w tabelach vec0
sqlite-vec v0.1.7 dodał natywną obsługę DELETE dla tabel wirtualnych vec0.26 Wcześniej usunięcie wektorów wymagało porzucenia i ponownego utworzenia tabeli. Teraz ścieżka usuwania plików w indekserze może bezpośrednio usuwać wektory:
# Before v0.1.7: required workaround (drop + recreate, or mark as inactive)
# After v0.1.7: direct DELETE works
db.execute("DELETE FROM chunk_vecs WHERE id = ?", [chunk_id])
Upraszcza to przyrostowe reindeksowanie, gdy notatki są usuwane lub przenoszone. Indekser nie musi już utrzymywać pomocniczej tabeli „aktywnych identyfikatorów” ani wykonywać wsadowych przebudów.
Kiedy wyszukiwanie wektorowe wygrywa
Wyszukiwanie wektorowe sprawdza się najlepiej w zapytaniach, gdzie liczy się koncept, 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 koncepty, zróżnicowana terminologia)
- Zapytanie: „approaches to testing asynchronous code” → Znajduje notatki o „pytest-asyncio fixtures”, „mock event loops” i „async test patterns” (ten sam koncept wyrażony przez 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 uszeregować właściwą definicję funkcji niżej niż konceptualne omówienia - 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ą embeddings, które odzwierciedlają wzorce strukturalne, a nie znaczenie semantyczne. Plik JSON z "review": true jest kodowany inaczej niż prozaiczne omówienie przeglądu kodu.
Łagodna degradacja
Jeśli sqlite-vec nie załaduje się (brak rozszerzenia, niekompatybilna platforma, uszkodzona biblioteka), system 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
System wyszukiwania sprawdza vec_available przed próbą wykonania zapytań wektorowych. Gdy jest wyłączony, wszystkie wyszukiwania korzystają wyłącznie z BM25, a krok fuzji RRF jest pomijany.
Reciprocal Rank Fusion (RRF)
RRF łączy dwie uporządkowane listy wyników bez konieczności kalibracji punktacji. Ta sekcja omawia algorytm, szczegółowy przebieg przykładowego zapytania, dostrajanie parametru k oraz powody wyboru RRF zamiast alternatywnych metod. Interaktywny kalkulator z edytowalnymi pozycjami, gotowymi scenariuszami i wizualnym eksploratorem architektury dostępny jest w pogłębionym omówieniu hybrydowego retrievera.
Algorytm
RRF przypisuje każdemu dokumentowi wynik oparty wyłącznie na jego pozycji w każdej z list:
score(d) = Σ (weight_i / (k + rank_i))
Gdzie:
- k to stała wygładzająca (60, zgodnie z Cormack i in.3)
- 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 na wielu listach, otrzymują wyższe wyniki po fuzji. Dokumenty obecne 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 mieszczą się w przedziale [0, 2]. Ich połączenie wymaga normalizacji, a parametry normalizacji zależą od zbioru danych. RRF wykorzystuje wyłącznie pozycje rankingowe — zawsze liczby całkowite zaczynające się od 1, niezależnie od metody punktacji.
Uczone modele fuzji wymagają oznaczonych danych treningowych — par zapytanie-dokument z oceną trafności. W przypadku osobistej bazy wiedzy takie dane treningowe nie istnieją. Ręczna ocena setek par zapytanie-dokument byłaby konieczna do wytrenowania użytecznego modelu. 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. Oryginalny artykuł o RRF wykazał, że RRF przewyższa metody Condorceta na danych ewaluacyjnych TREC.3
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 dwa pliki konfiguracyjne wyżej (zawierają słowo „review” bardziej prominentnie). 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 o wysokich pozycjach w obu listach trafiają na szczyt. Fragmenty obecne tylko na jednej liście otrzymują wynik z jednego źródła i spadają poniżej wyników podwójnie rankingowanych. Właściwa logika rozwiązywania sporów wygrywa, ponieważ obie metody ją odnalazły — BM25 poprzez słowa kluczowe, wyszukiwanie wektorowe poprzez semantykę.
Pełny przebieg krok po kroku z obliczeniami RRF dla każdej pozycji, wraz z możliwością testowania różnych wartości k, dostępny jest 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ży wpływ mają wyniki z czołowych pozycji w porównaniu z niższymi pozycjami:
- Niskie k (np. 10): Dominują wyniki z czołowych pozycji. Pozycja 1 otrzymuje wynik 1/11 = 0,091, pozycja 10 otrzymuje 1/20 = 0,050 (różnica 1,8x). Sprawdza się, gdy poszczególne metody rankingowe trafnie identyfikują najlepszy wynik.
- Domyślne k (60): Zbalansowane. Pozycja 1 otrzymuje wynik 1/61 = 0,0164, pozycja 10 otrzymuje 1/70 = 0,0143 (różnica 1,15x). Różnice między pozycjami są skompresowane, co zwiększa wagę obecności na wielu listach.
- Wysokie k (np. 200): Obecność na obu listach ma znacznie większe znaczenie niż pozycja rankingowa. Pozycja 1 otrzymuje wynik 1/201, pozycja 10 — 1/210 — wartości niemal identyczne. Warto stosować, gdy poszczególne metody rankingowe generują zaszumione wyniki, ale zgodność między listami jest wiarygodna.
Należy zacząć od k=60. Oryginalny artykuł o RRF wykazał odporność tej wartości na różnorodnych zbiorach danych TREC. Dostrajanie ma sens 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 pozycji na jednej liście i braku obecności na drugiej), remisy rozstrzygane są następująco:
- Preferowane są fragmenty obecne na obu listach względem fragmentów obecnych tylko na jednej
- Wśród fragmentów na obu listach preferowany jest ten o niższej łącznej pozycji
- Wśród fragmentów na jednej liście preferowany jest ten o niższej pozycji na danej liście
Kompletny potok wyszukiwania
Ta sekcja śledzi zapytanie 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: ~23 ms 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ć. Szacowanie wykorzystuje 4 znaki na token (rozsądne przybliżenie dla prozy angielskiej). Wyniki są obcinane zachłannie: dodawane w kolejności rankingowej aż do wyczerpania budżetu.
To konserwatywna strategia. Bardziej wyrafinowane podejście uwzględniałoby oceny jakości poszczególnych wyników i preferowało krótsze, wyższej jakości rezultaty nad dłuższymi o niższej jakości. Zachłanne podejście jest prostsze i sprawdza się 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ń. Brakujący komponent obniża jakość, ale nie powoduje błędów. Jedynym krytycznym przypadkiem jest brak pliku bazy danych.
Statystyki produkcyjne
Pomiary na sejfie (vault) zawierającym 16 894 pliki, 49 746 fragmentów, bazę danych SQLite o rozmiarze 83 MB, 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) | 12 ms |
| Opóźnienie zapytania wektorowego (p50) | 8 ms |
| Opóźnienie fuzji RRF | 3 ms |
| Opóźnienie wyszukiwania od początku do końca (p50) | 23 ms |
| 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 |
Hashowanie treści i wykrywanie zmian
Indekser musi wiedzieć, które pliki zmieniły się od ostatniego uruchomienia indeksacji. Ta sekcja omawia mechanizm wykrywania zmian oraz strategię hashowania.
Porównanie czasu modyfikacji pliku
Indekser przechowuje mtime_ns (czas modyfikacji pliku w nanosekundach) dla każdego fragmentu w tabeli chunks. Podczas przyrostowego uruchomienia indekser:
- Skanuje sejf (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 hash treści
Hashowanie treści (SHA-256 zawartości pliku) byłoby bardziej niezawodne niż porównanie mtime — pozwoliłoby wykryć przypadki, gdy plik został „dotknięty” bez faktycznej zmiany (np. git checkout przywracający oryginalny mtime). Hashowanie wymaga jednak odczytu każdego pliku przy każdym przyrostowym uruchomieniu. Dla 16 894 plików odczyt zawartości zajmuje 2–3 sekundy. Odczyt mtime z systemu plików zajmuje <100 ms.
Kompromis: porównanie mtime czasami wywołuje niepotrzebną reindeksację 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 (100 ms vs 3 sekundy) sprawia, że mtime jest pragmatycznym wyborem dla systemu uruchamianego przy każdej interakcji z AI.
Obsługa usunięć
Gdy plik zostaje usunięty z sejfu (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],
)
Instrukcja DELETE FROM chunk_vecs działa natywnie od wersji sqlite-vec v0.1.7.26 Wcześniejsze wersje wymagały obejść (usunięcie i ponowne utworzenie tabeli wirtualnej lub utrzymywanie zewnętrznego zestawu „aktywnych identyfikatorów”). W przypadku wersji starszej niż 0.1.7 warto zaktualizować przed poleganiem na bezpośrednich usunięciach.
Tabele FTS5 z synchronizacją treści wymagają jawnego usunięcia za pomocą INSERT INTO chunks_fts(chunks_fts, rowid, ...) VALUES('delete', ?, ...) dla każdego usuniętego wiersza. Indekser obsługuje to w ramach procesu usuwania pliku.
Indeksowanie przyrostowe a pełne
Indekser obsługuje dwa tryby: przyrostowy (szybki, do codziennego użytku) oraz pełny (wolniejszy, stosowany okazjonalnie). W tej sekcji omówiono, kiedy stosować każdy z nich, gwarancje idempotentności oraz odzyskiwanie po uszkodzeniach.
Indeksowanie przyrostowe
Kiedy stosować: Codzienne indeksowanie po edycji notatek. Tryb domyślny.
Jak działa: 1. Skanowanie sejfu w poszukiwaniu zmian w plikach (porównanie mtime) 2. Usuwanie fragmentów dla skasowanych plików 3. Ponowne dzielenie na fragmenty i generowanie embeddingów dla zmienionych plików 4. Wstawianie nowych fragmentów dla nowych plików 5. Synchronizacja indeksu FTS5
Typowy czas trwania: <10 sekund dla dziennych edycji w sejfie liczącym 16 000 plików.
python index_vault.py --incremental
Pełne indeksowanie
Kiedy stosować: - Po zmianie modelu embeddingów (wykryto niezgodność skrótu modelu) - Po migracji schematu (nowe kolumny, zmienione indeksy) - Po uszkodzeniu bazy danych (test integralności kończy się niepowodzeniem) - Gdy indeksowanie przyrostowe daje nieoczekiwane wyniki
Jak działa: 1. Usunięcie wszystkich istniejących danych (fragmentów, wektorów, wpisów FTS5) 2. Skanowanie całego sejfu 3. Podział wszystkich plików na fragmenty 4. Generowanie embeddingów dla wszystkich fragmentów 5. Budowanie indeksu 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 danego pliku przed wstawieniem nowych, więc ponowne uruchomienie indeksowania przyrostowego na aktualnej bazie danych nie wprowadza żadnych zmian. Ponowne uruchomienie pełnego indeksowania tworzy identyczną bazę danych.
Odzyskiwanie po uszkodzeniach
Jeśli baza 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 sejfu, nie baza danych. Baza danych to artefakt pochodny, który można odbudować w dowolnym momencie. To kluczowa właściwość projektowa — nigdy nie trzeba tworzyć kopii zapasowej bazy danych.
Flaga --incremental
Gdy indekser uruchamia się z flagą --incremental:
- Sprawdzenie skrótu modelu. Porównanie zapisanego skrótu modelu z aktualnym. W przypadku różnicy następuje automatyczne przełączenie na tryb pełnego indeksowania z ostrzeżeniem dla użytkownika.
- Skanowanie plików. Przejście dozwolonych folderów, zebranie ścieżek plików i znaczników czasu mtime.
- Wykrywanie zmian. Porównanie z zapisanymi danymi.
- Przetwarzanie wsadowe. Ponowne dzielenie na fragmenty i generowanie embeddingów dla zmienionych plików w partiach po 64.
- Raportowanie postępu. Wyświetlenie liczby przetworzonych plików i czasu trwania operacji.
- Łagodne zamykanie. Obsługa sygnału SIGINT poprzez dokończenie przetwarzania bieżącego pliku przed zatrzymaniem.
Filtrowanie poświadczeń i granice danych
Osobiste notatki zawierają sekrety: klucze API, tokeny bearer, ciągi połączeń do baz danych, klucze prywatne wklejone podczas sesji debugowania. Filtr poświadczeń zapobiega ich wprowadzeniu 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 token JWT, jak i klucz API zostałyby podzielone na fragmenty, przetworzone na embeddingi i zapisane w bazie danych. Wyszukiwanie „authentication” zwróciłoby fragment zawierający prawdziwe sekrety. Co gorsza, jeśli moduł wyszukiwania przekazuje wyniki do narzędzia AI przez MCP, sekrety pojawiają się w oknie kontekstowym AI i potencjalnie w logach narzędzia.
Filtrowanie oparte na wzorcach
Filtr poświadczeń działa 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, powyżej 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. To oczyszczony tekst jest przekształcany w 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.
-
Zamiana zamiast usuwania. Token
[REDACTED:pattern-name]zachowuje kontekst semantyczny otaczającego tekstu. Embedding oddaje informację, że „w tym miejscu znajdowało się coś przypominającego poświadczenie”, nie kodując samego poświadczenia. -
Logowanie wzorców, nie wartości. Filtr rejestruje, które wzorce zostały dopasowane (np. „Scrubbed 2 credential(s) from oauth-debug.md [jwt, bearer-token]”), ale nigdy nie loguje wartości poświadczenia.
Wykluczanie oparte na ścieżkach
Plik .indexignore zapewnia wykluczanie na poziomie ścieżek (gruboziarniste). Filtr poświadczeń zapewnia szczegółowe oczyszczanie wewnątrz indeksowanych plików. Oba mechanizmy są niezbędne:
.indexignore— dla całych folderów, o których wiadomo, że zawierają wrażliwe treści (notatki zdrowotne, dokumenty finansowe, materiały kariery zawodowej)- Filtr poświadczeń — dla sekretów przypadkowo osadzonych w treściach, które skądinąd 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 wpisów blogowych, notatki techniczne | Tak | Tak |
| Wewnętrzny | Plany projektowe, decyzje architektoniczne | Tak | Tak |
| Wrażliwy | Dane płacowe, dokumentacja medyczna | Nie (.indexignore) | Nie dotyczy |
| Zastrzeżony | Poświadczenia, klucze prywatne | Nie (.indexignore) | Nie dotyczy |
Architektura serwera MCP
Model Context Protocol (MCP) umożliwia udostępnianie retrievera jako narzędzia wywoływanego przez agentów AI. Ta sekcja obejmuje projekt serwera, zakres możliwoś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 środowisk zespołowych, w których skarbiec znajduje się na współdzielonym serwerze.
{
"mcpServers": {
"obsidian": {
"url": "http://localhost:3333/mcp"
}
}
}
Zalecenie: W przypadku osobistych skarbców warto korzystać z trybu STDIO. Jest prostszy, bezpieczniejszy (brak ekspozycji sieciowej), a cykl życia serwera zarządzany jest przez narzędzie AI. Tryb HTTP należy stosować wyłącznie wtedy, gdy wiele narzędzi lub wiele maszyn wymaga równoczesnego dostępu do tego samego skarbca.
Ewolucja specyfikacji MCP. Specyfikacja MCP z czerwca 2025 roku wprowadziła autoryzację OAuth 2.1, strukturalne wyjścia narzędzi (typowane schematy zwracanych wartości) oraz elicytację (inicjowane przez serwer zapytania do użytkownika). Wydanie z listopada 2025 dostarczyło Streamable HTTP jako pełnoprawny tryb transportu, odkrywanie
.well-knownURL umożliwiające automatyczne przeglądanie możliwości serwera, strukturalne adnotacje narzędzi deklarujące, czy narzędzie jest tylko do odczytu, czy modyfikujące, a także system standaryzacji warstw SDK.1821 Kolejne wydanie specyfikacji (wstępnie planowane na połowę 2026 roku) proponuje operacje asynchroniczne dla długotrwałych zadań, rozszerzenia protokołu specyficzne dla branż takich jak opieka zdrowotna i finanse oraz standardy komunikacji agent-agent dla przepływów wieloagentowych.21 W przypadku serwerów osobistych skarbców STDIO pozostaje najprostszą ścieżką. Transport Streamable HTTP oraz odkrywanie.well-knownprzynoszą korzyści przede wszystkim wdrożeniom enterprise HTTP z routingiem wielodostępnym i równoważeniem obciążenia. Warto śledzić plan rozwoju MCP pod kątem aktualizacji wpływających na wybór transportu.
Projektowanie możliwości
Serwer MCP powinien udostępniać minimalny zestaw narzędzi:
search — Podstawowe narzędzie. Uruchamia 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ą zawartość konkretnej notatki na podstawie ś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 uruchamia wyszukiwanie i formatuje wyniki jako blok kontekstu nadający się 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 odczyt. Serwer odczytuje skarbiec i bazę danych indeksu. Nie tworzy, nie modyfikuje ani nie usuwa notatek. Operacje zapisu (przechwytywanie nowych notatek) obsługiwane są przez osobne hooki lub skille, a nie przez serwer MCP.
-
Ograniczenie do skarbca. Serwer odczytuje wyłącznie pliki w obrębie skonfigurowanej ścieżki skarbca. Próby przejścia ścieżki (
../../etc/passwd) muszą być odrzucane. -
Filtrowanie poufnych danych na wyjściu. Nawet jeśli baza danych zawiera wstępnie przefiltrowaną zawartość, należy stosować filtrowanie poufnych danych na wyjściu jako dodatkową warstwę ochrony (defense-in-depth).
-
Ograniczenie tokenów w odpowiedziach. Należy wymuszać
max_tokenswe 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ć strukturalne 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ę hooków 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 wystartuje 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 itd.).
Integracja hooków
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 późniejszego 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 skille 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)
Skill /capture
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
Skill tworzy nową notatkę w 00-inbox/ z odpowiednim frontmatter i uruchamia przyrostowe ponowne indeksowanie, dzięki czemu nowa notatka jest natychmiast wyszukiwalna.
Wzorce poleceń niestandardowych
Skille Claude Code mogą opakowywać operacje na skarbcu w nazwane polecenia. Praktycy zbudowali biblioteki poleceń specyficznych dla Obsidian, które traktują skarbiec zarówno jako źródło odczytu, jak i cel zapisu.
Skanowanie sygnałów. Polecenie /scan-intel odpytuje zewnętrzne źródła, ocenia wyniki pod kątem osobistych zainteresowań badawczych i zapisuje kwalifikujące się sygnały jako notatki w skarbcu z frontmatter:
/scan-intel --topics "agent infrastructure, security" --lookback 7d
Polecenie pobiera dane ze skonfigurowanych źródeł (arXiv, HN, RSS), stosuje model oceny (trafność, wykonalność, głębokość, autorytet) i zapisuje zakwalifikowane sygnały do folderów tematycznych w skarbcu. Skarbiec staje się odbiorcą końcowym zautomatyzowanego potoku wywiadowczego.
Dziennik kapitański. Polecenie /captains-log agreguje dzienną aktywność git ze wszystkich repozytoriów, zapisuje ustrukturyzowany wpis dziennikowy do skarbca i uwzględnia podjęte decyzje, spostrzeżenia oraz otwarte wątki:
/captains-log
Polecenie pobiera historię commitów z GitHub, grupuje według repozytorium i formatuje jako narracyjny wpis dziennikowy. Z biegiem czasu codzienne wpisy tworzą przeszukiwalny zapis tego, co zostało wdrożone i dlaczego.
Przechwytywanie do Obsidian. Polecenie /obsidian-capture pobiera spostrzeżenie z bieżącej sesji Claude Code i zapisuje je bezpośrednio do skarbca z odpowiednimi metadanymi:
/obsidian-capture "SAST gates in agent loops increase security degradation"
--folder AI-Tools --tags security,agents
Wzorzec rozciąga się na dowolną operację w skarbcu: tworzenie MOC, aktualizowanie notatek o statusie projektu, łączenie powiązanych sygnałów czy generowanie cotygodniowych podsumowań z zebranych dziennych wpisów.
Przykłady społeczności. Praktycy publikują swoje biblioteki poleceń. Jeden deweloper udostępnił 22 niestandardowe polecenia Obsidian + Claude Code obejmujące codzienne przeglądy, planowanie projektów, przechwytywanie badań i procesy tworzenia treści.1 Inny zbudował skill „Visual Explainer”, który generuje notatki z diagramami w skarbcu na podstawie analizy kodu.2 Polecenia różnią się między sobą, ale architektura jest spójna: skille Claude Code jako interfejs, notatki w skarbcu jako warstwa przechowywania oraz infrastruktura wyszukiwania jako silnik zapytań.
Zarządzanie oknem kontekstowym
Integracja powinna uwzględniać okno kontekstowe Claude Code:
- Wstrzykiwany kontekst należy ograniczyć do 1500–2000 tokenów na zapytanie. Więcej konkuruje z pamięcią roboczą agenta.
- Należy dołączać atrybucję źródła. Zawsze trzeba uwzględnić ścieżkę pliku i nagłówek sekcji, aby agent mógł odwołać się do źródła.
- Tekst fragmentów należy przycinać. Długie fragmenty powinny być przycinane z wielokropkiem
...zamiast pomijane całkowicie. Pierwsze 300–500 znaków zwykle 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 na nim korzystają.
Integracja 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 wyszukiwania w skarbcu:
## 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 względem Claude Code
| Funkcja | Claude Code | Codex CLI |
|---|---|---|
| Konfiguracja MCP | settings.json |
config.toml |
| Hooki | ~/.claude/hooks/ |
Nieobsługiwane |
| Skille | ~/.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ć jawne instrukcje w AGENTS.md, 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 MCP dla Obsidian. Ta sekcja obejmuje konfigurację dla popularnych narzędzi.
Cursor
Należy dodać następującą konfigurację do pliku .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ń |
| Claudian (wtyczka Obsidian) | N/D (wbudowane) | Claude Code CLI | Ustawienia wtyczki Obsidian |
| Agent Client (wtyczka Obsidian) | N/D (wbudowane) | ACP | Ustawienia wtyczki Obsidian |
Rozwiązanie zastępcze dla narzędzi bez obsługi 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. To mniej eleganckie rozwiązanie niż integracja MCP, ale działa uniwersalnie.
Buforowanie promptów z notatek strukturalnych
Notatki strukturalne w skarbcu mogą służyć jako wielokrotnie używane bloki kontekstu, które zmniejszają zużycie tokenów w interakcjach z AI. Ta sekcja obejmuje projektowanie kluczy buforowania i zarządzanie budżetem tokenów.
Wzorzec
Zamiast wyszukiwać kontekst przy każdej interakcji, warto wcześniej zbudować bloki kontekstu z dobrze ustrukturyzowanych notatek 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 bufora
Unieważnianie bufora opiera się na dwóch sygnałach:
- Wygaśnięcie TTL. Każdy blok kontekstu ma czas życia (time-to-live). Po wygaśnięciu TTL blok jest odbudowywany poprzez ponowne zapytanie do skarbca.
- Wykrywanie zmian w skarbcu. Gdy indekser wykryje zmiany w plikach, które przyczyniły się do 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 w zależności od zapytania. To podejście hybrydowe 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 istotne 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 bywają rozwlekłe: ślady stosu, listy plików, wyniki testów. Hook PostToolUse może kompresować 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 uruchamianiu
Hook kompresji emitujący dane wyjściowe może uruchomić sam siebie, jeśli nie zostanie odpowiednio 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 |
Zliczenie zaliczonych/niezaliczonych, wyświetlenie 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 skarbca. Bez selekcji skarbiec gromadzi szum. Ta sekcja obejmuje pipeline oceniania, który kieruje sygnały do folderów domenowych.
Źródła
Sygnały pochodzą z wielu kanałów:
- Kanały RSS: Blogi techniczne, poradniki bezpieczeństwa, informacje o wydaniach
- Zakładki: Zakładki przeglądarki zapisywane za pomocą Obsidian Web Clipper lub bookmarkletu
- Newslettery: Kluczowe fragmenty z newsletterów e-mailowych
- Ręczne przechwytywanie: Notatki sporządzane podczas czytania, rozmów lub badań
- Dane wyjściowe narzędzi: Istotne wyniki narzędzi AI przechwycone za pomocą hooków
- Rozszerzenie udostępniania iOS: Aplikacja Obsidian na iOS (zaktualizowana na początku 2026 roku) zawiera rozszerzenie udostępniania, które zapisuje treści z Safari, sieci społecznościowych i innych aplikacji bezpośrednio do skarbca bez otwierania Obsidian.31 To tworzy ścieżkę przyjmowania na urządzeniach mobilnych z minimalnym oporem — wystarczy udostępnić artykuł z Safari, a pojawi się jako notatka w skarbcu gotowa do oceny.
- Obsidian CLI: Skrypty powłoki i hooki mogą tworzyć notatki za pomocą
obsidian file createlub dopisywać do istniejących notatek za pomocąobsidian file append, umożliwiając zautomatyzowane pipeline’y przyjmowania na komputerze.
Wymiary oceny
Każdy sygnał jest oceniany w czterech wymiarach (od 0,0 do 1,0 w każdym):
| Wymiar | Pytanie | Niska ocena (0,0–0,3) | Wysoka ocena (0,7–1,0) |
|---|---|---|---|
| Trafność | Czy dotyczy to moich aktywnych dziedzin? | Poboczne, poza zakresem | Bezpośrednio związane z bieżącą pracą |
| Przydatność praktyczna | Czy mogę wykorzystać tę informację? | Czysta teoria, bez zastosowania | Konkretna technika lub wzorzec do zastosowania |
| Głębokość | Jak merytoryczna jest treść? | Nagłówki, pobieżne streszczenie | Szczegółowa analiza z przykładami |
| Wiarygodność | Jak wiarygodne jest źródło? | Anonimowy blog, niezweryfikowane | Źródło pierwotne, recenzowane, uznany ekspert |
Ocena złożona i kierowanie
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 | Kolejkowanie do ręcznego przeglądu |
| < 0,40 | Odrzucenie (bez zapisu) |
Kierowanie domenowe
Sygnały z oceną powyżej 0,55 trafiają 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
Na przestrzeni 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 w Obsidian koduje relacje między notatkami. Ta sekcja obejmuje 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
[[Notatka 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 w tekście | „Jest powiązany z” | „Zobacz [[OAuth Token Rotation]], aby poznać szczegóły” |
| Link w nagłówku | „Ma podtemat” | „## Powiązane\n- [[Token Rotation]]\n- [[Session Management]]” |
| Link typu tag | „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 nawigowalne struktury:
---
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 którymś z 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 naturalne rozszerzenie struktury grafu.
Antywzorce
Osierocone klastry. Grupy notatek linkujących do siebie nawzajem, ale nieposiadających połączeń z resztą skarbca. Panel grafu w Obsidian uwidacznia je jako odłączone wyspy. Osierocone klastry wskazują na brakujące MOC lub brakujące linki międzydomenowe.
Rozrost tagów. Niespójne stosowanie tagów lub tworzenie zbyt wielu szczegółowych tagów. Skarbiec z 500 unikalnymi tagami na 5000 notatek daje średnio 1 notatkę na 10 tagów — tagi nie są przydatne do filtrowania. Warto 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 prozy. Takie notatki indeksują się źle, ponieważ chunker nie ma tekstu do wygenerowania embeddingów. Należy dodać przynajmniej akapit kontekstu wyjaśniający, dlaczego powiązane notatki są ze sobą związane.
Dwukierunkowe linki do wszystkiego. Nie każde odniesienie wymaga wiki-linka. Wzmianka o „OAuth” mimochodem nie wymaga [[OAuth 2.0 Overview]]. Wiki-linki warto rezerwować dla celowych, nawigowalnych relacji, w których kliknięcie linku dostarczyłoby użyteczny kontekst.
Przepisy na przepływy pracy programisty
Praktyczne przepływy pracy łączące wyszukiwanie w skarbcu 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 poprzedniego dnia. Skuteczniejsze niż ponowne czytanie wczorajszych komunikatów commitów.
Przechwytywanie wiedzy podczas kodowania
Podczas implementacji funkcji można utrwalać spostrzeżenia 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
Utrwalone spostrzeżenie jest natychmiast indeksowane i dostępne do przyszłego wyszukiwania. Z biegiem miesięcy te mikro-przechwycenia budują korpus wiedzy specyficznej dla implementacji.
Rozpoczęcie projektu
Przy rozpoczynaniu nowego projektu lub funkcji:
- Wyszukiwanie w skarbcu: „Co wiem o [technologii/wzorcu]?”
- Przegląd 5 najlepszych wyników pod kątem wcześniejszych decyzji i pułapek
- Sprawdzenie, czy istnieje MOC dla danej domeny; jeśli nie — utworzenie go
- Wyszukiwanie trybów awarii: „problemy z [technologią]”
Debugowanie z wyszukiwaniem w skarbcu
W przypadku napotkania 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 poprawkę. Jest to szczególnie wartościowe w przypadku powtarzających się problemów między projektami — skarbiec pamięta to, co umyka pamięci.
Przygotowanie do przeglądu kodu
Przed przeglądem PR:
Search my vault for patterns and conventions about [module being changed]
Skarbiec zwraca wcześniejsze decyzje, ograniczenia architektoniczne i standardy kodowania istotne dla recenzowanego kodu. Przegląd opiera się na wiedzy instytucjonalnej, a nie tylko na diffie.
Optymalizacja wydajności
Ta sekcja obejmuje strategie optymalizacji dla różnych rozmiarów skarbców i wzorców użytkowania.
Zarządzanie rozmiarem indeksu
| Rozmiar skarbca | Chunki | Rozmiar BD | 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 embeddingów - Użycie trybu WAL (domyślnego) dla współbieżnego dostępu - Uruchamianie pełnej reindeksacji poza godzinami szczytu
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")
Ma to kluczowe znaczenie, gdy serwer MCP obsługuje zapytania w trakcie przyrostowej aktualizacji indeksera.
Pula połączeń. Serwer MCP powinien ponownie wykorzystywać połączenia z bazą danych zamiast otwierać nowe połączenie dla każdego zapytania. Pojedyncze długotrwałe połączenie w trybie 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
Wejście/wyjście z mapowaniem pamięci. Pragma mmap_size informuje SQLite o konieczności użycia I/O 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');
Scala to wewnętrzne segmenty b-drzewa FTS5, zmniejszając opóźnienie zapytań przy kolejnych wyszukiwaniach.
Benchmarki skalowalności
Pomiary na Apple M3 Pro, 36 GB RAM, NVMe SSD:
| Operacja | 500 notatek | 5 tys. notatek | 15 tys. notatek | 50 tys. notatek |
|---|---|---|---|---|
| Zapytanie BM25 | 2 ms | 5 ms | 12 ms | 25 ms |
| Zapytanie wektorowe | 1 ms | 3 ms | 8 ms | 20 ms |
| Fuzja RRF | <1 ms | <1 ms | 3 ms | 5 ms |
| Pełne wyszukiwanie | 3 ms | 8 ms | 23 ms | 50 ms |
Wszystkie benchmarki obejmują dostęp do bazy danych, wykonanie zapytania i formatowanie wyników. Opóźnienie sieciowe komunikacji MCP STDIO dodaje 1–2 ms.
Rozwiązywanie problemów
Desynchronizacja indeksu
Objaw: Wyszukiwanie zwraca nieaktualne wyniki lub pomija niedawno dodane notatki.
Przyczyna: Inkrementalny indekser nie został uruchomiony po dodaniu notatek lub znacznik czasu 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 zapytań. 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 inkrementalnych aktualizacjach.
Przyczyna: Wewnętrzne segmenty FTS5 mogą ulec fragmentacji po wielu drobnych 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 uruchamia ł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 starcie serwera:
# In MCP server initialization
retriever = HybridRetriever(db_path, vault_path)
retriever.search("warmup", limit=1) # Trigger model load
Blokady plików 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 tylko jeden proces może zapisywać.
Rozwiązanie: Należy upewnić się, że tylko jeden proces (indekser) zapisuje do bazy danych. Serwer MCP i hooki powinny jedynie odczytywać dane. Jeśli konieczne 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; retriever działa wyłącznie w trybie BM25.
Przyczyna: Rozszerzenie sqlite-vec nie jest zainstalowane, nie zostało znalezione 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 (ponad 50 000 notatek).
Przyczyna: Rozmiar partii embeddings jest zbyt duży lub cała zawartość plików jest ładowana do pamięci jednocześnie.
Rozwiązanie: Zmniejszenie rozmiaru partii i inkrementalne przetwarzanie plików:
BATCH_SIZE = 32 # Reduce from 64
Należy również upewnić się, że indekser przetwarza pliki pojedynczo (odczytując, dzieląc na fragmenty i generując embeddings dla każdego pliku przed przejściem do następnego) zamiast ładować wszystkie pliki do pamięci.
Przewodnik migracji
Z Apple Notes
- Eksport Apple Notes za pomocą opcji „Eksportuj wszystko” (macOS) lub narzędzia migracyjnego, takiego jak
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
- [ ]do list kontrolnych — to standardowy markdown - Notion dołącza tabele właściwości jako HTML — należy je przekonwertować na frontmatter YAML
- Notion osadza obrazy jako ścieżki względne — należy skopiować obrazy do folderu załączników
- Dodanie standardowego frontmatter (
type,domain,tags) - Zastąpienie linków do stron Notion wiki-linkami 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
Ze zwykłych plików markdown (bez Obsidian)
Jeśli dysponujesz już katalogiem plików markdown:
- Otwarcie katalogu jako sejfu Obsidian (Obsidian → Open Vault → Open 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]]w miarę 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 stanowią źródło prawdy. Indeks jest artefaktem pochodnym.
- Weryfikacja po migracji. Warto uruchomić 10–20 zapytań, na które znana jest odpowiedź, i sprawdzić, czy wyniki odpowiadają oczekiwaniom.
Dziennik zmian
| Data | Zmiana |
|---|---|
| 2026-04-01 | Dodanie sekcji Obsidian CLI (polecenia v1.12 dla przepływów pracy AI). Dodanie sekcji wtyczek agentowych (Claudian, Agent Client). Dokumentacja podstawowej wtyczki Bases do organizacji sejfu. Aktualizacja liczby wtyczek do ponad 2 500. Dodanie iOS Share Extension jako źródła danych wejściowych. Aktualizacja macierzy kompatybilności z wbudowanymi wtyczkami agentowymi. |
| 2026-03-30 | MCPVault v0.11.0: narzędzie list_all_tags, obsługa .base/.canvas, zmiana nazwy na @bitbonsai/mcpvault. Obsidian Desktop v1.12.7 zawiera wbudowany plik binarny CLI dla szybszych interakcji terminalowych. |
| 2026-03-23 | Dokumentacja sqlite-vec v0.1.7 stable: obsługa DELETE dla tabel vec0, ograniczenia odległości KNN dla paginacji. Ogłoszenie indeksu DiskANN przybliżonego najbliższego sąsiada w nadchodzącym wydaniu. |
| 2026-03-07 | Dodanie potion-multilingual-128M (101 języków, maj 2025) do porównania modeli embeddings. sqlite-vec w wersji v0.1.7-alpha.10 (poprawki CI/CD, brak zmian funkcjonalnych). Potwierdzenie aktualności specyfikacji MCP i technik wyszukiwania. |
| 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 serwerów MCP społeczności. Aktualizacja Smart Connections do v4. |
| 2026-03-02 | Dodanie potion-base-32M i potion-retrieval-32M do porównania modeli. Dodanie sekcji kwantyzacji/redukcji wymiarowości. Dodanie uwagi o ewolucji specyfikacji MCP. |
| 2026-03-01 | Pierwsze wydanie |
Przypisy
-
Internet Vin, „22 commands I use with Obsidian and Claude Code,” March 2026, x.com/internetvin/status/2026461256677245131. ↩
-
Nicopreme, „Visual Explainer” agent skill with slash commands, x.com/nicopreme/status/2023495040258261460. ↩
-
Cormack, G.V., Clarke, C.L.A., and Buettcher, S. Reciprocal Rank Fusion outperforms Condorcet and individual Rank Learning Methods. SIGIR, 2009. Wprowadza RRF z parametrem k=60 jako bezparametrową metodę łączenia list rankingowych. ↩↩↩
-
OpenAI Embeddings Pricing. text-embedding-3-small: 0,02 USD za milion tokenów. Szacowany koszt pełnej reindeksacji skarbca: ~0,30 USD. ↩
-
van Dongen, T. et al. 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 średnio 50,03 w porównaniu z 56,09 dla all-MiniLM-L6-v2 (89% zachowanej jakości). ↩
-
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 ramach SQLite. ↩ -
Robertson, S. and Zaragoza, H. The Probabilistic Relevance Framework: BM25 and Beyond. Foundations and Trends in Information Retrieval, 2009. ↩
-
Karpukhin, V. et al. Dense Passage Retrieval for Open-Domain Question Answering. EMNLP, 2020. Gęste reprezentacje przewyższają BM25 o 9–19% w zadaniach odpowiadania na pytania w domenie otwartej. ↩
-
Reimers, N. and Gurevych, I. Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks. EMNLP, 2019. Fundamentalna praca nad gęstym podobieństwem semantycznym. ↩
-
Luan, Y. et al. Sparse, Dense, and Attentional Representations for Text Retrieval. TACL, 2021. Wyszukiwanie hybrydowe (hybrid retrieval) konsekwentnie przewyższa podejścia jednomodalne na MS MARCO. ↩
-
SQLite Write-Ahead Logging. Tryb WAL umożliwiający równoczesne odczyty przy jednym procesie zapisującym. ↩
-
Gao, Y. et al. Retrieval-Augmented Generation for Large Language Models: A Survey. arXiv, 2024. Przegląd architektur RAG i strategii dzielenia na fragmenty (chunking). ↩
-
Thakur, N. et al. BEIR: A Heterogeneous Benchmark for Zero-shot Evaluation of Information Retrieval Models. NeurIPS, 2021. ↩
-
Model2Vec: Distill a Small Fast Model from any Sentence Transformer. Minish Lab, 2024. ↩
-
Obsidian Documentation. 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 rozmiarze 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 wprowadza transport Streamable HTTP, wykrywanie przez .well-known URL, 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 trenowania i dostrajania. v0.5.0 (kwiecień 2025): przepisanie backendu, kwantyzacja, redukcja wymiarowości. v0.7.0 (październik 2025): kwantyzacja słownictwa, wsparcie tokenizatorów BPE/Unigram. ↩↩
-
Smart Connections for Obsidian. Smart Connections v4: lokalne embeddingi AI z priorytetem offline, wyszukiwanie semantyczne działa bez połączenia po początkowym indeksowaniu. ↩
-
potion-multilingual-128M. Minish Lab, maj 2025. Statyczny model embeddingów dla 101 języków, najlepiej działające wielojęzyczne embeddingi statyczne. Ta sama zależność wyłącznie od numpy co w pozostałych modelach potion. ↩
-
MCPVault v0.11.0. Marzec 2026. Nowe narzędzie
list_all_tagsdo skanowania tagów w frontmatter i hashtagów z licznikami. Ulepszona obsługa folderów z kropką, wsparcie plików.basei.canvas. Pakiet przemianowany na@bitbonsai/mcpvaultw npm. ↩ -
sqlite-vec v0.1.7 Release. 17 marca 2026. Stabilne wydanie: obsługa DELETE dla wirtualnych tabel vec0, ograniczenia odległości KNN do paginacji, ulepszenia testów fuzzingowych. Indeksowanie DiskANN przybliżonych najbliższych sąsiadów zapowiedziane na przyszłe wydanie. ↩↩↩
-
Introduction to Bases. Podstawowy plugin Obsidian wprowadzony w wersji 1.9.10. Widoki bazodanowe (tabele, galerie, kalendarze, tablice kanban) nad plikami skarbca wykorzystujące właściwości frontmatter jako pola. Pliki zapisywane w formacie
.base. ↩ -
Obsidian 1.12 Desktop Changelog. 27 lutego 2026. Wprowadza CLI Obsidian do automatyzacji skarbca z poziomu terminala. Polecenia obejmują wyszukiwanie, notatki dzienne, szablony, właściwości, pluginy, zadania oraz narzędzia deweloperskie. Dokumentacja CLI. ↩
-
Claudian. Plugin Obsidian osadzający Claude Code jako współpracownika AI w skarbcu. Zapewnia czat w panelu bocznym, prompty uwzględniające kontekst, wsparcie wizji, polecenia slash oraz tryby uprawnień. ↩
-
Agent Client. Plugin Obsidian zapewniający zunifikowany interfejs dla Claude Code, Codex CLI i Gemini CLI poprzez Agent Client Protocol (ACP). Obsługuje odwołania do notatek, wykonywanie poleceń powłoki oraz zatwierdzanie akcji. ↩
-
Obsidian iOS Changelog. Aktualizacje z początku 2026 obejmują Share Extension do zapisywania treści z innych aplikacji bezpośrednio do skarbca, poprawki widżetów Notatki Dziennej i Zakładek oraz ulepszenia odświeżania widżetu Podglądu Notatki. ↩