nanobot: 2026-09-10 12:33:37
This commit is contained in:
@@ -3126,3 +3126,332 @@ a zabránit opakování driftu.
|
||||
- `notes.md`: `ssh nanobot@nanobot.hell 'cd ~/.nanobot/workspace && git revert f136f50'`
|
||||
- `AGENTS.md` / `projects/proxmox/state.md`: `git checkout -- AGENTS.md projects/proxmox/state.md` v serverovém workspace repu (pozor: vrátí i případné cizí necommitnuté změny).
|
||||
- `skills/keep/`: `git revert a96a5c6` v tomto repu + rsync `skills/keep/` na server.
|
||||
|
||||
---
|
||||
|
||||
## 2026-09-04 07:20 — `keep` skill: description podle oficiálního formátu + přenositelnost
|
||||
|
||||
**Cíl:** Uživatel reklamoval `description` u `skills/keep/SKILL.md` — čeština uvnitř a špatně
|
||||
napsaná proti tomu, jak má `description` vypadat. V průběhu doplnil druhou vadu: absolutní
|
||||
cesta v těle skillu ho dělá nepřenositelným.
|
||||
|
||||
**Co jsem zkusil:**
|
||||
|
||||
1. Porovnal serverovou verzi s repem (`ssh nanobot@nanobot.hell cat …`) — **identické**,
|
||||
žádný drift od Dreamu k dotažení.
|
||||
2. Dostudoval dokumentaci. `nanobot.wiki` (0.3.0) frontmatter skillů **nedokumentuje** —
|
||||
`/docs/0.3.0/` nemá stránku o skillech, `use-nanobot/concepts` je zmiňuje jen jako obsah
|
||||
workspace. WebFetch na wiki vrací 403, curl s UA projde. Autoritou je proto
|
||||
[Anthropic Skill authoring best practices](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices)
|
||||
(formát je identický) + naše zjištění ze zdrojáku v `knowledge.md`.
|
||||
3. Změřil současnou verzi proti pravidlům: ✗ CZ triggery (EN-only + ověřeno, že
|
||||
dvojjazyčné nic nepřidají), ✗ otevírá noun-phrase definicí místo slovesné fráze,
|
||||
✗ obsahuje *jak* funguje (dedup/compaction/cesta), ✗ interní architektura
|
||||
(MEMORY.md / Dream), ✗ duplicita, ~ chybí odlišení od `/project`.
|
||||
Třetí osoba porušená nebyla.
|
||||
4. Přepsal `description` do oficiálního tvaru `<co dělá>. Use when <triggery>.` (385 znaků,
|
||||
limit 1 024), opravil poslední češtinu v těle (`"udělej compaction"` → `the user asks
|
||||
for compaction`) a absolutní cestu → `workspace/keep.md`.
|
||||
5. Nasadil rsyncem, ověřil vlastníka.
|
||||
|
||||
**Co fungovalo a proč:**
|
||||
|
||||
- Grepy čisté: žádná čeština, žádné `/home/nanobot` v `skills/keep/SKILL.md`.
|
||||
- **Funkční test routingu:** `nanobot agent -m "zapamatuj si, že testovací server pro
|
||||
staging je stag-07"` → odpověď `Kept: …`, záznam v `keep.md`. Skill se tedy trefil
|
||||
i bez českých triggerů — potvrzuje ověřený předpoklad z `/plan` (2026-05-31), že
|
||||
dvojjazyčné triggery jsou zbytečný token cost.
|
||||
- Testovací záznam z `keep.md` smazán (záloha `/tmp/keep.md.bak` na serveru).
|
||||
- `nanobot` binárka není v PATH non-login shellu — volat `~/.local/bin/nanobot`
|
||||
(stejná gotcha jako u `uv`).
|
||||
|
||||
**Co zbývá:**
|
||||
|
||||
- `skills/plan/SKILL.md:45` má stejnou absolutní cestu, přitom řádky 69 a 75 v témže
|
||||
souboru už jsou relativní. `bookmark` má absolutní cesty ve spouštěcích příkazech —
|
||||
tam opodstatněné (PATH gotcha u `uv`). Neřešeno, mimo scope zadání.
|
||||
- Ostatní skilly nebyly proti oficiálnímu tvaru `description` proměřeny.
|
||||
|
||||
**Jak vrátit zpět:** `git revert 18ab382` + rsync `skills/keep/` na server.
|
||||
|
||||
---
|
||||
|
||||
## 2026-09-09 20:20 — Final wiki: zhodnocení draftu hybrid RAG a přepis do `final-wiki-hybrid-rag.md`
|
||||
|
||||
**Cíl:** Zhodnotit serverový draft `plans/notes-search-hybrid-rag.md` (hybrid RAG index nad
|
||||
uživatelovými git poznámkami), rozhodnout, jestli by to tak šlo, a přepsat do finálního plánu.
|
||||
|
||||
**Co jsem zkusil:**
|
||||
|
||||
1. Přečetl draft a proměřil prostředí místo věření odhadům v něm:
|
||||
- Ollama na `nvidia.hell` dosažitelná; `qwen3-embedding` tam zpočátku nebyl (uživatel dodal).
|
||||
- Cold load modelu 1,81 s vs. warm 0,043 s; throughput 8,9 chunk/s po jednom vs.
|
||||
**108 chunk/s v batchi 32**.
|
||||
- Cosine nad 10⁵×1024 v BLOB+numpy: **594 ms** = read 382 + pack 190 + **dot jen 22**.
|
||||
Přes `.npy` + `mmap_mode='r'` 35 ms.
|
||||
- FTS5: `remove_diacritics 1`+ foldí diakritiku, `záloh*` pokryje českou flexi.
|
||||
- Korpus workspace: 234 md mimo `tmp/` → ~800 chunků (draft počítal 10⁴–10⁵).
|
||||
2. Ověřil `sqlite-vec` smoke testem celého navrženého schématu (`chunks` + `files` +
|
||||
`chunks_fts` + triggery + `vec_chunks`) — včetně toho, na čem D3 stálo.
|
||||
3. Ověřil model na reálném obsahu: cross-jazyk retrieval test + vliv instruct prefixu.
|
||||
4. Přepsal plán do `final-wiki-hybrid-rag.md`, nasadil na server i do repa, starý draft
|
||||
označil `> Superseded by`.
|
||||
|
||||
**Co fungovalo a proč:**
|
||||
|
||||
- **Díra v návrhu draftu**: RRF slučoval file-level BM25 s chunk-level cosine. Rank-merge dvou
|
||||
různých jednotek nemá definovaný význam. Fix: `chunks` je jediná retrieval jednotka, FTS5 je
|
||||
external-content nad ní, `vec0` má `rowid = chunks.id`, RRF slučuje `chunks.id`. Ověřeno.
|
||||
- **`vec0` obstálo ve všech kritických testech**: `DELETE`/re-`INSERT`/`UPDATE` po `rowid`,
|
||||
transakční `ROLLBACK`, `distance_metric=cosine`, `vec0` v témže souboru jako běžné tabulky.
|
||||
KNN k=20: **19 ms @ 10k, 212 ms @ 100k**. Kdyby `DELETE` nešel, D3 by padlo a vracel by se BLOB.
|
||||
- **Zdůvodnění D3 v draftu bylo špatné** — stavěl `sqlite-vec` jako „indexovaný KNN" (ANN).
|
||||
Není; dokumentovaná cesta je průchod (lineární 19 → 212 ms to potvrzuje). Skutečná výhoda je,
|
||||
že skenuje v C a odřízne těch 96 % Python režie.
|
||||
- **Ollama Cloud embeddingy neexistují** — 18 cloud modelů, žádný s capability `embedding`;
|
||||
`/api/embed` na cloud modelu vrací `unauthorized`, zatímco `/api/generate` na tomtéž projde
|
||||
(takže to není o autorizaci). Katalog na ollama.com potvrzuje: všech 12 embedding modelů
|
||||
je jen ke stažení.
|
||||
- **D1 potvrzeno měřením**, ne argumentem: dotaz „jak snížit elektroodpad ze stavebnic" →
|
||||
BM25 **1/5**, embeddingy **3/5**; lexikální dotaz naopak BM25 4/5. Obě poloviny si vydělávají.
|
||||
- **`exec` timeout 60 s** vs. plný index ~77 s → indexace nesmí běžet v tahu agenta. V draftu to
|
||||
nebylo; uživatel zvolil offline cron s lockem, čímž `fetch-on-query` z draftu vypadl.
|
||||
- **Whitelist místo blacklistu** (na dotaz uživatele): ve workspace je 234 md mimo `tmp/` a
|
||||
použitelných ~99 — zbytek `.venv/` 27, `cml/` 39, `skills/` 32, `backup/` 20, `tasks/` 16;
|
||||
`tmp/` drží dalších 135. Blacklist by musel trvale pokrývat právě adresář určený k balastu.
|
||||
Rozhodující je ale směr selhání: u blacklistu nový adresář *tiše vstoupí* do indexu.
|
||||
- DDL vytažené přímo z hotového dokumentu spuštěno proti `:memory:` — prošlo, a FTS5 i `vec0`
|
||||
vrátily shodné `rowid`.
|
||||
|
||||
**Co jsem měl špatně (korekce vlastních tvrzení):**
|
||||
|
||||
- Tvrdil jsem, že vynechání **instruct prefixu** stojí víc než přechod na menší model. Test na
|
||||
178 chunkách to vyvrátil: recall@5 s prefixem i bez něj **identický** (5/5 vs 5/5, 3/5 vs 3/5),
|
||||
similarita s prefixem dokonce nižší. Prefix zlepšil jen top-1 nejtěžšího dotazu. V plánu
|
||||
zůstává (je zdarma), ale zdůvodnění je teď „bitová identita indexace a dotazu", ne kritičnost.
|
||||
- Cold load jsem extrapoloval na 4–6 s; naměřeno **1,81 s**. `keep_alive: -1` je tedy výhodný,
|
||||
ne nutný.
|
||||
- Dvě chyby v mých vlastních test skriptech, obě mě zdržely: chybějící
|
||||
`isolation_level=None` (→ „cannot start a transaction within a transaction") a bufferovaný
|
||||
stdout Pythonu, kvůli kterému 9 minut běžící skript neukázal, že visí na síťovém čtení.
|
||||
|
||||
**Nálezy pro implementaci:**
|
||||
|
||||
- **`ON DELETE CASCADE` z `files` uklidí `chunks` i FTS5 (trigger se na kaskádě spustí), ale
|
||||
řádek ve `vec_chunks` osiří** — `vec0` není cílem foreign key. Indexer musí mazat `vec_chunks`
|
||||
explicitně. Je to v plánu jako regresní test; jinak by index při mazání souborů tiše hnil.
|
||||
- `keep_alive` musí být **číslo** `-1` (nebo `"24h"`); string `"-1"` vrací HTTP 400.
|
||||
|
||||
**Co zbývá:**
|
||||
|
||||
- **Seznam rep** (URL + kam mirrorovat) — jediná věc, kterou musí doplnit autor; bez toho
|
||||
nelze naplnit `config/notes-search.yaml`.
|
||||
- Zopakovat cross-jazyk benchmark na reálných repech (proběhl na workspace obsahu).
|
||||
- Návrh 6 vět do `decisions.md` je v plánu — **čeká na souhlas autora**, nezapsáno.
|
||||
- Mimo scope a jen hlášeno: `llm-wiki` řeší autor sám (zazálohuje a smaže);
|
||||
`wiki-compile` existuje jen na serveru a v žádném commitu (drift, pravděpodobně Dream).
|
||||
|
||||
**Jak vrátit zpět:** `git revert 36259d7`; na serveru smazat
|
||||
`plans/final-wiki-hybrid-rag.md` a odstranit vložený `> **Superseded by` řádek (druhý řádek)
|
||||
z `plans/notes-search-hybrid-rag.md`.
|
||||
|
||||
---
|
||||
|
||||
## 2026-09-09 — Oprava plánu final-wiki-hybrid-rag po plan-review
|
||||
|
||||
**Cíl:** Projet `final-wiki-hybrid-rag.md` skrz `/lab:plan-review` a opravit, co review najde,
|
||||
aby byl plán implementovatelný bez hádání.
|
||||
|
||||
**Co jsem zkusil:**
|
||||
|
||||
Review vrátila verdikt „vrátit k přepracování": 5 blokujících nálezů, 7 k doplnění, 3 poznámky.
|
||||
Všechny kotvy, které plán uváděl jako existující, jsem ověřil a všechny sedí
|
||||
(`remind/scripts/db.py` symboly, `note_capture.py:25`, `_ascii_fold`, `.compile.lock`,
|
||||
`llm-wiki/scripts/wiki_search.py`, `develop/history.md` + `knowledge.md` na serveru, crontab
|
||||
vzor, `db/` v `.gitignore`). Plán nebyl superseded — skill neexistoval.
|
||||
|
||||
Pět blokujících nálezů:
|
||||
|
||||
1. Vrstva 2 (TOC) byla vyhlášená, ale sync ji negeneroval, schéma pro ni nemělo tabulku
|
||||
a Verifikace ji netestovala.
|
||||
2. `git clone --mirror` je **bare** repo → vrstva 1 (`rg`) nemá working tree, co grepovat,
|
||||
a chunker neměl řečeno, jak se z mirroru dostane obsah souboru.
|
||||
3. Chunky s `embedded_at IS NULL` se nikdy nedobraly: krok 3 syncu končí „nic se nezměnilo
|
||||
→ exit 0", jenže po degradovaném běhu se soubory nezměnily. Verifikace bod 4 přitom
|
||||
tvrdila opak a partial index `idx_chunks_pending` nikdo nečetl.
|
||||
4. Skupinové klíče v YAML (`sources.git.*`, `sources.nanobot.*`) nešly namapovat na
|
||||
`CHECK(kind IN ('git','workspace'))` — `nanobot` by CHECK odmítl.
|
||||
5. RRF neměl jediný parametr: chybělo `k` pro KNN, počet kandidátů z BM25, RRF konstanta,
|
||||
výstupní top-k i váhy.
|
||||
|
||||
**Co fungovalo a proč:**
|
||||
|
||||
Před opravou jsem si nechal od autora rozhodnout adresářovou strukturu a jméno skillu, protože
|
||||
na tom visí deploy cesta, cron řádek i testy — a pak dvě věci **naměřil místo hádání**, když
|
||||
padla otázka „jak se to bude chovat, když do `include` dám i `*.py` a `*.cs`":
|
||||
|
||||
- markdown-it-py nad Python souborem: komentář `# TODO: fix this` se parsuje jako **ATX
|
||||
heading h1**. Chunker by řezal soubor v komentářích a stavěl breadcrumb
|
||||
`foo.py > TODO: fix this` — ne horší kvalita, ale nesmysl.
|
||||
- FTS5 `unicode61`: `send`/`async` uvnitř `SendAsync` → **miss** (camelCase se nedělí);
|
||||
`migrate` i `_migrate` → oba hit, nerozlišitelné. `tokenchars '_'` problém jen převrátí
|
||||
(`get_db` hit, `get` miss). Jediný použitelný tokenizer je `trigram` — a ten je vlastnost
|
||||
**tabulky**, ne řádku, takže smíšený korpus = druhá FTS tabulka a dvojí dotaz.
|
||||
|
||||
Tohle rozhodlo D15 (index zůstává md-only) měřením, ne názorem. Náhradou je, že grep vrstva
|
||||
jede přes celý klon včetně kódu — soubory už na disku leží, takže to stojí nula.
|
||||
|
||||
Dvě zjednodušení nálezy zrušila místo záplatování: **plochá konfigurace** s explicitním `kind`
|
||||
(padá nález 4) a **sloučení `workspace` + `develop`** do jednoho zdroje (stejný kořen, stejný
|
||||
kind, nebyl důvod je mít dva).
|
||||
|
||||
Autor dodal URL obou rep, ověřeno z `nanobot.hell`: obě dostupná klíčem uživatele `nanobot`,
|
||||
obě `HEAD → refs/heads/master`. Otevřená otázka 1 tím padla.
|
||||
|
||||
**Nálezy pro implementaci:**
|
||||
|
||||
- Workspace na serveru **je git repo** a `wiki/` v jeho `.gitignore` chybí. Nasazení musí
|
||||
přidat `wiki/*` + `!wiki/config.yaml`, jinak se do gitu commitne index (komprimovaná kopie
|
||||
osobního obsahu) i klony. Původní plán se opíral o to, že `db/` je gitignorované —
|
||||
přesunem pod `wiki/` ta záruka zmizela.
|
||||
- `note_compile.py:85-108` má stale-lock reclaim (mrtvý PID nebo stáří > 30 min). Plán měl
|
||||
jen holý `O_EXCL`, což by po pádu procesu navždy umlčelo minutový cron bez jediného řádku
|
||||
v logu. Převzato doslova.
|
||||
- Aritmetika v plánu byla špatně: 10⁴ chunků / 108 chunk/s je ~93 s, ne 77 s. Závěr
|
||||
(> 60 s `exec` timeout) drží.
|
||||
|
||||
**Co zbývá:**
|
||||
|
||||
- **Pustit `/lab:plan-review` znovu** — skill se nedá vyvolat přes `Skill` tool
|
||||
(`disable-model-invocation`), musí ho spustit autor. Cíl: 0 blokujících nálezů.
|
||||
- Návrh 4 vět do `decisions.md` — čeká na souhlas autora, nezapsáno: kořen runtime dat
|
||||
`workspace/wiki/`, non-bare klon místo `--mirror`, plochá konfigurace s explicitním `kind`,
|
||||
index výhradně nad markdownem.
|
||||
- Benchmark modelu na uživatelských datech zůstává jako výstup etapy 6.
|
||||
|
||||
**Jak vrátit zpět:** `git revert 43688c5`. Na server se nesahalo.
|
||||
|
||||
## 2026-09-09 22:10 — implementace a nasazení skillu `wiki` (hybrid RAG index)
|
||||
|
||||
**Cíl:** Vykonat plán [plans/final-wiki-hybrid-rag.md](plans/final-wiki-hybrid-rag.md),
|
||||
etapy 1–7 včetně nasazení na server.
|
||||
|
||||
**Co jsem zkusil / co fungovalo a proč:**
|
||||
|
||||
- **Etapy 1–6 implementované a otestované lokálně** (`skills/wiki/`, commit `aecf597`):
|
||||
87 testů, `ruff` i `ty` čisté. Pokrývají body 1–6 a 8 z Verifikace v plánu.
|
||||
- **Etapa 7 nasazena** na `nanobot.hell`: rsync skillu, `wiki/config.yaml`,
|
||||
`.gitignore` (`wiki/*` + `!wiki/config.yaml`), první plný index a minutový cron.
|
||||
Plný index: **152 souborů, 601 chunků, 601 vektorů, 0 pending za 45 s**
|
||||
(index 55/123, travel 5/9, workspace 92/469).
|
||||
- **Dotaz end-to-end 53–62 ms** (cíl plánu byl pod 1 s). Rozpad odpovídá plánu:
|
||||
embed dotazu ~55 ms, KNN 2–5 ms, BM25 < 1 ms, RRF zanedbatelné.
|
||||
- **Odchylky od plánu (mechanické, ne věcné):** moduly nesou prefix `wiki_`, protože
|
||||
`db.py`/`store.py` kolidují s `remind` a `wiki_search.py` s `llm-wiki` ve `ty.toml`
|
||||
`extra-paths` (ploché jmenné prostory, vyhrává první cesta). Přidány dva moduly nad
|
||||
pětici z plánu — `wiki_config.py` (layout + rozsah) a `wiki_embed.py` (Ollama klient
|
||||
+ `meta` guard), protože obě vstupní body je potřebují a guard **musí** být identický
|
||||
na obou stranách. `numpy` z dependency setu vypuštěno — `sqlite_vec.serialize_float32`
|
||||
stačí, normalizace je jeden `math.sqrt`.
|
||||
- **Reálné selhání sítě ověřeno neplánovaně:** z mého stroje nemá SSH klíč právo na
|
||||
`travel-notes.git`, takže první lokální běh doslova předvedl bod 8 — `WARN`, zdroj
|
||||
přeskočen, `index` dojel. Na serveru (klíč uživatele `nanobot`) projde.
|
||||
|
||||
**Nalezené a opravené vady (moje, ne plánu):**
|
||||
|
||||
1. **Sekce obsahující jen svůj nadpis vytvářela prázdný chunk.** U běžného tvaru
|
||||
`# Titul` → `## Sekce` by šum lezl skoro do každého dokumentu. Fix: taková sekce se
|
||||
zahodí, nadpis se do indexu dostane přes breadcrumb potomků a `files.headings`.
|
||||
2. **Víceřádkový git stderr v logu.** Trvale nedostupný zdroj sype WARN každou minutu;
|
||||
šestiřádkový stderr = tisíce řádků denně. Fix: `_one_line()` + strop 300 znaků.
|
||||
Plán mluví o „řádku WARN" — teď to řádek skutečně je.
|
||||
3. **Coverage report hlásil i `wiki/`** (klony v `wiki/remote/` obsahují md).
|
||||
Strukturálně nikdy nekandidát, ne rozhodnutí k revizi → z reportu vyřazen.
|
||||
|
||||
**Otevřená otázka č. 1 plánu (benchmark modelu) — uzavřena.** 12 parafrázových dotazů
|
||||
s jednoznačným cílovým souborem nad reálnými 123 chunky repa `index`:
|
||||
|
||||
| Varianta | recall@5 | recall@10 | MRR |
|
||||
|---|---|---|---|
|
||||
| BM25 sám | 5/12 | 5/12 | 0,257 |
|
||||
| qwen3-embedding:0.6b + instruct prefix | 4/12 | **8/12** | 0,261 |
|
||||
| qwen3-embedding:0.6b bez prefixu | 2/12 | 3/12 | 0,204 |
|
||||
| mxbai-embed-large | 6/12 | 6/12 | 0,261 |
|
||||
| nomic-embed-text | 3/12 | 4/12 | 0,170 |
|
||||
| **RRF hybrid** | 6/12 | 6/12 | **0,367** |
|
||||
|
||||
D1 i D2 platí: hybrid má lepší MRR než obě poloviny samostatně, `nomic` je nejhorší
|
||||
(plán ho zamítal správně). V top-10 najde cíl **jen vektory u 3 dotazů, jen BM25 u 0** —
|
||||
vektorová polovina na tomhle korpusu lexikální pokrytím dominuje, BM25 ale dává lepší
|
||||
top-1 rank když trefí. Čtyři dotazy nenašel nikdo, protože cílové soubory jsou stuby
|
||||
(50–483 B, většinou shell příkazy) — chyba mých labelů, ne retrievalu; na odpověďorelných
|
||||
dotazech je hybrid 6/8.
|
||||
|
||||
**Co nesouhlasí s plánem:**
|
||||
|
||||
- **Instruct prefix NENÍ „nekritická vlastnost"** (plán, Query kontrakt). Naměřeno
|
||||
recall@10 **8/12 s prefixem vs. 3/12 bez** na uživatelských datech. Plán měřil
|
||||
5/5 vs 5/5 na 178 chuncích jiného korpusu. Prefix je load-bearing; je zapsán
|
||||
v `meta.query_prefix` a jeho změna vyžaduje `--full`.
|
||||
- **D6 („prefix wildcard pokryje českou flexi") platí jen na půl.** Prokázáno na
|
||||
serveru: `cestu*` → 0 chunků v `travel/packaging-list.md`, `cesty*` → 3, `cest*` → 4.
|
||||
Wildcard je **prefixový**, takže pomůže jen když je dotazové slovo prefixem tvaru
|
||||
v dokumentu; „cestu" prefixem „cesty" není. Plán to dokládal dotazem `záloh*`, což
|
||||
je už samotný **kmen**, ne skloněné slovo. Důsledek: na dotaz „co si vzít na cestu
|
||||
do zahraničí" se doslovný seznam věcí na cestu nedostal ani do top 10.
|
||||
|
||||
**Co zbývá:** viz [todo.md](todo.md) — rozhodnout o dotazové vrstvě u české flexe
|
||||
a návrh 4 vět do `decisions.md` (čeká na souhlas autora, nezapsáno).
|
||||
|
||||
**Jak vrátit zpět:** na serveru `crontab ~/.nanobot/backup/crontab.bak-20260909-220814`,
|
||||
pak `rm -rf ~/.nanobot/workspace/wiki ~/.nanobot/workspace/skills/wiki` a vrátit řádky
|
||||
`wiki/*` + `!wiki/config.yaml` z `.gitignore`. Lokálně `git revert aecf597`.
|
||||
|
||||
## 2026-09-09 23:25 — README ke skillu `wiki` + oprava prázdných titulků
|
||||
|
||||
**Cíl:** Doplnit ke skillu `wiki` lidsky psaný `README.md` (mentální model, proč je co tak,
|
||||
operátorský runbook), protože vysvětlení bylo rozeseté po `history.md`, result dokumentu
|
||||
a konverzaci. `SKILL.md` je záměrně EN instrukce pro agenta — tohle do něj nepatří.
|
||||
|
||||
**Co jsem zkusil / co fungovalo a proč:**
|
||||
|
||||
- **`skills/wiki/README.md`** (214 řádků), česky, druhá osoba, ve stylu existujících
|
||||
`skills/reflect/README.md` a `skills/compact-memory/README.md` — což jsou jediné dva
|
||||
README v repu, takže konvence se dala odečíst přímo z nich. Sekce: tři vrstvy, offline
|
||||
indexace a proč, co se v tiku děje, proč hybrid, česká flexe, index jako nápověda,
|
||||
rozsah indexace, kde co leží, ruční spuštění, hlášky, ověření.
|
||||
- **Ze `SKILL.md` na README záměrně nevede odkaz.** Ani `reflect`, ani `compact-memory` ho
|
||||
nemají; README je pro člověka a nesmí stát agenta kontext. (`detach/architecture.md`
|
||||
odkaz má, ale to je anglický dokument jiného žánru.)
|
||||
- **Každé tvrzení v README ověřeno proti kódu**, ne napsáno z hlavy: hlášky doslova
|
||||
grepem z `wiki_search.py`/`wiki_embed.py`, cesty vyhodnocením konstant z `wiki_config.py`,
|
||||
flagy z `parse_args()`. Příkazy ze sekce „Ruční spuštění" spuštěny naostro na serveru
|
||||
včetně `--source` s neexistujícím id (exit 1, `WARN unknown source`).
|
||||
|
||||
**Nalezená a opravená vada — `files.title` byl NULL u všech 152 souborů.**
|
||||
|
||||
Vyšlo to při ověřování příkladu z README: `toc` tiskl prázdný sloupec s titulkem pro
|
||||
**celý korpus**, přitom plán ho ukazuje jako podstatnou část výstupu. Příčina: `title` se
|
||||
bral výhradně z frontmatteru `title:`, ale uživatelovy poznámky nesou titulek jako `# H1`.
|
||||
|
||||
Fix: fallback na první nadpis. Po `--full` je pokrytí **117/152** (`index` 55/55,
|
||||
`travel` 5/5, `workspace` 57/92); zbylých 35 jsou raw zachyty `/note` v `notes/done/`
|
||||
bez jediného H1–H3 nadpisu — tam není z čeho titulek vzít a slug v názvu souboru ho nese.
|
||||
|
||||
Podstatné rozhodnutí u toho fixu: fallback plní **jen katalog, ne breadcrumb root**.
|
||||
Kdyby šel do rootu, přepsal by se text všech chunků (breadcrumb je součástí embedovaného
|
||||
textu) a musel by se bumpnout `chunker_version` s vynuceným `--full` u každého uživatele.
|
||||
Takhle je to změna jednoho sloupce v `files`.
|
||||
|
||||
**Vedlejší nález:** padl existující test `test_malformed_frontmatter_stays_body`. Nebyla to
|
||||
regrese — u rozbitého frontmatteru (`---\ntitle: [unclosed\n---`) udělá markdown-it z toho
|
||||
řádku **setext nadpis**, takže ho fallback vezme jako titulek. Frontmatter se opravdu
|
||||
nespotřeboval (tagy zůstaly prázdné, text zůstal v těle), jen test tvrdil něco jiného,
|
||||
než byl jeho záměr. Přepsán na to, co má tvrdit.
|
||||
|
||||
**Co zbývá:** nic z tohoto zásahu. Otevřené položky skillu `wiki` v `todo.md` (česká flexe,
|
||||
návrh do `decisions.md`) se nemění.
|
||||
|
||||
**Jak vrátit zpět:** `git revert 3adc2ef`, pak na serveru rsync skillu a
|
||||
`wiki_sync.py --full` (titulky se vrátí na NULL). Index se tím nerozbije — `title` je
|
||||
jen sloupec v katalogu, retrieval na něm nestojí.
|
||||
|
||||
@@ -347,6 +347,16 @@ Pole `description` ve frontmatteru non-always skillu je **routing signál**, ne
|
||||
|
||||
Zdroj: `nanobot/agent/skills.py:111-159` (`build_skills_summary`, `_get_skill_description`), `skills.py:94-109` (`load_skills_for_context`, always skilly), `nanobot/agent/context.py:87-95`.
|
||||
|
||||
**Oficiální tvar `description`** ([Anthropic — Skill authoring best practices](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices), platí i pro nanobot, formát je identický). Nanobot wiki frontmatter skillů nedokumentuje — autoritou je tenhle dokument plus zjištění ze zdrojáku výše.
|
||||
|
||||
- Tvar: `<co skill dělá, slovesná fráze>. Use when <triggery/kontexty>.` Např. `Extract text and tables from PDF files, fill forms, merge documents. Use when working with PDF files or when the user mentions PDFs, forms, or document extraction.`
|
||||
- **Vždy třetí osoba** — `Processes Excel files…`, nikdy `I can help you…` ani `You can use this to…`; nekonzistentní osoba zhoršuje discovery.
|
||||
- Musí obsahovat **co dělá i kdy použít**, klíčový use case první, konkrétní klíčové termíny. Vágní (`Helps with documents`) je anti-pattern.
|
||||
- Limit **1 024 znaků** (Agent Skills spec); Claude Code listing ořezává na 1 536.
|
||||
- Nanobotí dodatek k tomu: „co dělá" = **schopnost**, ne postup. `Adds, deduplicates, and compacts entries in keep.md` je *jak* → do těla.
|
||||
|
||||
Aplikováno na `keep` (history 2026-09-04).
|
||||
|
||||
## Dream procesor — automatické self-improvement
|
||||
|
||||
Nanobot má vestavěný Dream procesor (`agent/memory.py:Dream`) který běží každé 2 hodiny. Jde o **dvou-fázový LLM pipeline** nad `history.jsonl`:
|
||||
@@ -1163,3 +1173,86 @@ Dream). Zbytek serverového workspace repa běžně stojí s necommitnutými zm
|
||||
(`keep.md`, `AGENTS.md`, `cron/jobs.json`, `reflect/*`) — to je normální stav, ne rozbité repo.
|
||||
Při ruční editaci `notes.md` proto commitni s prefixem `note:` a stage jen `notes/`.
|
||||
Zdroj: history 2026-09-04.
|
||||
|
||||
## Ollama Cloud embeddingy neexistují — cloud tier je completion-only (2026-09-09)
|
||||
|
||||
Žádný z 18 cloud modelů nemá capability `embedding`; `/api/embed` na cloud modelu vrací
|
||||
`unauthorized`, **zatímco `/api/generate` na tomtéž modelu projde** — takže to není problém
|
||||
autorizace, cloud tier ten endpoint prostě neobsluhuje a hláška je zavádějící. Katalog na
|
||||
ollama.com to potvrzuje: filtr `c=embedding` vrací 12 modelů, všechny jen ke stažení, žádný
|
||||
s cloud tagem. Embeddingy je tedy nutné hostovat lokálně. Zdroj: history 2026-09-09.
|
||||
|
||||
## Cena brute-force cosine v SQLite je z 96 % Python režie, ne matmul (2026-09-09)
|
||||
|
||||
Nad 10⁵×1024 float32: **594 ms = read 382 + pack 190 + dot 22**. Samotný numpy matmul je
|
||||
zanedbatelný; platí se za extrakci BLOBů a jejich složení do matice. Proto `sqlite-vec`
|
||||
(`vec0`) dá **19 ms @ 10k a 212 ms @ 100k** — skenuje v C. `.npy` + `mmap_mode='r'` je
|
||||
ještě rychlejší (35 ms), ale za cenu druhého souboru mimo DB. Pozor: `vec0` **není ANN
|
||||
index** — lineární škálování 19 → 212 ms to potvrzuje, výhoda je konstanta, ne asymptotika.
|
||||
Zdroj: history 2026-09-09.
|
||||
|
||||
## FTS5 a čeština: `remove_diacritics` + prefix wildcard stačí, trigram netřeba (2026-09-09)
|
||||
|
||||
`unicode61 remove_diacritics 1` a výš foldí diakritiku (dotaz `zaloha` najde `záloha`;
|
||||
hodnota `0` ne). Stemming `unicode61` neumí, ale **prefix wildcard to pokryje**: `záloh*`
|
||||
i `zaloh*` najdou *záloha, zálohování, zálohy*. Dotazová vrstva tedy lepí `*` na termy —
|
||||
trigram tokenizer není potřeba. Souvisí s varováním v `remind/scripts/store.py`
|
||||
(`find_active_by_exact_text()`), že SQLite `lower()` foldí jen ASCII. Zdroj: history 2026-09-09.
|
||||
|
||||
## `vec0` není cílem foreign key — kaskáda ho nevyčistí (2026-09-09)
|
||||
|
||||
`ON DELETE CASCADE` z nadřazené tabulky uklidí navázané řádky **i FTS5 external-content
|
||||
tabulku** (AFTER DELETE trigger se na kaskádě spustí), ale řádek ve `vec0` virtuální tabulce
|
||||
**osiří** — indexer ho musí mazat explicitně. Jinak index při mazání souborů tiše plní mrtvými
|
||||
vektory. Ověřeno smoke testem; `DELETE`/re-`INSERT`/`UPDATE` po `rowid` a `ROLLBACK` ve `vec0`
|
||||
jinak fungují normálně (sqlite-vec v0.1.6). Zdroj: history 2026-09-09.
|
||||
|
||||
## Ollama `keep_alive` musí být číslo, ne string (2026-09-09)
|
||||
|
||||
`{"keep_alive": -1}` i `{"keep_alive": "24h"}` → HTTP 200; **`{"keep_alive": "-1"}` → HTTP 400**.
|
||||
Cold load `qwen3-embedding:0.6b` (639 MB) je 1,81 s vs. warm 0,043 s, takže připíchnutí modelu
|
||||
se vyplatí, ale není kritické. Batching je naopak podstatný: 8,9 chunk/s po jednom vs.
|
||||
**108 chunk/s v batchi 32**. Zdroj: history 2026-09-09.
|
||||
|
||||
## Instruct prefix je u `qwen3-embedding` load-bearing, ne kosmetika (2026-09-09)
|
||||
|
||||
Na 12 parafrázových dotazech nad 123 reálnými chunky: **recall@10 8/12 s prefixem
|
||||
vs. 3/12 bez něj** (MRR 0,261 vs 0,204). Plán `final-wiki-hybrid-rag` ho označoval za
|
||||
„nekritickou vlastnost" na základě 5/5 vs 5/5 na jiném korpusu — na uživatelských datech
|
||||
to neplatí. Prefix musí být **bitově identický** při indexaci i dotazu, proto žije
|
||||
v `meta.query_prefix` a jeho změna vyžaduje `wiki_sync.py --full`. Zdroj: history 2026-09-09.
|
||||
|
||||
## FTS5 prefix wildcard NEfolduje českou flexi — jen prefixy (2026-09-09)
|
||||
|
||||
`záloh*` najde *záloha/zálohování/zálohy* jen proto, že `záloh` je **kmen**. Se skutečně
|
||||
skloněným dotazovým slovem to selže: naměřeno `cestu*` → **0** chunků v
|
||||
`travel/packaging-list.md`, `cesty*` → 3, `cest*` → 4. Wildcard je prefixový, takže pomůže
|
||||
jen když je dotazové slovo prefixem tvaru v dokumentu — a česká flexe mění koncovku, ne
|
||||
začátek. Důsledek: dotaz „co si vzít na cestu do zahraničí" nedostal doslovný seznam věcí
|
||||
na cestu ani do top 10. Tohle je polovičnost D6 v plánu, ne chyba implementace.
|
||||
Zdroj: history 2026-09-09.
|
||||
|
||||
## Hybrid RRF zlepšuje rank, ne pokrytí (2026-09-09)
|
||||
|
||||
Nad reálnými poznámkami: MRR **0,367 u RRF** vs 0,257 (BM25 sám) a 0,261 (vektory samy) —
|
||||
merge dvou ranků téže množiny opravdu vyhrává. Ale `recall@10` u RRF je **6/12**, zatímco
|
||||
vektory samotné 8/12: RRF řadí podle **shody** obou polovin, takže chunk, který našla jen
|
||||
vektorová polovina na ranku #9, vytlačí z top-10 chunky, na kterých se poloviny shodnou.
|
||||
Očekávaný kompromis, ne vada. Zdroj: history 2026-09-09.
|
||||
|
||||
## Bare `python3` neotevře `vec_chunks` — potřebuje `sqlite_vec.load()` (2026-09-09)
|
||||
|
||||
Dotaz na běžné tabulky (`chunks`, `files`) přes systémový `python3` projde, ale jakmile se
|
||||
sáhne na `vec0` virtuální tabulku, přijde `sqlite3.OperationalError: no such module: vec0`.
|
||||
Extension se musí načíst explicitně (`enable_load_extension(True)` + `sqlite_vec.load(conn)`),
|
||||
což skill dělá v `wiki_db.get_db()`. Při ruční inspekci indexu na serveru je proto nutné
|
||||
`uv run --with "sqlite-vec==0.1.6"`. Zdroj: history 2026-09-09.
|
||||
|
||||
## `ty.toml` `extra-paths` je plochý jmenný prostor — kolize modulů mezi skilly (2026-09-09)
|
||||
|
||||
Typechecker `ty` řeší `import store` proti seznamu `extra-paths` a **vyhrává první cesta**,
|
||||
takže dva skilly se stejným jménem modulu si navzájem rozbijí kontrolu (testy `wiki`
|
||||
dostávaly `store` z `remind`). Proto všechny skilly kromě `remind` prefixují moduly jménem
|
||||
skillu (`note_capture.py`, `wiki_sync.py`) — je to nutnost, ne estetika. U kolize, které se
|
||||
nelze vyhnout (`wiki_search.py` je i v retired `llm-wiki`), rozhoduje **pořadí** v
|
||||
`extra-paths`. Zdroj: history 2026-09-09.
|
||||
|
||||
Reference in New Issue
Block a user