Implementacja kodu z tej sesji porzucona na rzecz e7625cd (dopisek redakcyjny
na górze pliku), ale log jest jedynym zapisem faktów operacyjnych: osierocony
natywny ollama.service na PIHA wyłączony 2026-07-27 (backlog odinstalowania
≈2026-08-10), kalibracja live ollama-piha z werdyktem GO (peak ~983 MiB,
~4.2–5.3 s/embed) i baseline bramki §9 (HTTP-equivalence 0 rozbieżności,
sol-down Δ~3e-4 — do powtórki na masterze, raport dedup follow-up (b)).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
12 KiB
Sesja 2026-07-27 — KB faza 4: fallback embed SOLARIA→PIHA (krok 3, ostatni element rdzenia)
Dopisek redakcyjny (2026-07-30, dedup —
docs/kb/modules/05-fallback-dedup-raport.md): implementacja kodu z tej sesji (app/fallback.py, branchtask/kb-f4-fallback,3d4ee38) została porzucona — do mastera weszła równoległa, szersza implementacja tego samego kroku planu (e7625cd,app/embed_router.py, 2026-07-29) i to ona biega na PIHA. Ten log wciągnięto do repo, bo dokumentuje fakty operacyjne niezależne od porzuconego kodu: znalezisko i wyłączenie osieroconego natywnegoollama.servicena PIHA (§3, z backlogiem odinstalowania ≈2026-08-10), kalibrację live ollama-piha z werdyktem GO (§4 — konfiguracja kontenera identyczna na masterze, pomiar przenosi się) oraz metodologię i baseline bramki §9 (§5, Δ~3e-4). Wyniki bramki i testu sol-down dotyczyły kodu z brancha — na wdrożonym masterze wymagają powtórki (raport dedup, follow-up (b)). Sekcje o deployu (§6) i "Do zrobienia przez operatora" pkt 1–2 opisują stan sprzed merge'ae7625cd— historyczne. Z delty brancha uratowano ponadto:retrieval_eval.py --transport http(plan §2 D6/§9) i luki testowe T1/T2 przeniesione dotest_embed_router.py.
Zakres: docs/kb/modules/05-faza4-plan.md §2 decyzja 2 / §5 — aktywny fallback
embedu, ostatni brakujący element rdzenia fazy 4 (frontend i ingress LIVE od
2026-07-22/23, docs/sessions/2026-07-23-kb-f4-ingress.md). Zero zmian w schemacie
DB, zero zmian w kb_retrieval's retrieval logice — wyłącznie warstwa embed + health.
Praca wykonana w task worktree (task/kb-f4-fallback, .claude/skills/worktree-aware).
Zgodnie z ustaleniem na starcie sesji (patrz "Ustalenia proceduralne" niżej): kod
napisany i przetestowany lokalnie w worktree, produkcyjne kroki (kalibracja, deploy,
live-test) wykonane po jawnej zgodzie operatora, z osobnym potwierdzeniem przed
każdym kolejnym krokiem dotykającym PIHA/SOLARIĘ.
Ustalenia proceduralne
Zadanie wprost wymagało kroków produkcyjnych (kalibracja RAM/latencji na żywym PIHA,
symulacja sol-down dotykająca SOLARII, deploy, push) — sprzeczne z ogólną dyscypliną
worktree-aware ("nigdy nie uruchamiaj deployów/healthchecków przeciw produkcji z
worktree"). Zamiast rozstrzygać to samodzielnie, zapytano operatora:
- Recon read-only (bez zmian stanu) — zgoda bez pytania.
- Właściwe kroki produkcyjne (kalibracja, deploy, live-test, push) — operator potwierdził jawnie ("Yes, proceed with all of it") po zobaczeniu pełnego zakresu.
1. Kod (warstwa embed + health, zero zmian retrievalu/DB)
packages/kb_retrieval/embed.py:embed_chunkdostał opcjonalnytimeout_s(domyślnieNone, zero zmiany zachowania istniejących wołań) — potrzebny do twardego 3 s timeoutu na nodze SOLARIA bez zmiany zachowania nogi PIHA.services/kb-query/app/fallback.py(nowy):SolCircuitBreaker(cache 30 s, zegar wstrzykiwalny do testów) +resolve_sol_status(probe/api/tags, 500 ms) +embed_with_fallback(SOLARIA z twardym 3 s timeoutem → jednorazowe przełączenie na PIHA w tym samym requeście przy timeout/błędzie → PIHA bez dodatkowego timeoutu). Dokładnie maszyna stanów z planu §2 decyzja 2.app/search.py:run_searchliczy embedding raz przezembed_with_fallback, potem wołaflat_retrieve/cascade_retrieve/hybrid_retrieve(niskopoziomowe funkcjekb_retrieval, biorą gotowy wektor) zamiastflat_query/cascade_query/hybrid_query(które embedują same) — dzięki temu decyzja fallbacku żyje wyłącznie w warstwie HTTP kb-query, zero zmiany wkb_retrieval.sol_statusw odpowiedzi to teraz realny wynik, nie zahardkodowane"up".app/main.py:/healthzi/searchdzielą jedenSolCircuitBreaker(app.state.sol_breaker) — oba endpointy zawsze zgadzają się co do aktualnego stanu. Nowy envOLLAMA_PIHA_URL(domyślniehttp://localhost:11434— celowo "inertny" placeholder, fail-closed, dopóki operator nie ustawi realnego adresu).- Inwariant modelu: nie dodano drugiego, per-request sprawdzenia w DB —
EMBED_MODELto jedna stała wątkowana przez obie nogiembed_with_fallback, więc startowy check (app/startup.py, niezmieniony) pokrywa obie ścieżki z konstrukcji. Dodanie drugiego DB-checka chroniłoby przed scenariuszem, który nie może wystąpić (CLAUDE.md: nie dodawaj walidacji dla scenariuszy, które nie mogą się zdarzyć) — zamiast tego nowy test (test_both_legs_use_identical_embed_model) strukturalnie dowodzi, że obie nogi w tym samym requeście dostają identycznyembed_model. jobs/documents-ingest/eval/retrieval_eval.py: dodano--transport {direct,http}+--base-url(plan §2 decyzja 6 / §9) — dotąd nieistniejące (tylko ręczny smoke-test,docs/sessions/2026-07-23-kb-f4-ingress.mdfollow-up). Trybhttpwoła trzyGET /search(flat/cascade/hybrid) na żywym kb-query zamiast embedować+odpytywać lokalnie;envelope.sourcedo kryterium 4 bierze się z polasourcew odpowiedzi JSON, nie z osobnego zapytania do DB. Nie da się swipe'ować N przez HTTP (kb-query serwuje jeden N per request) — tryb http raportuje tylko przy--gate-n.
2. Nowy serwis services/ollama-piha
Klon wzorca services/ollama (owner_node: piha zamiast solaria, bez rezerwacji
GPU — PIHA to arm64 bez akceleracji), OLLAMA_KEEP_ALIVE=0 (model ładowany tylko na
czas requestu). mem_limit: 2560m (tentatywny wg planu, potwierdzony pomiarem —
patrz §3). Wpisany do hosts/piha/services.yaml (depends_on.local kb-query →
[kb-postgres, ollama-piha], fallback nie jest twardą zależnością na starcie).
3. Znalezisko: osierocony natywny ollama.service na PIHA
Podczas pierwszej próby deployu ollama-piha (bind 127.0.0.1:11434) — konflikt
portu. Okazało się, że PIHA ma natywny (nie-Docker) systemd ollama.service
(v0.6.1, enabled, działający od 2026-06-22, PATH env wskazujący na użytkownika
/home/pi/...), o którym nic nie wiadomo w repo — plan §1.2 wprost zakładał "PIHA:
brak Ollamy", co okazało się nieaktualne/błędne. To realna sprzeczność planu z
rzeczywistością → STOP, pytanie do operatora zamiast cichej decyzji.
Weryfikacja przed jakąkolwiek akcją: journalctl -u ollama --since "7 days ago" —
tylko własne, właśnie wykonane zapytania probe (/api/version, /api/tags),
total blobs: 0 od startu (nigdy nic nie pobrano). Operator potwierdził: martwy
balast, sudo systemctl disable --now ollama.service (disable, nie uninstall —
odwracalne). Port 11434 zwolniony, ollama-piha wystartował normalnie.
Backlog: PIHA host-level shadow — natywny ollama.service wyłączony
2026-07-27; odinstalować binarkę/unit po ~2 tygodniach jeśli nic się nie posypie.
4. Kalibracja (plan §5, gate) — werdykt: GO
Zmierzone na żywym PIHA pod normalnym obciążeniem (kb-postgres, paperless, Immich,
HA, Forgejo działające, nie okno nocnej ciszy), 3 kolejne wywołania /api/embeddings
po ollama pull bge-m3:
| Wywołanie | Latencja |
|---|---|
| 1 (pierwsze, zimny start) | 5.25 s |
| 2 | 4.41 s |
| 3 | 4.16 s |
Brak przyspieszenia między wywołaniami — zgodnie z projektem (OLLAMA_KEEP_ALIVE=0
zwalnia model po każdym requeście, ollama ps pokazuje zero rezydentnych modeli
między wywołaniami).
RAM: baseline idle ~66 MiB, szczyt podczas burst ~983 MiB (docker stats, próbkowane
co 0.3 s w trakcie 3 wywołań) — komfortowo w granicach ceilingu 2560m. free -h
systemowe: available nie spadło poniżej ~1.3 GiB w trakcie, osiadło na ~4.2 GiB po
(dla porównania: przed startem eksperymentu available = 3.7 GiB).
Werdykt: oba kryteria planu spełnione (latencja pojedyncze sekundy, nie
dziesiątki; RAM ze sporym zapasem) → włączony jako domyślny fallback, bez flagi
KB_QUERY_LOCAL_FALLBACK_ENABLED.
5. Bramka jakościowa (plan §9)
Wszystko uruchomione z ~/kb/venv na PIHA (istniejący venv z poprzednich sesji,
aiohttp/asyncpg/yaml już obecne) przeciw żywej bazie + żywemu kb-query.
HTTP-equivalence (--transport http vs --transport direct, SOLARIA up, ten sam
--gate-n 10): oba PASS, 0 rozbieżności w dist na wszystkich zapytaniach
(flat_top1_dist, hybrid_top1_dist, cascade[10].top1_dist) — identyczne bit w
bit, jak wymagał plan (nie ±epsilon, bo to ten sam kod, HTTP to tylko opakowanie).
Live sol-down fallback test: symulacja przez OLLAMA_URL=http://solaria:1
(zły port, zgodnie z rekomendacją planu — zero dotknięcia SOLARII/innych
konsumentów Ollamy) w .env kb-query, restart kontenera. /healthz →
sol_status: "down". /search → 200, wyniki z PIHA, ~4.3 s (zgodnie z kalibracją).
Pełna bramka retrieval_eval.py --transport http z SOLARIA-down: PASS —
identyczny wzorzec hit@3 co na SOLARII, dist w granicach epsilon:
| Zapytanie | dist (SOLARIA) | dist (PIHA fallback) | Δ |
|---|---|---|---|
| 1 | 0.341780 | 0.341509 | 0.000271 |
| 2 | 0.324808 | 0.324858 | 0.00005 |
| 3 | 0.428898 | 0.429184 | 0.000286 |
| 4 | 0.448199 | 0.447903 | 0.000296 |
| 5 | 0.386901 | 0.386816 | 0.000085 |
| N (negative control) | 0.598301 | 0.598017 | 0.000283 |
| N2 (negative control borderline) | 0.529772 | 0.529530 | 0.000242 |
Maksymalna rozbieżność: ~3e-4 — rząd wielkości mniejszy niż oczekiwany przez plan
(1e-3–1e-2), kolejność top-k identyczna, wynik bramki (gate.passed) identyczny.
Kb-query przywrócony do normalnej konfiguracji po teście (.env z prawdziwym
OLLAMA_URL, restart), /healthz z powrotem sol_status: "up".
6. Deploy
Kod nie był jeszcze zmergowany do master (dyscyplina worktree: agent nigdy nie
mergeuje/pushuje master) — deploy przez standardowy deploy-node.sh
niedostępny bez mastera. Zamiast tego: rsync zmienionych plików
(packages/kb-retrieval, services/kb-query, services/ollama-piha,
hosts/piha/runtime/ollama-piha, hosts/piha/services.yaml,
jobs/documents-ingest/eval/retrieval_eval.py + README) do żywego checkoutu
~/homelab-codex-ws na PIHA (bez zmiany brancha — working tree pozostaje na
master z niescommitowanym diffem 1:1 identycznym z tą gałęzią), potem
standardowy docker compose ... up -d --build z tego miejsca. Efekt: realny,
działający deploy, ale repo na PIHA ma dziś dirty working tree — wymaga domknięcia
(patrz "Do zrobienia przez operatora" niżej).
Zweryfikowane: kb-query (healthy), ollama-piha (healthy, bge-m3 w wolumenie),
curl https://kb.kapala.org/healthz → 200 {"sol_status":"up"},
curl https://kb.kapala.org/search?q=test → 200.
Stan na koniec sesji
| Element | Status |
|---|---|
packages/kb-retrieval — embed_chunk(timeout_s=...) |
✅ kod + testy |
services/kb-query/app/fallback.py — maszyna stanów |
✅ kod + testy (38/38 kb-query, 25/25 kb-retrieval) |
services/ollama-piha — nowy serwis GitOps |
✅ zdefiniowany, ✅ LIVE na PIHA |
Natywny ollama.service na PIHA (osierocony) |
✅ wyłączony (nie odinstalowany) |
| Kalibracja RAM/latencja | ✅ zmierzone — werdykt GO |
retrieval_eval.py --transport http |
✅ zaimplementowane, ✅ PASS na żywo |
| Live sol-down fallback test | ✅ PASS, Δ~3e-4 |
| Deploy kb-query + ollama-piha na PIHA | ✅ LIVE, working tree PIHA dirty (patrz niżej) |
Merge do master |
⛔ nie wykonany (dyscyplina worktree — operator) |
Do zrobienia przez operatora
- Merge
task/kb-f4-fallback→master(scripts/dev/agent.sh mergealbo ręcznie) — branch popchnięty doorigin/task/kb-f4-fallback(patrz commit poniżej). - Na PIHA:
cd ~/homelab-codex-ws && git statusbędzie dirty (diff identyczny z tym commitem, bo już wdrożony ad-hoc przezrsyncw tej sesji) — po mergu do mastera,git checkout -- .(working tree już ma dokładnie tę treść) albo zwyczajniegit pullpo mergu powinien wylądować "already up to date"/no-op, bo pliki na dysku już są zgodne z tym co przyjdzie z mastera. Zweryfikowaćgit diffjest puste po pull, nie zakładać. - Backlog: natywny
ollama.servicena PIHA wyłączonysystemctl disable --now2026-07-27 (§3 wyżej) — jeśli nic się nie posypie przez ~2 tygodnie, odinstalować binarkę/unit całkiem. - OIDC dla kb-query nadal odłożone (decyzja z 2026-07-23) — nie w zakresie tej sesji.