Files
nanobot-runtime/develop/history.md
2026-09-02 15:23:13 +02:00

3057 lines
319 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# History
## 2026-09-02 — reflect: noční běh padal na timeout a zahazoval hotové dávky
**Cíl:** Noční cron (`30 3 * * *`) neodběhl — ve 4:00 přišlo na Telegram
`reflect: ERROR — timeout po 30 min`. Zjistit proč a opravit.
**Co jsem zkusil / co jsem našel:**
Rozbor `log/reflect_cron.log` a `reflect/state.json` dal pět příčin, které se násobily:
1. **Cron žral celý backlog, ne denní přírůstek.** Za kurzorem (`2026-05-27T14:39`) leželo
412 session = 7 dávek po ~500 kB ≈ 90 min práce proti 30min stropu. Ostrý běh 1. 9. byl
omezený `--max-batches 1`, cron žádný strop neměl → deterministické selhání každou noc.
2. **All-or-nothing zápis.** `_write_findings`, kurzor i report se dělaly teprve po poslední
dávce. Dávky 0 a 1 přitom **vrátily platné nálezy** (03:38 a 03:55) — timeout je zahodil
a kurzor nechal na místě, takže druhý den totéž plus nové session. Ráčna, která se sama
nerozjede.
3. **Requesty na hraně timeoutu.** Prompt 185 k tokenů, `_OPENAI_COMPAT_REQUEST_TIMEOUT_S`
je 120 s a `NANOBOT_LLM_TIMEOUT_S` 300 s. 9× timeout; retry zahodí hotový prefill
a začne od nuly, dva tahy (600 s) skončily `Error calling LLM: timed out after 300s`.
4. **Infra chyba se tvářila jako vadná odpověď.** Ten error text šel do `parse_findings`
→ „the JSON is invalid" + zavádějící hint o uvozovkách → spotřeboval 1 ze 3 JSON pokusů
za dávku, a model dostal vytýkáno něco, co nenapsal.
5. **Tool cally v analytickém tahu** (`read_file SOUL.md`, `skills/*/SKILL.md`, `grep`) —
každá iterace je další plný prefill, 24 min na dávku.
**Co fungovalo a proč:**
- **Zápis po každé dávce** (`commit()` uvnitř smyčky v `_run`): nálezy, kurzor, report
i záznam běhu jdou na disk hned. Kurzor se navíc nikdy nepohne dozadu (`max` proti
stávající hodnotě), aby spadlý `--all` neshodil noční postup.
- **Soft deadline** `--deadline-minutes` (default 20) — nová dávka nezačne po limitu,
`TIMEOUT_SECONDS` zvednut na 45 min a je teď jen brzda na zaseknutou jednu dávku.
Když běh nedojde na konec, do Telegramu se přidá `Zpracováno N/M dávek.`
- **Infra chyba ≠ vadná odpověď:** `RunResult.stop_reason == "error"` (resp. `.error`) se
pozná dřív než validátor, nespotřebuje JSON pokus a přepošle **původní** prompt v čerstvé
session (selhaný tah nechal v té staré error zprávu i celý destilát). Strop 2 pokusy, pak
`ReflectError("model nedostupný: …")`.
- **Timeouty přes env** v hlavičce skriptu (`os.environ.setdefault`):
`NANOBOT_OPENAI_COMPAT_TIMEOUT_S=600`, `NANOBOT_LLM_TIMEOUT_S=900`.
- **Menší dávky:** `DEFAULT_BUDGET_CHARS` 500 000 → 200 000 (~70 k tokenů).
- **Strop na čtení souborů** v promptu („Read at most 2 files").
**Ověření (ostrý běh 06:51):** 26 session, jedna dávka, **147 s**, 83 k prompt tokenů,
**jedna iterace, žádný tool call, žádný provider timeout, JSON dobrý na první pokus**
(dřív ~13 min na dávku). Kurzor se posunul `2026-05-27T14:39``2026-05-29T09:21`,
`findings.jsonl` má 3 `open` + 4 `watch`, report `results/2026-09-02_reflect.md` sedí,
`state.json` má záznam běhu s `batches: 1`. Lokálně 115 testů zelených (11 nových), ruff čistý.
**Co zbývá:**
- Nechat proběhnout noční cron a ráno ověřit, že kurzor pokročil a nepřišel ERROR.
- Backlog (~386 session) se teď vysype po dávkách sám; kvalitu nálezů projít přes `/reflect`.
- **Pozor na falešné regrese:** analýza starých session hlásí `regression_of` u vzorů
opravených 1. 9. (`answer-self-config-from-guesswork`), protože ty session jsou z května,
tedy z doby před opravou. Regresní příznak má smysl teprve u session novějších než `applied`.
- Patche: tenhle běh nevrátil ani jeden (dřív 1 z 8). Sledovat, jestli to je náhoda, nebo
cena za strop na čtení souborů.
**Jak to vrátit zpět:** `git revert` commitu se změnou skillu v tomto repu + rsync na server
(`rsync -av --exclude '__pycache__' --exclude '.pytest_cache' skills/reflect/ nanobot@nanobot.hell:/home/nanobot/.nanobot/workspace/skills/reflect/`).
Kurzor v `reflect/state.json` na serveru se dá vrátit ručně na `2026-05-27T14:39:22.120065`,
nálezy z běhu 06:51 mají `created: 2026-09-02` (7 řádků v `findings.jsonl`).
## 2026-09-01 — reflect: review skillu a oprava tří tichých chyb + zjednodušení validátoru
**Cíl:** Uživatel chtěl důkladné review skillu `reflect` se zaměřením na to, jestli jsou skripty
zbytečně dlouhé a složité. Review našlo tři tiché chyby (nic nespadne, jen se děje něco jiného, než
co slibuje dokumentace) a čtyři místa zbytečné složitosti. Plán schválen, pak vykonán.
**Co jsem zkusil / co fungovalo a proč:**
1. **Kurzor nefiltroval.** `_run` ukládal `digest.started` zformátovaný (`2026-07-11 14:02`), ale
`collect_sessions` ho porovnávala proti syrovému `created_at` (`2026-07-11T09:00:12.345678`).
`'T'` (0x54) > `' '` (0x20) → porovnání vždycky vyšlo „novější". Ověřeno na 332 reálných session:
se starým formátem se **9 session z dne kurzoru analyzovalo znovu každý běh**, výskyty se počítaly
dvakrát a vzor se povyšoval na `open` dřív, než skutečně nastal podruhé — přesně to, čemu má práh
bránit. Fix: `SessionDigest.started` drží syrové ISO, formátuje se až v hlavičce destilátu.
Unit test to nechytal, protože si `since` podával jako literál v ISO — ve formátu, který volající
nikdy nevyrobí. Nový test dělá round-trip, který dělá `_run`.
2. **Zamítnutí vydrželo jen jeden běh.** `merge_findings` hledala `previous` jako záznam s nejnovějším
`created`. Po zamítnutí vznikl nový `watch` záznam s dnešním datem, ten příště přebil `rejected`
a vzor se otevřel. Reprodukováno: běh po zamítnutí → `watch`, o běh dál → `open`, přestože README
slibuje „zamítnutý vzor se znovu neotevře". Na serveru leží 4 zamítnuté vzory, takže by to bylo
vidět hned při backlog běhu. Fix: zamítnutí je vlastnost *vzoru* (`rejected_patterns` set přes
všechny záznamy), ne posledního záznamu. Stejná příčina brala i `regression_of` — přenáší se dál.
3. **`reflect_apply.py` hlásil „refused" po tom, co už soubor změnil.** Zápis byl před commitem, takže
selhání `git commit` vrátilo exit 2 se změněným necommitnutým souborem, bez audit řádku a s nálezem
pořád `open`. SKILL.md přitom agentovi říká „exit 2 = odmítnuto, jdi dál". Fix: rollback na původní
obsah (`check_patch` ho vrací, jen se zahazoval) + best-effort odstagování, původní chyba se
propaguje beze změny.
4. **Validátor zahazoval celou dávku.** Neznámý klíč, o 10 znaků delší diagnóza nebo 13 nálezů místo 12
vyhodily `FindingsError` a přeposlaly celý prompt — **~500 kB ≈ 420k tokenů**. Plán přitom říká
„zahodí vadné". Nově: retry jen když se nedá naparsovat JSON nebo nepřežil ani jeden nález; vadný
nález se zahodí, vadný patch se zahodí jen patch (diagnóza a návrh mají cenu i bez něj), dlouhý text
se ořízne, přebytečné nálezy se useknou. Důvody jdou na stderr, ať zahazování není neviditelné.
5. **Mrtvé a nekonzistentní věci:** `known-patterns.md` se generoval, ale nikdo ho nečetl (slovník pro
prompt se počítá přímo z `findings.jsonl`) — zrušen včetně řádků v obou dokumentacích.
`--check` existoval, ale SKILL.md místo něj instruoval agenta ověřit patch ručně — teď tiskne diff
a skill ho jen ukazuje. Dva různé výchozí budgety (1,5 M vs 500 k) sjednoceny na jednu konstantu.
`_session_start` četl celý JSONL kvůli pěti záznamům. Report dostal metriku „výskyt známých vzorů
na 100 session", kterou plán chtěl a nikdy nedostal.
**Co jsem záměrně neudělal:** přepis `Finding` dataclass na dicty (ušetřilo by ~45 řádků, ale je to
čistá kosmetika sahající na většinu prahových testů) a změnu struktury `_run` kvůli 30min timeoutu
(backlog jde bezpečně po dávkách přes `--all --max-batches 2`, jen to nebylo nikde napsané — doplněno
do README).
**Verifikace:** 105 testů zelených lokálně i na serveru (bylo 96; 9 nových pokrývá přesně ty tři chyby
a nové chování validátoru). End-to-end na 332 reálných session: 1. běh 332 session → kurzor, 2. běh
0 session; částečný běh po 2 dávkách pokračuje s nulovým překryvem. `ruff check` + `ruff format` čisté.
Dry-run na serveru: 337 session v 7 dávkách po ≤499 kB, `git status` workspace 18 souborů před i po
(nic se nezměnilo).
**Nasazení:** `rsync` na server (drift proti repu žádný — ověřeno před přepisem). Smazán mrtvý
`reflect/known-patterns.md`. **Kurzor v `reflect/state.json` přepsán** z `2026-05-27 14:39` na
`2026-05-27T14:39:22.120065`, jinak by se ten den zpracoval ještě jednou.
**Co zbývá:** `reflect/` na serveru **není v gitu** — ani trackované, ani ignorované, přestože plán
počítal s tím, že bude verzované jako auditní stopa nálezů a rozhodnutí. Rozhodnutí na uživateli.
**Jak vrátit zpět:** `git revert 46fe20f` (skill) a `git revert 69fe364` (rsync exclude v CLAUDE.md),
pak `rsync -av --exclude '__pycache__' --exclude '.pytest_cache' skills/reflect/ nanobot@nanobot.hell:/home/nanobot/.nanobot/workspace/skills/reflect/`.
Kurzor zpět: přepsat `reflect/state.json` na `2026-05-27 14:39`. `known-patterns.md` se obnoví sám
jen s revertovaným kódem.
## 2026-07-02 — bookmark: zestručnění frontmatter description (routing-focused), nasazeno
**Cíl:** Popis skillu byl moc dlouhý, prozaický a obecný („Manage a personal reading list…"). Přepsat na krátký, foldovaný (`>`) a zaměřený na *kdy aktivovat*, ne na výčet funkcí.
**Co jsem zkusil / co fungovalo:** Několik iterací s uživatelem (on sám mezitím editoval v IDE). Finální verze: `Use when the user wants to save a URL (with or without additional content) to read later or search in. Triggers on "bookmark", "save URL".` Cestou opraveny drobnosti (`an URL``a URL`, uzavření vsuvky, rozbité souvětí) a odstraněn zbloudilý `ks` mezi frontmatterem a `# Bookmark`. Detailní výčet příkazů zůstává v těle skillu, kam patří.
**Nasazení:** Server vs lokál se lišil **jen** v description (tělo identické → žádný Dream edit k dotažení). Nahrán `rsync` **jen `SKILL.md`** (ne celý adresář — `bookmark.py` a `html_to_markdown.py` jsou lokálně rozdělané a nasazení nebylo požadováno). Owner `nanobot:nanobot` ✓. Skill se čte per-tah bez restartu.
**Co zbývá:** Rozdělané změny skriptů (`bookmark.py`, `html_to_markdown.py`, testy) — nenasazeno, čeká na dokončení.
**Jak vrátit zpět:** `git checkout HEAD -- skills/bookmark/SKILL.md` (po commitu příslušný `git revert`), pak `rsync` na server.
## 2026-06-17 — llm-wiki: oprava reference souborů z code-review (10 nálezů)
**Cíl:** Vykonat schválený plán oprav reference souborů skillu (review byl zaměřený na `references/`). Nálezy: bare cesty + kroky workflow předpokládající interaktivního uživatele navzdory headless compile (cron drain).
**Co jsem zkusil / klíčový vhled:** SKILL.md ř. 22 má globální override „kdekoli docs říkají `wiki/`, čti `cml/wiki/`" — takže bare-`wiki/` cesty v referencích nejsou rozbité, jen přesměrované. Většina path-nálezů z review tím padá jako ne-bug. Reálné bugy jsou ty, které override nezachrání: kroky „discuss/triage with the user" (v drainu není uživatel → uvázne nebo tiše přeskočí, graf zastará) a „regenerate graph first" v query tahu (compile práce v interaktivním tahu). Server vs repo `diff` před i po prázdný (žádný Dream edit). Vykonání delegováno na sonnet agenta dle konvence repa; sweep cest udělán boundary-aware perlem (`(?<![-/\w.])wiki/`), aby nerozbil `llm-wiki/` ani `wiki_*.py`.
**Co fungovalo a proč:** Skupina A (4 fázové bugy): ingest Step 3 → interactive-only + drain skip; ingest Step 8b + graph Step 1 → Interactive/Drain větve (lint čistý→extract, chyby→log+skip, neblokovat); query Step 2b + graph ř. 106 → graf v query neregenerovat; divergentní skip podmínky sjednoceny na `graph:` metadata v obou souborech. Skupina B: na žádost uživatele plný sweep `wiki/``cml/wiki/`, `raw/``cml/raw/` (literál bezpečnější než spoléhat na override; reference byly stejně už půl-převedené) — `skills/llm-wiki/scripts/` netknuté, ověřeno grepem. Skupina C: smazán `references/agent-memory-integration.md` (SKILL.md ho na nanobotu stejně přeskakuje — wiki se nedrátuje do agent-memory) + úklid 2 odkazů v SKILL.md; odstraněna ne-akční sekce „Why this architecture" z architecture.md. Nasazeno `rsync` (smazaný soubor řešen explicitním `ssh rm`, ne `--delete`), owner `nanobot:nanobot` ✓, server-side spot-check čistý.
**Co zbývá:** Volitelný smoke test drainu na serveru (1 testovací zdroj — že neuvázne na „discuss with user"). 4 follow-up zlepšení (akční lint data, frontmatter šablona, narrow `--fix`, slug kolize) zůstávají v [todo.md](todo.md).
**Commit:** `c74e7de`. **Jak vrátit zpět:** `git revert c74e7de` (nebo `git checkout c74e7de~1 -- skills/llm-wiki/`), pak `rsync` na server + obnovit smazaný soubor z gitu.
## 2026-06-17 — llm-wiki: tvrdá report-only brána u lintu (lint tah se zacyklil 24 min)
**Cíl:** Uživatel hlásil, že lint nad wiki je „šíleně pomalý", zasekl se hned napoprvé, točil se 24 min a sežral hafo tokenů. Najít příčinu (bez opravy), pak opravit.
**Co jsem zkusil / příčina:** Z transkriptu: lint spustil `wiki_graph_lint.py` (nahlásil ~2030 chyb), ale místo report→STOP spadl do plného opravného režimu — 7 nových stránek, 30+ editací, debug skripty do `tmp/`, regenerace grafu, re-lint ve smyčce; `wiki_graph_lint.py` přečetl ~15× po sobě (vlastní slova: „četl jsem ty samý soubory dokola"). Příčiny: **(A)** „Lint is report-only" byla jediná měkká odrážka v SKILL.md, kterou referenční `lint-workflow.md` přímo popírala — má 6krokový postup celý o mutaci wiki (Step 2 fix, Step 5 index, Step 6 log), strukturálně čte jako „oprav vše a zaloguj". Stejná chyba jako capture/compile brána (history 16.6. 22:45). **(B)** Žádný strop na opravnou/debug smyčku; graph lint je provázaný (oprava jedné hrany vyrobí orphan) → ruční edity ho těžko uspokojí → smyčka edit↔lint. **(C)** Skript sám rychlý (0,150,26 s na téhle wiki) — 24 min byla 100 % agentní smyčka, ne CLI.
**Co fungovalo a proč:** Server=zdroj pravdy: `rsync` server→`tmp/`, diff `SKILL.md`+`references/lint-workflow.md` = IDENTICAL (žádný Dream edit). Fix (recept z capture brány): (1) tvrdá report-only brána v SKILL.md ř.26 — lint tah = spusť 2 skripty, předej report, **STOP** + výčet zakázaných akcí (žádné stránky, debug skripty, regenerace grafu, re-lint smyčka); (2) „lint workflow (summary)" přerámován na report-first + STOP-redirect + strop na opravnou smyčku (bounded batches, necti skript opakovaně, re-lint max 1× na ověření, pak ptej se); (3) `lint-workflow.md` dostal report-only banner nahoře + mutační kroky (2/5/6) označeny „fix turn — only after approval". Nasazeno `rsync` (přenesly se jen 2 soubory), owner `nanobot:nanobot` ✓. Skill se čte per-tah bez restartu.
**Co zbývá:** Behaviorální ověření dělá uživatel — příští `lint` tah musí jen nahlásit a zastavit. Vyhodnotit 7 follow-up návrhů nanobota (--fix mód, akční lint data, frontmatter šablona, slug kolize při add, timeout per krok).
**Jak vrátit zpět:** `git checkout HEAD~1 -- skills/llm-wiki/SKILL.md skills/llm-wiki/references/lint-workflow.md`, pak `rsync` na server.
## 2026-06-17 — llm-wiki: code review + oprava 4 chyb v capture bráně + zeštíhlení
**Cíl:** Code review skillu llm-wiki (correctness + verbosity). Uživatel požádal opravit vše.
**Co jsem zkusil / co fungovalo:**
- Review odhalil 4 correctness chyby a 6 verbosity problémů.
- Correctness opravy: (1) ⚠️ mega-odstavec zkrácen na 1-větový pointer — odstranil zdroj rozporů s řádky 38/39; (2) URL při capture: "fetch nebo zapiš" → vždy zapiš as-is; (3) idempotence "pages already exist" zpřesněno na konkrétní soubor `cml/wiki/sources/<slug>.md`; (4) escape hatch triggers nyní jen na jednom místě.
- Verbosity: odstraněny sekce "The pattern in one paragraph" a "When to use this skill" (duplikát frontmatter); directory tree opraven na reálné `cml/wiki/`/`cml/raw/` cesty; "Failure modes" ze 4 odstavců na 2 odrážky; "Bundled scripts" katalog odstraněn (wiki_stats přesunut do scalability).
- Výsledek: 198 → 169 řádků (17 %). Nasazeno na server.
**Commit:** `239dc4f`
## 2026-06-17 05:15 — llm-wiki: zeštíhlit `description` na čistý trigger (+ folded ≤100 zn.)
**Cíl:** `description` ve frontmatteru [SKILL.md](skills/llm-wiki/SKILL.md) byl ukecaný (~1060 zn.) — míchal *kdy spustit* (routing signál) s *jak funguje* (výklad). Dvě kola: (1) zalomit na ≤100 zn., (2) na žádost uživatele zeštíhlit obsah na čistý trigger.
**Mechanika (vysvětleno uživateli):** Do baseline kontextu jde jen jednořádkový `description` (routing signál — `nanobot/agent/skills.py`, viz `knowledge.md` „Skill description — progressive loading"). Tělo SKILL.md se čte on-demand při použití skillu; `references/*.md` nezná ani skill loader — jsou to obyčejné soubory čtené po jednom přes `read_file`, jen když na ně tělo navede. Rozsah referencí je proto kontextově fakticky zadarmo → osekávat se mají kvůli srozumitelnosti, ne tokenům.
**Co jsem udělal:**
- Block scalar `|` (literal — zachoval by vložené `\n`) → `>` (folded — řádky se složí na mezery), zalomeno na ≤100 zn. Folded scalar = v seznamu skillů se renderuje jako jeden řádek; v 1. kole ověřeno `yaml.safe_load`, že načtená hodnota je bajt po bajtu identická.
- Zeštíhlení obsahu (~1060 → ~480 zn.): vyhozeno (a) atribuce + datum („from Andrej Karpathy's April 2026 gist", „OmegaWiki") — origin, nulová routing hodnota, Karpathy už je v těle (SKILL.md:9); (b) how-it-works věta o škálování — už v sekci *The scalability discipline*; (c) dlouhý literální výčet trigger frází — redundantní (`knowledge.md` 2026-05-31: model pozná záměr napříč formulacemi). Ponecháno: co dělá + kdy (ingest/ask/lint/init), „i když uživatel neřekne wiki", a rozlišení vůči agent paměti (`note`/`keep`/`MEMORY.md`) — to má reálnou routing hodnotu. Tělo SKILL.md beze změny.
**Verifikace + deploy:** Server=zdroj pravdy: před každým kolem `rsync` server→`tmp/`, diff → IDENTICAL (Dream nesahal). Po deployi `rsync` zpět + diff → IDENTICAL, owner `nanobot:nanobot` OK. Skilly se čtou bez restartu.
**Jak vrátit zpět:** `git revert <commit>` + `rsync` předchozí verze SKILL.md na server. Zalomení je funkčně no-op; zeštíhlení mění obsah triggeru (užší, ale sémanticky pokrývá totéž).
## 2026-06-14 14:30 — note: tag u multi-line poznámky na header + spolknout osiřelý oddělovač
**Cíl:** V `/note list` (CLI/`nanobot agent`) u poznámky, jejíž URL `_urls_on_own_lines` rozhází na vlastní odrážky, padal tag za poslední URL odrážku (vizuálně nesmysl) a na konci headeru visel osiřelý oddělovač `—` + na první odrážce koncová čárka — pozůstatky inline formy (`… od vína — Universal: url, Amber: url`) po přesunu URL na odrážky.
**Co jsem udělal** ([skills/note/scripts/note.py](skills/note/scripts/note.py)):
- `_LABELED_URL_RE`: prefix `\s*` → třída oddělovačů `[\s,;—–-]*`, takže připojený konektor před URL/labelem padne do nezachyceného prefixu a zahodí se. Mizí osiřelý `—` i `,` mezi URL. DB i `cmd_show` netknuté (drží původní obsah verbatim).
- `cmd_list`: tag vkládám za **první řádek** (`partition("\n")``head` + tag + `sep` + `rest`) místo na konec celého bloku. Jednořádkové poznámky beze změny (`partition` vrátí `(text,"","")`).
**Verifikace + deploy:** Lokální test helperů na konkrétní poznámce (DT GLASS) + jednořádkové → OK. Server=zdroj pravdy: `rsync` server→`tmp/note.server.py`, diff proti lokálu → IDENTICAL (Dream nesahal) → deploy bezpečný. `rsync -av skills/note/` na server, owner `nanobot:nanobot` OK. Reálný `uv run … list` na serveru (přes `~/.local/bin/uv`, non-login PATH gotcha) → položka 7 má tag na headeru, čisté odrážky bez `—`/čárky, ostatní beze změny.
**Souvislost:** Čistě textový výstup skriptu. Nevyřešený WebUI card-renderer problém (`plans/note-wrong-urls.md`) tím není dotčen.
**Jak vrátit zpět:** `git revert <commit>` skillu (zatím necommitnuto) + `rsync` předchozí verze na server. DB beze změny.
## 2026-06-13 07:45 — note: posílit `show` k doslovnému plnému výpisu
**Cíl:** Agent při `/note show` zkracoval výstup a vypouštěl odkaz/URL z obsahu — defaultní terseness ze `SOUL.md` přebíjela slabou instrukci `echo the structured block unchanged`. Skript vypisuje plný blok správně, problém byl jen v prezentaci.
**Co jsem udělal:** V [SKILL.md](skills/note/SKILL.md) `## Show protocol` přepsal bod Exit 0 na emfatickou direktivu: *output the script's stdout verbatim — print every line exactly as emitted; do not summarize, shorten, rewrap, or drop any part of `content` including URLs/links; brevity directives do not apply here.* Žádná změna kódu skriptu.
**Verifikace + deploy:** `diff` serverové SKILL.md proti lokálu → jediný rozdíl moje nová úprava (server jinak nezměněn) → deploy bezpečný. `rsync` SKILL.md na server, owner `nanobot:nanobot` OK. Skilly se čtou bez restartu. Reálné ověření chování agenta na URL poznámce zbývá uživateli.
**Jak vrátit zpět:** `git revert <commit>` + `rsync` předchozí verze SKILL.md na server.
## 2026-06-13 07:40 — note: nový subcommand `show` (jedna poznámka v plném znění)
**Cíl:** `/note` uměl `add`/`list`/`delete`, ale ne vytáhnout **jednu poznámku se vším uloženým**. Přidat `show <display-id>`, který vypíše všechna pole (display ID, DB id, datum, tagy, obsah) ve formě vhodné pro LLM.
**Co jsem zkusil / udělal:**
- **Backend** ([skills/note/scripts/note.py](skills/note/scripts/note.py)): `cmd_show` — mapování display ID → DB id přes existující `_active_ids` (stejná logika jako `cmd_delete`), guard na rozsah s exit 1 + hláškou `No active note with display id=N.`. SELECT přidává `created_at` (delete ho nečte). Tiskne strukturovaný blok `Note [#N] (id=…)` / `created:` (ISO 8601 UTC beze změny) / `tags:` (`#tag` mezerou, jinak `(none)`) / `content:`. Log `SHOW` konzistentně s `LIST`. Subparser `show` + dispatch větev.
- **SKILL.md** ([skills/note/SKILL.md](skills/note/SKILL.md)): sekce `## Show protocol` (triggery, read-only, display IDs jako u list/delete) + edge case `/note show` bez ID.
- **Verifikace lokálně:** add (s tagy / bez) → list → `show` u obou (tagovaná i `tags: (none)`) → `show 999` exit 1 → delete úklid. Vše OK.
- **Server = zdroj pravdy:** `rsync` server→`tmp/note-server/`, `diff` SKILL.md i note.py → jediný rozdíl jsou moje nové `show` přírůstky, žádná autonomní divergence (Dream nesahal) → deploy bezpečný.
- **Deploy:** `rsync -av skills/note/` na server, owner `nanobot:nanobot` OK. Smoke `show 999` na serveru přes plnou cestu `/home/nanobot/.local/bin/uv` (interaktivní `uv` v non-login SSH PATH chybí — známý gotcha) → exit 1, OK.
**Co fungovalo a proč:** Skilly se čtou bez restartu. `show` je čistě čtecí — žádný dopad na DB ani delete logiku. Reuse `_active_ids` znamená, že display IDs jsou konzistentní napříč list/show/delete.
**Co zbývá:** Commit (tracking + skill zvlášť dle konvence).
**Jak vrátit zpět:** `git revert <commit>` skillu + `rsync` předchozí verze na server. DB beze změny.
## 2026-06-10 10:47 — remind: nový subcommand `upcoming` (co a kdy odejde)
**Cíl:** `/remind` neuměl dopředu říct, co a kdy odejde. Výpočet kdy reminder vystřelí už existoval (`remind_send.py` `_due_*`), šlo o jeho vystavení jako dotazu — **bez nafouknutí `remind_cli.py`** (491 řádků).
**Co jsem udělal:**
- Nový sdílený modul `scripts/forecast.py``window_for()`, `fires_in_window()`, `format_upcoming()`. Veškerá výpočetní + formátovací logika sem; CLI dostalo jen tenký handler `cmd_upcoming`.
- Znovupoužity hotové primitivy: `compute_fire_times` (random), `croniter.get_next` (cron), ISO string compare (at). Forecast je čistá projekce rozvrhu — **bez** dedupu proti `reminder_fires` a bez tolerance (na rozdíl od `delivered`, který ukazuje skutečnost).
- DRY: sestavení cfg z `schedule_random` řádku vytaženo do `random_times.random_cfg_from_row()`, použito ve `forecast` i `remind_send` (daemon jinak netknutý).
- Rozhraní: `upcoming` (= zbytek dneška), `--date YYYY-MM-DD` (celý den), `--days N` (N kalendářních dní vč. dneška); `--date`+`--days` se vylučují.
- SKILL.md: řádek do NL→command tabulky + věta do behavioral contractu.
- Testy `tests/test_forecast.py` (16 testů: window_for, at/cron/random, filtry, řazení, formát).
**Co fungovalo a proč:** Lokálně 46 testů green (`uv run --with pytest --with croniter pytest`). Smoke test CLI proti temp DB i mutual-exclusion chyba OK. Nasazeno na server `rsync -av` (vlastník `nanobot:nanobot`), `upcoming --days 2` proti ostré DB vypsalo korektní rozvrh.
**Jak vrátit zpět:** `git revert` příslušného kódového commitu; na serveru smazat `scripts/forecast.py` a `rsync` předchozí verze `random_times.py`/`remind_cli.py`/`remind_send.py`/`SKILL.md`.
**Pozn.:** `uv` na serveru není v non-interaktivním SSH PATH — je v `~/.local/bin/uv` (cron používá shebang `#!/usr/bin/env -S uv run --script`).
## 2026-06-07 19:57 — imageGeneration: provider z openrouter na ollama (žádný paid path)
**Cíl:** `tools.imageGeneration` v serverovém configu odkazoval na `openai/gpt-5.4-image-2` přes provider `openrouter` (placený). Uživatel nechce, aby cokoli kolem obrázků šlo přes openrouter — jen ollama provider / cloud modely.
**Zjištění:**
- `openai/...` byl jen default vypnutého toolu (`enabled: false`) — je to OpenRouter naming convention `vendor/model`, ne odkaz na (neexistující) OpenAI provider. Providery nakonfigurované jen tři: `openrouter`, `ollama`, `gemini`.
- Nanobot **podporuje** image-gen přes ollama (`OllamaImageGenerationClient``POST /api/generate` s `width/height/steps`).
- ALE **Ollama Cloud nemá žádný text→image model** — ověřeno z `ollama.com/search?c=cloud` i z `curl nvidia.hell:11434/api/tags`: samé LLM, vision-LLM (obrázky na *vstupu*) a embeddingy, žádný flux/SD/diffusion. Takže image-gen na ollama reálně nepoběží, není na co model namířit.
**Co jsem udělal:**
- Záloha: `cp -a ~/.nanobot/config.json ~/.nanobot/config.json.bak-imggen`.
- In-place Python edit `~/.nanobot/config.json``tools.imageGeneration`: `provider` `openrouter``ollama`, `model` `openai/gpt-5.4-image-2``""`, `enabled` ponecháno `false`. Config re-parsnut OK.
**Co fungovalo a proč:** Splňuje záměr — žádná cesta image-gen přes placený openrouter. Tool je `enabled: false`, takže neutrácí; i kdyby se zapnul, generace na ollama selže (žádný image model), nezačne nic platit.
**Co zbývá / pozn.:** Funkční generování obrázků by vyžadovalo placený provider (gemini/openrouter) nebo lokální diffusion mimo Ollamu. Na ollama to nejde.
**Jak vrátit zpět:** `cp -a ~/.nanobot/config.json.bak-imggen ~/.nanobot/config.json`.
## 2026-06-07 19:07 — Přidán model preset `gpt` (gpt-oss:120b-cloud, ollama)
**Cíl:** Přidat do serverové konfigurace nový model preset `gpt` = `gpt-oss:120b-cloud` přes Ollama.
**Co jsem zkusil / udělal:**
- Ověřil dostupnost: `curl http://nvidia.hell:11434/api/tags``gpt-oss:120b-cloud` v seznamu (přesná shoda).
- Záloha: `cp ~/.nanobot/config.json ~/.nanobot/backup/config.json.bak-20260607-190745`.
- In-place Python edit `~/.nanobot/config.json`: do `model_presets` (snake_case na disku) přidán klíč `gpt` se strukturou jako ostatní ollama presety — `provider: ollama`, `model: gpt-oss:120b-cloud`, `maxTokens: 16384`, `contextWindowTokens: 131072` (gpt-oss-120b = 128k kontext), `temperature: 0.1`, `reasoningEffort: null`, `label: null`.
- Restart **neproveden**`model_presets` se hot-reloadují (`_refresh_provider_snapshot()` před každým tahem), preset je dostupný okamžitě přes `/model gpt`.
**Co fungovalo a proč:** In-place Python edit zachoval ostatní klíče včetně secrets. Hot-reload presetů ověřen v knowledge.md → žádný výpadek služby.
**Jak vrátit zpět:** `cp ~/.nanobot/backup/config.json.bak-20260607-190745 ~/.nanobot/config.json` (nebo Python `del c["model_presets"]["gpt"]`).
## 2026-06-07 18:08 — Detach skill: ms timestamp + detach.log + popis ve výpisu
**Cíl:** Opravit tři problémy v `/detach` skillu: (1) riziko přepsání souboru úlohy při shodě slugu a sekundy, (2) neúplné logování (create a archive nelogoval vůbec), (3) výpis `list` ukazoval jen slug bez popisu co úloha dělá.
**Co jsem zkusil / udělal:**
- `create-task.py`: timestamp přepnut z `%Y-%m-%dT%H%M%S` (sekundy) na `%Y-%m-%d_%H_%M_%S_%f` (mikrosekundy); filename `2026-06-07_15_00_00_123456-slug.md`. Dvě souběžná volání se stejným slugem teď nemohou kolidovat na úrovni souboru.
- `tasks_common.py`: FILENAME_RE rozšířen pro oba formáty (starý T-joined zpětně kompatibilní); přidán `parse_timestamp()` s fallback na oba formáty; sdílená `log()``~/.nanobot/workspace/log/detach.log`; `render_table()` přijímá `list[Path]` místo `list[str]` a přidává sloupec `what` (první věta `# Goal` sekce); `goal_summary()` helper.
- `tasks-daemon.py`: importuje `log` z `tasks_common` místo vlastní implementace; maže lokální `WORKSPACE`, `LOG`, `log()`.
- `create-task.py`: loguje `CREATE <filename> slug=<slug>` po úspěšném `os.replace()`.
- `archive-tasks.py`: loguje `ARCHIVE <filename>` pro každý přesun.
- `list-tasks.py`: `list_dir()` vrací `list[Path]`, předává cestu do `render_table()`.
- Testy: 56/56 pass; přidány testy pro nový timestamp formát, `parse_timestamp`, `goal_summary`, `render_table` s reálnými soubory.
- Nasazení: `rsync -av skills/detach/ nanobot@nanobot.hell:/home/nanobot/.nanobot/workspace/skills/detach/` (7 souborů).
**Co fungovalo a proč:** Mikrosekundový timestamp je deterministické a jednoduché řešení — žádný slug-dedup ani zámky. Sdílený `log()` v `tasks_common.py` je správné místo pro sdílené utility.
**Jak vrátit zpět:** `git revert 587cb42` a redeployovat. Starý daemon log byl v `~/.nanobot/workspace/log/tasks-daemon.log` — po revertu bude daemon logovat tam; `detach.log` zůstane prázdný (nevadí, není kritický).
## 2026-06-07 — `/note`: display ID (1..N) místo rostoucího AUTOINCREMENT ID
**Cíl:** Zabránit tomu, aby museli uživatelé psát stále větší čísla (např. `delete 847`) při mazání poznámek.
**Co jsem zkusil:** Přidat display ID jako druhé ID pro manipulaci — 1..N mezi aktivními poznámkami, nejnovější = 1, přepočítá se po každém smazání.
**Co fungovalo a proč:**
- `_active_ids(conn)` vrátí seznam interních ID v pořadí `created_at DESC`
- `cmd_list` builduje `id_to_display` dict ze stejného pořadí → globálně konzistentní i při `--tag` filtru a `--offset` stránkování
- `cmd_delete` resolvuje display ID na interní ID přes `_active_ids`, log zaznamenává obě ID
- `cmd_add` hardcode `#1` — nová poznámka je vždy nejnovější
- Nasazeno na server přes rsync
**Co zbývá:** Nic.
## 2026-06-07 — `/note` skill: tagy, soft-delete, operation log, zkrácený SKILL.md
**Cíl:** Přidat tagy, soft-delete místo fyzického mazání, append-only operation log a zkrátit/přepsat SKILL.md (fully EN, bez 515 word limitu).
**Co jsem udělal:**
- `note.py` přepsán: nové schema (`tags TEXT DEFAULT '[]'`, `deleted_at TEXT`); `_connect()` context manager s WAL + row_factory (dle `bookmark.py` vzoru); `_migrate()` idempotentně přidá chybějící sloupce existující DB; `_validate_tags()` regex `^[a-z][a-z0-9-]*$`; `_tags_display()` formátuje `[#cli #tools]`; `_log()` ms-přesný timestamp `YYYY-MM-DD HH:MM:SS.mmm`; `cmd_delete()` je soft (UPDATE, ne DELETE); `cmd_list()` filtruje `deleted_at IS NULL`, podporuje `--tag` OR filtr přes `json_each()`; `cmd_add()` přijímá `--tags`.
- `SKILL.md` přepsán do EN: odstraněn 515 word limit → "one concept per entry, split if too complex"; přidána sekce Tag protocol (inline tagy jako první token, `arch`, `hw,linux`); delete protokol odkazuje na soft-delete a `log/note.log` jako primární audit trail.
- Migrace proběhla automaticky při prvním spuštění na serveru — 4 existující záznamy dostaly `tags='[]'`, `deleted_at=NULL`.
**Ověření:** Lokální smoke test (add s tagy, list, `--tag` OR filtr, delete, invalid tag, log) → vše OK. Na serveru ověřeno list (4 záznamy), add+delete test záznamu, `log/note.log` obsahuje všechny operace.
**Co zbývá:** `/keep` — zvážit analogický přechod na Python skript + logování (otevřený úkol v `todo.md`).
---
## 2026-06-07 — Konsolidace skillů `mind` + `note` → jeden `note` (SQLite backend)
**Cíl:** `mind` a `note` byly funkční dvojče (stejný účel, triggery, protokol), lišily se jen backendem — `mind` SQLite přes `mind.py`, `note` plochý `notes.md` editovaný LLM. Zachovat SQLite variantu (dělba rolí + determinismus + budoucí logování, viz todo.md a serverová MEMORY.md), zrušit markdown variantu, a výsledek pojmenovat `note` (uživatelské jméno skillu).
**Co jsem zkusil:**
1. **Porovnání + rozhodnutí:** `mind` zachovat (skill orchestruje, `mind.py` deterministicky zapisuje/čte/maže; má `id` + `created_at`; sedí na zamrzlé rozhodnutí „perzistentní SQLite vždy `db/*.sqlite`"). `note` (LLM sám edituje markdown) je opak — nedeterministický, neloggovatelný.
2. **Migrace dat:** 3 poznámky ze serverového `notes.md` přetaženy do `mind.sqlite` přes `mind.py add` (jazyk zachován, CZ). Po migraci 4 záznamy (id 25), nic se neztratilo.
3. **Ověření shody se serverem:** `rsync` + `diff -r` obou skillů → IDENTICAL, žádná Dream úprava k dotažení.
4. **Přejmenování (lokálně):** `git rm skills/note/SKILL.md` (markdown), `git mv skills/mind skills/note`, `git mv .../mind.py .../note.py`. V `note.py`: `DB_PATH``db/note.sqlite`, docstring + argparse description. SKILL.md přepsán `mind``note` napříč (frontmatter, triggery, cesty, `# Note`). Smoke test add/list/delete lokálně OK.
5. **Nasazení:** na serveru `mv db/mind.sqlite db/note.sqlite`, `rm -rf skills/mind skills/note`, `rm notes.md`, `rsync` nového `skills/note/`. Ověřeno: ownership `nanobot:nanobot`, `note.py list` vrací všechny 4 poznámky z `note.sqlite`, ve `skills/` zbyl jen `note` + `remind`.
**Co fungovalo a proč:** SQLite varianta přežila, protože jako jediná splňuje směr zapsaný v todo.md (dělba rolí skill+skript) i v serverové MEMORY.md (`/note`: SQLite, deterministický, krátký prompt + python skript). Migrace přes `add` zachovala terse styl i jazyk. Skilly se čtou per-tah bez restartu → `note` hned aktivní.
**Jak vrátit zpět:** Data jsou v `db/note.sqlite` (jen přejmenovaná z `mind.sqlite`, ne smazaná). Obnova skillů: `git revert`/`git checkout` lokálně + re-`rsync`; server `notes.md` byl smazán, ale jeho 3 řádky žijí v `note.sqlite` (id 35).
**Co zbývá:** Druhé kolo — `note.py` zatím **neloguje operace** (má `created_at` per řádek, ale `delete` nemaže do žádného audit logu → nejde dohledat, proč/kdy něco zmizelo). Doplnit append-only operation log (op + obsah + čas) do skriptu. Sleduje todo.md a MEMORY.md požadavek „logovat všechny provedené operace".
---
## 2026-06-03 — MiniLoop gemini-flash-lite a mistral-small-3.2 cache
**Cíl:** Otestovat `google/gemini-3.1-flash-lite` (OpenRouter) a ověřit `mistral-small-3.2`.
**Co jsem zkusil:** `dotnet run --no-build -- test gemini-flash-lite` a `dotnet run --no-build -- test mistral-small-3.2`.
**Co fungovalo a proč:** `gemini-flash-lite` — 17/17 ok, median **683 ms**, avg 719 ms, out 559 tok. Nejlepší výsledek ze všech dosud měřených OpenRouter modelů; poráží haiku-4.5 (1064 ms), gemma-3-27b (1413 ms) i glm-5.1-ollama (1690 ms). Žádný reasoning, přímý parse.
**Problém — `mistral-small-3.2` byl dřív naměřen s cache:** Původní výsledek 934 ms median (history 2026-06-03 „MiniLoop levné OSS") zřejmě těžil z cache providera. Opakovaný cold test ukázal 3 4007 400 ms na prvních 4 příkladech — přibližně 38× horší. Skutečná cold performance je cca 45 s median. Označeno v knowledge.md.
**Co zbývá:** ověřit gemini-flash-lite v plném produkčním nasazení (nanobot remind skill), zhodnotit cenu.
## 2026-06-03 — MiniLoop phi4:latest zamítnut
**Cíl:** Otestovat lokální `phi4:latest` (14.7B) jako kandidáta pro MiniLoop.
**Výsledek:** Zamítnut — 15/17, median 1744 ms / avg 1676 ms. Dvě skutečné chyby: (1) `příští pondělí``2026-06-05` (čtvrtek) místo `2026-06-08` — horší než codestral, který vrátil neděli; (2) `dopoledne` → window `09:00-12:00` místo `08:00-12:00`. Navíc pomalejší než gemma4:e4b (1744 vs 1136 ms). „Punches above weight" reputace se pro tuto úlohu nepotvrdila.
## 2026-06-03 — MiniLoop codestral:22b zamítnut
**Cíl:** Otestovat lokální `codestral:22b` jako kandidáta pro MiniLoop.
**Výsledek:** Zamítnut — 16/17, median 2483 ms / avg 2621 ms, skutečná chyba data: `příští pondělí``2026-06-07` (neděle) místo `2026-06-08` (pondělí). Stejná třída chyby jako ministral-3:8b (weekday aritmetika). Zároveň 2× pomalejší než gemma4:e4b (1136 ms). Coder specializace nepomohla u česky popsaných relativních dat.
## 2026-06-03 — MiniLoop gemma4:e4b local
**Cíl:** Otestovat lokální `gemma4:e4b` (8B, nvidia.hell) v MiniLoop — jestli zvládne 17/17 a jaké jsou časy.
**Co jsem zkusil:** `dotnet run -- test gemma4-local` — jediný model, bez gate (lokální Ollama, žádná kvóta).
**Co fungovalo a proč:** 17/17, median 1136 ms / avg 1719 ms, out=1530 tok. Žádné chyby data ani cronu. Odlehlé hodnoty (34 s) u složitějších vstupů (random times, multiple windows). Výsledek překvapivě silný pro lokální 8B model — lepší median než glm-5.1 cloud (1690 ms) a blízko gemma-3-27b-it OpenRouter (1413 ms). Bez reasoning skluzu (porovnej nemotron-nano: out=7805).
**Co zbývá:** porovnat s dalšími lokálními modely (codestral:22b, phi4) pokud bude potřeba offline fallback.
## 2026-06-02 — Fix: dynamická detekce verze nanobotu v check_nanobot_version.py
**Cíl:** Opravit hardcoded `CURRENT_VERSION = "0.2.0"` v `~/.nanobot/workspace/scripts/check_nanobot_version.py` — po upgradu na 0.2.1 by cron job navždy hlásil 0.2.1 jako "novou verzi".
**Co jsem zkusil:** Tři varianty detekce aktuální verze: `nanobot --version` (funguje, ale potřeba parsovat emoji prefix), `importlib.metadata` v systémovém Pythonu (nefunguje — nanobot-ai tam není), `importlib.metadata` s PEP 723 hlavičkou (funguje spolehlivě).
**Co fungovalo a proč:** PEP 723 shebang (`#!/usr/bin/env -S uv run --script`) + `dependencies = ["nanobot-ai"]` zajistí, že skript běží ve venv kde `nanobot-ai` je nainstalován → `importlib.metadata.version("nanobot-ai")` vrátí správnou verzi. Žádný subprocess, žádné parsování. Shodné s projekční konvencí.
**Ověření:** `uv run check_nanobot_version.py` vrátil `Current nanobot version: 0.2.1` a všechny tři zdroje (PyPI, GitHub, Docker) také 0.2.1 → žádná falešná notifikace.
**Co zbývá:** nic.
---
## 2026-05-31 — Skill `/plan` — plan mode jako v Claude Code
**Cíl:** Vytvořit nanobot skill `/plan`, který replikuje chování plan mode z Claude Code: nejdřív task read-only prozkoumat a naplánovat, plán předložit ke schválení, a teprve po explicitním schválení (klidně později) vykonat.
**Co jsem zkusil:**
1. **Průzkum** — prošel strukturu `skills/` (note, keep, deep-research jako vzory): skill = `SKILL.md` s YAML frontmatterem (`name` + víceřádkový `description` s triggery) + EN-only markdown tělo. Žádný harness, vše je promptová instrukce.
2. **Ověření subagentů** — původně jsem v plánu chybně tvrdil, že nanobot nemá subagenty. Uživatel mě opravil, ověřil jsem v upstreamu: nanobot **má** builtin `spawn` tool ([spawn.py](https://github.com/HKUDS/nanobot/blob/main/nanobot/agent/tools/spawn.py)) — hlavní agent spawne subagenta, ten běží **async** v pozadí a výsledek vrací přes MessageBus. Lokálně to potvrzují zmínky v `workspace/picoclaw/` (`spawn, subagent — vše enabled`).
3. **Návrh** — 4 lineární fáze: Explore (read-only) → Design → zápis plánu do `workspace/plans/<slug>.md` → zastavení a dotaz na schválení/vykonání. Read-only vynuceno promptem (`## Rules`), schvalovací smyčka přes chat jako náhrada `ExitPlanMode`. `spawn` jen volitelně pro velký dělitelný průzkum (async povaha by běžné iterativní plánování spíš zdržovala).
4. **Deploy**`rsync -av skills/plan/ nanobot@nanobot.hell:.../skills/plan/`, ownership `nanobot:nanobot` ověřeno. Skilly se čtou při každém tahu bez restartu → `/plan` hned aktivní.
**Co fungovalo a proč:** Konzistence se stylem `note`/`deep-research` (EN-only tělo, triggery CZ+EN, fázová struktura se status zprávami jako deep-research). Lineární default + volitelný `spawn` odpovídá tomu, že `spawn` je async — synchronní lineární čtení je pro plánování plynulejší.
**Jak vrátit zpět:** `ssh nanobot@nanobot.hell 'rm -rf ~/.nanobot/workspace/skills/plan'` + smazat lokální `skills/plan/`.
**Co zbývá:**
- Smoke test přes Telegram/WebUI: `/plan <malý úkol>` → ověřit read-only průzkum, zápis plánu do `workspace/plans/`, zastavení s dotazem, a vykonání až po „ano, vykonej" (po „ne" plán zůstane uložený).
## 2026-05-29 — Instalace nvm + zpřístupnění npx pro nanobot service
**Cíl:** Zprovoznit `npx` (a tím clawhub CLI) na serveru nanobot.hell.
**Co jsme zkusili:**
- Instalace nvm pro uživatele `nanobot`: `curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash`
- Instalace Node.js: `nvm install --lts` → nainstaloval se v24.16.0 do `/home/nanobot/.nvm/versions/node/v24.16.0/`
- nvm přidal inicializaci do `.bashrc` — to funguje v interaktivním shellu, ale systemd user service `.bashrc` nesourcuje
**Co fungovalo a proč:**
- Přidáno `Environment=PATH=/home/nanobot/.nvm/versions/node/v24.16.0/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin` do `/home/nanobot/.config/systemd/user/nanobot.service`
- Reload + restart přes `ssh nanobot@nanobot.hell "systemctl --user daemon-reload && systemctl --user restart nanobot"` (root přes sudo/machine nefunguje, přímé SSH jako nanobot ano)
- Ověřeno: `/proc/<pid>/environ` obsahuje správnou PATH s nvm cestou
**Co zbývá:** Při upgradu Node.js přes nvm je potřeba aktualizovat verzi v service souboru ručně.
**Jak vrátit zpět:** Odstranit řádek `Environment=PATH=...` ze service souboru, daemon-reload, restart.
## 2026-05-29 — SQLite konvence + sync workspace/*.md
**Cíl:** Přidat do AGENTS.md instrukci, že agent má SQLite databáze ukládat do `db/*.sqlite`.
**Co jsem zkusil / udělal:**
1. Zkoumal, kde žijí instrukce agenta — SSH na server, prošel `/home/nanobot/.nanobot/workspace/`.
2. Zjistil, že správné místo je `AGENTS.md` (operační pravidla), ne `SOUL.md` (osobnost) ani skills.
3. Přidal sekci `## Databases (SQLite)` do `workspace/AGENTS.md` (anglicky) a pushnu na server.
4. Při té příležitosti zjistil, že lokální `workspace/*.md` jsou zaostalé — chyběly sekce `Faktografická pravidla`, Telegram ID, `Scheduling`, `Životní filozofie`, apod.
5. Přepsal všech 5 souborů aktuální verzí ze serveru a commitnul do gitu.
**Co fungovalo:** Přímý SCP push AGENTS.md na server. Lokální `workspace/` jako tracking directory pro server soubory.
**Co zbývá:** sqlite MCP server v `config.json` je hardcoded na `/home/nanobot/.nanobot/workspace/tmp/test_mcp.db` — neodpovídá nové konvenci `db/*.sqlite`.
## 2026-05-29 — remind_edit.py: deterministický CRUD pro reminder.yaml
**Cíl:** Skill `remind` nechával LLM přímo editovat `reminder.yaml` — žádná validace cron výrazu, riziko poškozený YAML, potenciální race s `remind_send.py` (crontab každou minutu).
**Co jsem zkusil:** Zvažovaná alternativa MCP server — zamítnuta jako overkill (zbytečná vrstva pro jednoduchý YAML CRUD). Zvoleno: CLI Python skript volaný přes `exec`, stejný pattern jako `remind_send.py`.
**Co fungovalo:** Napsán `/home/nanobot/.nanobot/workspace/skills/remind/scripts/remind_edit.py` (PEP 723, `uv run`, `argparse`). Subcommandy `list`, `add --text ... --cron|--at ...`, `remove --keyword ...`. Atomický zápis přes `.yaml.tmp` + `os.replace()`. Validace: `croniter.is_valid()` pro cron výrazy, `datetime.fromisoformat()` pro `--at`. Všechny výstupy JSON; chyby na stderr + non-zero exit. Aktualizován `SKILL.md` — workflowy Create/List/Remove teď volají `remind_edit.py` místo přímé editace YAML.
**Verifikace:** Prošly testy 17 (list baseline, add cron, add at, invalid cron, remove ambiguous, remove unique, cleanup). YAML na serveru po testech obsahuje původní 4 připomínky beze změny.
**Co zbývá:**
## 2026-05-28 — detach skill: odstranění hardcoded chat_id
**Cíl:** `skills/detach/config.json` obsahoval soukromé Telegram chat ID přímo v gitu. Skill nebyl generický.
**Co jsem zkusil:** Přečetl jsem strukturu `~/.nanobot/config.json` na serveru a zjistil, že ID je uloženo pod `channels.telegram.allowFrom[0]`.
**Co fungovalo:** Odstraněn `DETACH_CONFIG` z `tasks-daemon.py`, `resolve_telegram_chat_id()` nyní čte `cfg["channels"]["telegram"]["allowFrom"][0]` z hlavního configu. `skills/detach/config.json` smazán z repo i ze serveru.
**Co zbývá:**
## 2026-05-28 — Smazán mrtvý skill `deliver-reminder-notifications`
**Cíl:** Uživatel si všiml, že na serveru ve `workspace/skills/` leží skill `deliver-reminder-notifications` z minulé session. Zjistit, proč tam je, a pokud je mrtvý, smazat.
**Co jsem zjistil:**
- Skill `deliver-reminder-notifications` (vytvořen 28. 5. 12:07) je zbytek po staré agentní architektuře připomínek. Volá skript `remind/scripts/remind_check.py`, který už neexistuje (v `remind/scripts/` je jen `remind_send.py`).
- Aktuální doručování běží přes system crontab (`uv run remind_send.py` každou minutu) přímo přes Telegram Bot API — žádný agent v loopu. Viz `remind/SKILL.md` na serveru, který explicitně píše *"Do not recreate a `remind-check` job"*.
- Žádný cron ani jiný skill `deliver-reminder-notifications` netriggeruje.
**Co fungovalo:**
```bash
ssh root@nanobot.hell "rm -rf /home/nanobot/.nanobot/workspace/skills/deliver-reminder-notifications"
```
Po smazání ve `workspace/skills/` zbývají: `detach`, `grill-me`, `remind`.
**Jak vrátit zpět:** Obnovit adresář se souborem `SKILL.md` (frontmatter `name: deliver-reminder-notifications`, popis polling + Telegram delivery v češtině). Ale nemělo by smysl — skript, na který odkazoval, neexistuje a delivery dělá cron.
## 2026-05-28 — Faktografická pravdomluvnost do SOUL.md, demystifikace `memory/`
**Cíl:** Uživatel pozoroval, že nanobot v chatu halucinoval o filmech. Pokusil se to „opravit" tak, že agentovi řekl pravidlo a ten si ho uložil do `~/.nanobot/workspace/memory/film_policy.md`. Halucinace pokračovaly. Otázka: jak to opravit správně + obecně zvýšit pravdomluvnost.
**Klíčové zjištění (ověřeno ve zdrojáku):** Z adresáře `memory/` se do system promptu auto-loaduje **výhradně `MEMORY.md`**. ContextBuilder ani MemoryStore žádné jiné soubory v `memory/` nečte. Tj. `memory/film_policy.md` byl pro agenta **neviditelný** — vytvořil ho, ale v dalším tahu už ho neměl v kontextu, takže pravidlo neaplikoval.
Zdroj: `/home/nanobot/.local/share/uv/tools/nanobot-ai/lib/python3.13/site-packages/nanobot/agent/{context.py,memory.py}` — verze 0.2.0:
- `context.py:25``BOOTSTRAP_FILES = ["AGENTS.md", "SOUL.md", "USER.md", "TOOLS.md"]`
- `memory.py:55,205,229``memory_file = memory_dir / "MEMORY.md"`, `get_memory_context()` čte výhradně tento jeden soubor
**Pozn. k upstream HEAD:** v `HKUDS/nanobot` master branch už `BOOTSTRAP_FILES` neobsahuje `TOOLS.md` (jen `AGENTS`, `SOUL`, `USER`). Naše verze 0.2.0 ho ještě má. Při budoucím upgrade nanobotu to ověřit a případně přesunout obsah `TOOLS.md` jinam.
**Co fungovalo (řešení):**
1. **Nová sekce v SOUL.md**`## Faktografická pravidla`, vložená před `## Styl výstupu` na serveru. Obsah: imperativní pravidlo „když nevím, řeknu to" + „u faktografických dotazů nejdřív dohledám tooly, pak odpovídám" + „halucinace = vážná chyba, ne kosmetika". Patch přes Python in-place na serveru (idempotence ověřena `assert "## Faktografická pravidla" not in text`), žádný restart nepotřeba (bootstrap soubor).
2. **Smazán `memory/film_policy.md`** — nebyl čten, jen mátl.
3. **Knowledge.md doplněn** o dvě nové sekce: „Co se auto-loaduje do system promptu (verze 0.2.0)" + tabulka „K čemu slouží jednotlivé workspace soubory" (SOUL/AGENTS/USER/TOOLS/MEMORY/HEARTBEAT). Stará minimální sekce „Workspace bootstrap soubory se čtou každý tah" nahrazena bohatší variantou; sekce „Workspace vzniká při prvním spuštění agenta" ponechána beze změny.
**Proč SOUL.md a ne AGENTS.md / skill:**
- AGENTS.md drží procesní pravidla (jaký tool kdy) — pravdomluvnost je hodnota, ne proces.
- Always-on skill pro jedno pravidlo je overkill. Skilly dávají smysl, když potřebuje izolaci (own helper scripts, modulární distribuce). Sémanticky horší než SOUL pro hodnotové pravidlo.
- MEMORY.md by Dream při konsolidaci mohl přepsat — nehodí se na trvalá pravidla chování.
**Co jsem zkusil mimo úspěšnou cestu:**
- Explore agent na začátku trval na konkrétních line numbers v `/tmp/nanobot-src/...` ještě před tím, než tam upstream byl klonovaný — fabulace zdroje, kterou jsem musel přebít přímým ověřením. Jasná ilustrace toho, proč user chce pravdomluvnost: i podagent halucinoval citace zdroje. Pravidlo „neopakuj číslo řádku, dokud jsi ho neviděl" by se hodilo i na můj vlastní workflow, ne jen na nanobota.
- Místně bylo `BOOTSTRAP_FILES = [AGENTS, SOUL, USER]` v upstream HEAD — málem jsem usoudil, že `TOOLS.md` se nečte. Ověření verze na serveru (`nanobot --version` → 0.2.0, `grep BOOTSTRAP_FILES` v nainstalovaném wheelu) odhalilo divergenci verzí.
**Jak to vrátit zpět:**
- SOUL.md: `ssh nanobot@nanobot.hell 'python3 -c "from pathlib import Path; p=Path.home()/\".nanobot/workspace/SOUL.md\"; t=p.read_text(); import re; p.write_text(re.sub(r\"## Faktografická pravidla.*?(?=## Styl výstupu)\", \"\", t, flags=re.S))"'`
- film_policy.md: obnovit z paměti (krátký, obsah je teď v history.md SOUL.md patchi nebo v původní git komitové historii local repo, pokud byl).
**Co zbývá:**
- Live verifikace v Telegramu — dotaz na film, sledování `journalctl -u nanobot.service -f` na `web_search`/`web_fetch` tool call před odpovědí. Cross-test s netriviálním dotazem (ověřit „nevím" místo improvizace). Negativní kontrola s in-context dotazem (např. „jaký mám preset?") — nesmí spustit zbytečné web search.
---
## 2026-05-28 — Pilot mattpocock skillu `grill-me`
**Cíl:** Ověřit, jestli Claude Code skilly (Anthropic Skills format) jdou pustit v nanobotovi bez konverze. Jako kanárek vybrán `grill-me` z [nicecode/plugins/mattpocock](https://github.com/lachtan/nicecode/tree/master/plugins/mattpocock) — nejmenší možná povrchová plocha (10 řádků, žádné helper skripty, žádné `@`-reference, žádné Claude-Code-specific tooly v těle).
**Co fungovalo:**
- Formát `SKILL.md` (YAML `name`+`description` + markdown tělo) je identický s nanobot skill formátem. Kopie 1:1, žádná konverze.
- Deploy přímo na server jedním SSH+curl one-linerem (`ssh nanobot@nanobot.hell``mkdir -p ~/.nanobot/workspace/skills/grill-me``curl -sSfL <raw github URL> -o SKILL.md`). Server-to-GitHub, lokálně se nic neukládá.
- Restart nanobot.service není potřeba — bootstrap soubory ve `workspace/skills/` se čtou každý tah ([knowledge.md "Workspace bootstrap soubory se čtou každý tah"](knowledge.md)).
- Uživatel ověřil v chatu: agent na trigger frázi začal pokládat otázky jednu po druhé s recommended answers, místo aby plán rovnou implementoval. Chování přesně podle popisu skillu.
**Co jsem zkusil mimo úspěšnou cestu:**
- WebFetch na `diagnose/SKILL.md` při průzkumu vrátil výstup s **fake `<system-reminder>` tagem** uvnitř markdown těla (prompt-injection patrně v Anthropic-fetch pipeline). Zaznamenáno v knowledge.md jako gotcha pro budoucí mattpocock skilly — markdown těla je nutné prohlédnout před deployem, protože nanobot je čte do system contextu.
- `WebFetch` celkově nedával užitečné odpovědi na otázky typu „obsahuje tento soubor X?" — vracel jen echo dotazu. Přepnul jsem na `curl | grep` přes Bash a dostal jednoznačné odpovědi.
**Manifest `.claude-plugin/plugin.json` se nekopíruje** — je Claude-Code-specific metadata pro marketplace, nanobot ho ignoruje. Přenositelná je jen složka `skills/`.
**Jak to vrátit zpět:** `ssh nanobot@nanobot.hell 'rm -rf ~/.nanobot/workspace/skills/grill-me'`. Žádné side effecty mimo tu složku.
**Co zbývá:** Zatím nic — uživatel zvolil pilotně jen jeden skill, další (`caveman`, `diagnose`, `tdd`, `triage`, `zoom-out`, `grill-with-docs`, `improve-codebase-architecture`) zatím neinstalovat. Plán nasazení existuje v `~/.claude/plans/jak-by-sel-pouzit-agile-thimble.md` — při rozšíření projít každý další skill na (a) Claude-Code-specific tool refs, (b) prompt-injection v markdown těle. `to-issues` a `to-prd` vyžadují `gh` CLI a GitHub kontext — na serveru pravděpodobně nepoužitelné bez adaptace.
---
## 2026-05-28 — Reminder logy do `log/` a čitelnější formát
**Cíl:** Logy `/remind` skiluu byly v rootu workspace (smetí v hlavním adresáři), formát byl `ISO+02:00 \t text` (tabulátor, timezone suffix nechtěný).
**Co fungovalo:**
- `LOG_DIR = WORKSPACE / "log"` v `skills/remind/scripts/remind_send.py`, `LOG_DIR.mkdir(parents=True, exist_ok=True)` před zápisem.
- Formát změněn na `datetime.now(TZ).replace(tzinfo=None).isoformat(timespec="seconds")` + mezera + text + `\n``YYYY-MM-DDTHH:MM:SS <text>`. TZ zůstává Prague, jen se nepíše do řetězce.
- Crontab pod nanobot userem přepsán: `>> /home/nanobot/.nanobot/workspace/log/reminder_cron.log 2>&1`. Soubor `reminder_cron.log` zůstává — chytá stdout/stderr crontabu pro debug (za zdravého běhu prázdný).
- Stávající `reminder.log` (8 řádků) přepsán do nového formátu a přesunut do `log/`; `reminder_cron.log` (0 B) jen přesunut.
- Ověřeno: cron tick `(nanobot) CMD (uv run …remind_send.py >> /home/nanobot/.nanobot/workspace/log/reminder_cron.log 2>&1)` v journalu, exit 0, log/ vznikl, root workspace už neobsahuje `*.log`.
- `TOOLS.md` a `skills/remind/SKILL.md` aktualizovány na novou cestu i formát; agent dostane upozornění při příštím tahu (always-on bootstrap).
**Proč dva logy:** `log/reminder.log` = aplikační (úspěšná doručení, čte agent). `log/reminder_cron.log` = zachytávka stdout/stderr crontabu (`>> ... 2>&1`) — bezpečnostní síť pro tracebacky / chyby uv. Funkce se nepřekrývají.
**Co selhalo / zákoutí:**
- První patch `remind_send.py` jsem zkusil pod `sudo -u nanobot``PermissionError`. Soubor byl owned `root:root` (vytvořen v předchozí session přes SSH jako root). Fix: patch jako root + `chown nanobot:nanobot` po editaci.
- Reformat stávajícího `reminder.log` přes „chytrý" Python jednořádkový regex (`ts.split("+")[0].split("-0")[0]`) zmrvil obsah na samé `2026 <text>``-0` matchlo už `-05` v datu. Fix: přepsat soubor staticky známým obsahem (8 řádků jsem stejně měl před očima z předchozího `cat`).
- Manuální dry-run `sudo -u nanobot uv run …` selhal dvakrát: nejdřív `uv: command not found` (PATH bez `~/.local/bin`), pak `failed to open /root/uv.toml` (cwd zůstal `/root`). Funguje až `cd /home/nanobot && sudo -Hu nanobot bash -lc "uv run …"`. Cron tohle neřeší — má vlastní `HOME` a `PATH` v hlavičce crontabu a běží z domovského adresáře nanobota.
**Jak to vrátit zpět:** v `remind_send.py` vrátit `LOG_FILE = WORKSPACE / "reminder.log"` a starý zápis s `\t` + tz, v crontabu vrátit cestu bez `log/`, soubory přesunout zpět.
---
## 2026-05-27 — Spam "Output was empty": skutečná příčina + přechod /remind mimo agenta
**Cíl:** Pochopit, proč chodí na Telegram zprávy "(Output was empty — no message sent.)" / "(Výstup byl prázdný…)" — a ne každou minutu, ale jen sem tam a pokaždé jinak formulované. Předchozí fix ("exit silently" v promptu) nestačil.
**Skutečná příčina (oprava dřívějšího závěru):**
1. Prompt `"The scheduled time has arrived. Deliver this reminder to the user now…"` **není** z jobs.json — je **natvrdo ve zdrojáku** `nanobot/cli/commands.py:on_cron_job`. `payload.message` z jobs.json se jen přilepí za `Reminder:`. Takže každý cron job dostane konfliktní zadání (kód přikazuje doručit, můj text říká mlčet) → agent improvizuje meta-odpověď.
2. Cron job VŽDY jede přes agenta (`agent.process_direct`) — neagentní typ jobu neexistuje. Agent komentuje své mlčení místo aby skutečně mlčel.
3. Doručení hlídá druhý LLM `evaluate_response` (`nanobot/utils/evaluator.py`), který je **fail-open**: při jakékoli chybě nebo když model nevrátí tool-call → `return True` (doruč). V journalu ~20×/den `"no tool call returned, defaulting to notify"`.
4. → Únik nastane jen v minutách, kdy evaluator LLM selže. Nedeterministické a model-závislé (zhoršilo se po přepnutí na slabší Ollama-cloud modely) → "jen sem tam". Text je pokaždé čerstvě generovaný → "pokaždé jiná zpráva".
**Co fungovalo (řešení — Design A, mimo agenta):**
- Nový `skills/remind/scripts/remind_send.py`: čte reminder.yaml, při shodě posílá **přímo přes Telegram Bot API** (token z `~/.nanobot/config.json``channels.telegram.token`), deduplikuje přes `.reminder_state.json` (každý fire 1×), loguje do `reminder.log` (`ISO-čas \t text`). Žádný LLM, žádný nanobot proces. Stdlib `urllib` (deps jen croniter+pyyaml).
- Odebrán nanobot cron job `remind-check` (id 1a7bb634): stop service → filtr v jobs.json → start. Po restartu se nevrátil. Záloha `cron/jobs.json.bak-*`.
- Crontab pod uživatelem nanobot (`crontab -l`): `* * * * * uv run …/remind_send.py >> reminder_cron.log 2>&1`, s `HOME` + `PATH` v hlavičce (cron má holé prostředí). Cron daemon `cron.service` na hostu běží. Ověřeno: `(nanobot) CMD (uv run …remind_send.py)` v journalu každou minutu, log prázdný (běží čistě).
- `TOOLS.md` (always-on bootstrap) dostal ukazatel na `reminder.log`; `SKILL.md` přepsána architektura; smazán starý `remind_check.py`.
**Co konkrétně přibylo do system promptů** (always-on bootstrap = každý tah agenta):
- `TOOLS.md` — nová sekce na konci souboru (řádky 51+):
> ## reminder.log — doručené připomínky
>
> Odeslané připomínky se logují do `reminder.log` v rootu workspace (append-only, formát `ISO-čas \t text`). Posílá je **systémový cron uživatele nanobot** (`skills/remind/scripts/remind_send.py`) přímo přes Telegram, mimo agenta. Když se uživatel ptá na minulé/dnešní připomínky („připomněl jsi mi…?", „co dnes přišlo?"), přečti tento soubor.
*(Pozn.: 28.5. tato sekce dostala druhý revize na `log/reminder.log` + nový formát — viz entry o `log/`.)*
- `skills/remind/SKILL.md` — sekce **Architecture** přepsána na popis systémového crontabu + `remind_send.py` + Bot API + dedup + audit log. Sekce **Cron Tool Usage** přejmenována na **Cron Tool — NOT used for reminders** s explicitním zákazem znovu vytvořit `remind-check` job přes `cron` tool (jinak by se vrátil starý fail-open agentní problém). Reference v dokumentaci přepnuta z `remind_check.py``remind_send.py`.
- Token + API ověřeno přes `getMe`; živý test `sendMessage` doručen (uživatel potvrdil "prislo").
**Jak to vrátit zpět:** obnovit `cron/jobs.json.bak-*` + restart service (vrátí agentní job `remind-check`); `crontab -r` jako nanobot (smaže crontab).
**Co zbývá:** Ověřit reálné doručení ostré připomínky (zítra 9:00, příp. 9:30 čt). Zvážit nahlášení fail-open evaluatoru upstream (HKUDS/nanobot).
---
## 2026-05-27 — Průzkum nanobot skill systému a Dream procesoru
**Cíl:** Zjistit jak se instalují nové skilly (konkrétně clawhub.ai/pskoett/self-improving-agent) a jestli má smysl ho nasadit.
**Co jsem zjistil:**
1. **Skill systém nanobot:** Skill = složka `~/.nanobot/workspace/skills/<name>/` se souborem `SKILL.md` (YAML frontmatter + markdown instrukce). Žádný `install` příkaz neexistuje — skill se vytvoří ručně. Bootstrap soubory se čtou při každém tahu → restart není potřeba.
2. **Clawhub.ai je pro jiný systém.** Clawhub / OpenClaw není nanobot. Instalace přes `clawdhub install` nebo `openclaw skills install` jsou příkazy pro ten jiný ekosystém. Skilly z clawhub se musí ručně adaptovat pro nanobot.
3. **Dream procesor** (`nanobot/agent/memory.py:Dream`) dělá přesně to, co má self-improving-agent skill — automaticky, každé 2 hodiny:
- **Fáze 1:** Plain LLM call projde `history.jsonl`, hledá fakta → `MEMORY.md`, korekce → `SOUL.md`/`USER.md`, opakující se workflow → `[SKILL]` kandidáti
- **Fáze 2:** AgentRunner s `read_file`/`edit_file` tools chirurgicky zapíše změny, umí sám vytvářet nové skilly (`write_file` do `skills/<name>/SKILL.md`), deuplikuje, detekuje stale content přes git blame
4. **Self-improving-agent skill je z 80 % redundantní s Dream.** Přidal by jen: explicitní error log s formátem `ERR-YYYYMMDD-XXX`, feature request tracking, okamžité zachycení (Dream čeká 2h). Nasazen jako experimentální `skills/self-improving-agent/SKILL.md` lokálně (zatím nedoručen na server).
5. **Modely pro dreaming:** DeepSeek V3 > Qwen 3.6+ > Kimi K2 ≈ GLM-5.1 > MiniMax M2 pro structured editing / instruction following. GLM-5.1 nijak nevyniká. Dream používá stejný model jako aktivní preset.
**Výsledek:** Skill nebylo potřeba nasazovat — Dream ho pokrývá. Lokální soubor `skills/self-improving-agent/SKILL.md` existuje pro případné budoucí použití.
---
## 2026-05-27 — Oprava spamu "Žádné připomínky" z cron jobu remind-check
**Cíl:** Zastavit opakované Telegram zprávy "Žádné připomínky k doručení." chodicí každou minutu.
**Co jsem zkusil:**
1. Zkontroloval hooks v `.claude/settings.json` a `~/.claude/settings.json` — nenašel nic relevantního.
2. `CronList` v harness — žádné naplánované joby tady.
3. Hledal skripty s "telegram"/"remind" na serveru — našel `/home/nanobot/.nanobot/workspace/cron/jobs.json`.
4. V `jobs.json` identifikoval job `remind-check` (id `1a7bb634`) s `"expr": "* * * * *"` (každá minuta).
5. Přečetl `remind_check.py` — skript správný, tiskne jen skutečné připomínky.
6. Problém: prompt agenta říkal "If output is non-empty, forward" — LLM to ignoroval a posílal "Žádné připomínky" jako potvrzení.
**Co fungovalo a proč:**
- Upravil `payload.message` v `jobs.json` přes Python in-place editor na serveru — přidal explicitní instrukci "If the output is EMPTY, do NOT send any message — exit silently."
- Restart nanobot služby: `su - nanobot -s /bin/bash -c 'XDG_RUNTIME_DIR=/run/user/$(id -u nanobot) DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u nanobot)/bus systemctl --user restart nanobot.service'`
**Slepá ulička:**
- `kill -HUP <pid>` na gateway proces — nanobot se nerestartoval, ale **skončil** a nenaskočil zpět. Poté bylo třeba ho ručně nastartovat výše uvedeným příkazem.
- `su - nanobot -c 'systemctl --user restart ...'` bez `XDG_RUNTIME_DIR` a `DBUS_SESSION_BUS_ADDRESS` selhalo s "bus not defined".
**Co zbývá:** Ověřit za pár minut, že spam ustal.
## 2026-05-27 — Doladění /remind skilu: path bug + jobs.json neuloženy
**Cíl:** Dokončit opravu /remind skilu — ověřit end-to-end doručení na Telegram.
**Co jsem zkusil / Co fungovalo:**
Skript `remind_check.py` vracelo prázdný výstup i po přepsání. Přímé testování odhalilo **path bug**: `Path(__file__).parent.parent.parent` z `.../skills/remind/scripts/remind_check.py` vede na `.../workspace/skills/``reminder.yaml` tam neexistuje → skript tiše skončil na `if not REMINDER_YAML.exists(): return`. Oprava: 4 levely nahoru (`parent.parent.parent.parent`).
Změny v `jobs.json` z předchozí session se **neuložily** — soubor stále obsahoval `remind_runner.py` a `channel: websocket` na ephemeral session ID. Opraveno přes Python in-place edit na serveru + restart service.
**Výsledek:** Cron job spouští `uv run remind_check.py` → stdout → nanobot `evaluate_response: should_notify=True` → doručeno na Telegram. Ověřeno uživatelem.
**Co zbývá:** Testovací záznam `TEST každou minutu` smaže uživatel přes chat.
---
## 2026-05-27 — Oprava /remind skilu
**Cíl:** Diagnostika a oprava nefunkčního /remind skilu — notifikace se neodesílaly správně, LLM občas vytvářel cron joby místo záznamu do reminder.yaml, Telegram polyká `/remind` příkazy.
**Co jsem zkusil / Co fungovalo:**
Průzkum ukázal dva Python skripty se odlišnou architekturou:
- `remind_runner.py` byl aktivní (cron job ho spouštěl), ale špatný — posílal Telegram přímo přes API (obcházel nanobot), neměl `at` field support, měl hardcoded UTC+2
- `remind_check.py` měl správnou architekturu (stdout → nanobot → user), podporoval `at` field, ale měl UTC timezone místo Prague
**Provedené opravy:**
1. `remind_check.py` přepsán: Prague timezone přes `zoneinfo`, zjednodušen z ~70 na ~30 řádků
2. `remind_runner.py` smazán
3. `cron/jobs.json` — job `remind-check` přepnut na `remind_check.py` + delivery kanál změněn na Telegram (byl websocket session)
4. `SKILL.md` — odstraněn `done` field ze schématu, přidány sekce "Telegram — jak zadat příkaz" a "Remind vs. cron tool"
5. `AGENTS.md` + `TOOLS.md` — přepsána sekce o reminderech s jasným rozlišením: osobní notifikace → reminder.yaml, background úlohy → cron tool
6. `reminder.yaml` vyčistěn: odstraněny `done: false` záznamy a zastaralý testovací záznam
**Telegram problém:** `/remind text` v Telegramu = bot příkaz, ne text. Řešení: psát přirozeně (`připomeň mi...`). SKILL.md má triggery na tato slova, ale uživatel o tom nevěděl — přidána sekce do SKILL.md.
**Co zbývá:** Ověřit v praxi že notifikace z Telegramu dorazí správně.
## 2026-05-26 — Přepracování CLAUDE.md a vytvoření šablony
**Cíl:** Zjednodušit a zpřehlednit CLAUDE.md pro nanobot projekt + vytvořit čistou šablonu pro reuse v dalších projektech.
**Změny (první vlna):**
- CLAUDE.md zkráceno z ~84 na ~46 řádků — pravidla přepsána do kompaktní tabulky + odrážkových sekcí
- Přidán `plans.md` do tabulky tracking souborů (byl osiřelý, v CLAUDE.md nezmiňovaný)
- Sekce commitu zhuštěna na 4 odrážky (byl zbytečně rozvláčný)
- Odstraněn slug z instrukce pro auto-memory override (byl křehký a projekt-specifický)
- Záložní šablona uložena do `/home/lachtan/CLAUDE.template.md`
**Změny (druhá vlna — workflow zkoušení/ladění):**
- **Distilace history → knowledge:** vyřešené netriviální chyby se z deníku vytahují jako jednořádkové *problém → příčina → fix* do `knowledge.md` s odkazem zpět. Automaticky, hned po vyřešení.
- **Otevřené otázky** mají domov v `todo.md` (prefix `otázka:`); po uzavření odpověď → `knowledge.md`.
- **Zamítnuté možnosti** se zaznamenávají do `knowledge.md` (co a proč zavrženo, ať se netestuje znovu).
- **Struktura záznamu v history.md** kodifikována: Cíl / Co jsem zkusil / Co fungovalo a proč / Co zbývá + rollback u destruktivních zásahů.
- **Secrets** přidány do NEDĚLEJ (nikdy do tracking souborů — jsou v gitu).
- Stejné změny promítnuty do šablony.
## 2026-05-26 — Nastavení locale en_US.UTF-8 na nanobot.hell
**Problém:** SSH session do `root@nanobot.hell` házelo opakované warningy:
```
bash: warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8): No such file or directory
locale: Cannot set LC_CTYPE to default locale: No such file or directory
```
**Diagnóza:**
- `locale -a` na serveru ukázal jen `C`, `C.utf8`, `POSIX``en_US.UTF-8` nebyl vygenerován.
- `/etc/locale.gen` byl bez aktivních řádků (vše zakomentované).
- `/etc/default/locale` měl `LANG="C"`.
- SSH klient přitom přes `AcceptEnv LANG LC_*` posílal `LC_ALL=en_US.UTF-8` atd. → server warning.
**Řešení (proběhlo na serveru):**
Nejdřív manuálně přes `locale-gen` + `update-locale`:
```bash
sed -i 's/^# *en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen
locale-gen
update-locale LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8
```
Pak na žádost uživatele přepnuto na `dpkg-reconfigure` (uloží volbu do debconf DB, takže příští reconfigure si ji pamatuje):
```bash
echo 'locales locales/locales_to_be_generated multiselect en_US.UTF-8 UTF-8' | debconf-set-selections
echo 'locales locales/default_environment_locale select en_US.UTF-8' | debconf-set-selections
rm -f /etc/locale.gen
DEBIAN_FRONTEND=noninteractive dpkg-reconfigure -f noninteractive locales
```
**Ověření:** Po fixu žádné `setlocale` warningy. `/etc/default/locale` obsahuje `LANG=en_US.UTF-8` a `LC_ALL=en_US.UTF-8`. Locale `en_US.UTF-8` je v `locale -a`.
**Poznámka:** V interaktivní session se stále zobrazuje `LANG=C` — to je hodnota poslaná SSH klientem (sshd má `AcceptEnv LANG LC_* COLORTERM NO_COLOR`), nikoli problém serveru. Defaulty serveru jsou správně nastavené.
## 2026-05-26 — Založení uživatele `nanobot` a instalace `uv`
**Cíl:** Pod uživatelem `nanobot` provozovat nanobot agenta. SSH klíče sdílené s rootem, `uv` nainstalované do user-local prefixu.
**Provedeno na serveru `nanobot.hell`:**
1. Nainstalován `curl` (chyběl v základní instalaci Debian 13 trixie):
```bash
apt-get update
apt-get install -y curl
```
Stáhlo se i `libcurl4t64` jako závislost.
2. Vytvořen uživatel `nanobot` (UID 1000, GID 1000) s home `/home/nanobot` a shellem `/bin/bash`. Heslo zůstalo locked (`!` v shadow) — login jen přes SSH klíče:
```bash
useradd -m -s /bin/bash nanobot
install -d -m 700 -o nanobot -g nanobot /home/nanobot/.ssh
install -m 600 -o nanobot -g nanobot /root/.ssh/authorized_keys /home/nanobot/.ssh/authorized_keys
```
3. Ověřeno přihlášení `ssh nanobot@nanobot.hell` stejným klíčem jako root — funguje.
4. Pod uživatelem `nanobot` nainstalováno `uv` přes oficiální installer (uživatel spustil ručně, automatický pokus přes `curl … | sh` byl zablokován harness klasifikátorem na sdíleném remote hostu):
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```
Výsledek: `uv 0.11.16 (x86_64-unknown-linux-gnu)` nainstalované do `/home/nanobot/.local/bin/{uv,uvx}`. Pro načtení do PATH je potřeba `source ~/.local/bin/env` (nebo nová shell session s upraveným `.profile`/`.bashrc`).
**Ověření:** `ssh nanobot@nanobot.hell '~/.local/bin/uv --version'` → `uv 0.11.16`.
## 2026-05-26 — Časová zóna Europe/Prague a NTP na nanobot.hell
**Cíl:** Přepnout timezone z `Etc/UTC` na `Europe/Prague` a aktivovat NTP.
**Stav před:**
```
Time zone: Etc/UTC (UTC, +0000)
System clock synchronized: yes
NTP service: inactive
```
**Provedeno:**
```bash
timedatectl set-timezone Europe/Prague
timedatectl set-ntp true
```
**Stav po:**
```
Local time: Tue 2026-05-26 18:08:44 CEST
Time zone: Europe/Prague (CEST, +0200)
System clock synchronized: yes
NTP service: inactive
```
**Poznámka k NTP:** `systemd-timesyncd` zůstává `inactive` záměrně — jednotka má `ConditionVirtualization=!container`, a protože `nanobot.hell` běží jako kontejner (LXC), service se přeskočí. Čas se dědí z hosta a `System clock synchronized: yes` to potvrzuje. V kontejneru tedy nemá smysl NTP klienta spouštět; pokud by bylo potřeba nezávislé synchronizace, musela by se na hostiteli zapnout odpovídající podpora (a/nebo se kontejneru povolit `CAP_SYS_TIME`).
## 2026-05-26 — Instalace nanobot-ai a zprovoznění Telegramu + WebUI
**Cíl:** Nainstalovat `nanobot-ai` pod uživatelem `nanobot` na `nanobot.hell`, napojit OpenRouter (model Kimi K2.6) a zprovoznit dva kanály — Telegram bot a WebUI.
**Provedeno:**
1. `uv tool install nanobot-ai` (verze 0.2.0) — uv stáhl 114 balíčků, vytvořil izolovaný venv v `/home/nanobot/.local/share/uv/tools/nanobot-ai/`, exposed CLI binárku `/home/nanobot/.local/bin/nanobot`.
2. Wizard `nanobot onboard --wizard` přeskočen — schéma configu z `docs/chat-apps.md` + `docs/websocket.md` + `docs/deployment.md` na GitHubu, config.json zapsán ručně.
3. `~/.nanobot/config.json` (mode 600, obsahuje secrets):
- `providers.openrouter.apiKey` — OpenRouter klíč
- `agents.defaults.provider=openrouter`, `model=moonshotai/kimi-k2.6`
- `channels.telegram` — `enabled`, bot token, `allowFrom: ["8826147089"]` (whitelist Telegram user ID)
- `channels.websocket` — `enabled`, `host: 0.0.0.0`, statický `token` (32B hex)
- `gateway.host: 0.0.0.0` — bind všech rozhraní (jinak gateway odmítne startnout s `websocket.host: 0.0.0.0` bez tokenu)
4. **Test LLM:** `nanobot agent --message "..."` přes OpenRouter odpověděl modelem `moonshotai/kimi-k2.6`. Workspace `~/.nanobot/workspace` se vytvořil při prvním spuštění (AGENTS.md, USER.md, SOUL.md, HEARTBEAT.md, TOOLS.md, memory/, git store).
5. **Test Telegramu:** `nanobot gateway --verbose` v popředí → bot `@lachran_nanobot` se připojil (polling mode). Uživatel poslal zprávu v Telegramu, bot odpověděl ("Běžím na modelu Moonshot AI Kimi K2.6. 🐈"). LLM usage: prompt=6464, completion=62, latency ~5.3s.
6. **Test WebUI:** `curl http://nanobot.hell:8765/` → HTTP 200, HTML SPA z `nanobot/web/dist/index.html`. WebUI HTML i WebSocket auth endpoint jsou na stejném portu 8765 (gateway zdraví zvlášť na 18790).
**Otevřené:** systemd unit (user-level), enable lingering, restart test.
**Hraniční zjištění:**
- WebUI HTML je v `~/.local/share/uv/tools/nanobot-ai/lib/python3.13/site-packages/nanobot/web/dist/` — součást wheelu, není to externí asset.
- README projektu odkazuje na `docs/configuration.md`, ale schéma `websocket` channelu v něm chybí — k poznání klíčů `host/port/path/token/allowFrom/streaming/websocketRequiresToken` jsem se dostal přes `docs/websocket.md` a `docs/deployment.md`.
- `nanobot status` ukázal `OpenAI Codex: ✓ (OAuth)` a `Github Copilot: ✓ (OAuth)` přestože nejsou v configu — patrně automatická detekce systémových OAuth tokenů. Žádný efekt na běh, ignoruji.
- Port 8765 je teď otevřený na všech rozhraních `nanobot.hell` — chráněn statickým bearer tokenem. Až nasadíme nginx reverse proxy na `nano.hell.cz`, vrátíme bind na `127.0.0.1`.
## 2026-05-26 — modelPresets: Kimi + Minimax + Qwen
**Cíl:** Mít víc modelů na výběr — Kimi K2.6 jako default, Minimax M2.7 a Qwen 3.7 Max jako alternativy.
**Zjištění:** Nanobot nepodporuje víc *pojmenovaných agentů*, ale **`modelPresets`** — pojmenované sady (provider, model), mezi kterými se přepíná za běhu `/model <preset>` v chatu (Telegram i WebUI). Jeden agent, několik presetů.
**Provedeno:**
1. Nejnovější Qwen na OpenRouteru zjištěn dotazem na `/api/v1/models` → `qwen/qwen3.7-max` (flagship Qwen 3.7 série).
2. `~/.nanobot/config.json` rozšířen o:
```json
"agents": { "defaults": { "modelPreset": "kimi" } },
"modelPresets": {
"kimi": { "provider": "openrouter", "model": "moonshotai/kimi-k2.6" },
"minimax": { "provider": "openrouter", "model": "minimax/minimax-m2.7" },
"qwen": { "provider": "openrouter", "model": "qwen/qwen3.7-max" }
}
```
3. Gateway restartován. `nanobot status` potvrdil `Model: moonshotai/kimi-k2.6 (preset: kimi)`. Startup log: `Runtime model switched for next turn: moonshotai/kimi-k2.6 -> moonshotai/kimi-k2.6` (preset resolver běží na začátku každého turn).
**Použití:** v chatu napsat `/model minimax` nebo `/model qwen` — od dalšího tahu se přepne. `/model` bez argumentu vrátí aktuální preset.
## 2026-05-26 — user-level systemd služba `nanobot.service`
**Cíl:** Aby `nanobot gateway` běžel perzistentně (přežil odhlášení uživatele i restart serveru), bez root práv pro vlastní službu.
**Volba:** user-level systemd unit (`~/.config/systemd/user/`) místo system-level. Důvod: služba sahá jen do home uživatele `nanobot`, není důvod ji mít v `/etc/systemd/system/` s root vlastnictvím. Lingering vyřeší to, že user manager (`user@1000.service`) běží nezávisle na přihlášení.
**Provedeno:**
1. **Lingering povolen** (vyžaduje root):
```bash
ssh root@nanobot.hell 'loginctl enable-linger nanobot'
```
`loginctl show-user nanobot` po fixu: `Linger=yes`. Vytvoří se `/run/user/1000/` při bootu automaticky, user manager je v `user@1000.service`.
2. **Unit file** `/home/nanobot/.config/systemd/user/nanobot.service`:
```ini
[Unit]
Description=nanobot AI gateway (Telegram + WebUI)
After=network-online.target
[Service]
Type=simple
ExecStart=/home/nanobot/.local/bin/nanobot gateway
Restart=on-failure
RestartSec=5
Environment=LANG=en_US.UTF-8
Environment=LC_ALL=en_US.UTF-8
[Install]
WantedBy=default.target
```
`After=network-online.target` je system unit — pro user-scope to nemá tvrdou závislost, ale ordering systemd respektuje, dokud lingering startuje user manager *po* network-online.
3. **Aktivace** (přes SSH, kde `XDG_RUNTIME_DIR` neexistuje v non-login session — nutno nastavit ručně):
```bash
ssh nanobot@nanobot.hell 'XDG_RUNTIME_DIR=/run/user/1000 systemctl --user daemon-reload \
&& systemctl --user enable --now nanobot.service'
```
Symlink `default.target.wants/nanobot.service` → enabled.
4. **Ověření:** `systemctl --user status nanobot.service` ukázal `active (running)`, Main PID 5657. Endpointy: WebUI `http://nanobot.hell:8765/` → HTTP 200, health `http://nanobot.hell:18790/health` → `{"status":"ok"}`, Telegram bot `@lachran_nanobot` připojený.
**Logování:** `journalctl --user -u nanobot.service -f` (nutno mít `XDG_RUNTIME_DIR=/run/user/1000` v env, pokud se přihlašuju non-login SSH session).
**Otevřené:** test, že služba přežije `reboot` serveru.
## 2026-05-26 — Reboot test: `nanobot.service` přežije restart serveru
**Cíl:** Potvrdit, že lingering + `WantedBy=default.target` opravdu nastartuje gateway i po cold rebootu, ne jen při interaktivním přihlášení.
**Provedeno:**
1. Pre-reboot `boot_id` zaznamenán (`fe922ca4-...`).
2. `ssh root@nanobot.hell 'systemctl reboot'` — SSH session dropped (čekáno).
3. Polling `ssh -o ConnectTimeout=3` v 3-sec intervalech, server zpátky po ~15 s, nový `boot_id` (`3d20399d-...`), `uptime -p` = `up 0 minutes`, `systemctl is-system-running` = `running`.
4. `nanobot.service` status: `active (running) since 18:36:13`, Main PID **233** (nízké PID = startovalo brzy v bootu, hned jak naběhl `user@1000.service`).
5. Journal potvrdil, že telegram i websocket channel naběhly bez chyby.
6. **Externí ověření** z mé strany: `curl http://nanobot.hell:8765/` → HTTP 200 (WebUI), `curl http://nanobot.hell:18790/health` → `{"status":"ok"}`.
**Závěr:** Lingering + user-level systemd unit funguje přesně jak má — žádný manuální zásah po rebootu není potřeba.
## 2026-05-26 — Swap `qwen` presetu na qwen3.6-plus
**Změna:** Uživatel chtěl místo `qwen3.7-max` použít "qwen-plus". Čistý `qwen-plus` slug na OpenRouteru neexistuje — dvě "plus" varianty:
- `qwen/qwen3.5-plus-20260420` — multimodal (text/image/video), 1M context
- `qwen/qwen3.6-plus-04-02` — hybridní lineární attention + sparse MoE
Zvolen novější `qwen/qwen3.6-plus-04-02` (vyšší verze + novější architektura).
**Provedeno:**
1. `~/.nanobot/config.json` upraven přes Python in-place (zachoval ostatní klíče): `modelPresets.qwen.model` → `qwen/qwen3.6-plus-04-02`.
2. `systemctl --user restart nanobot.service` — service `active`, oba kanály naběhly bez chyby.
**Poznámka:** `done.md` záznam o "Qwen 3.7 Max" upraven přímo (model je implementační detail dokončeného úkolu "přidej Qwen jako alternativu"), aby done.md odráželo realitu. History je append-only, tady je proto kompletní stopa.
## 2026-05-26 — Ollama na `nvidia.hell` jako druhý provider + přejmenování presetů
**Cíl:** Vedle OpenRouteru zapojit lokální Ollama instanci, která běží na `nvidia.hell:11434`. Ta hostuje i `:cloud`-tagované modely (Ollama si je tahá ze své cloud služby na vyžádání, takže lokální endpoint nabízí nejen lokálně stažené modely, ale i cloudové).
**Korekce hostname:** V dřívějším todo bylo `nvidila.hell` — překlep podle uživatelovy zprávy. Správně je `nvidia.hell` (192.168.4.45). `nvidila.hell` neexistuje v DNS.
**Zjištění Ollama modelů:** `curl http://nvidia.hell:11434/api/tags` — 57 modelů. Z toho cloud-tagované (`:cloud` suffix, ne `-cloud`):
```
deepseek-v3.2:cloud, gemma4:31b-cloud, glm-5.1:cloud, gpt-oss:120b-cloud,
kimi-k2.5:cloud, kimi-k2.6:cloud, minimax-m2.7:cloud,
nemotron-3-super:cloud, qwen3.5:cloud
```
Pro nás zajímavé: `kimi-k2.6:cloud` (nejvyšší Kimi) a `minimax-m2.7:cloud` (jediný dostupný Minimax).
**Provedeno:**
1. Přidán provider `ollama` s `apiBase: http://nvidia.hell:11434` (klíč `apiBase`, nikoli `baseUrl` — viz `docs/configuration.md`).
2. `modelPresets` přejmenovány a doplněny (uživatel chtěl rozlišit Ollama vs. OpenRouter v názvu):
| preset | provider | model |
|---|---|---|
| `kimi-k2.6-openrouter` (default) | openrouter | `moonshotai/kimi-k2.6` |
| `minimax-m2.7-openrouter` | openrouter | `minimax/minimax-m2.7` |
| `qwen-3.6-plus-openrouter` | openrouter | `qwen/qwen3.6-plus-04-02` |
| `kimi-k2.6-ollama` | ollama | `kimi-k2.6:cloud` |
| `minimax-m2.7-ollama` | ollama | `minimax-m2.7:cloud` |
3. `agents.defaults.modelPreset` přesměrován na `kimi-k2.6-openrouter` (původně `kimi`, nyní s novým názvem). Default zůstal stejný (Kimi K2.6 přes OpenRouter).
4. `systemctl --user restart nanobot.service` — service `active`, oba kanály naběhly. `nanobot status` potvrdil `Model: moonshotai/kimi-k2.6 (preset: kimi-k2.6-openrouter)`.
5. **Konektivita ověřena:** `curl http://nvidia.hell:11434/api/tags` z `nanobot.hell` projde, `kimi-k2.6:cloud` k dispozici. Reálné odeslání zprávy přes Ollama preset zatím netestováno — proběhne automaticky, jakmile někdo v Telegramu/WebUI udělá `/model kimi-k2.6-ollama`.
**Edit configu in-place:** Použit `python3 -c` na serveru (`json.loads → mutate → json.dumps`), aby se zachovaly ostatní klíče (token, telegram, gateway, websocket). Předtím jsem to dělal přepsáním celého souboru přes heredoc — bezpečnější, ale upovídanější.
## 2026-05-26 — Oprava Ollama provideru: `/v1` suffix v `apiBase`
**Problém:** Uživatel hlásil, že ollama presety nefungují. Test přes `nanobot agent --message ... --logs` s dočasným přepnutím default presetu na `kimi-k2.6-ollama`:
```
LLM returned error: Error: 404 page not found
```
Streamovaný startup log předtím říkal `Runtime model switched for next turn: kimi-k2.6:cloud -> kimi-k2.6:cloud`, takže slug modelu byl správný a request odešel — Ollama jen na cestě, kterou nanobot zavolal, vrátil 404.
**Diagnóza:** Přímý curl na `http://nvidia.hell:11434/api/chat` s payloadem `{"model":"kimi-k2.6:cloud","messages":[...]}` fungoval (model odpověděl). Z toho plyne, že nanobot **nevolá Ollama-native `/api/chat`, ale OpenAI-kompatibilní `/v1/chat/completions`**, který Ollama servíruje pod prefixem `/v1`. V configu jsem měl `apiBase: http://nvidia.hell:11434` — bez `/v1`.
**Fix:** `providers.ollama.apiBase` → `http://nvidia.hell:11434/v1`.
**Ověření:**
- `kimi-k2.6-ollama` přes `nanobot agent` → streamovaná odpověď, OK.
- `minimax-m2.7-ollama` → log `Runtime model switched for next turn: minimax-m2.7:cloud -> minimax-m2.7:cloud`, request prošel, model odpověděl. (Identifikoval se jako "Kimi K2.6" — halucinace modelu kvůli sdíleným session ID `cli:direct`, ne chyba nanobotu.)
Gateway restartován, `nanobot.service` aktivní s opraveným configem.
**Učení pro příště:** `docs/configuration.md` říká, že `apiBase` je "the Ollama server endpoint" a uvádí příklad `http://localhost:11434` — bez `/v1`. To je zavádějící pro nanobot, který interně používá OpenAI klienta proti Ollama. Pro Ollamu vždy přidat `/v1` na konec apiBase.
## 2026-05-26 — Přidán preset `glm-5.1-ollama`
**Cíl:** Mít GLM 5.1 (z Ollama na `nvidia.hell`) jako další volitelný model.
**Provedeno:**
1. Ověřeno, že `glm-5.1:cloud` je v `curl http://nvidia.hell:11434/api/tags` (figurovalo už ve výpisu cloud modelů z [záznamu o Ollama provideru](#2026-05-26--ollama-na-nvidiahell-jako-druhý-provider--přejmenování-presetů)).
2. `~/.nanobot/config.json` rozšířen přes Python in-place editor o nový preset:
```json
"glm-5.1-ollama": { "provider": "ollama", "model": "glm-5.1:cloud" }
```
3. **Restart služby zablokován** auto-mode klasifikátorem (`systemctl --user restart nanobot.service` přes SSH). Uživatel musí restart spustit ručně, nebo povolit Bash pravidlo.
**Otevřené:** restart `nanobot.service`, ověřit `/model glm-5.1-ollama` v chatu.
**Generalizace** zapsána do [knowledge.md](knowledge.md) jako recept "Postup: přidání nového modelu (preset)" — bude se hodit i pro další modely.
## 2026-05-26 20:39 — Zapnutí verbose logování gateway (`-v`)
**Cíl:** vidět průběh zpracování zprávy — volané tooly, odpovědi, tok tahu. Platí i pro WebUI.
**Zjištění (zdroj: DeepWiki CLI reference + `nanobot gateway --help`):** nanobot defaultně `logger.disable("nanobot")`. Gateway má `-v`/`--verbose` (DEBUG), agent má `--logs`. WebUI běží uvnitř gateway (websocket channel, port 8765) → `-v` ho pokrývá, separátní přepínač není.
**Provedeno:**
1. `sed` úprava `~/.config/systemd/user/nanobot.service`: `ExecStart=… nanobot gateway` → `… gateway -v`.
2. `systemctl --user daemon-reload && restart` → `active`.
3. Test: přes WebUI poslána zpráva „pouzij python pro zjisteni aktualniho casu“.
**Výsledek (journal):** plně vidět tok tahu — `Processing message …`, stavy `RESTORE→…→RESPOND`, `Tool call: exec({...})`, `LLM usage: prompt/completion/cached`, `Response to …`. NEloguje se tělo tool výsledku ani thinking (to jde do WebUI klienta zvlášť).
**Pozn.:** edit unitu přes SSH zprvu blokoval auto-mode klasifikátor i po vlastní AskUserQuestion; vyřešeno přidáním `Bash(ssh nanobot@nanobot.hell:*)` do `.claude/settings.json`.
**Zapsáno** do [knowledge.md](knowledge.md) jako "Logování: gateway `-v`/`--verbose`, agent `--logs`".
## 2026-05-26 — Přidán preset `deepseek-v3.2-ollama`
**Cíl:** zpřístupnit `deepseek-v3.2:cloud` z Ollama (přes `nvidia.hell`) jako přepínatelný model — kandidát na levný autonomní workhorse (cron/heartbeat). Vychází z rozvahy „top 10 modelů pro nanobot" (Ollama cloud + OpenRouter, bez Anthropic/OpenAI).
**Provedeno (postup dle [knowledge.md](knowledge.md) „Postup: přidání nového modelu"):**
1. Ověřeno `curl http://nvidia.hell:11434/api/tags` → `deepseek-v3.2:cloud` přítomno.
2. `~/.nanobot/config.json` rozšířen přes Python in-place editor:
```json
"deepseek-v3.2-ollama": { "provider": "ollama", "model": "deepseek-v3.2:cloud" }
```
3. `systemctl --user restart nanobot.service` → `active`.
**Výsledek:** preset funkční, v chatu přepnutelný `/model deepseek-v3.2-ollama`. Rutinní změna, žádné nové zjištění do knowledge.md.
## 2026-05-27 — Aktualizace šablony CLAUDE.md podle vzoru
**Cíl:** sjednotit šablonu `.claude/templates/CLAUDE.md` s dozrálým kořenovým `CLAUDE.md` (vzorem), aby nové projekty dostaly aktuální strukturu pravidel.
**Co jsem zkusil:** porovnal šablonu vs. kořenový CLAUDE.md. Šabloně chyběly soubory `knowledge.md` a `plans.md`, tabulkový přehled tracking souborů a per-soubor poznámky (struktura záznamu history, vyřešené chyby/zamítnuté možnosti v knowledge, otázky→knowledge). Přepsal šablonu: tabulka 6 souborů + per-soubor sekce + commit pravidlo s prefixy + NEDĚLEJ. Projektově specifické věci (SSH nanobot.hell, popis nanobota) jsem vynechal — šablona zůstává generická.
**Co fungovalo a proč:** Write přes celý soubor; obsah převzat ze vzoru a zobecněn (`<slug>` místo konkrétní cesty).
**Co zbývá:** nic; rutinní synchronizace.
## 2026-05-28 17:23 — Skill `detach` + tasks-daemon (background úlohy mimo agent loop)
**Cíl:** umožnit uživateli v chatu (Telegram/WebUI) říct „udělej X na pozadí, dej vědět až bude hotovo" a vrátit se k jiné práci. Use cases: deep research, wiki ingest, libovolný úkol moc dlouhý na synchronní čekání v chatu.
**Co jsem zkusil (cesta k finálnímu designu):**
1. **Cron tool + `at` schedule** (zavrženo). Builtin `cron(action="add", at=ISO, deliver=true)` auto-injektuje channel context (`cron.py:173-218`) a `delete_after_run=True` se zapne sám pro `at` schedule. Krásně jednoduché, ale `on_cron_job` v `commands.py:891-897` obaluje payload v hardcoded preamble *„The scheduled time has arrived. Deliver this reminder…do not narrate progress, summarize, include user IDs, or add status reports like 'Done'"*. To přímo odporuje tomu, co background úloha potřebuje — provést úkol, zapsat výsledek do souboru, vrátit informativní větu. Stejný preamble bug už dřív vedl k „Output was empty" spamu reminderů (history 2026-05-27).
2. **Externí daemon mimo agent loop** (uživatelův návrh, finální). Replikuje pattern `/remind` skillu — doručování úplně mimo agenta. Skill jen zapíše task soubor do `tasks/inbox/`, daemon ho přečte, projede `nanobot agent` v izolované session, výsledek uloží do souboru, pošle Telegram zprávu přes Bot API.
3. **Subprocess `nanobot agent --message` vs Python API** (Python API). CLI funguje, ale streamuje chaoticky (`` prefixované delty + finální čistá odpověď na konci). Postprocesovat by bylo křehké. `Nanobot.from_config().run()` (`nanobot.py:71-102`) vrací `RunResult.content` jako čistý string, bez streamovacích nečistot.
4. **Systemd `.timer` vs `.path` unit** (`.path`). Timer (každou minutu) by zaváděl zbytečné polling + 1-min latenci, kterou pro detach nepotřebujeme. `.path` unit s `DirectoryNotEmpty=` (inotify-driven) reaguje do desítek ms, navíc se `Type=oneshot` automaticky neserializuje souběh. Ověřeno test unitem před deployem.
5. **`flock` proti souběhu** (zavrženo, uživatelův návrh). Systemd `Type=oneshot` se nespustí podruhé, dokud první běh trvá; level-triggered `.path` ho po doběhu restartne, pokud kondice stále platí. Partial-write race řeší skill atomickým `mv` z `tasks/tmp/` (nehlídané systemd) do `tasks/inbox/` (hlídané) — daemon nikdy neuvidí napůl zapsaný soubor.
6. **Chat_id discovery** (vyřešeno bez patche). ContextBuilder vkládá do runtime contextu zprávy `Channel: <name>` a `Chat ID: <id>` (`context.py:123-139`). Skill v SKILL.md instruuje agenta, ať si chat_id přečte odtud, takže žádný session_info() helper tool nepotřebujeme.
**Co fungovalo a proč:** Smoke test prošel za 3 sekundy end-to-end:
- Vytvořen fake task soubor `2026-05-28T172259-smoke-test.md` v `tasks/tmp/`, `mv` do `tasks/inbox/`.
- Systemd `.path` unit spustil `tasks-daemon.service` okamžitě (z `inactive` do `active`).
- Daemon přesunul `inbox/ → running/`, zavolal `Nanobot.run("Odpověz jedním slovem: kolik je 2+2?")`, dostal `"čtyři"`.
- Appendnut `## Result` + metadata footer (`completed`, `duration_seconds`, `status`), `mv → done/`.
- `telegram_send(chat_id, "✅ Hotovo: čtyři. Detail: workspace/tasks/done/…")` — `NOTIFIED 8826147089` v daemon logu.
Logy:
```
2026-05-28T17:23:00 DRAIN start 1 task(s)
2026-05-28T17:23:00 START 2026-05-28T172259-smoke-test.md
2026-05-28T17:23:03 NOTIFIED 2026-05-28T172259-smoke-test.md → 8826147089
2026-05-28T17:23:03 END 2026-05-28T172259-smoke-test.md status=done duration=3s
```
**Architektura (finální):**
```
[uživatel v Telegram]
[skill detach]
1. write_file workspace/tasks/tmp/<ts>-<slug>.md
2. exec mv tmp/ → inbox/ (atomický)
3. ack uživateli 1 větou
[systemd .path: DirectoryNotEmpty=tasks/inbox] (inotify)
[tasks-daemon.service Type=oneshot]
tasks-daemon.py — Python API Nanobot.run() v izolované session detach:<stem>,
append ## Result, mv → done|failed, Telegram Bot API zpráva
```
**Soubory:**
- `~/.nanobot/workspace/skills/detach/SKILL.md` + `scripts/tasks-daemon.py` + `systemd/tasks-daemon.{path,service}` (server)
- Tracking: [skills/detach/](skills/detach/) v tomto repu
- Plán: [/home/lachtan/.claude/plans/pojdme-jeste-chvili-planovat-binary-harp.md](/home/lachtan/.claude/plans/pojdme-jeste-chvili-planovat-binary-harp.md) (kompletní rozhodovací stopa)
**Co zbývá:**
- Reálný test skillu z chatu (uživatel triggerne přes Telegram, ověří, že agent správně přečte Chat ID z runtime contextu, vyrobí slug+timestamp, atomicky přesune do inbox/).
- Smoke test #2 — timeout (úkol >15min → status=failed).
- Smoke test #3 — primární use case (deep research s `subagent`/`web_fetch`).
- **Python skript se uživateli moc nelíbí — bude pročištěn v dalším kole** (čistší struktura, méně kódu, lepší error handling).
- Zvážit přesun plánu do tracking repa do `plans/detach-background-tasks.md` (zatím žije v `~/.claude/plans/`).
**Zapsáno do knowledge.md:** „Non-interactive nanobot CLI vs Python API", „Agent vidí Channel/Chat ID v runtime contextu", „Cron preamble je hardcoded — pro non-reminder background úlohy obejít", „Detach skill — background úlohy přes externí daemon", „Systemd .path unit s DirectoryNotEmpty=". Opraveno zastaralé tvrzení „jobs.json změna se projeví po restartu" — hot reload funguje, `_load_store()` per tick.
## 2026-05-28 18:37 — Detach skill iterace #2: fallback chat_id + subactions list/read + SKILL.md do EN
**Cíl:** opravit dva nedostatky z reálného testu (uživatel pustil úkol z WebUI):
1. **Bug 1**: Telegram notifikace selhala HTTP 400, protože WebSocket session má v runtime contextu `Chat ID: <UUID>`, ne Telegram chat ID. Skill ho poslušně dal do frontmatteru, daemon ho strčil do Bot API.
2. **Bug 2**: i kdyby notifikace dorazila, je v ní jen jednořádkové shrnutí + cesta k souboru — chyběl ergonomický mechanismus pro přečtení výsledku v chatu.
**Rozhodnutí (uživatel):** Telegram + WebUI současně (Fáze 2a = fallback Telegram chat_id, Fáze 2b = WebSocket push odloženo); rozšířit `detach` skill o subactions (`list`, `read`), ne nový skill. **SKILL.md přepsat do angličtiny** — slabší modely lépe následují anglické instrukce (stejný pattern jako `deep-research/SKILL.md`).
**Co jsem zkusil a co fungovalo:**
1. **Daemon `resolve_telegram_chat_id(fm)`:** vrátí `(chat_id, source)`. Když `fm.channel == "telegram"` → použij `fm.chat_id` (multi-user ready). Jinak → načti `fallback_telegram_chat_id` z `~/.nanobot/workspace/skills/detach/config.json`. Log line `NOTIFY chat=<id> source=<frontmatter|fallback>`.
2. **Nový soubor `config.json` ve skill adresáři:**
```json
{"fallback_telegram_chat_id": "8826147089"}
```
Editovatelný bez touch daemon kódu, single-user setup vyhraje hardcode.
3. **Telegram notifikace text** přepracován — obsahuje slug v první řádce + výzvu „v chatu si vyžádej plný report: `výsledek <slug>`":
```
✅ Hotovo: `fallback-test`
hello
V chatu si vyžádej plný report: `výsledek fallback-test`
```
4. **SKILL.md kompletně přepsán anglicky** — tělo (postup, příklady, failure handling) v EN, trigger fráze v `description` zachovány dvojjazyčné (CZ + EN, uživatel mluví česky). Skill explicitně instruuje agenta „respond to user in their language" — ack zpráva a notifikace se přeloží.
5. **Tři subactions v jednom skillu:**
- **`detach`** (default) — beze změny krom slug v ack zprávě.
- **`list`** — `exec ls -1t tasks/{running,done,failed}/`, markdown tabulka grouped by status.
- **`read <identifier>`** — identifier = slug, část slugu, timestamp fragment, nebo prázdný (nejnovější done). `grep -i` přes ls výstup, `read_file` matche, předlož `# Result` sekci + metadata footer.
**Co fungovalo (smoke test fallback):**
```
2026-05-28T18:37:18 DRAIN start 1 task(s)
2026-05-28T18:37:18 START 2026-05-28T183718-fallback-test.md
2026-05-28T18:37:25 NOTIFY 2026-05-28T183718-fallback-test.md chat=8826147089 source=fallback
2026-05-28T18:37:25 END 2026-05-28T183718-fallback-test.md status=done duration=6s
```
Fake task s `channel: websocket`, `chat_id: "68254619-fake-websocket-uuid-test"` → daemon rozeznal non-telegram channel → použil fallback `8826147089` → HTTP 200 OK. Trvání 6s (krátký úkol bez tools).
**Co zbývá:**
- **Reálný retest Bug 1**: uživatel spustí detach z WebUI → Telegram zpráva musí dorazit.
- **Reálný retest Bug 2**: v chatu (Telegram nebo WebUI) řekni „výsledek fallback-test" → agent musí přečíst soubor a předložit Result sekci. Také „výsledky?" → list všech.
- **Fáze 2b odložena** (Fáze 3): WebSocket push z daemonu do nanobot gateway pro live notifikaci v aktivní WebUI session. Důvod: nanobot WS gateway vyžaduje JWT auth (`channels.websocket.token_issue_secret`), WS klient v daemonu, envelope format — komplexní, zatím vyřešeno přes `výsledek <slug>` pull mechanismus.
- **Pročištění Python skriptu** — uživatel řekl v iteraci #1, že to udělá sám v dalším kole.
---
## 2026-05-29 — Skill `/keep` — doplnění MEMORY.md cross-check (dedup krok 4)
**Cíl:** Skill `/keep` deduplikoval pouze v rámci `keep.md`. Dream pipeline destiluje fakta do `workspace/memory/MEMORY.md` na pozadí — pokud tam podobný fakt už je, zápis téhož do `keep.md` je zbytečná duplicita. Doplnit do write protokolu krok, který před appendem přečte `MEMORY.md` a pokud tam je sémanticky podobný fakt, uživatele upozorní a defaultně přeskočí.
**Co jsem zkusil / Co fungovalo a proč:**
1. **`skills/keep/SKILL.md` — write protocol:** vložen nový krok 4 (čti `workspace/memory/MEMORY.md`, porovnej sémanticky; pokud shoda → zeptat se, default: skip). Původní kroky 47 přečíslovány na 58.
2. **`skills/keep/SKILL.md` — Rules:** řádek `"do not cross-write"` nahrazen přesnější formulací (`"never cross-write. Read MEMORY.md only for dedup check (step 4); never edit it from this skill."`).
3. **Deploy:** `rsync -av skills/keep/ nanobot@nanobot.hell:/home/nanobot/.nanobot/workspace/skills/keep/` — `SKILL.md` odeslán.
4. **Ověření na serveru:** `grep -A4 'MEMORY.md'` vrátil nový krok 4 a nový Rules řádek — obojí správně.
**Co zbývá (pro uživatele):**
- End-to-end test: vybrat fakt, který již je v `workspace/memory/MEMORY.md`, poslat ho agentem jako `keep: <fakt>` → agent by měl reagovat hláškou „Already in MEMORY.md: …" a defaultně nepsat.
- Regrese dedup v `keep.md` pořád funguje: nový fakt 2× po sobě → při druhém pokus se agent zeptá na duplicitu v `keep.md`.
---
## 2026-05-29 14:04 — Skill `/keep` — explicit memory store
**Cíl:** Vytvořit on-demand skill pro okamžitou explicitní paměť. Uživatel řekne "keep X" → agent reformuluje → zapíše do `workspace/keep.md`. Trvalé povědomí zajistit referencí v `USER.md` (auto-loadovaný každý tah), nikoli `always: true` skillem — šetří context window.
**Klíčové rozhodnutí (přijaté před implementací):** `BOOTSTRAP_FILES` v `nanobot/agent/context.py:25` jsou hardcoded — nelze přidat vlastní soubor bez patche. Persistent awareness `keep.md` tedy řeší krátká sekce v `USER.md` (ten je bootstrap), ne further always-on skill. Skill `/keep` je čistě on-demand write endpoint.
**Co jsem zkusil / Co fungovalo a proč:**
1. **Lokální `skills/keep/SKILL.md`** — vytvořen dle plánu. Frontmatter on-demand (bez `always`), tělo anglicky: write protocol (extract fact → reformulate → read → dedup → append), compaction (>150 řádků), edge cases, pravidla.
2. **Deploy** přes `rsync -av skills/keep/ nanobot@nanobot.hell:/home/nanobot/.nanobot/workspace/skills/keep/` — nanobot user, ownership `nanobot:nanobot` automaticky.
3. **USER.md reference** — append sekce `## workspace/keep.md` + popis na konec `/home/nanobot/.nanobot/workspace/USER.md` (jako `nanobot` user přes SSH, tee -a). Ověřeno `tail -7`.
**Ověření (sanity check):**
- `ls -la /home/nanobot/.nanobot/workspace/skills/keep/` → `SKILL.md` owned `nanobot:nanobot`.
- `head -25 SKILL.md` → frontmatter + tělo správně.
- `tail -7 USER.md` → sekce `## workspace/keep.md` na konci.
**Co zbývá (pro uživatele):**
- End-to-end test: v Telegramu/WebUI říct „keep: Honza z marketingu má alergii na arašídy" → ověřit `keep.md` na serveru.
- Persistent awareness test: v nové session se zeptat na Honzu → agent zmíní alergii.
- Compaction test: naplnit `keep.md` na 151+ řádků, spustit `/keep`, ověřit compaction report.
---
## 2026-05-28 19:46 — Detach skill iterace #3 — uv-native + čitelnostní cleanup
**Cíl:** Přepnout `tasks-daemon.py` na uv-native invokaci (PEP 723 + `uv run --script`) a provést drobné čitelnostní úpravy: modulový import, rozložit `summary_line` oneliner, odstranit zbytečné `str()` wrappy v `shutil.move()`. Žádné nové abstrakce.
**Co jsem zkusil / Co fungovalo a proč:**
1. **Shebang + PEP 723 hlavička** — `#!/usr/bin/env -S uv run --script` + inline script metadata (`requires-python = ">=3.11"`, `dependencies = ["nanobot-ai"]`). `uv run --script` samo vytvoří izolované venv s `nanobot-ai`, bez závislosti na konkrétní cestě `~/.local/share/uv/tools/nanobot-ai/bin/python`. Lokální dry-run: `uv` stáhl 114 packages, skript skončil 0 (inbox prázdný).
2. **Modulový `from nanobot import Nanobot`** — přesun z těla `run_agent()` na úroveň modulu (Python konvence: import uvnitř funkce jen pro circular deps nebo heavy optional deps, `nanobot` je core dep).
3. **`summary_line` rozložen** — jednořádkový `(result_text.strip().splitlines() or ["(prázdný výstup)"])[0][:200]` na 2 čitelné řádky.
4. **`str()` wrappy pryč** — `shutil.move()` přijímá path-like objekty od Python 3.9, `str()` byl cargo-cult. Odstraněno ze 4 výskytů (3× v `process_task()`, 1× zbyl jen argument `running → target_dir`).
5. **Docstring sjednocen na CZ** — EN anotace v hlavičce přeložena do češtiny, diff se tím nezvětšil nad limit.
6. **tasks-daemon.service doplněn o `Environment=PATH=%h/.local/bin:/usr/bin:/bin`** — bez toho by systemd nenašel `uv` (není v defaultní PATH user unitů). `daemon-reload` přes `XDG_RUNTIME_DIR=/run/user/1000`.
7. **Smoke test prošel**: task `2026-05-28T174616-uv-smoke.md` (`channel=telegram`, `chat_id=8826147089`, goal=`odpověz \`hotovo\``) → daemon zpracoval za 4s, log:`START`,`NOTIFY chat=8826147089 source=frontmatter`,`END status=done duration=4s`. Telegram zpráva dorazila.
**Diff:** 23 changed lines v `tasks-daemon.py` (14 insertions, 9 deletions) — pod limitem 30.
**Rsync:** `rsync` přes `nanobot` uživatele selhal (`rsync: command not found` na straně serveru), použit `scp` přes `root` + `chown nanobot:nanobot`.
**Co zbývá:**
- Verifikace z plánu: `uv tool uninstall nanobot-ai && uv tool install nanobot-ai` přežití (důkaz nezávislosti na tool venv cestě) — nízká priorita, PEP 723 pattern to garantuje designově.
---
## 2026-05-29 — Skill `/note` — explicit notes bez auto-loadu
**Cíl:** Vytvořit skill pro ukládání poznámek do `workspace/notes.md`. Na rozdíl od `/keep` se soubor neauto-loaduje do context window, nemaže se automaticky, a mazání probíhá pouze explicitně přes `/note delete <pattern>`.
**Klíčové rozhodnutí:** Žádná reference v `USER.md` ani jiném bootstrap souboru — `notes.md` nikdy nevstupuje do kontextu automaticky. Žádná auto-kompakce. Žádný dedup check (ani proti `notes.md`, ani `keep.md`, ani `MEMORY.md`) — blind append.
**Co jsem udělal:**
1. Vytvořen lokální `skills/note/SKILL.md` s write protokolem (extract → reformulate → append), delete protokolem (by index nebo substring, multi-match ukáže seznam), edge cases a rules.
2. Deploy: `rsync -av skills/note/ nanobot@nanobot.hell:/home/nanobot/.nanobot/workspace/skills/note/` — `SKILL.md` odeslán, ownership `nanobot:nanobot`.
3. Žádný patch `USER.md` — záměrně, notes jsou off-context.
**Co zbývá:**
- Verifikace end-to-end: `note: testovací poznámka` v Telegramu/WebUI → ověřit `notes.md` na serveru.
- Test delete by substring, by index, multi-match.
- Ověřit, že notes nejsou v auto-contextu: `grep -i note /home/nanobot/.nanobot/workspace/USER.md` → žádná shoda.
---
## 2026-05-29 — Skill `/keep` — EN-only tělo + pravidlo o jazyku
**Cíl:** Revize kvality `skills/keep/SKILL.md`. Hlavní vada: chybělo pravidlo „zachovej jazyk vstupu = výstupu", které `/note` nedávno dostal (`ea81761`, `c8667eb`) — keep na fix zapomněl, takže český vstup se mohl uložit anglicky.
**Klíčové rozhodnutí uživatele:** tělo skillu držet **striktně anglicky**, žádné české příklady. Stačí jasné pravidlo *„preserve input language, never translate"* — model ho dodrží sám. Dvojjazyčné triggery v `description` frontmatteru **ponechat** (nutné pro routing; bez českých frází se skill na český vstup nespustí). Potvrzovací hlášku model lokalizuje sám podle jazyka odpovědi → CZ varianta `Zapamatoval jsem:` v těle byla redundantní.
**Co jsem udělal:**
1. **Write protocol krok 2** — přidána věta *„Preserve the language of the input — never translate. Czech input → Czech entry, English input → English entry."* Bez českého příkladu, stávající EN příklad ponechán.
2. **Confirm krok 8** — odstraněna CZ varianta `Zapamatoval jsem:`, nechán jen `Kept: <fact>` + poznámka, že model lokalizuje sám.
3. **Rules** — přidána odrážka „Preserve input language; never translate." a sloučeny dvě překrývající se odrážky o separaci od MEMORY.md/Dream do jedné.
4. Krok 4 (MEMORY.md dedup lookup) **beze změny** — uživatel potvrdil ponechat.
5. Deploy: `rsync -av skills/keep/SKILL.md nanobot@nanobot.hell:.../skills/keep/SKILL.md`, ownership `nanobot:nanobot` ověřeno, nasazená verze == lokální (diff = IDENTICAL).
**Co fungovalo a proč:** EN-only tělo + explicitní pravidlo o jazyku je čistší než dvojjazyčné příklady — méně šumu, model jazyk dodrží sám. Triggery v `description` jsou oddělená vrstva (routing), proto zůstaly dvojjazyčné.
**Co zbývá:**
- Smoke test přes Telegram: česky „zapamatuj si že schůzka je v pátek" → očekávaný zápis i potvrzení česky; anglicky „keep the deploy window is Friday" → zápis i potvrzení anglicky.
---
## 2026-05-30 — Skill `/note` — sjednocení s EN-only principem (jako `/keep`)
**Cíl:** Aplikovat na `skills/note/SKILL.md` stejnou revizi, jakou prošel `/keep`. `/note` jazykové pravidlo *měl* (na rozdíl od keepu), ale drželo se upovídaně se dvěma příklady (CZ + EN) — přesně to, co uživatel z keepu odstranil. Navíc dvě vady, které keep neměl: smart-quote překlepy a český placeholder.
**Co jsem udělal:**
1. **Write protocol krok 2** — zkráceno: pravidlo *„Preserve the language of the input — never translate. Czech input → Czech entry, English input → English entry."* + **jediný anglický příklad** (`deploy window — Fridays after 18:00`). Odstraněna dvojice CZ/EN příkladů. Sjednoceno s keepem.
2. **Confirm krok 5** — odstraněna CZ varianta `Zapsal jsem: <zkratka>`, nechán jen `Noted: <terse fact>` + poznámka, že model lokalizuje potvrzení sám. (Zarovnáno s keep krokem 8.)
3. **Edge cases** — opraveny smart-quote překlepy `„…"` → rovné `"…"` (ř. 53-57; uvnitř anglických vět byly české uvozovky, zbytek souboru má rovné ASCII). Český příklad `"tamtu věc"` → `"that thing"`.
4. **Rules** — beze změny (`Preserve input language` už přítomno).
5. Deploy: `rsync -av skills/note/SKILL.md nanobot@nanobot.hell:.../skills/note/SKILL.md`, ownership `nanobot:nanobot` ověřeno, nasazená == lokální (diff = IDENTICAL). 70 → 68 řádků.
**Ověřeno:** `notes` není v `USER.md` (`grep` = žádná shoda) → tvrzení skillu „no auto-load" platí. Live `notes.md` na serveru potvrzuje, že skill funguje dle návrhu (terse CZ fakta, bez dat).
**Co fungovalo a proč:** Symetrie s keepem — stejný EN-only vzor v obou near-identických skillech. Méně šumu, jazyk dodrží model sám z pravidla.
**Co zbývá:**
- Smoke test přes Telegram: česky „poznamenej si že deploy je v pátek" → zápis i potvrzení česky; anglicky „note the deploy window is Friday" → zápis i potvrzení anglicky.
---
## 2026-06-01 17:37 — Vypnutí reasoning streamu (``) na konzoli
**Cíl:** Uživatele rušily na konzoli (`nanobot agent` CLI chat) řádky prefixované ``, streamované token po tokenu (`✻ The`, `✻ user wants`, …). Identifikováno jako **reasoning/thinking stream** modelu, ne debug ani chyba.
**Co jsem zkusil / zjistil:**
1. Prošel upstream (`tmp/nanobot-upstream`, HEAD `2b4c984`). Zobrazení `` řídí jediný config klíč `channels.show_reasoning` (default `True`).
- Schema: `nanobot/config/schema.py:39` (`show_reasoning: bool = True`).
- Gate: `nanobot/cli/commands.py:345` a `:354` — když `not channels_config.show_reasoning`, reasoning buffer se vyprázdní a netiskne. Vykreslení `` na `commands.py:301`.
- Žádný runtime flag `nanobot agent` na to neexistuje (`--logs/--no-logs` řídí jen loguru runtime log, ne reasoning stream).
2. Ověřeno na serveru: klíč nebyl nastaven (`channels` mělo jen `telegram`, `websocket`) → padalo na default `True`.
3. **Per-channel to NEJDE** — `show_reasoning` je jeden globální flag na celém bloku `channels`, gate čte globální `channels_config.show_reasoning`. Nelze vypnout jen pro konzoli a nechat zapnuté ve WebUI.
**Co fungovalo a proč:** Uživatel nastavil `channels.showReasoning = false` v `~/.nanobot/config.json` a potvrdil, že `` řádky na konzoli zmizely. Progress (``) a tool-hinty ponechány. CLI čte config čerstvě při startu (restart service netřeba); pro gateway/WebUI/Telegram by se projevilo až po restartu služby.
**Jak vrátit zpět:** smazat klíč `channels.showReasoning` z `~/.nanobot/config.json`, nebo nastavit na `true` (default).
**Co zbývá:** Reasoning se ve WebUI hodí při ladění („proč něco jde/nejde") — globální vypnutí ho zruší i tam. Per-channel přepínač upstream nemá → viz otázka v `todo.md`.
---
## 2026-06-02 06:30 — Remind skill: náhodný (deterministický) čas
**Cíl:** Rozšířit skill `remind` o režim „N× denně v náhodný, ale deterministický čas" uvnitř časového okna — aby libovolný skript spočítal pro daný den stejné časy a sender zůstal bezstavový. Návrh a rozhodnutí v [plans/remind-random-time.md](plans/remind-random-time.md).
**Co jsem zkusil / udělal:**
1. Nový sdílený modul `skills/remind/scripts/random_times.py` (stdlib only): `MIN_GAP_MIN = 15` + `compute_fire_times(date, text, cfg)`. Seed = `f"{datum}|{text}"` (per-reminder, jinak by všechny náhodné připomínky padaly ve stejné minuty). Strukturální validace (okno, počet, dny, data, feasibilita) běží *před* datovými filtry → jeden průchod validuje config nezávisle na datu.
2. Napojeno do `remind_send.py` (větev `random` v `_due_fire`, dedup/log beze změny) a `remind_edit.py` (`add --random-times-per-day/-window/-days/-from/-until`, validace přes `compute_fire_times`).
3. YAML: vnořený blok `random:` vedle `at`/`cron_exprs`. Glanceable ukázka v `reminder.example.yaml`.
4. Testy: **pytest** (nová konvence repa) v `skills/remind/tests/` + `conftest.py` (přidá `../scripts` na sys.path). 21 testů.
5. Úklid SKILL.md na žádost uživatele: celý skill EN-only (folded `description`, žádná čeština, doručovaný prefix `⏰ Reminder:`), žádné absolutní cesty (relativní `scripts/...`), odstraněna sekce `## Architecture` (provozní detail žije v knowledge.md, řádek 167).
**Co fungovalo a proč:** Před nasazením ověřena shoda serveru s baseline (commit `5a748f9`) — žádný autonomní drift. Nasazeno `rsync -av` (bez `__pycache__`), vlastník `nanobot:nanobot` OK. Na serveru `uv run --with pytest pytest tests/` → 21 passed. `remind_edit.py list` čte reálný `reminder.yaml` (stávající cron/at/at_times kompatibilní), infeasible add korektně chybuje bez zápisu. Commity: skill `bc2cf51` + předchozí, plán, todo.
**Jak vrátit zpět:** `git revert` skill commitů + `rsync` staré verze; nové soubory (`random_times.py`, `tests/`, `reminder.example.yaml`) na serveru smazat. Crontab se neměnil (stejná cesta `remind_send.py`).
**Co zbývá:** Reálná zkouška doručení přes Telegram (přidat random připomínku s krátkým oknem, ověřit doručení + `log/reminder.log`, pak smazat) — posílá skutečné notifikace, takže až na výzvu. Doladit `MIN_GAP_MIN` podle provozu.
---
## 2026-06-02 — Remind skill: per-entry ID (rozbor, zavrženo)
**Cíl:** Posoudit, jestli by přidání unikátního `id` ke každému záznamu v `reminder.yaml` přineslo skillu `remind` výhodu (lepší mazání nebo jiné), nebo ne.
**Co jsem zkusil:** Přečetl celý skill — `SKILL.md`, `remind_edit.py` (remove = `--keyword` substring na `text`, chyba při 0/>1 shodě), `remind_send.py` (dedup klíč `sha1(text)[:8]` ve `.reminder_state.json`). Zjistil, že identitou reminderu je `text` na obou místech.
**Co fungovalo a proč (závěr):** Pro běžné použití (hrstka reminderů s odlišnými texty, řízené konverzačně) ID **nepřináší nic** — keyword-remove i text-dedup fungují a jsou jednodušší; ID by jen přidalo list-then-remove krok. Jediný reálný zisk = **scénář duplicitních textů**: Create Workflow je v `SKILL.md` výslovně připouští, ale `remove` je neumí rozlišit (`ambiguous`) a dedup klíč `sha1(text)` je pro oba stejný (jeden odpal může potlačit druhý). Podle „prefer the simpler solution" ID **nezavádět plošně**. Pokud by duplicity byly reálně potřeba → buď interní dedup klíč `sha1(text+schedule)` (vyřeší jen dedup, nula změn v UX/YAML), nebo plné `id` (vyřeší i mazání). Levnější alternativa bez ID: zakázat duplicitní `text` při `add`.
**Co zbývá:** Otevřená otázka, která rozsekne směr — *chceme reálně umět dva remindery se stejným textem?* Dokud nepadne, zůstává status quo (bez ID). Zaznamenáno do knowledge.md (gotcha + zamítnutá možnost).
---
## 2026-06-02 — Detach skill: úklid SKILL.md + extrakce capture do skriptu (jako remind)
**Cíl:** Stejná revize, jakou prošel `/remind` — SKILL.md čistý a EN-only, žádné zavádějící cesty, a co má dělat skript ať dělá skript (ne agent rukama).
**Co jsem zkusil / udělal:**
1. **`scripts/tasks_common.py`** (nový, stdlib-only, importovatelný) — vystěhované sdílené čisté helpery: `TASKS`, `FILENAME_RE`, `parse_frontmatter`, `parse_kv`, `parse_filename`, `format_time/age`, `render_table`, `extract_section`, `format_result`, `build_task_filename/content`. DRY — dřív duplikované napříč třemi skripty.
2. **`scripts/create-task.py`** (nový, PEP 723, deps `[]`) — capture: `--goal/--slug/--channel/--chat-id [--constraint]`, vygeneruje timestamp + frontmatter, zajistí fronty, atomicky `tmp→inbox`. Nahrazuje ruční `date`+`write_file`+`mv`+`mkdir` v SKILL.md.
3. **Refaktor** `list-tasks.py`/`read-task.py`/`tasks-daemon.py` → import z `tasks_common` (tenké entry skripty).
4. **SKILL.md** přepsán: description trim na core EN triggery, capture = jediný `exec skills/detach/scripts/create-task.py …`, confirm jako EN instrukce (ne český natvrdo template), list/read triggery EN-only, cesty workspace-relativní, „respond in user's language" sjednoceno do jedné Rules sekce.
5. **`tests/`** (nové, pytest + conftest dle remind) — 34 testů čisté logiky.
6. **`systemd/tasks-daemon.service`** — doplněn `Environment=PATH=%h/.local/bin:/usr/bin:/bin` (repo bylo zastaralé, server ho měl → drift fix dle „server je zdroj pravdy").
**Co fungovalo a proč:** Ověřeno CWD chování v upstreamu (`shell.py:148` `working_dir=ctx.workspace`, `:370` `cwd = working_dir or workspace_root`) → `exec` běží z **workspace rootu**, ne ze skill dir; proto stávající home cesty nebyly bug a relativizace na `scripts/…` by skill rozbila. Zvoleny workspace-relativní `skills/detach/scripts/…`. Před nasazením ověřena shoda serveru s baseline session (žádný autonomní drift). Nasazeno `rsync -av` (bez `tests/`+`__pycache__`), vlastník `nanobot:nanobot`, `+x` obnoveno na entry skriptech (rsync je přinesl jako 644). Smoke test na serveru pod reálným uv: `list-tasks.py` čte/renderuje, `create-task.py --help` postaví venv + import OK, `tasks-daemon.py` s prázdným inboxem naběhne (nanobot-ai venv + `tasks_common` import, exit=0, žádný task nespuštěn). Lokálně 34 pytest zelených.
**Jak vrátit zpět:** `git revert` skill commitu + `rsync` starých verzí; nové soubory (`tasks_common.py`, `create-task.py`) na serveru smazat. systemd `Environment=PATH` na serveru ponechat (byl tam před touto změnou).
**Co zbývá:** Plný live e2e (create → daemon → agent → Telegram → done/) — posílá skutečnou Telegram notifikaci a spustí agenta, takže až na výzvu uživatele.
---
## 2026-06-02 19:02 — Context window presetů: zvednut z defaultních 65k na limit modelů
**Cíl:** Uživatel se ptal proč má nanobot kontext jen 65k. Zjistit příčinu a zvednout na reálné limity modelů.
**Co jsem zkusil / co fungovalo a proč:** Příčina = nanobot má hardcoded default `context_window_tokens = 65_536` (upstream `nanobot/config/schema.py:101,124` — `ModelPresetConfig` i `AgentDefaults`). Žádný preset v serverovém `config.json` tuto hodnotu nepřepisoval (měly jen `provider` + `model`), takže každý model jel na 65k bez ohledu na schopnosti. Klíč v JSON: `contextWindowTokens` (Base má `alias_generator=to_camel`, `populate_by_name=True` → projde camelCase i snake_case, `schema.py:24`). Reálné limity ověřeny z ollama.com: kimi-k2.6 256k, qwen3.5 256k, nemotron-3-super 256k, minimax-m2.7 200k, glm-5.1 198k, deepseek-v4-flash 1M. Nastaveno per-preset (`contextWindowTokens`: kimi/qwen/nemotron 262144, minimax 204800, glm 196608 [bezpečně pod 198k], deepseek 1048576; openrouter kimi 262144) + `maxTokens` 16384 u všech (default byl jen 8192). Editováno in-place Python skriptem přes `ssh nanobot@`, záloha `config.json.bak-20260602-190206`. Vlastník zůstal `nanobot:nanobot`. **Bez restartu** — `modelPresets` se hot-reloadují (`_refresh_provider_snapshot()` před každým tahem, viz knowledge.md). Pozn.: `:cloud` modely hostí kontext Ollama cloud, takže `contextWindowTokens` reálně rozšíří budget, není to lokální `num_ctx` žeroucí RAM.
**Jak vrátit zpět:** `cp ~/.nanobot/config.json.bak-20260602-190206 ~/.nanobot/config.json` na serveru jako nanobot.
**Co zbývá:** Sledovat, zda u velmi dlouhých sessions neklesá kvalita / neroste latence u slabších modelů (viz odpověď na dotaz uživatele).
---
## 2026-06-02 19:50 — Develop kontext zpřístupněn serverovému agentovi (workspace/develop/)
**Cíl:** Dát nanobot agentovi na serveru přístup k tracking dokumentům (`knowledge.md`, `history.md`, `memory.md`), aby věděl, jak byla tahle instance rozšiřována a laděna, a mohl z toho těžit.
**Co jsem zkusil / co fungovalo a proč:** Bootstrap soubory jsou hardcoded (`BOOTSTRAP_FILES` v `nanobot/agent/context.py:25`), takže vlastní auto-loadovaný soubor přidat nelze — odkaz musí být reference v existujícím bootstrap souboru (precedent: `keep.md` ref v `USER.md`). Soubory v `develop/` nejsou v žádné auto-load cestě ani je nekonzumuje Dream → bezpečné, neplýtvají context window, čtou se on-demand. Postup: (1) `rsync` tří tracking souborů do `~/.nanobot/workspace/develop/` (adresář už existoval, prázdný); (2) vytvořen `develop/README.md` (česky) vysvětlující, co to je a jak to používat, vč. „ne upstream kód, do toho nezasahujeme"; (3) do `AGENTS.md` připojena krátká sekce „## How you were extended & tuned" (1 věta + odkaz na README) — nejdřív re-fetch serverové verze (1155 B, žádný drift), pak append a upload zpět; (4) pravidlo o průběžné synchronizaci do `memory.md`. Vše owner `nanobot:nanobot`, bez restartu (workspace soubory se čtou každý tah fresh). Rozhodnutí uživatele: synchronizovat develop/ po každé změně tracking souborů (ne jednorázová kopie).
**Jak vrátit zpět:** Na serveru `rm -rf ~/.nanobot/workspace/develop/*` a z `AGENTS.md` smazat sekci „## How you were extended & tuned" (poslední 4 řádky). Lokálně revert commitu pravidla v `memory.md`.
**Co zbývá:** Volitelný funkční test v chatu s agentem (zeptat se „co je develop/") — na výzvu uživatele.
---
## 2026-06-03 06:26 — MiniLoop: realizace .NET PoC `/remind add` parseru
**Cíl:** Naimplementovat v nejmenší možné míře .NET PoC ze [plans/remind-standalone-bot-dotnet.md](plans/remind-standalone-bot-dotnet.md) — jediná LLM cesta `add` (text → JSON dle `reminder.yaml`). Dvě věci: zadávací smyčka (REPL, hned odpoví) a testovací běh, který projede všechny páry z `remind-prompt-examples.txt`, porovná s očekávaným JSON a protočí to nad seznamem modelů + změří wall-clock a tokeny. Zdroje do `src/MiniLoop/`.
**Co jsem zkusil / co fungovalo a proč:** .NET 10 console app, prompt-only přístup (ne `GetResponseAsync<T>` — schéma z `remind-prompt.md` má optional/nested pole, do striktního typu se mapují krkolomně a slabé modely by selhaly). Jeden `IChatClient` (`Microsoft.Extensions.AI` nad `OpenAI` SDK) s konfigurovatelným `Endpoint` → swap providera = jen jiný řádek configu. Soubory: `Config.cs` (providers + models + cesty k promptu/příkladům, klíče v configu), `PromptSource.cs` (extrahuje ` ```text ` blok z `remind-prompt.md`, substituuje `{{NOW}}`; parsuje páry z examples), `ReminderParser.cs` (volání + `Stopwatch` + `UsageDetails`, extrakce JSON od prvního `{` po poslední `}`), `JsonCompare.cs` (sémantická rovnost — klíče objektu order-insensitive, pole order-sensitive, čísla podle hodnoty), `Program.cs` (routing `repl [model]` / `test`). Režim 1. argumentem, model 2. argumentem. NOW: repl = reálný Prague čas, test = fixní `2026-06-03T14:30:00 (Wednesday)`. Provideři: ollama `http://nvidia.hell:11434/v1` (kimi-k2.6:cloud, glm-5.1:cloud), openrouter (claude-haiku-4.5, gpt-5.4-nano, klíč zkopírován z `openrouter.key.txt` do `config.json`). `config.json` + `openrouter.key.txt` v `.gitignore`.
Build čistý (0 warnings). Repl smoke: kimi na „za 10 minut vyndat pizzu" → `{"text":"vyndat pizzu z trouby","at_times":[<teď+10min>]}` správně. Plný test (17 párů × 4 modely):
| Model | Úspěšnost | Wall median / avg | Tokeny in / out |
|---|---|---|---|
| kimi-k2.6 (ollama) | 17/17 | 5981 / 7327 ms | 21536 / 6698 |
| glm-5.1 (ollama) | 17/17 | 1279 / 1410 ms | 21118 / 2765 |
| claude-haiku-4.5 (openrouter) | 16/17 | 917 / 1200 ms | 23668 / 691 |
| gpt-5.4-nano (openrouter) | 17/17 | 1426 / 1621 ms | 20987 / 553 |
To jediné „FAIL" (haiku) je **false negative v testovacích datech**: model vrátil gramaticky správné `"zkontrolovat pečení"`, očekávané je `"pečeni"`. JsonCompare porovnává `text` přesně. Potvrzení teze PoC: ~1,2k input tokenů na `add` (vs ~2832k přes nanobot agent loop), ~11,5 s wall-clock u rychlých modelů (vs ~10 s změřených u `/remind list`). Řádově sedí s odhady z `remind-standalone-bot.md`.
**Co zbývá:** Zdrojový kód v `src/MiniLoop/` zatím necommitnut (CLAUDE.md: commituj jen tracking soubory; src commit na výzvu uživatele). Vědomě mimo PoC: `reminder.yaml`/`list`/`delete`, Cronos validace, retry, structured-output schema. Případná oprava `pečeni``pečení` v examples (na rozhodnutí uživatele).
## 2026-06-03 — MiniLoop: paralelizace testu + nová sada modelů
**Cíl:** Testovací běh `test` jel striktně sekvenčně (`foreach` přes modely čekal na dokončení každého) — u pomalého modelu to trvalo moc dlouho. Uživatel: modely musí běžet souběžně; každý řádek výstupu musí nést značku modelu, pořadové číslo (`6/17`) a vlastní wall-clock (tokeny stačí jako součet per model). Souběžnost zvolena „jen modely" (příklady uvnitř modelu sekvenčně, šetrnější k provideru).
**Co jsem zkusil / co fungovalo a proč:** Změna jen v `Program.cs`. `RunTest` nahradil sekvenční `foreach` za `await Task.WhenAll(cfg.Models.Select(entry => RunModel(...)))`; blokovou hlavičku `## {model}` zrušil (výstup se prolíná) a per-model souhrn sebral do závěrečné tabulky `## Souhrn` po `WhenAll`. `RunModel` teď vrací `record ModelResult` (název, provider, passed, count, wallMs, tokeny, SkipReason) místo tisku souhrnu uvnitř; každý dokončený příklad vypíše atomický blok `[model] N/17 ok|FAIL|ERR <ms> ms <request>`. Souběžné tasky píšou do stejné konzole → víceřádkový FAIL/ERR blok by se prokládal, proto top-level `object consoleLock` + `lock` kolem `Console.WriteLine(block)` (per-volání je `Console` thread-safe, víceřádkový blok ne). Build čistý. Ověřeno: řádky 6 modelů se reálně prolínají, FAIL bloky drží pohromadě, pořadí 1→17 roste (sekvenční foreach uvnitř modelu), na konci souhrnná tabulka.
Plný paralelní běh 6 modelů (NOW fixní `2026-06-03T14:30:00`):
| Model | Úspěšnost | Wall median / avg | Tokeny in / out |
|---|---|---|---|
| glm-5.1 (ollama) | 17/17 | 3240 / 5048 ms | 21118 / 3029 |
| deepseek-v4-flash (ollama) | 17/17 | 7005 / 8848 ms | 21654 / 3480 |
| qwen3.5 (ollama) | 15/17 | 10852 / 16845 ms | 22871 / 22671 |
| gemma4-31b (ollama) | 15/17 | 752 / 815 ms | 23571 / 600 |
| claude-haiku-4.5 (openrouter) | 16/17 | 894 / 988 ms | 23668 / 691 |
| gpt-5.4-nano (openrouter) | 17/17 | 1400 / 2615 ms | 20987 / 553 |
**Klíčové zjištění (proti očekávání z plánu):** předpoklad „per-model latence se paralelizací nezmění" **neplatí**. Všechny 4 ollama `:cloud` modely sdílí jeden backend → při souběhu si konkurují a latence se nafoukne (glm median sekvenčně ~1,3 s → paralelně 3,2 s; jednotlivá qwen volání až 59 s). OpenRouter (haiku, gpt-nano) na vlastní infře skoro netknutý. Takže paralelní `test` = rychlý celkový průchod + spolehlivý pass/fail, ale per-model latenci u sdíleného providera nadhodnocuje; čistou latenci měřit izolovaně. Reasoning modely (qwen out=22671 tok ≈ jako in!, deepseek) jsou pomalé bez ohledu na kontenci. FAILy: gemma „příští pondělí" → `2026-06-09` (úterý) místo `06-08` = skutečná chyba data; zbytek false negatives (`pečení`/`pečeni`, `jít na trénink`/`trénink`, `mít poradu`/`porada`).
**Co zbývá:** Zdroje `src/MiniLoop/` (vč. této změny) stále necommitnuté — čeká na výslovnou výzvu uživatele. Souhrn čísel viz knowledge.md „MiniLoop".
## 2026-06-03 — MiniLoop: strop souběžnosti per provider + qwen→minimax
**Cíl:** Po paralelizaci se ollama modely při souběhu drasticky zpomalily (qwen volání až 59 s). Uživatel doplnil příčinu: jeho ollama předplatné povoluje **max 3 paralelní dotazy** ([ollama.com/pricing](https://ollama.com/pricing)) — se 4 ollama modely naráz 4. dotaz přeteče kvótu a čeká ve frontě, což se počítá do wall-clocku. Zároveň zahodit qwen3.5 (pomalý reasoning, out=22671 tok) a přidat minimax-m2.7:cloud.
**Co jsem zkusil / co fungovalo a proč:** Nejdřív jsem mylně tvrdil „víc spojení nepomůže, je to sdílený výpočet" — uživatel upřesnil, že jde o kvótu paralelních dotazů. To je správná diagnóza. Řešení: per-provider strop souběžnosti přes `SemaphoreSlim`. `Provider` record rozšířen o `int? MaxConcurrency` (Config.cs); `RunTest` z providerů se stropem postaví slovník semaforů a předá ho do `RunModel`. Nový helper `Parse(parser, gate, request)` získá slot `await gate.WaitAsync()` **před** voláním (tedy mimo stopky v `ParseAsync`, které obalují jen HTTP call) a uvolní ve `finally` → čekání na slot se nezapočte do měřené latence. config.example.json: ollama `maxConcurrency:3`, qwen→minimax. Reálný config.json (gitignored): uživatel ho mezitím sám protrimoval na 4 modely; po dohodě přidán jen minimax (→ 5 modelů: glm, deepseek, minimax, haiku, gpt-nano) + `maxConcurrency:3`. Build čistý.
Běh 5 modelů s gate=3:
| Model | Úspěšnost | Wall median / avg | Tokeny in / out |
|---|---|---|---|
| glm-5.1 (ollama) | 17/17 | 1690 / 1927 ms | 21118 / 2896 |
| deepseek-v4-flash (ollama) | 17/17 | 4894 / 6118 ms | 21654 / 3487 |
| minimax-m2.7 (ollama) | 17/17 | 4815 / 4604 ms | 21969 / 2317 |
| claude-haiku-4.5 (openrouter) | 16/17 | 1064 / 1135 ms | 23668 / 691 |
| gpt-5.4-nano (openrouter) | 17/17 | 3034 / 4003 ms | 20987 / 553 |
**Co fungovalo:** glm median spadl z 3240 ms (kontence) na 1690 ms, žádné 2060 s odlehlé hodnoty. minimax 17/17 (čistší než qwen). Pozn.: se 3 ollama modely strop (3) zatím nepřekáží — funguje jako pojistka, kdyby přibyl 4. ollama model. Gotcha: `dotnet run --project src/MiniLoop` z repo rootu nenajde `config.json` (relativní k cwd) → spouštět s `MINILOOP_CONFIG=src/MiniLoop/config.json` nebo z adresáře projektu.
**Co zbývá:** Zdroje `src/MiniLoop/` stále necommitnuté — čeká na výslovnou výzvu. config.example.json má pro ilustraci 6 modelů (vč. gemma4-31b), reálný config.json 5 (bez gemmy) — záměrný rozdíl (template vs. pracovní sada).
## 2026-06-03 — MiniLoop: test levných/OSS modelů z OpenRouteru
**Cíl:** Uživatel chtěl projít nabídku ollama cloud ([ollama.com/search?c=cloud](https://ollama.com/search?c=cloud)) + levné/OSS z OpenRouteru a vybrat, co stojí za vyzkoušení. Pak přidat vybrané do configu a pustit test jen na ně (ne přetáčet už známé). Mezitím uživatel sám upravil `Program.cs`: `test` teď bere výčet modelů nebo `all` (`test <model...> | test all`) — díky tomu jdou nové modely testovat samostatně.
**Co jsem zkusil / co fungovalo a proč:** Z OpenRouteru (katalog přes `curl https://openrouter.ai/api/v1/models`, filtr na OSS klíče + cena) vybráno 5 levných instruct (ne-thinking) modelů a přidáno do config.json (provider openrouter): `mistralai/mistral-small-3.2-24b-instruct`, `qwen/qwen3-30b-a3b-instruct-2507`, `google/gemma-3-27b-it`, `z-ai/glm-4-32b`, `openai/gpt-oss-120b`. Spuštěno `test mistral-small-3.2 qwen3-30b-a3b gemma-3-27b glm-4-32b gpt-oss-120b`.
| Model | Cena $/M | Úspěšnost | Wall median / avg | out tok |
|---|---|---|---|---|
| mistral-small-3.2 | 0.075/0.20 | 17/17 | 934 / 1050 ms | 610 |
| gemma-3-27b | 0.08/0.16 | 17/17 | 1413 / 1611 ms | 608 |
| glm-4-32b | 0.10/0.10 | 16/17* | 1905 / 2072 ms | 519 |
| qwen3-30b-a3b | 0.043/0.17 | 16/17* | 1991 / 1866 ms | 588 |
| gpt-oss-120b | levný | 16/17 | 7238 / 12164 ms | 3769 |
\* false negative (slovosled `se protáhnout`/`protáhnout se`; `pečení`/`pečeni`). gpt-oss got = expected (rozvrh sedí, JsonCompare škobrtl na formátu). Žádná skutečná chyba.
**Co fungovalo a proč:** `mistral-small-3.2` je nejlepší poměr ze všech dosud měřených (934 ms median, rychlejší než glm-5.1 1690 ms i haiku 1064 ms; 17/17; out 610 tok; cena pakatel) — potvrzená evropská/česká stopa Mistralu. `gemma-3-27b` těsně za ním. `gpt-oss-120b` jediný propadák — reasoning → 7 s a 6× víc out tokenů. Silné potvrzení teze o thinkingu: `qwen3-30b-a3b-instruct` 1991 ms vs cloud `qwen3.5` (thinking) 10852 ms + out=22671 — past byl režim thinking, ne qwen rodina.
**Co zbývá:** Zvážit prořezání gpt-oss-120b z config.json (propadák). Ollama cloud tipy k případnému pullnutí na nvidia.hell (netestováno): `gemini-3-flash-preview`, `gemma4:26b`, `nemotron-3-nano:30b`; už stažené netestované `nemotron-3-super`, `deepseek-v4-pro`. Souhrn čísel: knowledge.md „Levné / OSS modely z OpenRouteru".
## 2026-06-03 — MiniLoop: test ministral-3 a nemotron-3-nano přes ollama
**Cíl:** Uživatel chtěl vyzkoušet ještě `ministral-3` a `nemotron-3-nano` přes ollama (mezitím sám prořezal config.json na 5 modelů + nastavil mistral-small-3.2 jako interactive).
**Co jsem zkusil / co fungovalo a proč:** Oba mají `:cloud` variantu → žádný GB download, jen registrace pointeru na ollama cloud přes `curl POST http://nvidia.hell:11434/api/pull {"model":"…","stream":false}``{"status":"success"}`. Vybráno `ministral-3:8b-cloud` (8b = „latest", 3b by na CZ byl slabý) a `nemotron-3-nano:30b-cloud`. Přidáno do config.json (provider ollama), spuštěno `test ministral-3-8b nemotron-3-nano-30b`. **Vrácení zpět:** `curl POST /api/delete {"model":"…"}` nebo `ollama rm` na nvidia.hell.
| Model | Úspěšnost | Wall median / avg | out tok |
|---|---|---|---|
| ministral-3:8b-cloud | 15/17 | 1043 / 1169 ms | 653 |
| nemotron-3-nano:30b-cloud | 16/17 | 2015 / 2277 ms | 7805 |
**Výsledek — oba zavrženy, nepřekonaly mistral-small-3.2:** `ministral-3-8b` udělal skutečnou chybu dne v týdnu (`každý pátek` → cron `* * 6` sobota místo `* * 5`) — u připomínek vážné, na 8b znát; navíc není ani rychlejší než mistral-small (1043 vs 934 ms). `nemotron-3-nano-30b` má reasoning sklony (out=7805 tok ≈ 12× mistral), 2× pomalejší; jediný FAIL = rozsekání `1,3,5` na tři cron výrazy (rozvrh ekvivalentní, formát ne). Poznatek: pod ~24b instruct klesá spolehlivost cronu, malé „nano" buď chybují, nebo zbytečně reasonují.
**Co zbývá:** mistral-small-3.2 zůstává vítěz. ministral/nemotron-nano v config.json ponechány pro případné srovnání (lze prořezat). Souhrn: knowledge.md „Malé ollama modely".
## 2026-06-06 11:01 — Skill `/keep`: ukládání *why*, ne jen *what*
**Cíl:** Uživatel zaznamenal, že Claude memory.md vzor ukládá u feedback/project paměti i *why* (`**Why:**` / `**How to apply:**`), a ptal se, zda by `/keep` neměl dělat totéž. Cílem bylo minimalisticky upravit prompt skillu, aby why ukládal — ale jen kde to dává smysl.
**Co jsem zkusil / co fungovalo a proč:**
- Analýza: Claude vzor přidává why **jen podle typu** — feedback/project ano, reference/fakt ne. `/keep` je ekvivalent reference/fakt vrstvy (terse, auto-load každý tah přes USER.md referenci), why-vrstvou nanobotu je Dream → MEMORY.md. Bloat why ke každému řádku by zdvojnásobil trvalý token cost.
- Potvrzeno článkem Nick Babich „MEMORY.md for Claude Code projects" (UX Planet, 2026-05-28): bod #3 „Explain WHY, Not Just WHAT" + #5 „Record Failures and Dead Ends" — ale jeho příklad why (`Switched to Redis because…`) je **rozhodnutí**, ne fakt → potvrzuje úzkou variantu.
- Iterace promptu (3 kola): (1) přidán why-řádek do kroku 2 + mantinel do Rules, s 2 few-shot příklady (decision, dead-end). (2) Po dotazu na model: lokální modely zavrženy, reálně běží Ollama Cloud (kimi-k2.6, deepseek-v3.2, qwen, glm-5.1) + OpenRouter (sonnet/haiku/gemini) — silné modely → fakt-vs-rozhodnutí zvládnou zero-shot → **oba příklady odebrány**. (3) Přidáno: pokud je vstup rozhodnutí/dead-end **bez důvodu**, model se **jednou doptá** na why (decline/self-evident → ulož bez něj).
- Finální změny v `skills/keep/SKILL.md`: krok 2 Write protokolu (3 pravidla pro why) + 1 mantinel v Rules. Žádná změna formátu souboru (pořád 1 řádek = 1 záznam), why jen u rozhodnutí/preferencí/dead-endů.
**Nasazení:** ověřeno server == lokál HEAD před přepisem (žádná Dream úprava nezahozena) → `rsync -av skills/keep/ nanobot@nanobot.hell:/home/nanobot/.nanobot/workspace/skills/keep/`. Vlastník `nanobot:nanobot`, příklady ověřeně pryč. Skilly se čtou každý tah bez restartu.
**Co zbývá:** Sledovat, jestli silné modely doptávání na why nepřehánějí (riziko nagování u hraničních „rozhodnutí"). Pokud ano → zúžit trigger nebo doplnit 1 negativní příklad.
## 2026-06-06 11:16 — Nový skill `python` (coding conventions)
**Cíl:** Dát nanobotovi pravidla pro práci s Pythonem — uv tool, type hints, linter/formatter, PEP + clean code.
**Co jsem zkusil / udělal:**
- Založen `skills/python/SKILL.md`. Tělo vychází z `.claude/rules/python.md` (použito celé) + doplněna sekce **Tooling** (uv, `ruff format`, `ruff check --fix`, definice „done").
- Z `.claude/rules/clean-code.md` přidány jen body zvyšující čitelnost/strukturu: boolean flag args, CQS, param count 03, fail fast / guard clauses, max zanoření, magic numbers → konstanty. Designové body (composition, DI, immutability) vynechány.
- Na žádost uživatele vyházeny věci, které modely dělají samy: casing (snake_case/PascalCase), 4-mezery, f-stringy, řazení importů. Naming + Imports sekce zrušeny, Style zúžen na line-length 120.
- `description` schválně vágní („Use for anything involving Python code") — ať se skill aktivuje skoro vždy.
**Co fungovalo a proč:** `rsync -av skills/python/ nanobot@nanobot.hell:/home/nanobot/.nanobot/workspace/skills/python/`. Vlastník `nanobot:nanobot` ověřen. Skilly se čtou každý tah bez restartu.
**Co zbývá:** Sledovat, jestli vágní description nezpůsobuje načítání i mimo Python kontext (přijatelné — „lepší načíst zbytečně než minout").
## 2026-06-06 11:20 — Nový skill `bash` (shell conventions)
**Cíl:** Stejně jako `python` — převést `.claude/rules/bash.md` na nanobotí skill.
**Co jsem udělal:**
- Založen `skills/bash/SKILL.md` z `.claude/rules/bash.md`.
- Vyházeno: Naming sekce (casing — modely trefují samy), `[[ ]]` vs `[ ]` (default), zmínka o `check-bash.sh` hooku (hook tohoto repa, na serveru irelevantní), `paths:` frontmatter (Claude Code feature).
- Ponecháno to, kde modely v bashi reálně chybují / projektový standard: strict mode, double-quoting, local/readonly + návrat přes stdout, arrays vs IFS, `command -v` místo `which`, stderr, mktemp+trap, script_dir idiom, shellcheck, hook exit kódy 0/2.
- `description` vágní, ať se aktivuje široce.
**Co fungovalo:** `rsync -av skills/bash/ nanobot@nanobot.hell:/home/nanobot/.nanobot/workspace/skills/bash/`. Vlastník `nanobot:nanobot` ověřen.
**Co zbývá:** nic.
## 2026-06-07 — Měření rychlosti glm-5.1 vs minimax-m3 (Ollama nativní streaming)
**Cíl:** Tvrdá data na dotaz „je minimax línej?" — porovnat time-to-first-token, rychlost generování a celkovou dobu odpovědi mezi `glm-5.1:cloud` a `minimax-m3:cloud` na reálné úloze typu `/remind list`.
**Co jsem zkusil:**
1. **Journal nestačí.** `journalctl --user -u nanobot` loguje jen `Processing message → Tool call → Response` (časové značky). **Žádné počty tokenů, žádné tok/s, žádný TTFT.** Wall-clock z journalu navíc míchá běh nástrojů (`uv run` cold-start ~27 s) a víc kol tool-callů → na rychlost modelu nepoužitelné. (Z journalu jen kontext: na `minimax-m3` přepnuto poprvé 2026-06-05, dnes 09:03; jeden reálný `/remind list` pod m3 trval wall 37 s včetně 2 tool-callů.)
2. **Ollama `:cloud` nativní timing fields jsou `None`** (`eval_duration`, `prompt_eval_duration`, `load_duration`) — výpočet jede přes Ollamin cloud, lokální server na `nvidia.hell` je nezná. Vrací jen `eval_count` (out tok) + `total_duration`.
3. **Řešení = streaming `/api/chat`.** Skript `/tmp/bench_ollama.py` (na serveru): TTFT = čas prvního content chunku, generace = `eval_count / (total TTFT)`, identický prompt (`/remind list` formátování 8 položek do češtiny), 3 běhy/model.
**Naměřeno (per běh):**
| Model | TTFT | Total wall | Out tok | Generace* |
|---|---|---|---|---|
| glm-5.1 | 5,50 / 7,78 / 5,93 s | 6,43 / 8,83 / 7,15 s | 1209 / 1729 / 1511 | ~1290 / 1650 / 1240 tok/s |
| minimax-m3 | 5,98 / 7,25 / 6,75 s | 9,76 / 11,36 / 11,28 s | 457 / 419 / 428 | ~121 / 102 / 95 tok/s |
\* `eval_count / (total TTFT)`.
**Co fungovalo a proč:**
- **TTFT srovnatelný (~67 s u obou)** — minimax neprohrává startem, oba mají podobný cloud cold start.
- **Celková doba: minimax ~50 % delší** (medián ~10,8 s vs ~7,5 s glm), a to **přestože generuje 34× MÉNĚ tokenů** (~430 vs ~1480). To je hlavní důkaz „lenosti" minimaxu.
- **Generace tok/s:** minimax **~95120 tok/s je čisté číslo** (streamuje token po tokenu). GLM hodnotu „1300 tok/s" NEbrat doslovně — je to **artefakt cloud bufferu** (glm flushne odpověď v dávce po TTFT). Robustní srovnatelná metrika je **end-to-end průtok = out/total: glm ~198 tok/s vs minimax ~40 tok/s → glm ~5×.**
**Závěr:** Intuice potvrzena tvrdě — **minimax-m3 je výrazně línější**: pomalý decode (~100 tok/s) + delší celková doba i u kratšího výstupu. Na interaktivní úlohy je glm-5.1 jednoznačně svižnější. Pozn.: glm je upovídaný (12001700 tok vs ~430), ale i tak hotový dřív.
**Jak vrátit zpět:** nic se na serveru nemĕnilo (jen read-only měření + dočasný `/tmp/bench_ollama.py`).
**Co zbývá:** nic.
---
## 2026-06-07 09:54 — Znalostní báze modelů pro nanobot agenta (workspace/knowledge/)
**Cíl:** Dát nanobot agentovi na serveru znalostní bázi o modelech, ze které může těžit při dotazech typu „jaký model na kódování / na zpracování fotky" — fakta a naměřené hodnoty, žádná hotová doporučení (volba je na agentovi).
**Co jsem zkusil:**
- Vytáhl reálné presety ze serverového `config.json``model_presets` (7 presetů: glm-5.1 default, minimax-m3, kimi-k2.6 na ollama; sonnet, haiku na openrouter; gemini-flash na gemini; gemini-flash-lite na openrouter). Liší se od lokálního `models.md`, který řešil jen 3 Ollama Cloud modely.
- Konsolidoval do `knowledge/models.md` (CZ, facts-only): tabulka presetů (provider, model id, kontext, multimodalita), přímé měření rychlosti na Ollama Cloud (glm ~198 tok/s vs m3 ~40 tok/s, m3 o ~50 % pomalejší), AA profil verbozity, rozdíly ve schopnostech (multimodál, HLE, kódování, dlouhý kontext), caveaty (vendor vs produkce, prompt caching jen openrouter/anthropic/bedrock, ollama kvóta 3 paralelní), kontextová okna, appendix s MiniLoop měřeními (jasně označeno jako jiný kontext, ne agent presety).
- `knowledge/README.md` jako rozcestník (vzor podle `develop/README.md`).
- Napojení: přidána sekce `## Knowledge base` do `AGENTS.md` (bootstrap, čte se každý tah) odkazující na `knowledge/README.md` „read on demand" — stejný vzor jako existující odkaz na `develop/README.md`.
- Kanonická kopie v repu `knowledge/`; nasazeno `rsync`em do `~/.nanobot/workspace/knowledge/`. AGENTS.md staženo, upraveno lokálně, posláno zpět.
**Co fungovalo a proč:** Vlastník na serveru `nanobot:nanobot`, ověřeno. `knowledge/` se čte bez restartu (bootstrap soubory i workspace soubory čte agent při tahu). AGENTS.md napojení znamená, že agent o bázi ví a sáhne tam on-demand.
**Jak vrátit zpět:** `ssh nanobot@nanobot.hell 'rm -rf /home/nanobot/.nanobot/workspace/knowledge'` + smazat sekci `## Knowledge base` z `~/.nanobot/workspace/AGENTS.md`.
**Co zbývá:** nic. Případně až nanobot bázi reálně použije, ověřit, že odpovídá rozumně.
**Oprava (09:5x):** Sekce `## Knowledge base` v `AGENTS.md` byla moc konkrétní (vyjmenovávala modely). Zobecněna — `knowledge/` je obecná báze, modely uvedeny jen jako příklad; konkrétní výčet zůstává v `knowledge/README.md` (read on demand).
---
## 2026-06-07 13:14 — Detach: volitelný výběr modelu pro background task
**Cíl:** Umožnit, aby detached task běžel na explicitně zvoleném modelu (jiném než default) — background = latence nebolí, vyplatí se silnější model. Když se model neuvede → default (zpětně kompatibilní).
**Co jsem zkusil:**
- Ověřil v upstreamu (`tmp/nanobot-upstream`), že `Nanobot.run()` model override nepřijímá, ale `AgentLoop.set_model_preset(name)` existuje (přesně to dělá `/model <preset>` v chatu) a hodí `KeyError` na neznámý preset.
- Zamítl variantu „zdědit aktivní model na kanálu": `/model` přepnutí je in-memory, globální (ne per-kanál), neperzistuje a daemon (samostatný proces) ho nevidí; šlo by jen patchem upstream `_build_runtime_context` → fork balíčku. Příliš drahé. Rozhodnutí s uživatelem: **explicitně v požadavku, jinak default**; resolve názvu **fuzzy proti `config.json`**.
- `tasks_common.py`: `load_preset_names()` (čte `modelPresets` i `model_presets` — server má na disku snake_case), `resolve_preset(token, names)` (exact CI → unikátní substring → `KeyError` not found / ambiguous), `build_task_content(..., model=…)` přidá frontmatter řádek `model:` jen když je zadán. `format_result` ukáže `model:` v meta řádku.
- `create-task.py`: `--model` (optional) → resolve při captue, na `KeyError` vypíše hlášku na stderr a `return 1` (fail-fast, nic nezapíše).
- `tasks-daemon.py`: `preset = fm.get("model")`, `run_agent(..., preset)``bot._loop.set_model_preset(preset)` před `run()`. Log `START … preset=<x|default>`.
- Dokumentace: `SKILL.md` (krok „Optional model" + failure handling pro unknown/ambiguous), `architecture.md` (frontmatter `model:`, resolve při captue, daemon switch). Testy: +9 v `tests/test_tasks_common.py` (43 passed).
- Nasazeno `rsync`em na server, vlastník `nanobot:nanobot` ověřen.
**Co fungovalo a proč:**
- Resolve proti reálnému serverovému configu: 7 presetů (`gemini-flash, gemini-flash-lite, glm, haiku, kimi, minimax, sonnet`), `kimi→kimi`, `flash-lite→gemini-flash-lite`, `gemini→ambiguous`, `nonsense→not found`.
- Fail-fast: `create-task.py --model nosuchmodel` → exit 1, hláška se seznamem, `inbox/` beze změny.
- **E2e:** task `model-test-kimi` (`--model kimi`, goal „reply PONG") doběhl za 3 s, log `START … preset=kimi`, výsledek `PONG`, `read-task` ukázal `model: kimi`. Tím ověřeno, že `bot._loop.set_model_preset` funguje i s **nainstalovaným** balíčkem nanobota (ne jen v upstream klonu). Testovací task zarchivován.
- Gotcha potvrzen: serverový `config.json` má top-level klíč **`model_presets` (snake_case)**, ne camelCase; `agents.defaults.modelPreset` je naopak camelCase. Proto `load_preset_names` čte oba tvary — nebýt toho, vracelo by prázdno a každý `--model` by failoval.
**Jak vrátit zpět:** přenasadit předchozí verzi skillu ze staré git revize: `git checkout <rev> -- skills/detach` + `rsync -av --exclude __pycache__ skills/detach/ nanobot@nanobot.hell:/home/nanobot/.nanobot/workspace/skills/detach/`. Bez `--model` se chování nemění, takže rollback není urgentní.
**Co zbývá:** Návrh do `decisions.md` (load-bearing) — čeká na souhlas autora. Zvážit zvednutí 15-min timeoutu u explicitně zvoleného „heavy" modelu (M3/Kimi jsou pomalé/verbózní) — samostatné rozhodnutí.
---
## 2026-06-07 13:xx — Detach: zvednut timeout 15 → 45 min
**Cíl:** Background tasky na silnějších modelech (M3/Kimi — na Ollama Cloud pomalé/verbózní) můžou u hlubokého researche přerůst 15 min. Uživatel nemá v příkazu řešit timeout, tak zvednout globální strop.
**Co jsem zkusil:**
- Ověřil, že systemd `tasks-daemon.service` nemá vlastní start-timeout: `Type=oneshot``TimeoutStartUSec=infinity`, `RuntimeMaxUSec=infinity` (`systemctl --user show`). Jediný strop je tedy Python `asyncio.wait_for(TIMEOUT_SECONDS)`.
- `TIMEOUT_SECONDS = 15*60 → 45*60` v `tasks-daemon.py`; sjednoceny zmínky „15 min" v docstringu, `architecture.md`, `knowledge.md`.
- Nasazeno `rsync`em, vlastník `nanobot:nanobot`, konstanta na serveru ověřena. Bez restartu (oneshot = nový proces na každý běh).
**Co fungovalo a proč:** Timeout je čistě pojistka proti zaseknutému běhu — reálný task doběhne dřív; delší strop nic nestojí kromě toho, že zaseknutý task déle drží frontu (u single-user low-volume OK). 45 min dává headroom pro deep research na silném modelu.
**Jak vrátit zpět:** v `tasks-daemon.py` zpět na `15 * 60` + rsync.
**Co zbývá:** nic. Sledovat, zda 45 min stačí / nepřehání.
---
## 2026-06-07 17:51 — Měření rychlosti glm-5.1 vs minimax-m2.7 (Ollama nativní streaming)
**Cíl:** Na dotaz „jak je m2.7 rychlý oproti glm-5.1" dát tvrdá apples-to-apples data — m2.7 zvažovaný jako náhrada za m3 (BLOCKED, viz knowledge.md). Předchozí streaming benchmark (téhož dne) měřil jen m3, ne m2.7.
**Co jsem zkusil:**
1. `/api/tags` na `nvidia.hell` ukázal jen `minimax-m3:cloud` (m2.7 z katalogu zmizel — Ollama Cloud ho nahradila m3). `POST /api/pull {"model":"minimax-m2.7:cloud"}` ale vrátil `{"status":"success"}` → cloud pointer **jde dotáhnout**, jen není v defaultním seznamu.
2. Streaming `/api/chat` benchmark (`/tmp/bench_ollama2.py`), identický `/remind list` prompt (8 položek do češtiny), **5 kol prokládaně** (každé kolo glm pak m2.7, ať sdílí stejnou zátěž cloudu) + mediány. TTFT = 1. content chunk; e2e = `eval_count / total`.
**Naměřeno (mediány, 5 kol):**
| Model | TTFT | Total wall | Out tok | e2e (out/total) |
|---|---|---|---|---|
| glm-5.1 | 15,4 s | 18,3 s | 1558 | **~91 tok/s** |
| minimax-m2.7 | 9,2 s | 11,3 s | 351 | **~28 tok/s** |
**Co fungovalo a proč:**
- Absolutní čísla nafouklá oproti rannímu m3 měření (glm TTFT 15 s vs ~6 s ráno) — cloud byl vytížený. Proto bráno jen jako **poměr v rámci běhu**, prokládání kol drží srovnatelnost.
- **Decode m2.7 ~3× pomalejší než glm** (~28 vs ~91 tok/s e2e) — dokonce **horší než m3** (~40 tok/s). Líný decode je rys celé MiniMax řady, m2.7 to nezlepšil.
- **m2.7 doběhne dřív ve wall-clocku** (11 vs 18 s) **jen díky terseness** — vygeneroval ~4,5× méně tokenů (351 vs 1558). glm je upovídaný, ale per-token mnohem rychlejší. Konzistentní s MiniLoop 2026-06-03 (m2.7 4815 ms vs glm 1690 ms, tam podobné token county → glm vyhrál decodem).
**Závěr:** m2.7 není pro agenta rychlostní výhra — pomalý decode bolí u delších výstupů (tool args, kód). Wall-clock výhoda platí jen pro krátké odpovědi. Pro background deep-research drž glm-5.1 / Kimi.
**Jak vrátit zpět:** read-only měření + dočasný `/tmp/bench_ollama2.py`. Na ollamě přibyl pointer `minimax-m2.7:cloud` (`ollama rm minimax-m2.7:cloud` na `nvidia.hell` ho odebere, pokud nemá zůstat).
**Co zbývá:** ověřit m2.7 v reálném detach běhu (jestli neskončí ve smyčce jako m3).
---
## 2026-06-07 18:26 — Detach `list`: odrážkový výpis místo markdown tabulky + reconciliace driftu
**Cíl:** `/detach list` na minimax-m2.7 rozsypal formát — Running se vyrenderoval jako tabulka, Done/Failed jako syrové `| … |` roury (na GLM OK). Příčina: výstup relayuje LLM token po tokenu a markdown tabulka má dálkovou závislost (hlavička + `|---|`); slabý model u 2./3. sekce hlavičku zahodí. Cíl = formát robustní vůči LLM relay.
**Co jsem zkusil:**
- `render_table``render_list` v `tasks_common.py`: dvouřádková odrážka na úkol (slug · čas · stáří + odsazený popis), žádná tabulková gramatika. Popis se vynechá, když chybí `# Goal`. JSON zamítnut — jen posouvá křehkost na model, který ho převádí pro člověka.
- `list-tasks.py` přepnut na `render_list`; `SKILL.md` `list` zpřísněn na „output verbatim, do not convert to a table".
- Testy přepsány (`test_render_list_*`, +`omits_goal_line_when_empty`) → **57 passed**. Lokální render proti dočasnému workspace ověřil starý i nový timestamp, soubor bez Goalu i neparsovatelný filename.
- **Drift na serveru** (uživatel varoval): nanobot si sám opravil `NameError` v `tasks-daemon.py` — repo volá `LOG.parent.mkdir` na ř. 152, ale `LOG` nebyl importován (zaneseno minulou session). Server záplatoval lokálním `from tasks_common import LOG as _LOG`. Dotaženo do repa čistší formou — `LOG` přidán do top-level importu (ř. 37), což serverový workaround subsumuje.
- Deploy `rsync`em, vlastník `nanobot:nanobot`, deployed `list-tasks.py` ověřen proti reálným serverovým taskům (odrážky, truncation `…` na 80 zn., žádné `|`).
**Co fungovalo a proč:** Odrážkový seznam přežije relay, protože každý `- …` řádek stojí sám — není hlavička, jejíž ztráta rozbije vše pod ní. Je to zároveň drátový i zobrazovací formát, takže odpadá transformační krok, kde slabý model selhával.
**Jak vrátit zpět:** `git revert` commitu se skill změnami + `rsync` repa zpět na server (vrátí i `render_table`). Pozn.: vrácení daemon importu by znovu zavedlo `NameError` — opravu importu `LOG` ponechat.
**Co zbývá:** end-to-end potvrzení v chatu na minimax-m2.7 i GLM (uživatel ověří interaktivně).
---
## 2026-06-07 19:33 — tasks-daemon zaseknutý v start-limit-hit + hardening retry
**Cíl:** Uživatel hlásil problém s user systemd službami `tasks-daemon.path` / `tasks-daemon.service`. Zjistit příčinu, navrhnout fix, nasadit po schválení.
**Co jsem zkusil / zjistil:**
- `systemctl --user status` → obě unity `failed`, `.service` s `Result: unit-start-limit-hit`, „Start request repeated too quickly".
- `stderr.log` + journal: v 18:19 daemon 5× po sobě spadl na `NameError: name 'LOG' is not defined` (`tasks-daemon.py:152`) → za <2 s vyčerpal default rate-limit (`StartLimitIntervalSec=10s`, `Burst=5`) → systemd zalatchoval **`.service` i `.path`**.
- Časová osa: do 17:29 OK (4× done); 17:44 task na `minimax`; **18:19:14 SIGTERM (status=143)** — žádný timeout v unitu (předchozí běhy i 1553 s doběhly), takže externí stop (deploy fixu); 18:19:3638 `.path` (level-triggered, inbox neprázdný) hned restartoval → 5× NameError → latch.
- **Klíčové:** ten `NameError` už byl opravený předchozí session (import `LOG` na ř. 37, viz záznam výše) a fix byl na serveru nasazený. Ruční test daemonu s korektní PATH → **`exit=0`**. Příčina pádu pryč; jediný zbytek = systemd zaseknutý ve `failed`, který se sám nezotaví.
**Co fungovalo a proč:**
- **A — recovery:** `systemctl --user reset-failed tasks-daemon.service tasks-daemon.path` + `restart tasks-daemon.path``.path` zpět `active (waiting)`. Inbox prázdný, nic se hned nespustilo.
- **B — orphan:** smazán `failed/2026-06-07_18_28_43_…-gemini.md` (vznikl 18:28 po latchi, nikdy nezpracován — bez `## Result` sekce).
- **C — hardening unitu** (`skills/detach/systemd/tasks-daemon.service`): přidáno `Restart=on-failure` + `RestartSec=60` (delay mezi pokusy po pádu; čistý exit 0 i SIGTERM od systemd nerestartují) a `StartLimitIntervalSec=1800` + `StartLimitBurst=20` (až 20 pokusů / 30 min, pak pauza + auto-resume jak okno klouže — už žádný permanentní latch ani ruční `reset-failed`). Doc ověřena: `man systemd.service` — pro `Type=oneshot` jsou zakázané jen `Restart=always/on-success`, `on-failure` povolený.
- Deploy `rsync`em jen `systemd/` podadresáře (změna izolovaná do unitu), `daemon-reload`, vlastník `nanobot:nanobot`. `systemctl show` potvrdil `Restart=on-failure / RestartUSec=1min / StartLimitIntervalUSec=30min / StartLimitBurst=20`.
**Jak vrátit zpět:** `git revert` commitu se změnou `tasks-daemon.service` + `rsync` `systemd/` zpět na server + `daemon-reload`. Recovery (A) a smazaný orphan (B) se nevrací.
**Co zbývá:** End-to-end potvrzení (nový detach task projde `.path` → drain → done) — uživatel ověří interaktivně; daemon na prázdném inboxu už ověřen (`exit=0`).
---
## 2026-06-10 07:35 — git cleanup: přestat trackovat gitignored soubory na serveru
**Cíl:** `.gitignore` v serverovém repu (`~/.nanobot/workspace`) přibyl pozdě — `__pycache__/*.pyc` a `MEMORY.md.bak` se dostaly do gitu dřív, takže je ignore neřešil a pořád byly trackované. Vyndat je z gitu, nechat na disku.
**Co jsem zkusil:**
- `git ls-files -i -c --exclude-standard`**11 souborů** trackovaných i přes `.gitignore`: 10× `*.pyc` v `__pycache__/` (skills `detach`/`remind`) + `memory/MEMORY.md.bak`.
- `git ls-files -i -c --exclude-standard -z | xargs -0 git rm --cached` → odstranění z indexu, soubory na disku zůstaly (ověřeno `ls`).
- Commit `f93c1cf` jen s těmito removaly (explicitní pathspec) — repo mělo rozdělanou autonomní práci (Dream procesor: `cron/jobs.json`, `memory/history.jsonl`, skripty `remind/`), té se commit nedotkl. Po commitu `git ls-files -i -c --exclude-standard` = **0**.
**Co fungovalo a proč:** `.gitignore` ignoruje jen *netrackované* soubory; co už je v indexu, musí ven přes `git rm --cached` (smaže z indexu, nechá na disku). Od teď se nové `__pycache__/` už necommitují.
**Jak vrátit zpět:** `git revert f93c1cf` (znovu je začne trackovat).
---
## 2026-06-10 07:55 — Revize přepsaného remind skillu (YAML → SQLite)
**Cíl:** Uživatel nechal nanobota přepsat `/remind` skill z `reminder.yaml` na SQLite. Zkontrolovat výsledek, posoudit funkčnost, otestovat, navrhnout změny (vč. textu SKILL.md). Nic neměnit bez souhlasu.
**Co jsem zkusil:**
- Stáhl serverový skill (`rsync`) do `tmp/server-remind/`, přečetl `SKILL.md`, `db.py`, `remind_edit.py`, `remind_send.py`, `random_times.py` + všechny testy.
- Ověřil reálný stav serveru: schema `reminders.sqlite` (nové: `days_filter/from_date/until_date`), 14 reálných připomínek migrováno (25 `at`, 6 cron, 9 random), `reminder_fires`=0.
- Pustil test suite na serveru: **43 passed**.
- Read-only kontrola deployed senderu proti **kopii** prod DB (`REMIND_DB`, bez odeslání) — všech 9 random se spočítá bez výjimky, nic chybně due.
- Live test doručení: `remind_edit.py add --at` na +2 min (id=15), ověřeno `reminder_fires` zápis `('at', delivered, 07:54:01)` + DELIVER v logu + reálný příchod na Telegram (potvrdil uživatel), pak `remove --keyword` (soft-delete).
**Co fungovalo a proč:** Přechod na DB je funkčně nasazený a doručování jede. Nalezené defekty:
- **P1 schedule_type collision:** `schedule_{at,cron,random}` mají vlastní AUTOINCREMENT id → překryv (at 125, cron 16, random 19). UNION-ALL inference v `remind_send.main()` označí každý cron/random fire jako `'at'``reminder_fires.schedule_type` špatně + dedup pro cron/random nefunkční (maskuje jen 60s tolerance). Fix: každá `_due_*` vrací svůj typ.
- **P1 stale bootstrap:** serverové `AGENTS.md` + `TOOLS.md` pořád mluví o `reminder.yaml` a starém formátu `reminder.log` (auto-load každý tah).
- **P2 log regrese:** `log_operation` píše UTC + míchá ADD/EDIT/…/DELIVER; `TOOLS.md` „co dnes přišlo“ čeká Prague-time delivery-only.
- **P3:** mrtvý `import yaml`/pyyaml dep, ignorovaný sloupec `timezone`, hardcoded `CHAT_ID`, duplicitní text neřešitelný přes keyword (chybí `--id`), `__import__("datetime")`, `--replace-schedules` bez schedule → němá připomínka.
- **SKILL.md text:** matoucí „python3 required“, chybí instrukce odpovídat jazykem uživatele a jak řešit read-back doručení.
- **Repo desync:** lokální `skills/remind/` je pořád YAML verze + `IMPROVEMENTS_REPORT.md` + `reminder.example.yaml`; `knowledge.md` /remind sekce neplatná.
**Co zbývá:** Uživatel odsouhlasil rozsah oprav (P1 schedule_type, P1 bootstrap docs, P3 cleanupy, repo+knowledge sync); P2 (reminder_fires dotaz vs. čistý delivery-log) ještě nerozhodnuto.
**Jak vrátit zpět:** Test připomínka (id=15) už odstraněna (soft-delete). Žádná jiná změna na serveru neproběhla.
---
## 2026-06-10 08:10 — Implementace oprav remind skillu (po odsouhlasení)
**Cíl:** Provést odsouhlasené opravy z revize výše: P1 schedule_type, P1 bootstrap docs, P2 read-back přes reminder_fires, P3 cleanupy, repo+knowledge sync.
**Co jsem zkusil / udělal:**
- **remind_send.py:** každá `_due_{at,cron,random}` vrací `schedule_type`; smazána chybná UNION-ALL inference v `main()`. Odstraněn mrtvý `import yaml` + dep `pyyaml`. `CHAT_ID``_telegram_config()` čte `channels.telegram.allowFrom[0]` z configu, fallback konstanta.
- **remind_edit.py:** helper `_resolve_one` (výběr přes `--id` nebo `--keyword`, ambiguous vypíše ids); `--id` přidáno k remove/edit/enable/disable; guard na `--replace-schedules` bez nového schedule; nový subcommand `delivered [--since]` (čte `reminder_fires`, Prague time); `from datetime import date` místo `__import__`; dep `croniter` only.
- **SKILL.md:** instrukce odpovídat jazykem uživatele, dokumentace `delivered` + `--id` + duplicit, oprava matoucího Environment.
- **Testy:** +5 (schedule_type collision, --id disambiguace, resolve vyžaduje id/keyword, replace-schedules guard, delivered) → **48 passed** lokálně i na serveru.
- **Nasazení:** ověřeno, že server skill mezitím nikdo nesáhl (diff = jen mé změny), `rsync` na server, owner `nanobot:nanobot`, server pytest 48 OK, smoke `list`+`delivered` proti reálné DB OK. `delivered` ukázal reálné doručení „Panama" 08:02 — potvrdilo, že stará verze zapsala random odpal jako `schedule_type='at'` (P1 bug v praxi).
- **Bootstrap:** `AGENTS.md` + `TOOLS.md` na serveru — `reminder.yaml` → SQLite `db/reminders.sqlite`, sekce o `reminder.log` přepsána na `delivered`/`reminder_fires`. Push ověřen.
- **Repo:** `skills/remind/` synced z deploye, smazány `reminder.example.yaml` + `IMPROVEMENTS_REPORT.md` (commit `b244c01`). `knowledge.md` /remind sekce přepsána.
**Co fungovalo a proč:** Skilly se čtou bez restartu (exec subprocess + bootstrap fresh každý tah), takže fix je živý okamžitě. Dedup pro cron/random teď reálně funguje (správný schedule_type), ne jen díky 60s toleranci.
**Co zbývá:** Jeden historický řádek `reminder_fires` (reminder 7, 08:02) má pořád `schedule_type='at'` z bugu — oprava přes přímý prod UPDATE byla blokována auto-classifierem (neautorizovaný prod write), čeká na svolení uživatele. Kosmetické (znovu se neodpálí). Kandidáti do `decisions.md` (čekají na přeformulování autorem): úložiště = SQLite; read-back i audit = reminder_fires.
**Jak vrátit zpět:** Skill: `git revert b244c01` + rsync zpět na server. Bootstrap: serverové AGENTS.md/TOOLS.md vrátit na `reminder.yaml` formulaci (DB ale existuje, takže to nedává smysl). DB schema/data beze změny.
**Dodatek (08:13):** Po svolení uživatele opraven zaseknutý audit řádek — `UPDATE reminder_fires SET schedule_type='random' WHERE reminder_id=7 AND fire_time='2026-06-10T08:02:00'` (1 řádek). Tím je audit konzistentní s fixem.
---
## 2026-06-10 08:50 — Code review remind skillu + cleanup (P1+P2+P3)
**Cíl:** Detailní revize skillu `/remind`, ověřit shodu lokál↔server, navrhnout a po schválení provést zlepšení (čistota, čitelnost, normy).
**Co jsem zkusil / udělal:**
- **Review:** stáhl serverovou verzi, `diff` všech 5 skriptů + SKILL.md → **lokál identický se serverem**. Nálezy rozděleny P1/P2/P3, plán odsouhlasen uživatelem (rozsah: vše; `list` nemá vracet JSON).
- **P1 — korektnost:** `cmd_list` vypisoval **pozici** `{idx}.`, ne skutečné DB `id` → agent z `list` četl špatné číslo pro `--id`. Přepsáno na čitelný formát `#<id> text [status]` + odsazené schedule řádky, HH:MM okno, prázdný store → `(no active reminders)`. Helper `_schedule_lines`. SKILL.md popisuje nový formát místo neexistujícího „JSON". `db.log_operation(details: str)``str | None` (volá se s `None`).
- **P2 — struktura:** `cmd_edit` validace (prázdný text, `_build_random`) přesunuta **před** `BEGIN` (žádná otevřená transakce na early-return). Duplicitní `_parse_window`/`_hhmm_to_minutes` v `remind_edit.py` smazány → reuse `parse_window` z `random_times.py` (publikováno odebráním podtržítka). `_build_random` se volá jen jednou — `_insert_schedules` dostává hotový `random_cfg`.
- **P3:** `remind_send._now``_now_prague() -> datetime` (konec kolize s `remind_edit._now`, který vrací str); `_telegram_config()` čteno jednou v `main()` + early-return na prázdné `due`; `cmd_delivered` f-string SQL → dvě parametrizované query; `_find_by_keyword` escapuje LIKE wildcardy + `ESCAPE`; shebang obou skriptů → `uv run --script`. Testy: odstraněn nepoužitý `capsys`, `_run_send` korektně zachytává/obnovuje funkce, +3 testy (random `days_filter` e2e, retry po failed fire, `delivered` default „dnes").
- **Verifikace:** lokálně **51 passed**. Deploy `rsync` celý adresář, owner `nanobot:nanobot`. Server pytest **51 passed**. Smoke produkční `list` (reálná id, nový formát), `_due_random` dotaz proti reálné DB OK (9 random reminderů). Ověřeno, že produkční schéma `schedule_random``days_filter/from_date/until_date`.
**Co fungovalo a proč:** Skilly se čtou bez restartu. `reminder_cron.log` mtime 07:36 (před deployem) = od deploye crontab sender nezapsal žádnou novou chybu → běží čistě. Staré traceby v logu pochází z dávno mrtvé verze (`_process_reminder`, sloupce `days/start_date/end_date`, `ROLLBACK` v main) — irelevantní.
**Co zbývá / gotcha:**
- **`log_operation` ignoruje `REMIND_DB`** — píše vždy do reálného `workspace/log/reminder.log` přes `__file__`-relativní cestu. Spuštění test suite **na serveru** proto zapsalo 24 fixture řádků (timestamp `2026-06-10T06:47:32`) do reálného logu. **Poučení: testy spouštět jen lokálně.** Úklid logu (odstranit 24 řádků + truncate staré traceby v `reminder_cron.log`) byl blokován auto-classifierem (neautorizovaný prod write) — čeká na svolení uživatele.
**Jak vrátit zpět:** `git revert <commit>` skillu + rsync předchozí verze na server. DB schema/data beze změny.
## 2026-06-10 09:50 — Remind skill úklid: rename `remind_edit.py`→`remind_cli.py`, edit-guidance, hybrid SKILL.md
**Cíl:** Tři papercuts najednou (jeden deploy): (1) zavádějící název `remind_edit.py` (dělá i `list`/`delivered`, read-only); (2) agent při opravě textu maže+vytváří záznam znovu, místo `edit --text` (skript to **už uměl**, jen SKILL.md k tomu nenaváděl); (3) SKILL.md duplikoval výčet flagů s argparse → drift. Plán: [plans/](plans/) (schválen v plan mode).
**Co jsem zkusil / udělal:**
- **A. Rename** `git mv remind_edit.py → remind_cli.py` + `test_remind_edit.py → test_remind_cli.py` (import + `remind_cli.*` + argv[0]). Cross-importy nedotčené (`remind_send` importuje jen z `db`/`random_times`). **Crontab nezměněn** — spouští `remind_send.py`.
- **B. Dedup:** `_minutes_to_hhmm` byl doslova zkopírovaný v cli i send → přesunut do `random_times.py` jako veřejné `minutes_to_hhmm`, obě kopie smazány, importy upraveny.
- **C. Edit-guidance + test:** SKILL.md explicitně „oprava textu = `edit --id <n> --text`, NIKDY remove+add"; nový test `test_edit_text_by_id` (pokrývá dřív netestovanou `--id` větev editace).
- **D. SKILL.md → hybrid:** zachován NL→příkaz mapping + behaviorální kontrakt (formát `list`, JSON mutace, keyword/id ambiguous, `delivered`); vyčerpávající výčet flagů → `<cmd> --help`. Řeší drift z [memory.md](memory.md).
- Lokální změny delegovány na sonnet agenta; deploy + verifikaci řídil opus.
**Co fungovalo a proč:**
- Lokálně **52 passed** (51 + nový test).
- Server = zdroj pravdy: `rsync` server→`tmp/server-remind/`, `diff` všech 10 zdrojů proti **HEAD****identické**, žádná autonomní divergence (Dream procesor nesahal) → deploy bezpečný.
- Deploy `rsync` (exclude `__pycache__`/`.pytest_cache`/`.venv`), stale `remind_edit.py` + starý test **explicitně `rm`** (rsync bez `--delete` je nechává). Owner `nanobot:nanobot` OK.
- Server `remind_cli.py --help` + `list` (reálná data) OK; `remind_edit.py` pryč; crontab dál `remind_send.py`.
- **Živý edit test (bez Telegram fire):** `add --at 2020-01-01` (v minulosti → nikdy due), `edit --id 17 --text "TEST opraveny text"` → text změněn, `at` schedule (id 27) **zachován**, pak `remove --id 17` (úklid).
**Co zbývá:** Commit + push (tracking zvlášť dle konvence). `tmp/server-remind/` je v `.gitignore`.
**Jak vrátit zpět:** `git revert <commit>` skillu + na serveru rsync předchozí verze a `mv remind_cli.py remind_edit.py` zpět (DB beze změny).
## 2026-06-13 07:48 — note skill: zákaz vymýšlení tagů
**Cíl:** Odstranit nešvar, kdy agent při zakládání poznámky automaticky vymýšlí tagy z obsahu. Tagy se mají přidat jen když je uživatel explicitně napíše (první token za triggerem).
**Co jsem zkusil:** Příčina čistě v textu `skills/note/SKILL.md` — skript `note.py` je v pořádku (`--tags` volitelný, default `[]`, žádné auto-tagování). Svádějící formulace: hlavička „extract tags", věta „Tags are created automatically on first use", krok 1 Write protocolu „Extract inline tags". Upravil jsem všechny tři: hlavička → „take only explicitly-typed tags"; do Tag protocolu přidáno tvrdé pravidlo, že tagy pochází *jen* z prvního tokenu, nikdy se neodvozují z obsahu; přeformulováno „DB accepts any new tag without prior registration — not a license to make up tags"; krok 1 → „Take … only … tags field stays empty — never fill it from the content".
**Co fungovalo a proč:** Server↔lokál před úpravou shoda (diff prázdný). Nasazeno `rsync -av skills/note/ nanobot@nanobot.hell:…/skills/note/`, vlastník `nanobot:nanobot` ověřen. Skilly se čtou každý tah bez restartu.
**Co zbývá:** Verifikace reálným chováním (poznámka bez explicitního tagu → žádné tagy). Commit tracking zvlášť.
**Jak vrátit zpět:** `git revert <commit>` + rsync předchozí verze SKILL.md na server. DB beze změny.
## 2026-06-13 10:25 — Display ID (přečíslování on-the-fly) do /remind a /bookmark
**Cíl:** Přenést mechanismus z `/note` (sekvenční display ID 1..N počítané on-the-fly, nezávislé na rozházených interních DB id, přečíslování po každém smazání → snazší mazání/úpravy podle krátkého čísla) do `/remind` a `/bookmark`. Uživatel řekl „/article", ale to je čistě sumarizační skill bez seznamu — potvrzeno, že myslel `/bookmark`. Plán: [plans/](../.claude/plans/) (schválen v plan mode).
**Co jsem zkusil / udělal:**
- **Vzor z note (zrcadlit, ne vymýšlet):** helper vrací seřazené interní id aktivní množiny → výpis postaví `{interní_id: pořadí}` → mutace berou display ID, `idx = n-1`, bounds-check, přeloží na interní id.
- **remind** (`remind_cli.py`, `forecast.py`): nový `_active_display_order` (řazení `ORDER BY id`); `cmd_list` tiskne `#{display}`; **load-bearing: `--id` u remove/edit/enable/disable přeznačeno z interního DB id na display ID** + `--keyword` beze změny; `_resolve_one` bounds-check (`{"error":"no match","display_id":n}`), nejednoznačný keyword vrací `matches:[{"display_id":…}]`; `upcoming` + `format_upcoming(fires, id_to_display)` tisknou stejné display ID jako `list`; interní id zmizelo z user-facing výstupu (zůstává v audit logu a v JSON mutací).
- **bookmark** (`bookmark.py`): dvě množiny — `_ordered_ids(read=False)` nepřečtené (`created_at DESC`), `read=True` přečtené (`read_at DESC`); `read`/`show` řeší proti `list`, `unread` proti `history`; `_print_bookmark(row, display_id)`; `cmd_list` počítá display ID nad **celou** nepřečtenou množinou (filtr `--tag` drží globální čísla → očekávané mezery, jako note); `cmd_add` echo `#1` (newest-first); odstraněn dead `bid`.
- SKILL.md obou skillů aktualizovány (EN-only): display ID = sekvenční, on-the-fly, přečíslovává se; který výpis pro který příkaz; interní id se neukazuje.
- Testy remind: `test_forecast.py` opraven na novou signaturu `format_upcoming` (+ ověřuje překlad interní→display); přidány `test_display_id_renumbers_after_remove`, `test_id_out_of_range_reports_display_id`, `test_ambiguous_keyword_returns_display_ids`.
**Co fungovalo a proč:**
- Lokálně **71 passed** (`uv run --with pytest --with croniter pytest`).
- bookmark nemá testy → ruční ověření přes import s patchnutým `DB_PATH` na temp DB: add→#1, list 1..3 newest-first, `read 2` → přečíslování, `show 1`, `--tag` global ID, `history` vlastní číslování, `unread 1` přes history pořadí. Vše dle návrhu.
- Server = zdroj pravdy: `rsync` server→`tmp/srv/`, diff všech zdrojů proti **HEAD** → identické (žádná autonomní divergence Dreamem) → deploy bezpečný.
- Deploy `rsync` (exclude `__pycache__`); omylem se nahrál `.pytest_cache/` → smazán ze serveru. Owner `nanobot:nanobot` OK. Smoke test server `remind list` → display ID #1#4 (interní id rozházená po smazáních). Skilly se čtou bez restartu.
**Co zbývá:** Commit kódu skillů (tracking zvlášť dle konvence — řeší uživatel). Návrh zápisu do `decisions.md`: load-bearing volba „`--id` u remind mutací = display ID, ne interní DB id".
**Jak vrátit zpět:** `git revert <commit>` skillů + rsync předchozí verze (HEAD) na server. DB schema/data beze změny.
## 2026-06-13 19:24 — Detach: recovery démona + úklid (code-review fixy #1,#2,#4,#5,#6,#7)
**Cíl:** Opravit 6 nálezů z code-review skillu `/detach` — dvě reálné díry v recovery démona (#1 osiřelé `running/`, #2 poison-loop) a čtyři úklidové (#4 duplicitní CONFIG, #5 duplicitní ensure_queue_dirs, #6 Markdown notifikace posílaná jako plain text, #7 duplicitní řazení v read-task). Plán: [plans/](../.claude/plans/) (schválen v plan mode). Implementaci lokálních editů dělal sonnet subagent (konvence „plány vykonává sonnet"), opus revize + deploy.
**Co jsem zkusil / udělal:**
- **#1+#2 společný refactor** `tasks-daemon.py`: vytažen `finalize_task(...)` (zápis Result+meta, mv do done/failed, resolve chat_id v `try`, guardovaná notifikace) — sdílí ho `process_task` i nový `reclaim_orphans()`. `process_task` přeskládán na **move-first** (resolve chat_id už neběží před mv ani mimo try → konec poison-loopu u ne-telegram kanálu s vadným `allowFrom`). `reclaim_orphans()` na startu `main()` označí přežité `running/*.md` jako `failed` + `(INTERRUPTED…)` + best-effort notifikace.
- **#1d** `tasks-daemon.path`: přidán druhý `DirectoryNotEmpty=…/tasks/running`, aby reclaim nečekal na další inbox task.
- **#4** `CONFIG` importováno z `tasks_common` (smazána lokální definice). **#5** `QUEUE_DIRS` + `ensure_queue_dirs()` do `tasks_common`, použito v create-task i daemonu. **#6** notifikace bez zpětných apostrofů. **#7** `find_matches` reuse `completed_files()`.
- **Drift při porovnání se serverem:** server měl `TIMEOUT_SECONDS = 20 * 60` (repo/HEAD = 45), upraveno mimo repo. Per „server je zdroj pravdy" zachováno 20 min, dotaženo do repa **samostatným commitem** (5581411) PŘED refactorem. Potvrzeno uživatelem.
**Co fungovalo a proč:**
- Lokálně **57 passed** (`uv run --with pytest pytest`).
- Server = zdroj pravdy: `rsync` server→`tmp/detach-server/`, diff proti HEAD → jediný drift `tasks-daemon.py` (timeout). Ostatní soubory identické.
- Deploy `rsync` (exclude `__pycache__`, `.pytest_cache`). `.path` unit je **symlink** z `~/.config/systemd/user/` do skill dir → rsync ho aktualizoval; `systemctl --user daemon-reload` + `restart tasks-daemon.path` (active). Owner `nanobot:nanobot` OK.
- **Live #1 verifikace:** dummy `running/2026-06-13_11_30_00_000001-reclaim-test.md` (chat_id `"1"` = neplatný, ať nespamuje reálný Telegram) → nový `running/` watch spustil démona → reclaim → přesun do `failed/` se `status: failed` + `(INTERRUPTED…)`. Log: `RECLAIM` + `END status=failed` + `NOTIFY-FAILED HTTP 400` (guard zafungoval). Test artefakt smazán z `failed/`.
- **#2** ověřeno strukturálně (resolve uvnitř finalize po mv, v try) + reclaim test exercoval guardovanou notify cestu; live failure-injection na prod config zbytečně invazivní.
**Co zbývá:** Návrh zápisu do `decisions.md` (load-bearing, čeká na souhlas autora): (a) `.path` unit hlídá i `running/` jako self-heal mechanismus reclaimu; (b) osiřelý task → `failed` (ne retry/re-queue) kvůli neopakování side-effectů agenta.
**Jak vrátit zpět:** `git revert 999b33c` (refactor) a/nebo `git revert 5581411` (timeout) + rsync předchozí verze (HEAD) na server; obnovit původní `.path` unit (jen inbox watch) + `daemon-reload` + `restart tasks-daemon.path`. DB/tasks data beze změny.
## 2026-06-14 08:46 — note: explicitní registrace tagů (konec auto-create)
**Cíl:** `/note` už nesmí tagy zakládat implicitně prvním použitím. Vznik tagu = samostatný explicitní příkaz; neznámý tag při zakládání poznámky → skill se zeptá, zda ho založit.
**Co jsem zkusil / udělal:**
- `note.py`: nová tabulka `tags(name PRIMARY KEY, created_at)`; `_backfill_tags` v migraci nasází distinct tagy z existujících poznámek (jen když je registr prázdný — idempotentní).
- `cmd_add`: po formát-validaci kontrola proti registru; neznámý tag → stderr `Unknown tag(s): …` + **exit 2**, poznámka se NEUKLÁDÁ. Exit kódy: 0 ok / 1 špatný formát / 2 neznámý tag.
- Nové příkazy `tag-add NAME` (idempotentní, reuse `_validate_tags`) a `tag-list` (řazení podle jména, bez display ID — tagy se referencují jménem; mazání se nezavádí).
- `SKILL.md`: přepsán Tag protocol (registrace povinná), Write protocol (větev na exit 2 — Ano→`tag-add`+re-run, Ne→re-run bez tagu), nová sekce Tag management, edge case `tag add` bez jména.
**Co fungovalo a proč:** Lokální verifikace na throwaway kopii (`tmp/verify`, real db naseedovaná) — backfill (arch/cli/test), known tag exit 0, unknown exit 2 + neuloženo, `tag-add` idempotentní, špatný formát exit 1, mixed known+unknown exit 2. Server je zdroj pravdy: před nasazením `rsync` serverové verze do `tmp/` + diff = jen mé změny (žádný Dream edit k záchraně). Nasazeno `rsync -av`, vlastník `nanobot:nanobot`. Migrace+backfill na serveru proběhla při prvním `tag-list` — zaregistrováno 5 tagů (chata/devops/dt-glass/shop/test). `uv` na serveru je v `~/.local/bin/uv` (není na non-interactive PATH).
**Co zbývá:** Návrh zápisu do `decisions.md` (čeká na souhlas autora): (a) registr tagů jako separátní tabulka + `add` odmítá neznámé (exit 2), vznik výhradně `tag-add` — load-bearing; (b) tagy se referencují jménem (bez display ID), mazání se nezavádí; (c) backfill jen při prázdném registru.
**Jak vrátit zpět:** `git revert <tento commit kódu>` + `rsync -av` předchozí verze `skills/note/` na server. Tabulka `tags` v DB může zůstat (stará verze ji ignoruje); pokud vadí: `sqlite3 db/note.sqlite "DROP TABLE tags;"` lokálně i na serveru.
## 2026-06-14 10:11 — note: render-safe URL ve `list` (inline Markdown odkaz)
**Cíl:** Ve `/note list` se u poznámky s holými URL (#7 „DT GLASS") v chat UI nevykreslilo pořadové číslo. Diagnóza (read-only): skript i data jsou OK — `note.py list` tiskne `N.` správně a model dostává správný text. Rozbíjí to až render chatu: řádek je položka číslovaného seznamu a holé `https://` URL se zároveň autolinkne do rich-link „chipu" (favicon „DG"), který se vykreslí na začátek řádku a překryje marker čísla. Ostatní řádky bez URL OK.
**Co jsem udělal:**
- `note.py`: nový helper `_linkify(text)` — regexem `https?://\S+` obalí holé URL do inline Markdown odkazu `[url](url)` (inline odkaz se nerenderuje jako chip → číslo přežije, URL zůstane celé i klikací). Trailing-punctuation peel (`.,;:!?)]`) drží čárku/závorku za URL mimo cíl odkazu.
- Použito jen v `cmd_list` (řádek výpisu). `cmd_show` záměrně netknut — má verbatim kontrakt; navíc není číslovaný seznam, ke kolizi nedochází. `add`/`delete` jednořádkové, bez kolize.
- `SKILL.md` List protocol: věta, že skript URL vypisuje jako inline odkazy a model má echovat beze změny (ať nepřebaluje zpět na holé URL).
**Co fungovalo a proč:** Regex izolovaně lokálně (čárka i `)` mimo odkaz). Server = zdroj pravdy: `rsync` server→`tmp/` + diff `note.py`/`SKILL.md` proti HEAD = IDENTICAL (žádný Dream edit k záchraně). Nasazeno `rsync -av skills/note/`, vlastník `nanobot:nanobot`. Server `note.py list` po deployi: #7 má URL ve tvaru `[https://…](https://…)`, marker `7.` na začátku. Prezentační změna — DB obsah i display-ID logika beze změny.
**Co zbývá:** Reálné ověření renderu v nanobot UI na konkrétním modelu dělá uživatel (zda inline odkaz UI neunfurluje do chipu). Fallback při unfurlu: backtick `` `url` `` (bez klik) nebo zkrácený popisek `[odkaz](url)`. Změna kódu `skills/note/` zatím necommitnuta (čeká na pokyn).
**Jak vrátit zpět:** `git checkout HEAD -- skills/note/scripts/note.py skills/note/SKILL.md` + `rsync -av skills/note/` předchozí verze na server. Bez dopadu na DB.
## 2026-06-14 11:33 — note: URL ve `list` — revert `_linkify`, model formátuje (bare URL na vlastní řádky)
**Cíl:** Dotáhnout render `/note list` u poznámek s URL tak, aby bylo vidět pořadové číslo **i** klikací odkaz. Předchozí iterace (`_linkify` → `[url](url)`, viz záznam 10:11) v reálném testu selhala stejně jako holé URL.
**Klíčové zjištění (přímý test s nanobot agentem):** Chyba je v markdown rendereru UI, ne v textu. UI **slučuje dva sousední inline odkazy `[text](url)`** do jednoho klikacího bloku a druhý URL schová → přebije i číslo položky. Naopak **holé URL se renderují správně a jsou klikací** (autolink); když je každý URL na vlastním řádku / pod odrážkou, neslučují se a číslo zůstane. `_linkify` (`[url](url)`) tedy dělal přesně tu rozbíjející věc.
**Co jsem udělal:**
- `note.py`: smazán helper `_linkify`; `cmd_list` vrácen na čistý výpis `N. {content}{tags}` (bare URL, jeden řádek). `cmd_show`/`add`/`delete` netknuty, DB i display-ID logika beze změny.
- `SKILL.md` List protocol: místo věty o „inline Markdown links" nová sekce *URLs in a note* s příkladem vstup→výstup — model má každý URL dát na vlastní řádek pod odrážku, **holý (nikdy `[text](url)`)**, popisek spárovat s URL, číslo i `[#tag]` zachovat. Důvod (slučování sousedních inline odkazů) ve skillu stručně.
- **Architektura:** formátování dělá model dle příkladu (umí spárovat popisek s URL). Deterministické formátování ve skriptu (zlom před každý URL) drženo **v záloze** v plánu pro případ, že glm příklad nedodrží.
**Co fungovalo a proč:** Server = zdroj pravdy: `rsync` server→`tmp/note-server` + diff proti repu = IDENTICAL (žádný Dream edit). `ast.parse` OK, `_linkify` pryč, `re` stále používáno (`_TAG_RE`). Nasazeno `rsync -av skills/note/`, vlastník `nanobot:nanobot`. Server `note.py list`: #7 „DT GLASS" má bare URL na jednom řádku, marker `7.` na začátku, žádný `[url](url)`.
**Co zbývá:** Reálné ověření v nanobot UI dělá uživatel — zda model (glm) příklad dodrží a render ukáže číslo + klikací URL pod odrážkami; otestovat i konzoli/Telegram. Když glm příklad nedodrží → přepnout na zálohu (deterministika ve skriptu). Změna kódu `skills/note/` zatím necommitnuta (čeká na pokyn).
**Jak vrátit zpět:** `git checkout HEAD -- skills/note/scripts/note.py skills/note/SKILL.md` + `rsync -av skills/note/` předchozí verze na server. Bez dopadu na DB.
## 2026-06-14 11:39 — note: URL ve `list` — fallback do skriptu (model příklad nedodržel)
**Cíl:** Přístup „model formátuje dle SKILL příkladu" v reálném testu selhal — glm vypsal raw řádek beze změny (jeden řádek, holé URL), UI ho celý udělalo klikací a číslo přebilo. Přepnuto na zálohu: formátování deterministicky ve skriptu.
**Co jsem udělal:**
- `note.py`: helper `_urls_on_own_lines(text)` + regex `_LABELED_URL_RE` (`\s*([^\s,]+:\s*)?(https?://[^\s,]+)`) — každý URL i s předsazeným „Label:" tokenem (pokud je) na vlastní odrážku ` - `. URL zůstává holé (autolink), comma/space url neukončuje předčasně (`[^\s,]+`). Použito jen v `cmd_list`.
- `SKILL.md` *URLs in a note*: model už neformátuje — **echovuje verbatim** (skript dodá odrážky); zákaz collapse zpět na jeden řádek a `[text](url)` zůstává.
- Heuristika popisku: „Label:" se spáruje s URL jen když je těsně před ním (jen `\s*` mezi) — vzdálený dvojtečkový token (např. „poznámka: … url") se nechytí. Cena: u prvního URL zůstane oddělovač („—") viset na konci hlavičkového řádku; přijatelné.
**Co fungovalo a proč:** Lokální test helperu na 4 vstupech (dvě labeled URL, bez URL, url uprostřed věty, jediný url) — výstup čistý, popisky spárované, no-URL poznámka beze změny. `ast.parse` OK. Nasazeno `rsync -av`, vlastník `nanobot:nanobot`. Server `note.py list`: #7 „DT GLASS" má hlavičku + dvě odrážky `- Universal: <url>` / `- Amber: <url>`, číslo `7.` na začátku.
**Co zbývá:** Reálné ověření v nanobot UI dělá uživatel (číslo + klikací URL pod odrážkami; ať model echovuje verbatim a neslepí to). Otestovat i konzoli/Telegram. Změna kódu `skills/note/` zatím necommitnuta (čeká na pokyn).
**Jak vrátit zpět:** `git checkout HEAD -- skills/note/` + `rsync -av skills/note/` předchozí verze na server. Bez dopadu na DB.
## 2026-06-11 20:02 — SOUL.md: přidány 2 behaviorální principy + zachycena autonomní divergence serveru
**Cíl:** Uživatel zvažoval přidat do nanobotových instrukcí blok behaviorálních pravidel (simplest approach, don't assume, surface tradeoffs, minimal code, loop-until-verified). Posoudit přínos a případně přidat.
**Co jsem zkusil / udělal:**
- Analýza navrženého textu proti `SOUL.md`/`AGENTS.md` (oba se čtou do system promptu každý tah, fresh — viz knowledge.md). Závěr: ~80 % už pokryto v SOUL.md (execute-immediately ř.19, don't-assume ř.21, ask-when-unsure ř.10/23/27, verify-after-changes ř.24). Coding-specific body (minimal code, surgical diffs, clean up orphans) pro asistenta irelevantní. Reálně nové jen 2: **jednoduchost** a **surface tradeoffs**.
- Stáhl serverové `SOUL.md`+`AGENTS.md` (`rsync`→`tmp/`). `diff` serveru proti `workspace/SOUL.md` (repo).
- **Server autonomně divergoval od posledního sync (`7d89c56`):** zahodil 3 řádky (`Předpokládám znalost u uživatele…`, `U nevratných akcí raději počkat na potvrzení`, `Žádné emojis…`) a přidal 1 (`Čísla, limity, kvóty… ověřuj na primárním zdroji`). Pravděpodobně Dream procesor.
- Dle pravidla „server = zdroj pravdy": server vyhrává, 3 zahozené řádky **nekřísím**. Mé 2 řádky přidal do serverové kopie:
- `## Hodnoty`: „Jednoduchost před komplexností — nejjednodušší funkční řešení; složitější vzory nenavrhuj, dokud o ně uživatel neřekne"
- `## Pravidla provedení`: „Když existuje víc cest, vynes tradeoff nahlas místo tichého výběru jedné"
- `rsync` zpět na server, ověřen owner `nanobot:nanobot` + oba řádky (ř.14, ř.26). Repo `workspace/SOUL.md` dotaženo na nový serverový stav (vč. autonomních změn serveru).
**Co fungovalo a proč:** SOUL.md se čte fresh každý tah → **bez restartu** platí od příští zprávy. Porovnání se serverem odhalilo autonomní divergenci dřív, než by ji slepý přepis lokální verzí zahodil.
**Co zbývá:** Zvážit, zda 3 řádky zahozené serverem (zvlášť „U nevratných akcí raději počkat na potvrzení") chceme obnovit, nebo respektovat rozhodnutí Dreamu. Zatím ponecháno tak, jak je server nastavil.
**Jak vrátit zpět:** `git revert <commit>` + rsync předchozí verze na server.
## 2026-06-11 20:06 — SOUL.md: obnoven řádek o potvrzení nevratných akcí
**Cíl:** Z 3 řádků, které server autonomně zahodil (viz záznam 20:02), obnovit ten bezpečnostně relevantní.
**Co jsem udělal:** Posoudil všechny 3 zahozené řádky proti zbytku SOUL.md. Jen `U nevratných akcí raději počkat na potvrzení` byl skutečná mezera (ostatní 2 — „předpokládám znalost", „žádné emojis" — pokryto `bez ozdob`/terseness/USER.md, ponechány zahozené). Obnoven do `## Pravidla provedení` k pravidlu o potvrzování vícekrokových úkolů, přeformulován obecněji: „U nevratných akcí (smazání, přepis, odeslání) raději počkej na potvrzení — i u jednokrokových". `rsync` na server, ověřen owner + řádek (ř.22), repo dotaženo.
**Co fungovalo a proč:** Fresh read každý tah → bez restartu.
**Jak vrátit zpět:** `git revert <commit>` + rsync předchozí verze na server.
## 2026-06-15 05:30 — /remind: náhodný rozvrh napříč týdnem + sync display-id featury ze serveru
**Cíl:** Rozšířit `/remind` o „N× náhodně v týdnu" (vedle stávajícího N× za den) — N náhodných odpálení rozprostřených přes celý týden.
**Co jsem zkusil / udělal:**
- **Návrh:** engine `compute_fire_times(target_date, text, cfg)` zůstává **per-day volaný** — pro weekly režim spočítá celý týdenní plán (seed = pondělí týdne + text, ne den) a vrátí jen fires daného dne. Tím daemon i forecast nemění iterační logiku. Semantika (potvrzeno uživatelem): N **různých** dnů (PoNe), 1 čas na den, okno povinné; `min(count, dostupné dny)` clamp pro hraniční týdny.
- **Změny:** sloupec `schedule_random.period` ('day'/'week', default 'day') + idempotentní `_migrate(conn)` v `get_db` (PRAGMA-guarded ALTER, protože `init_db` na živou DB nesahá); `_weekly_fire_times` v `random_times.py`; flag `--random-times-per-week` (add+edit, mutually exclusive s `--random-times-per-day`); `period` do INSERT/SELECT/list výpisu (`× weekly`/`× daily`). +10 testů.
- **Server = zdroj pravdy (klíčový krok):** `rsync` server→`tmp/server-remind/`, `diff` proti **git HEAD** odhalil, že server **autonomně divergoval** — měl feature **display IDs** (přečíslování reminderů na 1-based pozici v `list`/`upcoming`, `--id` i chybové hlášky pracují s display ID), která **nebyla v repu**. Pravděpodobně Dream procesor.
- Postup dle pravidla: weekly diff uložen jako patch (`git diff HEAD > /tmp/weekly.patch`), pracovní strom resetován na HEAD, serverové verze 5 divergovaných souborů (`SKILL.md`, `remind_cli.py`, `forecast.py`, `test_remind_cli.py`, `test_forecast.py`) přeneseny do repa → **commit „sync display IDs"** (71 testů). Pak `git apply --3way` weekly patche — všechny `.py` čistě, konflikt jen v `SKILL.md` (oba odstavce do stejného místa, ponechány oba) → **commit weekly** (80 testů).
- **Deploy:** `rsync` (exclude `__pycache__`/`.venv`/`.pytest_cache`), owner `nanobot:nanobot` OK. `list` na serveru → prod DB se zmigrovala (`period` přidán), stávajících 13 reminderů dál `× daily`.
**Co fungovalo a proč:** Per-day volací rozhraní enginu = daemon/forecast bez změny logiky (jen `sr.period` do SELECTů, protože `random_cfg_from_row` ho čte). Porovnání se serverem proti **HEAD** (ne proti pracovnímu stromu, kde už byly mé editace) odhalilo cizí divergenci — slepý rsync by display-id feature smazal. Dvoukrokový merge (sync commit → weekly na něj) zachoval obě featury, ověřeno 80 zelenými testy + kombinovaným smoke testem (display IDs #1/#2 + `2× weekly` + 2 různé dny).
**Co zbývá:** Commit tracking souborů. Skripty se čtou per-invocation → daemon bere nový kód bez restartu.
**Jak vrátit zpět:** `git revert 17feee6` (weekly) a `git revert fcfe794` (display-id sync), pak `rsync` předchozí verze skillu na server. Sloupec `period` v prod DB lze nechat (default 'day' = původní chování) nebo `ALTER TABLE schedule_random DROP COLUMN period`.
## 2026-06-15 05:44 — /remind: vytažení data-access vrstvy do `store.py`
**Cíl:** Reakce na uživatelovu otázku, zda `remind_cli.py` (534 ř.) není moc dlouhý. Po re-review ne kvůli délce, ale kvůli dvěma smellům: (1) `db.py` deklarovaný jako storage layer neobsahoval žádné dotazy — veškeré SQL žilo v CLI; (2) transaction boilerplate (`get_db`/`BEGIN`/`COMMIT`/`ROLLBACK`/`close`) duplikovaný ~7× napříč `cmd_*`. Cíl: CLI = čistá prezentační vrstva, SQL + lifecycle do nového modulu. Veřejné API beze změny (testy musí projít).
**Co jsem zkusil:** Plán (`plans/` přes plan mode), implementaci delegoval na sonnet agenta dle konvence. Nový `scripts/store.py` (240 ř.): context managery `connection()`/`transaction()` + dotazové funkce (`insert_reminder`, `insert_schedules`, `fetch_reminder`, `schedules_for`, `list_active`, `find_active_by_id/keyword`, `active_display_order`, `soft_delete`, `update_text`, `delete_schedules`, `touch`, `set_enabled`, `delivered_since/today`). `remind_cli.py` 534→400 ř., všechny `cmd_*` přes `store.*`, žádné inline SQL ani `BEGIN/COMMIT`. `remind_send.py` zatím nedotčen (volitelný krok).
**Co fungovalo a proč:** 80/80 testů zelených (`uv run --with croniter --with pytest pytest`), smoke add→list→remove OK. **Past, do které jsem nespadl:** working tree měl na startu necommitnutý revert display-ID → raw-DB-id v `remind_cli.py` (testy k němu neupravené → padaly by). Ověřením proti **serveru (zdroj pravdy přes `ssh`)** potvrzeno, že server i HEAD mají display-ID → raw-id byl stray edit, který agent správně zahodil. Bez ověření serveru bych mohl chybně „zachovat" rozbitý stav.
**Co zbývá:** Nasadit na server (`rsync` celého skillu) — server má pre-refactor display-ID verzi bez `store.py`; před přepisem porovnat se serverem (žádná autonomní divergence se nečeká, ověřeno). Skripty se čtou per-invocation → bez restartu. Případný `decisions.md` zápis (samostatný `store.py` vs. growth `db.py`) k odsouhlasení autorem.
**Jak vrátit zpět:** `git checkout HEAD -- skills/remind/scripts/remind_cli.py && rm skills/remind/scripts/store.py` (lokálně necommitnuto). Na serveru: `rsync` předchozí verze skillu zpět.
## 2026-06-15 05:50 — /remind: dokončení refaktoru (remind_send.py) + nasazení na server
**Cíl:** Dotáhnout volitelný krok — přesun read dotazů `remind_send.py` do `store.py` — a nasadit celý refaktor na server.
**Co jsem zkusil:** Do `store.py` přidány sender helpery (`due_at`, `enabled_cron`, `enabled_random`, `is_fire_delivered`, `record_fire`). `remind_send.py` přepsán: `_due_*` volají `store.*`, `main()` přes `store.connection`, žádné inline SQL ani `get_db`/`init_db`/`conn.close`. `record_fire` bez časové logiky (store je bezčasový) — `delivered_at` počítá `remind_send._record_fire` a předává explicitně. Nasazení: `rsync` server→`tmp/server-remind/` + diff proti lokálu — `db.py`/`forecast.py`/`random_times.py`/`SKILL.md` shodné (žádná autonomní divergence), lišil se jen refaktor → `rsync` lokál→server, owner `nanobot:nanobot` ověřen.
**Co fungovalo a proč:** 80/80 testů zelených (testy volají jen `main()`+patch `_send_telegram`/`_now_prague`/`DB_PATH`, vnitřní `_due_*` netestují přímo → přesun bezpečný). Server smoke: `list` (display-ID #1/#2/#3) + import `remind_send`+`store` OK. Cron sender (`* * * * * uv run .../remind_send.py`) vezme nový kód při příštím běhu — per-invocation read, bez restartu. **Lokálně necommitnuto** (kód skillu commituji jen na vyžádání).
**Jak vrátit zpět:** `git checkout HEAD -- skills/remind/scripts/remind_cli.py skills/remind/scripts/remind_send.py && rm skills/remind/scripts/store.py`, pak `rsync` HEAD verze skillu na server.
## 2026-06-15 13:21 — /remind: explicitní instrukce pro vypsání čtecích příkazů
**Cíl:** Uživatel hlásil, že po nedávných úpravách `/remind` dva různé modely „nedokážou vrátit celou tabulku" a každý to obchází jinak. Najít příčinu (nic neměnit), pak opravit instrukce ve skillu.
**Co jsem zkusil:** Reprodukce CLI lokálně i na serveru (`uv run … list`) → **exit 0, kompletní 18řádková tabulka**. Vyloučeno: pád, `uv`/PATH (uv jen v login shellu `~/.local/bin`, ale nanobot `exec` ho má), chybějící croniter (systémový python3 ho nemá — `from croniter import croniter` na ř. 22 → ImportError, ale agent jede přes uv), i refaktor `store.py` (`3de1d6b` sáhl jen na Python, `SKILL.md` se nedotkl). Stáhl jsem dvě webui session z poslední hodiny (`workspace/sessions/`): `552c62c9` (**kimi27**) i `ee623e4b` (default model) — **obě** spustily `list` úspěšně a dostaly celou tabulku, ale kimi27 odpověděl jen *„Máš 18 aktivních připomínek."* a druhý si postavil vlastní parafrázovanou markdown tabulku.
**Co fungovalo a proč:** Příčina = **mezera v `SKILL.md`, sekci `## Behavioral contract`**: popisuje *formát* výstupu `list` (uvozený jako ``` ukázka → vypadá jako schéma dat), ale nikde neříká „výstup předej uživateli kompletní". Modely to čtou jako surová data a každý si je naformátuje/zkrátí po svém. Fix: nový odstavec **Showing read results** na začátku sekce — `list`/`upcoming`/`delivered` vrací user-ready text, vypsat každou položku, nesumarizovat na počet, nevynechávat řádky; hezčí formát (tabulka, parafráze cronu) povolen, pokud zůstane `#display-id`. Plus oprava zastaralé argparse help hlášky `remind_cli.py` (`list` „as JSON" → „as readable text"). Nasazeno `rsync`em (owner `nanobot:nanobot` OK), CLI dál exit 0 / text, help aktualizován. Skilly se čtou per-tah bez restartu.
**Co zbývá:** Behaviorální ověření dělá uživatel ve webui — znovu `/remind list` s kimi27 i default modelem, ověřit že oba vypíšou všech 18 položek s `#display-id`. CLI test to neověří (chyba byla v chování modelu, ne ve skriptu).
**Jak vrátit zpět:** `git checkout HEAD -- skills/remind/SKILL.md skills/remind/scripts/remind_cli.py`, pak `rsync` předchozí verze skillu na server.
## 2026-06-15 13:28 — /remind: iterace formátu výpisu — kompaktní, model-side, jen enabled
**Cíl:** Po prvním kole (13:21) modely vypisovaly *všech* 18 položek, ale věrně relayovaly raw formát CLI → ukecané, víceřádkové, `[enabled]` u všech, nečitelné. Uživatel chce, ať to **model sám přepíše do pěkné kompaktní podoby**, vypíše **jen enabled** a `[enabled]` tag vynechá. **CLI se nemění — dělá to model.**
**Co jsem zkusil:** Přepsán odstavec **Showing read results** v `SKILL.md` (ř. 35): pro `list` model přepíše raw výstup do kompaktní podoby vlastní volby (řádek na připomínku, cron parafrázovaný do přirozené řeči), vypíše jen enabled (disabled přeskočí), zachová `#display-id` přesně jak je CLI vypsalo (mezery z přeskočených disabled OK — aby `--id` dál sedělo na `active_display_order`, které zahrnuje i disabled), bez `[enabled]` tagu. Obecné „nesumarizovat na počet" zůstává pro list/upcoming/delivered. Raw-formát blok pod tím ponechán (dokumentuje vstup, který model přepisuje). CLI `cmd_list`, `store.py` ani testy nedotčeny.
**Co fungovalo a proč:** Záměrně model-side render, ne změna Pythonu — uživatel chce „pěknou podobu podle sebe", což je prezentační volba, ne deterministický kontrakt. Klíčový constraint v instrukci: zachovat původní `#display-id` i při skrytí disabled, jinak by se rozbila vazba `--id` ↔ pořadí v CLI. Nasazeno `rsync`em (owner `nanobot:nanobot` OK), bez restartu.
**Co zbývá:** Behaviorální ověření dělá uživatel ve webui — `/remind list` s kimi27 i default modelem: kompaktní seznam, řádek/připomínka, bez `[enabled]`, jen enabled, se zachovanými `#display-id`.
**Jak vrátit zpět:** `git checkout HEAD -- skills/remind/SKILL.md` (vrátí na verzi z 13:21), pak `rsync` na server.
**Potvrzeno (13:3x):** Uživatel ve webui ověřil — výpis vypadá pěkně, kompaktní. Smyčka uzavřená.
## 2026-06-16 21:20 — llm-wiki: port pluginu jako skill (lokální PoC) + nasazení na server
**Cíl:** Portovat [llm-wiki-plugin](https://github.com/praneybehl/llm-wiki-plugin) (Karpathyho „LLM Wiki" pattern) jako nanobot skill — PoC, čistě lokálně, wiki v `workspace/cml/`, git nad `workspace/` = safety net. Plán: [plans/llm-wiki-skill.md](plans/llm-wiki-skill.md). (Širší multi-klient vize je odložená case study `plans/wiki-znalostni-baze.md`.)
**Co jsem zkusil:** Naklonoval plugin do `tmp/`. Zjištění proti WebFetch odhadu: **SKILL.md má jen 160 ř.** (ne 2200 — tělo už rozdělené, detaily v `references/`), 7 stdlib skriptů (PyYAML jen `wiki_graph_lint`+`wiki_graph_extract`, `wiki_graph_query` čte sqlite). Zkopíroval `SKILL.md`+`scripts/`+`references/`+`assets/` (assets nutné — `init_wiki.py` z nich kopíruje šablony) do `skills/llm-wiki/`; vynechal `commands/` (CC-only slash, duplikují SKILL.md), `.claude-plugin/`, `integrations/`. Skripty: PEP 723 hlavička (`uv run --script`, deps `[]` / `["pyyaml"]`); default cesty `Path("wiki")`→`Path("cml/wiki")` u 4 skriptů s defaultem (robustní — agent nemusí cestu pamatovat). Docstringy skriptů nechal generické (skript je generický, nanobot-ismus patří do SKILL.md). SKILL.md: sed `python …scripts/`→`uv run skills/llm-wiki/scripts/`, `.py wiki/`→`.py cml/wiki/`; přidal **„Nanobot adaptation"** sekci (fixní `cml/wiki`+`cml/raw`, uv run, separate store mimo note/keep/MEMORY, **Dream nesahá do cml/**, **lint report-only**, jazyk uživatele, local PoC); krok agent-memory-integration vypnut. Bootstrap: `init_wiki.py . --wiki-dir cml/wiki --raw-dir cml/raw` (root `.` existuje, init `<project-root>` sám nevytváří).
**Co fungovalo a proč:** Lokální smoke (`tmp/wiki-test`): init→2 vzorové stránky→lint (detekoval broken `[[wikilink]]`)/stats/search (BM25)/graph_lint (validace ontologie)/graph_extract (2 nodes/3 edges, 0,15 s)/graph_query (neighbors prochází `mentions`+typed edge) — vše přes `uv run` s defaultem `cml/wiki` bez předání cesty. Server: `rsync` skillu (owner `nanobot:nanobot` ✓), git nad `workspace/` potvrzen, `uv` v `~/.local/bin/uv` (mimo non-login PATH — pro SSH/cron plná cesta, gotcha #8). Init na serveru vytvořil `cml/wiki`+`cml/raw` (`nanobot:nanobot`), lint/stats/graph_extract běží pod serverovým `uv` (PyYAML stažen, 0,26 s). Skill se čte per-tah bez restartu. **Pattern sedí na nanobot 1:1** — `edit_file`=chirurgické `str_replace`, exec=python, nezávislost na CC hooks/MCP potvrzena.
**Co zbývá:** (1) Behaviorální E2E přes Telegram/WebUI — NL trigger („ingest this into my wiki…", „what does my wiki say about X") dělá uživatel; ověřit reálný ingest (chunkování velkého zdroje vs 60 s exec limit, #5). (2) **Fáze B:** periodický `wiki_lint` přes HEARTBEAT (report-only). (3) Kód skillu lokálně **necommitnuto** (commituji jen na vyžádání).
**Jak vrátit zpět:** Lokálně `rm -rf skills/llm-wiki`. Server: `ssh nanobot@nanobot.hell 'rm -rf ~/.nanobot/workspace/skills/llm-wiki ~/.nanobot/workspace/cml'`.
## 2026-06-16 22:15 — llm-wiki: rychlý capture + dávkový compile (systémový cron → wiki_compile.py)
**Cíl:** PoC ingest přes WebUI trval **1:41** (agentní compile, ne skripty) → nepoužitelný interaktivně. Oddělit **capture** (instantní hod zdroje do `cml/raw/`) od **compile** (těžký raw→wiki) a compile spouštět na pozadí přes systémový cron. Plán: [plans/llm-wiki-skill.md](plans/llm-wiki-skill.md), sekce „Capture + dávkový compile".
**Co jsem zkusil:** Nový `skills/llm-wiki/scripts/wiki_compile.py` (vzor `detach/tasks-daemon.py`): shebang `uv run --script` + PEP 723 `["nanobot-ai"]`, ale **`from nanobot import Nanobot` odložen do `run_compile()`** — levná per-minutová pre-kontrola nesmí platit import těžkého balíku. Tok: (1) scan `cml/raw/` na pending regulérní soubory (mimo `_done/`/`_hard/`/`assets/`) → prázdno = exit 0 bez LLM; (2) atomický lockfile `cml/.compile.lock` (PID+timestamp) přes `os.open(O_CREAT|O_EXCL)`, stale-detekce (mrtvý PID přes `os.kill(pid,0)` / stáří > 30 min se přebere); (3) `Nanobot.from_config()` + `asyncio.wait_for(bot.run(drain_goal, session_key="wiki-compile"), 25min)` — drain = všechno nasbírané v jednom dávkovém běhu; (4) tiše, jen log do `log/wiki_compile_cron.log`. SKILL.md: nová sekce **„Capture vs compile"** (capture zapíše `cml/raw/<slug>.md` + STOP, escape hatch „compile now"; `_done/`/`_hard/` konvence) + ingest workflow zarámován jako compile/drain + **fix slug-kolize** (concept/entity = krátký název pojmu, source = slug zdroje — řeší původní kolizi RoPE). Crontab `nanobot` (PATH= už nastaven kvůli remind): `* * * * * uv run …/wiki_compile.py >> …/wiki_compile_cron.log 2>&1` (forma `uv run <path>` jako remind, **ne** holý shebang — non-login/cron PATH nemá uv → `env: uv: No such file or directory`, gotcha #8).
**Co fungovalo a proč:** Lokálně (mock workspace v `tmp/`, `--dry-run`, čistý stdlib bez uv) ověřeny všechny větve: prázdný inbox→exit 0 bez logu; pending→dry-run+lock uvolněn; živý lock (reálný PID)→SKIP, lock zachován; stale lock (mrtvý PID)→reclaim. Server: deploy `rsync` (owner ✓), **reálný compile** leftover `rotary-position-embedding.md` proběhl na pozadí za 100 s, `remaining=0`, přesun do `_done/`, vznikly `concepts/rope.md` + `sources/rotary-position-embedding.md` + `entities/paper-rope.md` (slug fix drží — žádná kolize), index+log+graph (nodes/edges/sqlite/graphml) regenerovány. **Empty-inbox fast path: 0,119 s, exit 0, žádné volání LLM** — to je jádro efektivity minutového pollingu. **Živý cron test:** dropnut `alibi-position.md` → cron ho zvedl za ~50 s (hranice minuty), compile 107 s, přesun do `_done/`, `concepts/alibi.md`+`entities/paper-alibi.md`+`sources/alibi-position.md`, cross-link `[[rope]]`. Celý pipeline (capture instant → cron compile dávkově) běží end-to-end.
**Co zbývá:** (1) Capture přes WebUI/Telegram ověří uživatel — „ulož X" musí vrátit **instant ack** bez 1:41 (kód capture je instrukce v SKILL.md, ne skript → behaviorální). (2) Drobnost: agent v `log.md` napsal concept jako `concepts/rotary-position-embedding.md`, reálný soubor je `concepts/rope.md` (kosmetické, `[[rope]]` v indexu sedí). (3) Kód skillu lokálně **necommitnuto** (commituji jen na vyžádání). (4) Fáze B periodický lint přes HEARTBEAT — stále odloženo.
**Jak vrátit zpět:** Crontab: `ssh nanobot@nanobot.hell 'crontab -l | grep -v wiki_compile | crontab -'` (smaž i komentář řádek). Skill: `git checkout HEAD -- skills/llm-wiki/SKILL.md && rm skills/llm-wiki/scripts/wiki_compile.py`, pak `rsync` na server. Test artefakty (PoC demo obsah, lze nechat): pages pro rope/alibi v `cml/wiki/` + `cml/raw/_done/*` + `log/wiki_compile_*.log`.
## 2026-06-16 22:45 — llm-wiki: zpřísnění capture brány (capture/compile split se nedodržoval)
**Cíl:** Ověřit capture chování (uživatel chtěl testovat přes WebUI). Test odhalil regresi: capture/compile split se nedodržuje.
**Co jsem zkusil:** Pustil capture-style goal přes `bot.run(session_key="wiki-capture-test")` (transport-nezávislé, jako WebUI tah): „Ulož mi do wiki: <YaRN fakt>". **Agent ignoroval capture instrukci a udělal celý inline compile za 233 s** — vytvořil concept/entity/source stránky, upravil rope.md, rozšířil ontologii, překompiloval graf. Tedy přesně to, čemu jsme se chtěli vyhnout (původně 1:41, teď ještě hůř). Příčina: tělo SKILL.md je převážně návod „jak udělat plný ingest", a model „ulož do wiki" čte jako „proveď ten workflow"; moje sekce „Capture vs compile" to nepřebila — byla příliš měkká a tonula v okolním textu. **Druhý problém:** inline compile nepřesunul zdroj do `_done/`, takže cron pak zvedl `cml/raw/yarn.md` znovu a drain agent se **zacyklil** (132 iterací) reconciliací už existujících stránek. Runaway proces (PID 320950, ~6 min) jsem `pkill -TERM`-l, smazal lock, přesunul `yarn.md` → `_done/` (zastavilo příští cron tik). Fix SKILL.md: (1) **tvrdé pravidlo nahoře** v „read this first" — interaktivní požadavek = CAPTURE ONLY, inline compile je bug; (2) capture bullet přepsán na 3 kroky (zápis do `cml/raw/`, jednořádkový ack, STOP) + **výčet zakázaných akcí** (žádné čtení SCHEMA/index, žádné stránky, žádné skripty, žádný graf); (3) escape hatch zúžen („add to wiki" NENÍ escape); (4) STOP-redirect na začátku ingest workflow sekce. `wiki_compile.py` drain goal dostal **idempotenci** (existující stránky → `_done/`, necyklit; vždy přesunout z `cml/raw/`).
**Co fungovalo a proč:** Po redeploy znovu-test (Flash Attention): **capture 26 s** (jen cold-start `bot.run`, ne compile), odpověď „Zachyceno — zkompiluju na pozadí.", vznikl jen `cml/raw/flash-attention.md`, **žádná wiki stránka inline** — brána drží. Cron pak flash-attention zkompiloval na pozadí čistě (188 s, `remaining=0`, přesun do `_done/`, 3 stránky, **bez loopu** — idempotence + nový zdroj). Skill commitnut (`751a11a` celý skill na výslovnou žádost; `9bc52d8` zpřísnění brány).
**Co zbývá:** (1) Uživatel otestuje capture/query/lint sám přes WebUI (teplá session → svižnější než 26 s). (2) **Drobnost — slug kolize u Flash Attention:** source i concept dostaly slug `flash-attention` (u RoPE se přirozeně lišily `rope`/`rotary-position-embedding`); pravidlo „při kolizi suffixuj" agent neaplikoval → `[[flash-attention]]` dvojznačný mezi `sources/`+`concepts/`. Demo obsah, ne pipeline bug. (3) **Compile time roste s velikostí wiki** (100→188 s) — agent čte víc kontextu; na pozadí nevadí latencí, ale roste token cost. (4) Fáze B periodický lint přes HEARTBEAT — odloženo.
**Jak vrátit zpět:** `git checkout HEAD~2 -- skills/llm-wiki/SKILL.md skills/llm-wiki/scripts/wiki_compile.py` (před zpřísněním), `rsync` na server. Runaway compile už zabit, lock čistý, `cml/raw/` prázdné (vše v `_done/`).
## 2026-07-01 10:40 — note: kompletní přepis na capture → compile pipeline (nasazeno)
**Cíl:** Překopat `/note` z SQLite row-store (add/list/show/delete/tagy/display ID) na osobní znalostní bázi: dumb capture z různých kanálů → periodický compile do JEDNOHO strukturovaného `notes/notes.md` s tematickými sekcemi; hledání = načíst celý dokument. Bez DB, čistě detach-style adresáře. Plán: [plans/note-prepis.md](plans/note-prepis.md).
**Návrh (brainstorming + serverová verifikace):** Architektonicky stejný vzor jako `/llm-wiki`, ale záměrně lehčí (jeden doc, žádný index/graph/lint). Rozhodnutí: oddělený lehký skill; tematické sekce řídí LLM; **dual-mode** `/note <text>` = okamžitě inline compile (default), `/note cron <text>` = odloženě přes cron; tagy zrušeny; mazání = editace dokumentu. Ověřeno na serveru před psaním kódu: **`web` tool JE zapnutý** (config `tools.web`: DuckDuckGo + Jina Reader fetch — `TOOLS.md` ho nezmiňuje!) → URL fetch přes něj, ne curl; crontab uživatele `nanobot` má `PATH=` hlavičku → bare `uv run` funguje; `notes/` existoval prázdný; žádný drift skillu; `note.sqlite` = 17 aktivních notes.
**Co jsem udělal:**
- `note_capture.py` — dumb atomic capture (timestamp `%Y-%m-%d_%H_%M_%S_%f`, staging→`os.replace`, frontmatter provenience, ASCII-fold slug pro diakritiku) → `notes/inbox/` + řádek do `log/note.log`.
- `note_compile.py` — tenký launcher (kostra z `wiki_compile.py`): fs pre-check bez importu nanobota, lockfile se stale-reclaim, `Nanobot.from_config().run(DRAIN_GOAL)` (process_direct, **bez cron preamble** — proto systémový crontab, ne jobs.json), timeout 15 min. DRAIN_GOAL jmenuje skill → headless agent načte SKILL.md.
- `SKILL.md` přepsán (EN, dual-mode, sdílený compile workflow, URL přes `web` tool + paywall→`hard/`, search = celý `notes.md`). Description zpřesněn proti kolizi s `keep`.
- Lokální pytest (16, zelené). Starý `note.py` odstraněn z repa. Commity: `a8f1997` (plán), `afe05d8` (skill), `02f9e7a` (description/search fix).
- Nasazeno `rsync`, crontab řádek přidán (`* * * * * uv run …/note_compile.py`, každou minutu jako wiki/remind). Migrace: 17 aktivních notes → inbox seeding → první drain postavil `notes.md`. Starý `note.sqlite` → `backup/note-retired/`; stray `notes.db`/`note.py`/`__pycache__` smazány.
**Co fungovalo a proč:** První manuální drain 17 notes = **45 s**, `notes.md` s čistými sekcemi (DevOps, Chata, DT Glass, Work/ZOT…); **URL reálně stažena** (Jina Reader) — DT Glass expandováno na produkty s cenami/rozměry, obě URL bare na vlastních řádcích, žádná fabrikace. E2e přes reálného agenta: cron **auto-fire** (deferred zachyt zpracován do ~80 s), `/note <text>` **immediate** (routing → note skill, capture + inline compile → nová sekce), `/note search` (načte `notes.md`, odpoví). Vše zelené.
**Co zbývá / poznámky:** (1) **Kolize triggerů `note` vs `keep`:** NL „poznamenej si …" původně spadlo do `keep` (obě claimovaly „ulož si"). Fix: description zpřesněn (odstraněno „ulož si", přidán kontrast „NOT durable facts → keep"). Spolehlivý je explicitní `/note` prefix (ten routuje správně — ověřeno). Uživatel ať v Telegramu používá `/note …` / `/note cron …`. (2) Paywall detekce je prompt-driven, live paywall netestován (logika ověřena návrhem). (3) Compile běží na default presetu; volitelně jde přepnout na rychlejší. (4) Immediate režim blokuje tah 15100 s a roste s velikostí `notes.md` — proto `/note cron` pro dávkové syp­ání.
**Jak vrátit zpět:** Repo: `git revert 02f9e7a afe05d8` (nebo `git checkout afe05d8~1 -- skills/note/`). Server: `git`-verzi note skillu `rsync`-ni zpět + obnov data `mv ~/.nanobot/workspace/backup/note-retired/note.sqlite ~/.nanobot/workspace/db/`. Crontab: `crontab -l | grep -v note_compile | crontab -` (smaž i komentář). `notes/` (notes.md, inbox/done/hard) lze smazat — původní data jsou v `backup/note-retired/note.sqlite`.
## 2026-07-01 11:35 — note: mazání s odsouhlasením + git commit `notes.md` po každé změně
**Cíl:** Doplnit do přepsaného `/note` (viz 10:40) dvě chybějící provozní věci, na které uživatel upozornil: (1) **mazání poznámek** — s tvrdým pravidlem, že model před smazáním vždy ukáže přesný text a čeká na odsouhlasení; (2) **verzování `notes.md`** — po každé změně `git add notes/` + `git commit`. Plán: `~/.claude/plans/u-nove-vytvoreneho-skillu-declarative-unicorn.md`.
**Klíčové zjištění (proč to bylo potřeba):** Workspace `~/.nanobot/workspace/` **je** git repo, ale commituje ho **jen Dream** (`dream: periodic memory consolidation`) a ten se `notes/` dotýkat nesmí. Ověřeno na serveru: celé `notes/` bylo **untracked** (`?? notes/`) → `notes.md` se nikdy necommitovalo. Žádný existující skill vlastní commit nedělá → vzor nový.
**Rozhodnutí (dotazy na uživatele):** Mazání = **přirozeným popisem** (žádná display ID / `/note list` — sedí na prózový návrh). Před smazáním **hard-gate**: ukázat verbatim text + čekat na „ano". Commit = `git add notes/` + `git commit` (verzuje se celé `notes/`, jen `.compile.lock` do `.gitignore`). **Jeden committer per cesta, žádné dvojité commity:** inline `/note <text>` a mazání → agent přes `exec`; cron drain → **deterministicky v Pythonu** v `note_compile.py`. Sdílený *Compile workflow* zůstává bez commitu (commit je odpovědnost volajícího). Prefix `note:` (konzistentní s `dream:`).
**Co jsem udělal:**
- `SKILL.md`: do `description` přidány delete trigger fráze (`smaž poznámku o X`, `zapomeň X`, `uprav poznámku o X`); do `/note <text>` krok commit přes `exec`; sekce `Delete / edit` přepsána na tvrdý dvoukrok (najdi → **ukaž přesný text + čekej na potvrzení** → surgical `str_replace` + commit; totéž pro edit; pending zachyt = smaž soubor v `inbox/`); nová sekce `Versioning (git)`.
- `note_compile.py`: `commit_notes(count)` — po úspěšném drainu (za drženého locku, `.compile.lock` je gitignored → nestaguje se) `git status --porcelain notes/` → prázdné = no-op; jinak `git add notes/` + `git commit -m "note: cron drain (N captures)"`. Nikdy `git add -A` (Dream vlastní zbytek). Chyba commitu se jen loguje (drain už uspěl, nesmí se hlásit jako fail).
- Testy `test_note_compile.py`: +3 (`commit_notes` vytvoří commit / no-op při čistém stavu / respektuje gitignored lock), fixture dostal `WORKSPACE`. **19 zelených** lokálně.
- Server `.gitignore`: přidán `notes/.compile.lock` **před** deployem (živý pid-lock držený během commitu do gitu nepatří) — commit `ad3337f`.
- Deploy `rsync` (`--exclude __pycache__`), owner `nanobot:nanobot` ✓.
**Co fungovalo a proč:** **E2E cron cesta ověřena na reálné infře:** seed testovacího zachytu (unikátní marker `ZZZTESTVERIFY…`, přímo do `inbox/` — ne přes capture, aby nešpinil `note.log`) → cron tik do ~80 s → agent zařadil do `notes.md` pod sekci, launcher udělal commit `note: cron drain (1 captures)`, `git status notes/` **čistý** (`.compile.lock` se nepletl → gitignore OK), cron log `END … COMMIT notes/ (1 captures)`. Tento první commit navíc **bootstrapnul celý dosud untracked `notes/` strom** (notes.md + 18 done souborů) — přesně očekávané. `git` je `/usr/bin/git` v2.47.3, `tools.exec` má prázdné `allowPatterns`/`denyPatterns` (git neblokovaný) + reálný workspace commit prošel → exec-git i launcher jsou spolehlivé. Úklid **forward** (bez přepisu historie): odstraněn testovací řádek + done soubor, commit `chore: remove note commit verification test` — marker pryč, `## Nanobot` sekce nedotčená. Serverové commity: `ad3337f` (.gitignore), `cffe3b3` (drain — bootstrap notes/), `5ed6832` (cleanup).
**Co zbývá:** Inline `/note <text>` commit přes `exec` a **mazání s odsouhlasením** nebyly spuštěny živě (LLM-driven; mazání navíc čeká na interaktivní potvrzení → nejde smysluplně headless). Vysoká jistota ze statického ověření (git dostupný, exec bez denylistu, workspace commit funguje) — dořeší reálné použití přes Telegram (`/note …` / „smaž poznámku o …"). Poznámka: `git add notes/` verzuje i `done/` → historie poroste (dle přání uživatele; případné prořezání mimo rozsah).
## 2026-07-01 — note/keep/bookmark: zpřesnění description o vzájemné hranice (routing)
**Cíl:** Dispatcher nanobota má jednoznačně poznat, kam vstup patří — `note`, `keep` a `bookmark` mají překrývající se doménu (poznámka vs. fakt vs. URL na přečtení). Vykonání schváleného plánu: doplnit do `description` všech tří skillů explicitní hranice vůči sousedům.
**Co jsem zkusil:** Server byl před editací ověřen jako shodný s lokálem (žádný Dream zásah), takže editace šla přímo do lokálního repa bez tahání ze serveru. Tři exact-string úpravy `description`:
- `skills/keep/SKILL.md` — nově explicitně „durable personal facts, preferences, and decisions" + „NOT collecting notes, links, or articles (that is note)".
- `skills/note/SKILL.md` — poslední dva řádky přeformulovány na „reference material you file and later query" + „NOT durable personal facts, preferences, or decisions (that is keep), and NOT a bare URL you just want to read later (that is bookmark)".
- `skills/bookmark/SKILL.md` — doplněno „NOT filing or summarizing content into a knowledge base (that is note)".
**Co fungovalo a proč:** První pokus o editaci `note/SKILL.md` selhal (`old_string` s pouze poslední dvěma řádky nebyl v souboru nalezen), oprava — rozšířil jsem `old_string` o předchozí kontextový řádek, čímž byl znovu jednoznačný, a editace prošla; obsah po opravě odpovídal plánu přesně. Nasazeno `rsync -av` (celé adresáře, ne jen `SKILL.md`) na server přes IP `192.168.4.64` (hostname `nanobot.hell` se z tohoto stroje nerozeznává) pro všechny tři skilly. Vlastník na serveru ověřen `ls -l` — všechny tři `SKILL.md` `nanobot:nanobot`. Zpětné stažení serverových `SKILL.md` a `diff` proti lokálu — pro všechny tři soubory prázdný diff (plná shoda). Skilly se čtou při každém tahu bez restartu služby, takže zpřesnění je live okamžitě.
**Co zbývá:** Nic — nasazeno a ověřeno. Behaviorální potvrzení (že dispatcher skutečně routuje lépe na hraničních vstupech) přijde z reálného použití přes Telegram/WebUI.
**Jak vrátit zpět:** Repo: `git checkout HEAD~1 -- skills/note/` (vrátí SKILL.md + launcher + testy). Server: `rsync` starší verzi skillu zpět; `git.gitignore` řádek `notes/.compile.lock` odstranit + `git revert ad3337f`. Feature je aditivní — necommitování `notes.md` je návrat k původnímu chování (Dream se `notes/` stejně nedotýká).
## 2026-07-01 12:00 — note: bug při `/note delete` (hledal v SQL DB, neptal se, mazal done) → hardening SKILL.md
**Cíl:** Uživatel po nasazení mazání hlásil, že `/note delete zot dokumentace` (a) se na nic nezeptalo, (b) smazalo i soubor v `done/`, (c) agent se to snažil hledat v SQL DB. Najít příčinu (systematic-debugging), pak opravit.
**Co jsem zjistil (z reálné stopy `sessions/websocket_*.jsonl`):** Přesná sekvence tahu: agent paralelně `read_file SKILL.md` **a zároveň** `grep "zot" v db/` → `exec sqlite3 db/notes.sqlite "SELECT … FROM notes WHERE …"` (tím **vytvořil prázdný `db/notes.sqlite`**) → `.tables` → `find_files db` → teprve pak `grep *.md` → našel v `notes.md` + `done/…zot.md` → `edit_file` + `rm done` **bez potvrzení**. Reasoning: *„Let me check the notes database more carefully."* **Příčiny:** (1) model má silný prior „`/note delete` = smaž řádek v notes DB" (chování STARÉHO SQLite note skillu) + `AGENTS.md` konvence „SQLite do `db/*.sqlite`"; (2) `SKILL.md` **nikde nezakazoval** DB/sqlite a **nedokumentoval `/note delete`/`/note edit` jako příkazy** → model spadl do starého vzoru; (3) potvrzovací brána byla jen „prozaická", ne tvrdá STOP jako u `/note cron`. **NENÍ to stará paměť:** `MEMORY.md` note nezmiňuje, `history.jsonl` (kde stará „note=sqlite" lekce je, ř. 344) agent per-tah nečte (jen Dream) — je to prior modelu + slabý model (kimi/glm třída).
**Fix (SKILL.md hardening, per rozhodnutí uživatele „done/ nechat jako interní"):**
- **Anti-DB callout** v Architecture: „No database — ever … never search `db/`, never run `sqlite3`, never create/open `.sqlite`/`.db`; AGENTS.md SQLite konvence se na notes nevztahuje; starší verze DB měla, je pryč." Přímo proti krokům ze stopy.
- **Delete/edit přepsán** na `## /note delete <query> / /note edit <query>` (mapování na příkaz, který uživatel reálně napsal) + **tvrdá dvou-tahová STOP brána** (vzor z `/note cron`, který prokazatelně funguje): Turn 1 = najdi **jen v `notes.md`** (zákaz db/sqlite/inbox/done/hard) + ukaž verbatim text + **STOP the turn** (zakázáno `str_replace`/`rm`/`git`); Turn 2 = teprve po explicitním „ano" smaž + commit. **Mazání se dotýká jen `notes.md`, NE `done/`** (breadcrumb je interní plumbing).
- Úklid: smazán prázdný `db/notes.sqlite` (0 B, artefakt chybného SQL pokusu) — jeho existence agenta utvrzovala v mylné víře (`find_files db` → notes.sqlite).
**Co fungovalo:** Deploy `rsync` (jen SKILL.md), owner `nanobot:nanobot` ✓, oba fixy v nasazeném souboru ověřeny grepem. Skill se čte per-tah bez restartu → příští `/note delete` běží na opravené verzi.
**Co zbývá:** Behaviorální potvrzení dělá uživatel (příští `/note delete` musí: hledat jen v `notes.md`, ukázat text, zastavit a čekat, nesahat na `done/`). U slabšího modelu STOP brána riziko silně snižuje, ale negarantuje 100 %. Poznámka: smazaná poznámka „ZOT dokumentace" byla reálná (ne testovací) — jde obnovit z gitu (`git show cffe3b3:notes/notes.md`), nabídnuto uživateli.
**Jak vrátit zpět:** Repo `git checkout HEAD~1 -- skills/note/SKILL.md`, pak `rsync` na server. (Aditivní/behaviorální změna — revert jen oslabí brány, nerozbije data.)
## 2026-07-01 12:15 — note: čeština v SKILL.md + zbytečné `/note` varianty v description
**Cíl:** Uživatel v IDE otevřel `skills/note/SKILL.md` a upozornil na dvě věci: (1) v souboru je čeština, ačkoli EN-only pravidlo je explicitní v projektovém `CLAUDE.md`; (2) proč description obsahuje `/note X`/`/note cron X` varianty, když nanobot ví, jak skill spustit. Požádal i o celkovou kontrolu kvality promptu.
**Co jsem zjistil:** Čeština byla na 4 místech (description trigger fráze + 3 místa v těle — cron potvrzení, delete/edit triggery, potvrzovací slova Turn 2). Pro otázku 2 jsem našel přímý podklad v `knowledge.md` („Skill description — k čemu reálně slouží"): description je čistě routing signál (kdy/proč, ne jak) a viditelný jako jeden řádek `- **note** — <description>`, kde jméno skillu je vždy vidět — `/note X` a `note X` tedy nic nerozlišují nad rámec samotného jména, a `/note cron X` je navíc detail subakce („jak"), ne routing info. Sesterský `bookmark` (upravovaný ve stejném committu jako `note`) tento princip u slash-forem už dodržuje.
**Co fungovalo a proč:** Oprava description (odstraněny CZ fráze + `/note X`/`/note cron X`, zachována jen anglická disambiguace od `keep`/`bookmark`) + 3 CZ místa v těle převedena na anglické ekvivalenty + doplněn jazykový tag ```text``` k fencovanému stromu adresářů (chybějící dle `markdown-style.md`). Uživatel si mezitím sám upravil první větu description na „Capture notes, texts, ideas…" — necháno beze zásahu. Vedlejší nález: `fix-markdown.ps1` hook po Edit tahu měnil emphasis z hvězdiček na podtržítka (`*only*`→`_only_`, `*Versioning*`→`_Versioning_`) — v rozporu s `markdown-style.md` („Never underscores"); opraveno zpět, hook to už neměnil zpátky. Nasazeno na server: hostname `nanobot.hell` se z tohoto stroje nerozeznává (`Could not resolve hostname`), IP `192.168.4.64` z `CLAUDE.md` funguje. Server byl před přepisem ověřen jako identický s předchozím lokálem (žádný Dream zásah) — `rsync -av` bezpečný. Po nasazení `diff` server↔lokál prázdný, owner `nanobot:nanobot` ✓.
**Co zbývá:** Stejný „zbytečná /command varianta" vzor mají i `keep` (`/keep X`), `remind` (`/remind`) a `plan` (`/plan X`) — mimo scope tohoto zadání (uživatel se ptal jen na `note`), zmíněno uživateli jako volitelný follow-up, nikam nezapsáno jako todo (čeká na rozhodnutí, jestli o to stojí).
**Jak vrátit zpět:** Repo `git checkout HEAD~1 -- skills/note/SKILL.md`, pak `rsync` na server (IP `192.168.4.64`).
## 2026-07-01 19:33 — note: doplnění diakritiky při reformulaci + echo přesného uloženého textu
**Cíl:** Uživatel chtěl dvě vylepšení `/note`: (1) když napíše text bez diakritiky, skill má diakritiku při reformulaci doplnit; (2) u okamžitého režimu (`/note <text>`) má potvrzovací zpráva ukázat přesný zapsaný text, ne jen název sekce. Přes plan mode (Explore + diff proti serveru) potvrzeno, že jde jen o immediate mode — `/note cron` v daném tahu nic nereformuluje, takže echo tam nedává smysl.
**Blocker a jeho vyřešení:** SSH na `nanobot.hell` (pod `nanobot` i `root`, stejný klíč) nejdřív padalo na `Permission denied (publickey,password)` a lokálně chyběl `rsync` — sandbox tohoto sezení neměl potřebný klíč/nástroj. Uživatel obojí mimo tuto session doplnil („uz funguje, opravil jsem to" / „uz jsem dodal i rsync"), pak SSH i rsync fungovaly bez úprav z naší strany.
**Co fungovalo a proč:** V `skills/note/SKILL.md` (1) bod „Reformulate" v Compile workflow rozšířen o instrukci: text bez diakritiky (příklad „kdyz uz to psal bez hacku") se při reformulaci opraví na spisovnou diakritiku, už-diakritizovaný a ne-český text se nechá být; (2) krok 5 v `/note <text>` nahrazen tak, aby kromě sekce citoval i doslovný zapsaný text (verbatim, ne převyprávění). Před přepisem staženo `rsync` ze serveru a porovnáno s lokálem — server měl starší, ale skriptově identickou verzi (chyběly jen předchozí neasazené kosmetické commity popisu, žádný Dream zásah) → bezpečné nahradit celý adresář. Nasazeno `rsync -av skills/note/` (celý adresář, vč. `scripts/`, `tests/`), owner `nanobot:nanobot` ověřen.
**Co zbývá:** Ruční test v chatu — `/note psal jsem bez hacku a carek` (diakritika v `notes.md`) a `/note <krátký text>` (potvrzení obsahuje přesný zapsaný text). Po ověření přesunout z `todo.md`/nezapsáno jako todo, protože šlo o přímé zadání bez čekání ve frontě.
## 2026-07-02 15:25 — bookmark: archivace plného textu článku jako čistý markdown
**Cíl:** Uživatel se zeptal, co skill `bookmark` udělá, když vloží URL a k tomu celý zkopírovaný článek (Ctrl+A/Ctrl+C). Zjištěno, že skill neměl žádné pravidlo — celý neočištěný článek by teoreticky přistál do `description` (`TEXT` bez limitu) a rozbil formát výpisů. Zadání: skill má rozpoznat plný článek, vyčistit ho (navigace, menu, reklamy, patičky), převést na markdown a uložit; `description` si dogenerovat sám, pokud ho uživatel neuvede.
**Klíčové zjištění (přes plan mode, 2× Explore agent):** Novou pip závislost (markdownify/trafilatura) na server **nedostanu** — bwrap sandbox na bare-metal `nanobot.hell` nemá přístup k uv cache/data adresářům (`knowledge.md` ř. 615634), balíčky by při stahování padaly. Proto **čištění a převod do markdownu dělá agent (LLM)** na úrovni instrukcí v `SKILL.md`, skript zůstává **stdlib-only**. Precedent pro echo uloženého textu už existuje v `/note` (commit 739b9c8) — přejat.
**Co fungovalo a proč:**
- `bookmark.py`: schéma rozšířeno o nullable `content TEXT` + idempotentní migrace v `_init_db` (`PRAGMA table_info` → `ALTER TABLE ADD COLUMN`, protože `CREATE IF NOT EXISTS` sloupec do existující DB nepřidá). `add --content-file <path>` (`-` = stdin, agent volá přes heredoc `<<'ARTICLE'` — jedno atomické volání, žádné shell-escapování). Nový `content <id>` vypíše uložený markdown. 📄 marker v `list`/`history`/`show` u bookmarků s obsahem (SELECT tahá `content IS NOT NULL AS has_content`, ne celý blob).
- Migrace ověřena na **kopii reálné serverové DB** (11 řádků) před nasazením — všech 11 řádků zachováno, `content` přidán jako NULL. ALTER ADD COLUMN je nedestruktivní.
- Testy `skills/bookmark/tests/test_bookmark.py` (6×, vzor z `note`): migrace legacy DB, add s/bez obsahu, `content` příkaz, 📄 marker. Všechny zelené.
- `SKILL.md`: nová sekce „Saving an article's full text" (detekce + čištění + heredoc + echo verbatim), pravidlo pro URL bez textu (zkus `web`/Jina Reader, ale paywall/nesmysl NEUKLÁDAT, `⚠ paywall/incomplete`, nefabrikovat), `content` příkaz, marker v Output formatting, rozšířený Workflow.
- Před zásahem dotažena serverová drift úprava frontmatteru (`NOT ... (that is note)`) do repa (samostatný commit) — server byl zdroj pravdy.
- Nasazeno `rsync -av skills/bookmark/`, owner `nanobot:nanobot` ✓ (omylem nahrané `__pycache__` uklizeny na serveru i lokálně). End-to-end na serveru přes heredoc: obsah uložen, 📄 naskočil, `content 1` vypsal přesný markdown vč. zachované diakritiky. Testovací řádek pak smazán z reálné DB přes `sqlite3` (skill nemá delete příkaz) → zpět na 11 řádků.
**Co zbývá:** Behaviorální ověření dělá uživatel v chatu — vložit URL + článek a zkontrolovat, že agent text vyčistí, uloží a odcituje verbatim úsek; otestovat i URL-only (fetch) a paywall (neuloží obsah). Známé omezení: `content <id>` čte jen z **unread** setu (jako `show`) — obsah už přečtených bookmarků z `history` zatím nedostupný.
**Jak vrátit zpět:** Repo `git checkout HEAD~1 -- skills/bookmark/` + `git rm skills/bookmark/tests/`, pak `rsync` na server. Migrace DB je aditivní a nedestruktivní — `content` sloupec může na serveru zůstat (ignoruje ho starší verze skriptu). Data v `content` by revert skriptu zneviditelnil, ne smazal.
## 2026-07-02 17:00 — bookmark: soft delete, HTML→markdown utilita (trafilatura), fetch-fail výzva
**Cíl:** Tři navazující požadavky na `bookmark` (přes plan mode, opakované AskUserQuestion): (A) když se nepodaří stáhnout obsah URL, agent si má o vložení článku sám říct; (B) delete pro mazání článků (omylem vložený duplikát); (C) když je vložený obsah HTML, převést ho na markdown Python utilitou, ať to nedělá drahý model.
**Klíčová oprava předpokladu:** U hlavní feature (viz záznam 15:25) jsem tvrdil, že bwrap sandbox nepustí pip závislost. **Ověřeno naživo — na `nanobot.hell` to neplatí:** produkční `exec` běží `"sandbox": ""` (bez bwrap), `uv` + PyPI fungují, `uv run --with trafilatura` stáhne a naimportuje (v2.1.0). Jiné skilly reálně jedou se závislostmi (remind→croniter, llm-wiki→pyyaml). Bwrap limit z `knowledge.md` ř. 615634 platí jen pro nasazení se zapnutým sandboxem. → doplněna korekce do `knowledge.md`.
**Rozhodnutí uživatele (AskUserQuestion):** delete = **jen soft delete** (flag `deleted_at`, skryje z výpisů, řádek zůstává v DB; **bez** restore/deleted příkazů) + **potvrzení před smazáním** (řeší agent). HTML převod: uživatel navrhl markitdown; ověřeno ze zdrojáku, že markitdown pro HTML = markdownify (převede celé `<body>`, **neodstraní boilerplate**) + těžší (magika/onnxruntime) → dominovaná. Zvolena **trafilatura** (odstraní boilerplate A vrátí markdown v jednom kroku). Plain text: model čistí **jen při zjevném balastu**. Detekce HTML: **rozhodne agent**.
**Co fungovalo a proč:**
- `bookmark.py`: `deleted_at TEXT` + migrace (idempotentní ALTER, vzor jako `content`); `AND deleted_at IS NULL` do obou větví `_ordered_ids` + přímých SELECTů v `list`/`history` (žádný „subset" refaktor — unread/read je jen negace `read_at`, na připomínku uživatele); nový `cmd_delete` (soft, resolve z unread setu). Potvrzení je záměrně na úrovni agenta (SKILL.md), ne skriptu.
- `html_to_markdown.py`: nový PEP 723 helper (`dependencies=["trafilatura"]`, shebang `uv run --script`), stdin HTML → stdout markdown; prázdný vstup exit 1, nic neextrahováno exit 2. Agent ho **pipe-uje** do `bookmark.py add … --content-file -` → převedený markdown teče shellem, **neprochází kontextem modelu** (levné). Když trafilatura nic nevrátí → prázdný content → NULL → fallback na čištění modelem.
- `SKILL.md`: rozcestník HTML (pipe helper) vs plain text (uložit rovnou, čistit jen při balastu); delete + potvrzovací flow + display-ID caveat; při paywallu/nezdaru stažení agent **požádá uživatele o vložení článku** místo tichého fallbacku.
- Testy (10× zelené): soft delete (skryje, řádek zůstává), delete out-of-range, migrace `deleted_at`, `html_to_markdown` extrakce (boilerplate pryč, `#`/`##` nadpisy) + prázdný vstup. Lokálně ověřeno i na realistickém HTML (nav/reklama/patka odstraněny) a exact pipe konstrukt.
- Migrace `deleted_at` ověřena na kopii reálné serverové DB (16 řádků zachováno, NULL). Nasazeno `rsync -av --exclude=__pycache__`, owner `nanobot:nanobot`. E2E na serveru: HTML pipe → `content 1` vrátí čistý markdown; `delete 1` → soft (`deleted_at` set, řádek v DB). Testovací řádek pak hard-smazán přes `sqlite3` → zpět 16.
**Co zbývá:** Behaviorální ověření uživatelem v chatu (agent má u HTML pipe-ovat helper, u plain textu čistit jen při balastu, u paywallu požádat o vložení, před delete potvrdit). Známá omezení: `delete`/`content` jen z unread setu (`list`), ne z `history`; žádný restore (soft-smazané jen ručně přes DB); trafilatura potřebuje realistickou stránku — na drobném snippetu boilerplate nepozná (fallback na model to řeší).
**Jak vrátit zpět:** Repo `git revert` skill committu + `rsync` na server. Migrace (`deleted_at`) aditivní/nedestruktivní — sloupec může na serveru zůstat. Soft-smazané řádky mají `deleted_at` set; revert skriptu je zase zviditelní (data se neztratí).
## 2026-07-06 — remind: deterministická pojistka proti duplicitnímu textu + steering
**Cíl:** `/remind` občas místo jednoho reminderu s víc časy založil víc samostatných reminderů se stejným textem. Nanobot (Dream) na to reagoval přidáním jednoho příkladu do serverového `SKILL.md`. Uživatel se ptal, jestli to stačí — po rozboru padlo rozhodnutí přidat i tvrdou pojistku: jeden text nikdy nepotřebuje víc záznamů, takže `add`/`edit` mají duplicitu odmítat chybou.
**Důkaz příčiny (ne bug v kódu):** Serverový `log/reminder.log` — „UFO burger" `id=46` (18:00) + `id=47` (07:00) 7 s po sobě (split jednoho zadání); dřív totéž „note #work" `id=41/42/43`. CLI mechanismus byl přitom vždy správný: `--at`/`--cron` jsou `action="append"`, `insert_schedules` vloží všechny naráz → `add --at X --at Y` = jeden reminder, dva časy. Příčina = chování agenta, který zadání rozdělil do víc `add` volání.
**Co jsem udělal:**
- `store.py`: `find_active_by_exact_text(conn, text, exclude_id=None)` — porovnání Python-side `casefold()` na trimnutém textu (ne SQL `lower()`, to v SQLite nefolduje unicode diakritiku → „Čaj"/„čaj" by prošlo).
- `remind_cli.py`: helper `_reject_duplicate_text` (vrací `{"error":"duplicate text", display_id, hint}` na stderr, exit 1). Zapojen v `cmd_add` (před transakcí) a `cmd_edit` (při změně textu, `exclude_id=rid` → přejmenování na vlastní text projde). Hint navádí na recovery po splitu: `edit --id N --replace-schedules` se všemi časy.
- `SKILL.md`: do mapovací tabulky přidán cron multi-time řádek (`--cron … --cron …`), zachován serverový `--at`/`--at` příklad (dotažen do repa); do Behavioral contract explicitní pravidlo „jeden text = jeden reminder".
- Testy: 3 nové (`test_add_rejects_duplicate_text`, `test_add_duplicate_case_and_diacritics_insensitive`, `test_edit_text_collision_rejected`); 2 existující dup testy (`test_remove_by_id_disambiguates_duplicates`, `test_ambiguous_keyword_returns_display_ids`) staví duplicitu přes nový helper `_seed_duplicate` (přímý `store.insert_reminder`, obchází guard) — disambiguace pre-existujících dupů zůstává validní.
**Co fungovalo a proč:** 83 testů zelených. Lokální smoke (temp `REMIND_DB`): add#1 OK, add#2 stejný text → odmítnut (exit 1, display_id, hint), správná cesta `add --at … --at …` → jeden reminder se dvěma `at`. Před deployem ověřeno, že serverové skripty == git HEAD (žádná autonomní divergence Dreamem), SKILL.md se lišil jen o ten jeden `--at` řádek (dotažen). Nasazeno `rsync -av skills/remind/`, owner `nanobot:nanobot` ✓. Server == lokál u všech změněných souborů. Na serveru jsem mutace nespouštěl (`log_operation` ignoruje `REMIND_DB` → zapsalo by do reálného `reminder.log`).
**Co zbývá:** Zápis do `decisions.md` (návrh připraven, čeká na formulaci autora — ruší dosavadní rámování „duplicitní texty povoleny by design"). Behaviorální ověření dělá uživatel v chatu.
**Jak vrátit zpět:** Repo `git revert 61e7068` + `rsync` na server. Změna je čistě logika CLI (žádná migrace DB), revert je bezpečný.
## 2026-07-15 — sync skillů ze serveru: bookmark autonomně upravený Dreamem
**Cíl:** Uživatel požádal o obecný sync skillů ze serveru do repa (bez upřesnění, který). Podle pravidla v `CLAUDE.md` („server je zdroj pravdy") jsem prošel checksumem všechny lokální skilly proti serveru před jakýmkoli přepisem.
**Co jsem zkusil:** `rsync --dry-run --checksum --itemize-changes` pro každý skill (`article`, `bash`, `bookmark`, `deep-research`, `detach`, `grill-me`, `keep`, `llm-wiki`, `note`, `plan`, `python`; `remind` ověřen zvlášť plným stažením a `diff -rq`).
**Co fungovalo a proč:** `remind` beze změny (server == lokál, potvrzuje předchozí záznam). `detach` a `llm-wiki` hlásily rozdíl jen v mtime (`.f..t......`, bez `c`/`s`) → obsah identický, ignorováno. `bookmark/SKILL.md` měl skutečný obsahový rozdíl (`>fcst......`) — server má autonomní úpravu (pravděpodobně Dream): (1) přehozené pořadí ve formátu výpisu — `<description>` teď před `[<tags>]`, ne za nimi; (2) nová sekce „Known limitations" (chybí `edit`/`update`, `delete` je jen soft-delete, weby za Cloudflare nejdou auto-fetchnout). Stáhl jsem přesně tuto verzi do `skills/bookmark/SKILL.md`, ověřeno `diff` na 100% shodu.
**Co zbývá:** Server != repo u `keep` (SKILL.md) — dry-run to ukázal, ale nebylo součástí zadání, needitoval jsem. Zvážit v příští session. Drobná nekonzistence, kterou sync odhalil a nechal beze změny: příklad výpisu na řádku `#3 [hackaday.com](...) [linux, strace] — lepší strace` v `bookmark/SKILL.md` neodpovídá nově popsanému formátu (description by měl být před tagy) — je to takto i na serveru, nešlo o repo vs. server rozdíl, takže jsem to nepravil bez zeptání.
## 2026-07-15 — compact-memory-auto-daily: diagnóza "podivných hlášek" na Telegramu + migrace na crontab s čerstvou session
**Cíl:** Uživatel hlásil podivné/nesmyslné hlášky na Telegramu z posledních dní; v noci má běžet `compact-memory` z cronu, ale něco je špatně. Nejdřív jen prozkoumat (bez zásahu), pak — po potvrzení, že denní doručení je záměr — opravit kontaminaci kontextu.
**Diagnóza (nainstalovaný `nanobot-ai` v0.2.2):** Cron job `compact-memory-auto-daily` (`jobs.json`, id `69484aa6`) byl `agent_turn` s `payload.session_key = "telegram:8826147089"` — **stejná trvalá session jako běžný Telegram chat** (potvrzeno v `sessions/telegram_8826147089.jsonl`: 75/77 zpráv obyčejná konverzace, jen 2 cron marker). `is_bound_cron_job`/`run_bound_cron_job` (nahrazuje starý "hardcoded reminder preamble + evaluate_response" mechanismus popsaný výše v knowledge.md pro starší verzi) doručuje odpověď **bez jakéhokoli gatingu** — cokoli agent v tahu napíše, jde přímo do Telegramu. Navíc `origin_delivery_context` (kam se doručí) je **nezávislé** na `session_key` (do jaké session/historie se zapíše) — to je klíč k opravě. 2026-07-14 se agent v cron tahu spletl a pokusil se spustit neexistující `scripts/compact_memory.py` (skill ho nikdy neměl mít), napsal do chatu zmatenou zprávu, uživatel ho opravil ve stejné session — a protože session je trvalá, ten omyl kontaminoval i další noci (přesně text, co uživatel citoval jako "podivnou hlášku", je z nočního běhu 2026-07-15 02:00).
**Řešení (schváleno přes plan mode):** Migrace na vzor `/remind`/`detach` — systémový crontab + samostatný skript mimo nanobot `cron/jobs.json`, s **unikátní `session_key` per běh** (nikdy znovu použitý → nikdy žádná akumulace historie), doručení přímo přes Telegram Bot API (mimo nanobot channel pipeline).
**Co jsem udělal:**
- Stáhl `skills/compact-memory/SKILL.md` ze serveru do repa poprvé (skill dosud nebyl trackovaný lokálně) + přidal větu "This skill has no accompanying script — every step below is performed by you, the agent..." — cílená prevence přesně té 07-14 halucinace.
- Nový `skills/compact-memory/scripts/compact_memory_auto.py` (PEP 723, vzor `tasks-daemon.py`/`remind_send.py`): `Nanobot.from_config().run(GOAL, session_key=f"compact-memory-auto:{timestamp}")` s 10min timeoutem, výsledek poslán přímo `urllib` na Telegram Bot API (token/chat_id z `config.json`, stejný `_telegram_config()` vzor).
- Nasazeno `rsync -av skills/compact-memory/` (celý adresář), owner `nanobot:nanobot` ✓.
- Přidán řádek do system crontabu (`0 2 * * * uv run .../compact_memory_auto.py >> log/compact_memory_auto_cron.log`), stávající 3 řádky (remind/wiki-compile/note-compile) nedotčené.
- Odebrán job `69484aa6` z `jobs.json` (in-place Python edit, bez restartu — zbylé 3 joby beze změny).
- Opravena zastaralá věta v serverovém `memory/MEMORY.md` (psala, že job běží v Telegram session — teď už neplatí).
**Co fungovalo a proč:** Ruční test skriptu na serveru proběhl čistě (exit 0): reálný audit MEMORY.md (32 řádků), skutečně smazal 1 duplicitu a sloučil 2 dvojice, zapsal zálohu do `backup/2026-07-15_0541_memory.backup.md` i do `log/memory-clean.log`, report doručen do Telegramu. Ověřeno, že vznikla **nová izolovaná** session `sessions/compact-memory-auto_20260715-054147.jsonl` a `telegram_8826147089.jsonl` zůstal nedotčený (78 řádků beze změny) — potvrzuje, že `session_key` a `origin_channel`/`origin_chat_id` jsou v `nanobot-ai` 0.2.2 opravdu nezávislé, přesně jak ukázala explorace zdrojáku.
**Co zbývá:** Počkat na první reálný noční běh (příští 2:00) a ověřit stejné chování automaticky. Navrhnout uživateli větu do `decisions.md` (čeká na formulaci autora): "Nanobot `agent_turn` cron joby pro neinteraktivní/background úlohy vždy mimo `jobs.json`, přes systémový crontab + přímé `Nanobot.run()` s per-run unikátní `session_key` — stejný vzor jako `/remind` a `detach`." Zvážit doplnění knowledge.md o `is_bound_cron_job`/`origin_delivery_context` mechanismus 0.2.2 (nahrazuje starší popsaný "hardcoded preamble + evaluate_response" — ten je teď verzí-specifický/zastaralý).
**Jak vrátit zpět:** V `jobs.json` přidat job `69484aa6` zpět (zálohovaný obsah je v git historii commitu před touto změnou), odebrat řádek z crontabu (`crontab -e`), smazat `skills/compact-memory/scripts/compact_memory_auto.py`. Migrace `MEMORY.md` (smazaná duplicita, sloučené bullet) je nedestruktivní — plný snapshot je v `backup/2026-07-15_0541_memory.backup.md` na serveru.
## 2026-07-22 — nový skill `project` (nahrazen netrackovaný server-side skill, migrace `radio-1`)
**Cíl:** Uživatel chtěl nový skill `project`, analogii Claude.ai "Projects" — pojmenované dlouhodobé pracovní kontexty s vlastním promptem, historií a artefakty. Plánováno v plan mode: prozkoumáno 13 existujících skillů + `knowledge.md` pro konvence (`description` = jen routing signál s "Triggers on:", cesty workspace-relativní, žádný sticky session-state u nanobota). Uživatel v review odmítl jmenovat konkrétní jiné skilly v description/těle (odlišení jen funkčně) a trval na tom, že skill smí zakládat nový projekt jen po explicitním potvrzení, nikdy automaticky. Přidán čtvrtý soubor `state.md` (živý, přepisovaný dokument "kde to teď je") vedle append-only `memory.md`.
**Co jsem zkusil:** Před nasazením (pravidlo "server je zdroj pravdy") jsem ověřil `~/.nanobot/workspace/skills/project/` na serveru — **a objevil tam už existující, plně funkční `project` skill, nikde v tomto repu netrackovaný** (žádná zmínka v `history.md`/`knowledge.md`/`decisions.md`, žádná git historie k `skills/project/`). Starý design: plochý soubor `projects/<slug>.md` s YAML frontmatterem (`status: active|paused|done`, `priority: high|medium|low`), tělo se sekcemi `## Poznámky`/`## Další krok`, CLI backend `scripts/project.py` (add/list/show/status), `switch` ukládající aktivní projekt do `my(action="set", key="project_context", ...)` scratchpad nástroje, frontmatter měl i `aliases: [proj]`. Existovala reálná data: `projects/radio-1.md` (založeno 2026-06-09, projekt na stříhání audio streamu Radia 1).
**Co fungovalo a proč:** Zastavil jsem se a nechal uživatele rozhodnout (AskUserQuestion) — odpověď: přepsat novým designem, `radio-1` migrovat na `radio1`. Provedeno: `rsync -av --delete skills/project/ nanobot@...` (smazal starý `scripts/project.py`, nahradil `SKILL.md` novým), obsah `radio-1.md` ručně rozparsován do nového formátu — dated bullet z `## Poznámky` → `projects/radio1/memory.md`, cíl+stav+priorita syntetizovány do `projects/radio1/state.md`, `prompt.md` prázdný (nic k migraci), `artifacts/` prázdný adresář — nahráno přes `rsync` do `~/.nanobot/workspace/projects/radio1/`, starý `projects/radio-1.md` smazán. Ověřeno vlastnictví `nanobot:nanobot` u všech nových souborů/adresářů.
**Co zbývá:** Ověřit na živém chatu, že skill funguje end-to-end (aktivace, switch, ukončení, listing, zápis do `memory.md`/`state.md`) — zatím jen nasazeno a data migrována, ne vyzkoušeno v konverzaci. Zvážit zápis do `knowledge.md`: nanobot má `my(action="set"/"get", key=..., value=...)` scratchpad nástroj pro perzistentní stav napříč tahy (starý skill ho používal pro `project_context`) — objeveno náhodou, nebylo součástí dřívějšího průzkumu "nanobot nemá sticky session-state"; stálo by za ověření zdrojáku, jak přesně `my` funguje (per-session, nebo globální — starý skill sám poznamenává "lost on restart", žádnou zmínku o per-session scoping). Navrhnout `decisions.md` zápis (viz níže, čeká na souhlas autora).
**Jak vrátit zpět:** Stará verze skillu (`SKILL.md` + `scripts/project.py`) je stažená v `tmp/server-project-check/` (negitované, lokální). Obsah `radio-1.md` je beze změny zachycen ve stejném adresáři přes `git show`/`cat ~/.nanobot/workspace/projects/radio1/memory.md`+`state.md` (sloučit zpět ručně). Žádná záloha stažené staré verze v gitu — je jen v `tmp/`, které je gitignored; pokud má mít trvalou stopu, je potřeba ji dodatečně zkopírovat mimo `tmp/`.
## 2026-07-22 — nový projekt `life` založen na serveru
**Cíl:** Uživatel chtěl na serveru přes skill `project` založit dlouhodobý projekt `life` — osobní průvodce/deníček k udržitelnému životnímu stylu (cvičení, běh, jídelní návyky, skloubení s rodinou a dítětem), kam si bude průběžně zapisovat úvahy. Primární cíl projektu: změna váhy + dobrá kondice.
**Co jsem zkusil:** Ověřil stav serveru (skill `project` nasazený a identický s repem, existoval jen `radio1`, žádný `life`). V plan mode přes AskUserQuestion vyjasnil scope. Rozhodnutí uživatele: jídlo řešit jako návyky (večerní jedení, sladké), NE počítat kalorie ani zapisovat jídlo (kalorické tabulky má stranou); oblasti navíc = alkohol + protahování/mobilita (dělá po běhu, chce integrovat pravidelněji); cvičení (vlastní váha/kettlebell) i běh zatím nepravidelné; pokrok sleduje denním vážením + vnímanou kondicí a bolestmi; role agenta = průvodce/deníček, přístup se bude zpřesňovat; do `artifacts/` jdou prohřešky a reálné překážky.
**Co fungovalo a proč:** Připravil 4 soubory lokálně ve scratchpadu a nasadil `rsync -av scratchpad/life/ nanobot@nanobot.hell:.../projects/life/` — `prompt.md` (kontext + oblasti + kam co zapisovat + „první session" checklist), `state.md` (skeleton výchozího stavu/cílů), `memory.md` (první záznam se založením), `artifacts/prekazky-a-prohresky.md` (prázdný starter). Ověřeno: strom 4 soubory + adresář, vše `nanobot:nanobot`. Skilly/projekty se čtou bez restartu služby.
**Co zbývá:** Vyzkoušet v živém chatu aktivaci („projekt life" → načtení `prompt.md`). Výchozí čísla (aktuální váha, časový rozpočet, cílová váha/horizont) doplní uživatel v první session — záměrně nevyplněno (skill: nefabrikovat obsah). Projekt žije jen na serveru, do repa se necommituje (stejně jako `radio1`).
---
## 2026-07-25 — compact-memory: zkrácení nočního reportu (jeden řádek při nule) + append-only fix logu
**Cíl:** Noční běh `compact-memory` (crontab `0 2 * * *`) posílal na Telegram 7001300 znaků i v nocích, kdy nic nesmazal. Uživatel: při nule stačí věta „nebylo nic k smazání", při reálné změně stačí *co* bylo smazáno/sloučeno a *proč*. Frekvence zůstává.
**Diagnóza:** Server = repo (`diff` čistý, žádný drift). Poslední 4 noci (07-22…07-25) všechny `deleted: 0, merged: 0` a přesto narativní walkthrough sekce po sekci + statistický code block + „Borderline kept". Čtyři zdroje, všechny předepsané:
1. `SKILL.md` Auto mode krok 4 — „Print a detailed list of changes."
2. `SKILL.md` Output format → Auto mode report — vynucený blok `deleted/merged/unchanged` + sekce `Details:`, kterou agent vyplnil i při nule.
3. `SKILL.md` Rules — „list the borderline items you kept in the report" (vyrobilo dvoubodový seznam 07-25).
4. `GOAL` ve skriptu — žádné omezení výstupu + formulace jako uživatelský příkaz → agent si to vyložil jako explicitní vyvolání („protože jsi explicitně vyvolal skill, dělal jsem full audit" — 3 ze 4 nocí).
**Co jsem zkusil:** `SKILL.md` Auto mode přepsán na dvě výstupní cesty (nula → jedna věta, stop, žádná záloha ani log; změna → záloha + řádek per položku s kategorií a důvodem), zrušeno hlášení borderline-kept a přidán explicitní zákaz narace auditu, `Output format` má teď dva auto tvary. `GOAL` doplněn o „unattended nightly run, not an interactive request" + převeden z implicitní konkatenace literálů na triple-quoted string (precedens `SCHEMA = """…"""` v `bookmark.py`/`db.py`). Interactive mode nedotčen. Nasazeno `rsync -av skills/compact-memory/` (celý adresář), owner ✓, bez restartu.
**Co fungovalo a proč:** Tři reálné běhy na serveru (exit 0):
- **Nula změn:** doručeno 56 znaků (`Memory compact: nothing to delete (MEMORY.md, 28 lines).`), žádná nová záloha, log nedotčen, `MEMORY.md` beze změny.
- **Se změnou** (reverzibilní test — vložena zjevně ephemeral odrážka do `MEMORY.md`): doručeno 165207 znaků = hlavička + jeden řádek `[ephemeral] "…" — důvod`, smazána právě ta odrážka, vznikla záloha `backup/2026-07-25_0640_memory.backup.md`, `MEMORY.md` se vrátil do původního stavu (`diff` vs. pretest kopie čistý až na doplněný trailing newline).
**Vedlejší nález a fix — agent přepisoval `memory-clean.log`:** V prvním testu změnové cesty log po zápisu **zmenšil** (246 → 196 B) — starý řádek z 07-19 zmizel. Tohle je stejná ztráta, jaká už 07-15 spolkla zápisy o reálném mazání; prózové „append-only" nestačí, když má agent `write_file`, a moje nová formulace („log obsahuje jen smazané položky") ho ještě pozvala nevyhovující řádek „uklidit". Fix: `SKILL.md` teď předepisuje **deterministický append přes `exec`** (`printf '%s\n' '…' >> log/memory-clean.log`), zakazuje `write_file`/`edit_file` na log a explicitně zakazuje mazat/reformátovat existující řádky, i když neodpovídají aktuálnímu formátu. Po redeployi třetí běh: 196 → 350 B, starý řádek zůstal ✓. (Oba záznamy skončily na jednom řádku, protože předchozí zápis neměl trailing newline — soubor teď newline má, takže se to samo srovná.)
**Co zbývá:** Zkontrolovat noční běh 2026-07-26 v `log/compact_memory_auto_cron.log` (řádek `Response to cli:user:` má být krátký).
**Jak vrátit zpět:** `git checkout ef0d6cc~1 -- skills/compact-memory/` + `rsync -av skills/compact-memory/ nanobot@nanobot.hell:/home/nanobot/.nanobot/workspace/skills/compact-memory/`. Testovací zásahy do `MEMORY.md` jsou už vrácené (agent smazal právě ty vložené odrážky); zálohy z běhů jsou v `backup/2026-07-25_06*`.
## 2026-07-26 — compact-memory: model navrhuje, skript validuje a aplikuje (konec prompt-compliance)
**Cíl:** Noční běh 2026-07-26 02:00 poslal na Telegram 1737 znaků narace („Now I have all four files. Let me audit MEMORY.md systematically…"), přestože včerejší oprava přesně tohle zakazovala. Uzavírá položku „zkontrolovat noční běh 07-26" z předchozího záznamu — a odpovídá na ni negativně.
**Diagnóza:** Skill na serveru identický s repem (`diff -ru` → IDENTICAL), takže žádný drift. V session `compact-memory-auto_20260726-020001.jsonl` je finální assistant zpráva 1737 znaků: celá úvaha jako text, na konci správný jednořádkový report. Skript posílal `result.content` beze změny → doručilo se všechno. Srovnání s včerejšími testy (56 a 165 znaků, 3 iterace, dávkové tool cally) vs. dnešek (8 iterací po jednom volání, soubory čtené přes `grep pattern="."`): stejný skill, stejný `GOAL`, stejný model `glm-5.1:cloud`, jiné chování. Závěr: formát výstupu nesmí viset na prompt-compliance, 3 čisté testy nedokazují nic.
**Co jsem zkusil (návrh schválen v plan mode, plán `~/.claude/plans/dobre-pojme-vylepsit-ten-buzzing-quasar.md`):** Obrácení zodpovědností — agent v nočním režimu **needituje nic** a vrací jen JSON change-set (`{"changes": [{op, category, original, new_text, reason}]}`), skript ho validuje, aplikuje, zálohuje, loguje a **sám skládá českou zprávu**. Klíčové volby: `original`/`new_text` jsou **pole řádků** (ne string s `\n` — tam modely kazí escapování); položky se identifikují **doslovným textem, ne čísly řádků**, a blok se musí v souboru vyskytovat právě jednou (halucinovaný citát spadne na validaci místo aby smazal špatný řádek); cílový soubor je **konstanta ve skriptu**, ne pole v JSON (uživatelova připomínka: pole `file` je zbytečná ceremonie, když je whitelist jednoprvkový); limity (`reason` ≤ 120 znaků, `new_text` ≤ 3 řádky/300 znaků, `original` ≤ 20 řádků, smazání < 50 % souboru) jsou **chyba, ne oříznutí**; nevalidní výstup se až **3× vrací modelu** s konkrétním seznamem chyb ve stejné (jednorázové) session; **SHA-256 guard** pozná, kdyby agent soubor upravil sám — agent má `write_file`/`exec` pořád k dispozici, takže zákaz v promptu je jen prosba. Noční běh navíc přepnut z `glm` na preset **`kimi27`** (`kimi-k2.7-code:cloud`) přes `Nanobot.from_config(model_preset=…)` — dopad omezen na tento job. Tvrdá selhání (timeout, výjimka) nově taky posílají jednořádkovou hlášku, dřív skončila tiše exit kódem 1.
**Co fungovalo a proč:**
- **Offline testy parseru** (30 kontrol, `scratchpad/test_parser.py`, stub modulu `nanobot` + `importlib`): validní delete/merge, prázdný change-set, bare JSON bez fence, JSON obalený narací, `reason` přes limit, citát co v souboru není, citát vyskytující se 11×, neznámé pole, špatná kategorie/`op`, merge co nezkracuje, překryv bloků, smazání 19 z 28 řádků, sběr chyb ze všech položek najednou — plus aplikace na kopii (záloha, append-only log, přestavěný soubor) a formát reportu. Vše zelené.
- **Test 1 (nic k smazání):** jeden pokus, model vrátil přesně `{"changes": []}`, exit 0, hash `MEMORY.md` beze změny, log 350 B beze změny, žádná nová záloha.
- **Test 2 (reverzibilní změna):** vložena ephemeral odrážka → model ji jako jedinou navrhl smazat, skript aplikoval, `diff` proti pretest kopii čistý, log 350 → 550 B (starý řádek zachován), nová záloha `backup/2026-07-26_0648_memory.backup.md`.
- **Test 3a (chybová cesta):** kopie skriptu s `GOAL`, který vynucuje prózu → 3 pokusy, model postupně konvergoval (próza → JSON bez `changes` → JSON se špatným `op`), exit 1, na Telegram 145 znaků `Memory compact: CHYBA — po 3 pokusech: …`, `MEMORY.md` nedotčen.
- **Test 3b (zotavení po odmítnutí):** kopie s uměle odmítnutým prvním pokusem → follow-up přijat, druhý pokus prošel, exit 0.
- **`kimi27` uspěl na první dobrou** ve všech reálných bězích a i pod chybovým tlakem reagoval na konkrétní validační hlášky.
**Co zbývá:** Zkontrolovat noční běh 2026-07-27 (`log/compact_memory_auto_cron.log`) — odpověď modelu tam teď bude JSON, ale doručená zpráva z ní nevzniká. Zvážit zápis do `knowledge.md` (jak SDK přepíná model per běh, seznam presetů) a návrh do `decisions.md` (viz níže, čeká na formulaci autora). Interaktivní režim skillu zůstal beze změny a nebyl retestován.
**Pozor na `__pycache__`:** Lokální test skriptu přes `importlib` vyrobil `skills/compact-memory/scripts/__pycache__/`, který se `rsync -av` dostal na server (smazáno na obou stranách). Deploy skillů se skripty dělat s `--exclude='__pycache__'`.
**Jak vrátit zpět:** `git checkout 9c82894~1 -- skills/compact-memory/` + `rsync -av --exclude='__pycache__' skills/compact-memory/ nanobot@nanobot.hell:/home/nanobot/.nanobot/workspace/skills/compact-memory/`. Testovací zásahy do `MEMORY.md` jsou vrácené (ověřeno `diff` proti kopii pořízené před testy, hash `249015fd…` shodný před i po). Testovací záznam v `log/memory-clean.log` a záloha `backup/2026-07-26_0648_*` zůstávají jako stopa po testu — log je append-only, nemažou se.
## 2026-07-27 — compact-memory: anglické hlášky + testy, a ruff/ty lint hook pro celý repo
**Cíl:** Tři věci na jedno zadání. (1) `compact_memory_auto.py` měl anglické docstringy, prompty i validátorové hlášky, ale sedm uživatelských hlášek zůstalo česky — doručený report byl jazykově rozpolcený. (2) Skript má bohatou čistou logiku (parser, validátor, přestavba souboru, report), ale jako jediný netriviální skill neměl `tests/`; offline testy parseru z 07-26 žily jen ve scratchpadu a zmizely. (3) V repu nebyla žádná Python lint konfigurace ani hook, přestože `skills/python/SKILL.md` definuje hotovo jako `ruff format` + `ruff check --fix`.
**Co jsem zkusil:** Plán `~/.claude/plans/vsechny-hlasky-do-anglictiny-curious-lantern.md`, schválený v plan mode.
- **Jazyk:** přeloženo všech 7 českých hlášek (`nebylo nic k smazání` → `nothing to remove`, `CHYBA —` → `ERROR —`, atd.). Součástí byla i změna `SKILL.md`: pravidlo `reason` — max 120 characters, **in Czech** → **in English** plus dva příklady v JSON schématu. Bez toho by report zůstal míchaný, protože `reason` od modelu se vkládá doslova do Telegram zprávy i do `log/memory-clean.log`.
- **Testy:** nový `skills/compact-memory/tests/test_compact_memory_auto.py` — 51 testů, vzor podle `skills/note/tests/test_note_compile.py` (`sys.path.insert` na `../scripts`, `tmp_path` fixtura). Pokrývá `_extract_json`, validaci všech polí, agregaci chyb, hledání/unikátnost bloku, překryv, 50% limit, `apply_change_set` (obsah, záloha, append-only log, počet řádků), `format_report`, `_log_line`, `_shorten`, `_quote`, `_telegram_config`, `_workspace`. Aby to šlo importovat bez `nanobot-ai`, přesunut `from nanobot import Nanobot` z module-level do `_run()` + `TYPE_CHECKING` import pro anotaci — stejný vzor, jaký už používá `note_compile.py`.
- **Lint:** nový `ruff.toml` (line-length 120, target py311, `select = ["E","F","I","UP","B","BLE","SIM","C4","RUF"]`), nový `ty.toml` (extra-paths na všechny `skills/*/scripts`, `unresolved-import = "ignore"`), nový hook `.claude/hooks/python-check.sh` registrovaný jako třetí položka v existující `PostToolUse` skupině `Edit|Write|MultiEdit`.
**Co fungovalo a proč:**
- **Hook je fail-fast a ověřený všemi cestami:** non-`.py` → exit 0; soubor pod `tmp/` → exit 0; neopravitelný ruff nález (`F821`) → exit 2 s `ruff findings`; čistý lint ale typová chyba → exit 2 s `ty findings`; čistý soubor → exit 0. Auto-fix ověřen (nepoužitý import ruff sám smazal). Latence: ruff ~10 ms, ty ~95 ms.
- **`unset VIRTUAL_ENV` v hooku je nutnost, ne opatrnost.** V tomhle prostředí je proměnná zděděná a ukazuje na neexistující `/home/lachtan/.pyenv/versions/3.11.5`; `ty` na tom padá s `Failed to discover local Python environment` ještě před kontrolou.
- **Explicitní `select` v `ruff.toml` je taky nutnost.** Ruff 0.16 má bez configu velmi širokou výchozí sadu (125 nálezů repo-wide včetně `DTZ*` a `EXE001`) a délku řádku 88, což odporuje `.claude/rules/python.md` (120). `BLE` je ve výběru schválně: skript má dvě promyšlená `# noqa: BLE001` a bez `BLE` by je `RUF100` označil za mrtvá a `--fix` je i s vysvětlujícím komentářem smazal.
- **Ruff sám opravil, co jsem nehledal:** `asyncio.TimeoutError` → `TimeoutError` (`UP041`) a sloučení tří zalomených `raise ChangeSetError(...)` na jeden řádek (dřív zalomené kvůli 88 znakům).
- **`ty` našel reálnou chybu:** `sorted(set(raw) - allowed)` — klíče jsou po `isinstance` zúžení typované `object`, což nesplňuje `SupportsRichComparison`. Opraveno na `sorted(str(key) for key in set(raw) - allowed)`.
- **Server byl před nasazením identický s repo HEAD** (`rsync` do `tmp/srv-check-cm/` + `diff -r`) — žádný drift od Dream procesoru. Po nasazení `md5sum` shodné na obou stranách, vlastník `nanobot:nanobot`, `py_compile` na serveru OK, crontab beze změny.
**Zádrhel — PEP 723 přepíná `ty` do script-mode, kde projektový `ty.toml` neplatí na pravidla.** `unresolved-import = "ignore"` fungovalo na běžné moduly, ale ne na `compact_memory_auto.py`. Izolováno probe souborem: identický soubor **s** `# /// script` hlavičkou hlásí `unresolved-import`, **bez** ní projde. `--project .` nepomůže; `--config "rules.unresolved-import='ignore'"` ano, ale flag v hooku by znamenal, že ruční `ty check` dělá něco jiného než hook. Řešení: inline `# ty: ignore[unresolved-import]` u obou `nanobot` importů + komentář v `ty.toml`, že sekce `[rules]` na PEP 723 skripty nedosáhne.
**Zádrhel — `rsync` dvou souborů naráz je položil do stejného cílového adresáře.** `rsync SKILL.md scripts/compact_memory_auto.py server:.../compact-memory/` udělal na serveru `compact-memory/compact_memory_auto.py` vedle `compact-memory/scripts/compact_memory_auto.py`. Stray kopie smazána, skript nasazen zvlášť do `scripts/`. Příště buď celý adresář, nebo jeden soubor na jeden `rsync`.
**Co zbývá:** Ostrý noční běh 2026-07-28 02:00 — první, který poběží s anglickými hláškami a anglickým `reason`; zkontrolovat `log/compact_memory_auto_cron.log` a nové řádky v `log/memory-clean.log` (historické české řádky zůstávají, log je append-only). Manuální ostrý běh jsem nedělal — mutuje reálný `MEMORY.md` a posílá Telegram. Repo-wide zbývá 92 ruff a 16 ty nálezů v ostatních skriptech; hook je nahlásí, až se těch souborů někdo dotkne (nejvíc `skills/llm-wiki/scripts/wiki_lint.py` = 9). Ostatní skripty s českými hláškami (`tasks-daemon.py`, `wiki_compile.py`, `note_compile.py`) zůstaly podle rozhodnutí česky. `.claude/rules/bash.md` řádek 45 tvrdí, že bash formátování hlídá `check-bash.sh` PostToolUse hook — ten v repu neexistuje ani není registrovaný; dokument je rozsynchronizovaný.
**Jak vrátit zpět:** `git checkout <tento commit>~1 -- skills/compact-memory/ .claude/settings.json` + `rm ruff.toml ty.toml .claude/hooks/python-check.sh` + `rsync -av --exclude='__pycache__' --exclude='tests' skills/compact-memory/ nanobot@nanobot.hell:/home/nanobot/.nanobot/workspace/skills/compact-memory/`. Na serveru se nic nemazalo ani nemutovalo mimo dva přepsané soubory (`SKILL.md`, `scripts/compact_memory_auto.py`) — `MEMORY.md`, zálohy ani logy zásah nepotkal.
## 2026-07-27 — Kontrola presetu `kimi3` (Kimi K3 na Ollama Cloud) + oprava context window
**Cíl:** Ověřit, že nově vydaný Kimi K3 (`kimi-k3:cloud` na Ollamě) je v `~/.nanobot/config.json` nastavený správně a má správné parametry.
**Co jsem zkusil:**
- `ssh nanobot@nanobot.hell` → dump `config.json`. Pozor: top-level klíč je `model_presets` (snake_case), ne `modelPresets`, jak tvrdila knowledge — první pokus o výpis vrátil `null`.
- `curl http://nvidia.hell:11434/api/tags` (model v katalogu je) a `api/show` pro `kimi-k3:cloud` i pro srovnání `kimi-k2.6`, `kimi-k2.7-code`, `glm-5.1`, `glm-5.2`.
- Funkční test: `curl .../v1/chat/completions` s `kimi-k3:cloud`, pak totéž s `glm-5.2:cloud` a `kimi-k2.7-code:cloud` jako kontrolní skupina.
- `journalctl --user -u nanobot.service` — hledání stop po přepnutí na preset.
- Online ověření na žádost uživatele: [ollama.com/pricing](https://ollama.com/pricing), [ollama.com/library/kimi-k3](https://ollama.com/library/kimi-k3), oznámení Ollamy na X.
- `curl https://openrouter.ai/api/v1/models` — hledání alternativní cesty ke stejnému modelu.
**Co fungovalo a proč:**
- **Konfigurace presetu je věcně správná.** `kimi3` = `{model: kimi-k3:cloud, provider: ollama, maxTokens: 16384, temperature: 0.1, reasoningEffort: null}`. Název modelu přesně sedí s `api/tags`, `apiBase` má povinný `/v1` suffix, model má capabilities `tools`+`thinking`+`vision`. Journal potvrdil, že runtime přepnutí funguje: `Runtime model switched for next turn: glm-5.1:cloud -> kimi-k3:cloud`. `reasoningEffort: null` není opomenutí — `schema.py:141` říká, že `None` zachovává default providera.
- **Blokuje to Ollama, ne config.** Přímé volání endpointu vrací HTTP 402 `this model uses extra usage only (not included plan usage) and your extra usage balance is empty`. Kontrolní skupina (`glm-5.2:cloud`, `kimi-k2.7-code:cloud`) přitom odpovídá normálně → není to stav účtu ani API klíč, je to per-model politika. Online potvrzeno: K3 vyžaduje Pro/Max **a** nenulový dokoupený extra usage balance, kapacita se teprve rozšiřuje. Nanobotí hláška *„API key is out of quota or the account is in arrears"* je zavádějící překlad tohoto 402.
- **Oprava parametru:** `contextWindowTokens` bylo `967000` proti reálnému `1048576` (z `api/show`, klíč `kimi-k3.context_length`) — ~81k tokenů ležel ladem. Ostatní presety drží ~97 % reálného okna (`glm-5.1`: 196608/202752, `glm-5.2`: 976000/1000000, kimi K2.x: 262144 = přesně). Srovnáno na **1 020 000**. Záloha `~/.nanobot/backup/config.json.bak-20260727-202245`, `diff` proti ní ukazuje jediný změněný řádek, vlastník `nanobot:nanobot` a práva `600` beze změny. Restart nebyl potřeba — presety se načítají každý tah (`_refresh_provider_snapshot`).
- **Vedlejší nález:** knowledge tvrdila konvenci pojmenování presetů `<model>-<provider>` (`kimi-k2.6-openrouter`), reálně jsou na serveru krátké aliasy (`kimi`, `kimi27`, `kimi3`, `glm`, `glm52`, `sonnet`, `haiku`, `gemini-flash`). Opraveno v `knowledge.md` spolu s názvem klíče `model_presets`.
**Co zbývá:** Model je do dobití extra usage na ollama.com nepoužitelný. Alternativa bez dobíjení, pokud ji uživatel bude chtít: preset přes OpenRouter (`moonshotai/kimi-k3`, ctx 1 048 576, $3/M in + $15/M out) — provider `openrouter` je v configu už nakonfigurovaný. Nezakládal jsem ho, nebylo zadáno.
**Jak vrátit zpět:** `ssh nanobot@nanobot.hell 'cp ~/.nanobot/backup/config.json.bak-20260727-202245 ~/.nanobot/config.json'` — mimo tuhle jednu hodnotu jsem na serveru nic neměnil.
## 2026-08-01 — Upgrade nanobot 0.2.2 → 0.3.0: rozbité `uv` v `exec`, osiřelý `TOOLS.md`, mrtvý `maxMessages`
**Cíl:** Po upgradu na 0.3.0 (31. 7. 15:42, restart 16:58) přestalo v chatu fungovat `uv run` — `/remind list` padal na `uv: command not found`, exit 127. Zadání: zjistit příčinu, pak opravit všechny dopady upgradu včetně `TOOLS.md` a `agents.defaults.maxMessages`.
**Co jsem zkusil (diagnóza):**
- `ssh` průzkum: `uv` je v `/home/nanobot/.local/bin/uv`, systemd unit má vlastní `Environment=PATH` bez `~/.local/bin`.
- Stažení nainstalovaného balíčku 0.3.0 (`rsync` site-packages do `tmp/srv-0.3.0/`) a čtení `agent/tools/shell.py`.
- Klon upstreamu do `tmp/nanobot-upstream/`, `git log -S` nad `shell.py`, `context.py`, `config/schema.py` mezi `v0.2.2..v0.3.0`.
- Reprodukce exec prostředí přes `env -i HOME=… /bin/bash -c` vs `-lc` (s a bez `NANOBOT_PATH_*`).
- Analýza `sessions/*.jsonl` — kdy a jak selhání vypadala.
**Co fungovalo a proč — tři nezávislé regrese:**
1. **`uv` v `exec`.** Upstream `13c951aa` (25. 6., *„change exec login-shell default from true to false"*) přepnul default `login` z `True` na `False` — profil sourcovaný login shellem vracel do exec prostředí secrets, které `_build_env()` schválně vyhazuje. Jenže `_build_env()` na Unixu **PATH vůbec nepředává** (jen `HOME`/`LANG`/`TERM`/`PYTHONUNBUFFERED`), takže bez `bash -l` se PATH dopočítá z vestavěného defaultu bashe `/usr/local/bin:…:/bin:/sbin:.`, kde `~/.local/bin` není. Odhalilo to **existující chybu v našem configu**: `tools.exec.pathAppend` obsahoval `/home/nanobot/.local/bin/uv` — cestu k **binárce**, ne k adresáři, takže do PATH lookupu nepřispíval ničím. Do 0.2.2 to maskoval login shell.
- Naměřeno: `bash -c` + starý `pathAppend` → `uv` NENALEZEN; `bash -lc` + tentýž `pathAppend` → nalezen; `bash -c` + adresář → nalezen.
- Rozsah: `remind`, `note`, `llm-wiki`, `python`. Nezasažené: crontab (vlastní `PATH=`), `bookmark` (absolutní cesta v SKILL.md), `detach` (systemd unit s PATH).
- **„Zlobí jen u některých modelů" byl klam.** Selhává každé bare `uv run`; liší se jen schopnost modelu se vzpamatovat. Model, který zkusil `PATH="$HOME/.local/bin:$PATH" uv run …`, uspěl; modely, které zkusily `which uv || find /home -name uv`, narazily na `restrictToWorkspace: true` (`Command blocked by safety guard`) a vzdaly to.
2. **`TOOLS.md` osiřel.** Upstream `d29fcaf5` (21. 5., *„internalize tool contract prompt"*) přesunul `templates/TOOLS.md` → `templates/agent/tool_contract.md` a rendruje ho přímo do promptu; `BOOTSTRAP_FILES` je nově `["AGENTS.md", "SOUL.md", "USER.md"]`. Obsah tedy nezmizel — naopak je bohatší — ale přestal být uživatelsky editovatelný. Náš workspace soubor s vlastními dopisky zůstal ležet a nikdo ho nečetl.
3. **`maxMessages` mrtvý.** Upstream `dacc6992` (29. 6.) klíč vyřadil ze schématu a přidal do `_migrate_config()` shim, který ho zahodí a zaloguje warning. Spamoval journal — **145 výskytů za hodinu**.
**Proč u `TOOLS.md` neproběhla migrace, a u `maxMessages` ano:** nanobot migruje **config**, ne **workspace**. `sync_workspace_templates()` má v docstringu doslova *„Creates missing files without overwriting user files"* — jen dotváří chybějící, nikdy nepřepisuje ani nemaže, protože workspace `*.md` jsou user data. Když šablona zmizí z balíčku, kopie ve workspace prostě zůstane, tiše. Config naopak shim + warning dostane (a `281b4b7f` ukazuje, že se ty shimy po verzi systematicky mažou).
**Co jsem změnil:**
- `~/.nanobot/config.json`: `tools.exec.pathAppend` → **`pathPrepend`** = `/home/nanobot/.local/bin:/home/nanobot/.nvm/versions/node/v24.16.0/bin` (adresář místo binárky); odstraněn `agents.defaults.maxMessages`. Zálohy `backup/config.json.bak-20260801-0703{54,}` a `-070420`. *(Obě změny nakonec zapsal uživatel sám v 07:03:54, můj skript je zastihl už hotové — ověřil jsem výsledek.)*
- `~/.config/systemd/user/nanobot.service`: `ExecStart` zpět na `/home/nanobot/.local/bin/nanobot gateway`, `Environment=` řádky odkomentované, `PATH` opraven na adresář. Wrapper `~/bin/nanobot.sh` smazán. *(Taky provedl uživatel.)* Wrapper měl tutéž chybu (`$HOME/.local/bin/uv`) a i kdyby byl správný, na `exec` by nedosáhl — ale `Environment=PATH=` v unitu **zůstává**, protože `run_cli_app` spouští CLI appky s `env=os.environ.copy()` (`apps/cli/service.py:1372`).
- `workspace/AGENTS.md`: přeneseny dvě unikátní sekce z `TOOLS.md` — `python — use uv` a `Doručené připomínky` (při přenosu opraveno `remind_edit.py` → `remind_cli.py` po přejmenování z 2026-06-10). Sekce `exec`/`grep`/`cron` zahozeny jako duplicita `tool_contract.md`.
- `workspace/TOOLS.md` smazán na serveru i v repu (šablona ho neobnoví — v `nanobot/templates/` už není).
- Restart přes `~/bin/restart.sh` v 07:05:20.
**Ověření (vše zelené):**
- Reprodukce exec prostředí: `uv → /home/nanobot/.local/bin/uv`, `node`/`npm` z nvm.
- Reálný běh `uv run skills/remind/scripts/remind_cli.py list` v simulovaném prostředí → exit 0, výpis reminderů.
- **Ostrý test přes agenta** v izolované session `cli:uv-verify-20260801` (`nanobot agent -m …`): agent zavolal bare `uv run skills/remind/scripts/remind_cli.py list`, **jedno volání, exit OK, 1664 B**, žádný fallback na absolutní cestu, žádných 127.
- `maxMessages` warning: **0 výskytů** od restartu (před restartem 145/h). Journal jinak bez chyb.
- MD5 `AGENTS.md` shodné server ↔ repo, vlastník `nanobot:nanobot`, config práva `600`.
- Doručování reminderů nedotčené (crontab má vlastní PATH): `reminder_cron.log` má mtime **2026-07-15**, tj. sender od té doby neprodukoval výstup ~23 000 běhů.
**Vedlejší nález (neopraveno, mimo zadání):** v `reminder_cron.log` je z 2026-07-15 traceback `sqlite3.OperationalError: cannot rollback - no transaction is active` na `remind_send.py:182` — `conn.execute("ROLLBACK")` na neaktivní transakci **maskuje původní chybu** (tam šlo o výpadek DNS při doručování). Latentní, od 07-15 se neopakoval. Kandidát na `todo.md`.
**Co zbývá:** Zvážit návrh do `decisions.md` (čeká na formulaci autora): (1) PATH pro `exec` se konfiguruje výhradně přes `tools.exec.pathPrepend`, ne přes systemd unit ani `allowedEnvKeys`; (2) systemd unit spouští binárku přímo, bez shell wrapperu. Testovací session `sessions/Y2xpOnV2LXZlcmlmeS0yMDI2MDgwMQ.jsonl` ponechána jako doklad. Repo kopie `workspace/original/` (referenční upstream šablony) nesahané — pořád obsahují starý `TOOLS.md`.
**Jak vrátit zpět:** `cp ~/.nanobot/backup/config.json.bak-20260801-070420 ~/.nanobot/config.json` + `systemctl --user restart nanobot`; `git checkout fc45e19 -- workspace/` + `rsync -av workspace/AGENTS.md workspace/TOOLS.md nanobot@nanobot.hell:/home/nanobot/.nanobot/workspace/`. Unit se dá vrátit z tvaru zaznamenaného výše. Nic se nemazalo nenávratně — `TOOLS.md` je ve verzované historii repa (commit `fc45e19`).
## 2026-09-01 06:45 — Skill `reflect`: sebe-diagnostika ze `sessions/` se schvalovací smyčkou
**Cíl:** Zrealizovat plán `plans/reflect-skill.md` — vytěžit 534 nikdy nečtených session logů (16 MB) na opakující se chyby agenta, nálezy předkládat uživateli po jednom ke schválení a aplikovat jen schválené. Klíčový požadavek uživatele: diagnózu musí dělat LLM běžící na nanobotu (jinak to nemá smysl), nic se nesmí aplikovat automaticky, Telegram jen notifikuje.
**Co jsem zkusil / co fungovalo a proč:**
*Změřená východiska.* Z 534 session je 332 reálných konverzací (12 311 zpráv). **89 % objemu (12,5 z 14,1 MB) tvoří návratové hodnoty toolů** — pro diagnózu bezcenné, stačí `name(args) → ok|ERROR, velikost`. Přírůstek 4,4 session/den, aktivita jen 70 % dní. Preset `glm53` má reálně 976k okno (`contextWindowTokens` presetu přebíjí `defaults` 65536, `agent/loop.py:476`), input budget ~958k tokenů.
*Architektura — tři skripty, tvrdě oddělené role.* `reflect_distill.py` dělá **jen mechanickou destilaci** (filtr šumu podle prefixů, ořez tool výsledků); žádné detektory, protože chyby má hledat LLM. `reflect_auto.py` je cron runner podle vzoru `compact_memory_auto.py` (fresh `session_key`, Telegram přímo přes Bot API, zpráva se skládá ve skriptu) a **nemá v sobě žádnou cestu k zápisu do cizího souboru**. `reflect_apply.py` je jediné místo, kudy vede editace.
*Aplikační brána přesunuta z promptu do kódu.* Původně měl patch aplikovat agent podle postupu v `SKILL.md`. Přepsal jsem to na skript — recept z `develop/history.md` říká, že měkká instrukce nestačí, musí to být tvrdá brána. `reflect_apply.py` odmítne patch, jehož `old_text` v souboru není nebo je tam vícekrát, odmítne nález, který není `open`, commituje výhradně `git add -- <file>` a rozdělaný cílový soubor nejdřív checkpointne. 96 testů, včetně reálného `git revert`.
*Práh notifikace.* Denní dávka (medián 4 session) je moc malá na vzor. Nález poprvé → `watch`, mlčí; při opakování (≥2× a ≥2 session) → `open` + Telegram; zamítnutý vzor se **už nikdy neotevře** (zamítnutí je rozhodnutí, ne odklad); vzor po `applied` → `open` s příznakem regrese.
*Guard proti zápisu při analýze.* Před a po agentním tahu se porovná `git status --porcelain` + HEAD celého workspace. Kdyby agent při analýze cokoli zapsal, nálezy se zahodí.
**Ověřeno ostrým během (15 session, 90 kB destilátu):** model našel 8 vzorů, všechny konkrétní a doložené citacemi session — mj. `answer-self-config-from-guesswork` (vymyšlené schéma `tools.my.allow_set` zapsané do živého configu), `tool-call-leaked-as-text` (surový tool call se speciálními tokeny jako poslední zpráva session), `retry-without-diagnosis` (18× ve 4 session, vč. rozbité dvojité URL `r.jina.ai/http://r.jina.ai/http://…`). Dva běhy po sobě → **8 nálezů bez jediné duplicity**, dedup přes `pattern` funguje. `--check` na reálném patchi do `SOUL.md` prošel a soubor nezměnil. Sebe-vyloučení ověřeno na reálné `reflect:*` session.
**Dvě chyby, které běh odhalil, a jejich fix:**
1. `uv` **není v `PATH` neinteraktivního SSH** — první běh spadl na `timeout: failed to run command 'uv'`. Fix: plná cesta `/home/nanobot/.local/bin/uv`. Crontab má vlastní `PATH`, tam stačí `uv`.
2. **Model rozbil JSON českou uvozovkou** — napsal `(„repeated external lookup blocked")`, kde zavírací uvozovka je ASCII `"`, což předčasně ukončí JSON string. Hláška „no parseable json block" navíc retry nedala nic použitelného. Fix: validátor hlásí řádek, sloupec a výřez okolo chyby + prompt zakazuje uvozovky uvnitř string hodnot. Po opravě prošel **první pokus**.
3. Odhad tokenizace byl mimo: naměřeno **1,2 znaku na token** (ne 3), takže dávka 800 kB by dala ~670k tokenů. Sníženo na 500 kB.
**Nasazeno:** `skills/reflect/` na server (`nanobot:nanobot`), crontab `30 3 * * *`. Stav žije v `workspace/reflect/` (mimo adresář skillu, aby ho `rsync` nepřepsal) a je verzovaný v git workspace.
**Co zbývá:**
- **Uživatel projde prvních 8 nálezů přes `/reflect`** — to je zpětná vazba na kvalitu. Backlog (zbývá ~405 session, cursor stojí na `2026-05-27 14:39`) jsem **záměrně nepouštěl celý**: nemá smysl generovat desítky nálezů, dokud se neověří, že jsou k něčemu.
- Workspace na serveru má 11 nezacommitovaných změn (`cron/jobs.json`, `memory/*`, …) — před ostrým používáním uklidit, jinak bude diff po patchi zašuměný.
- Po každé review dotáhnout změněné soubory ze serveru do repa (obě git repa jsou bez remote, nespojená).
- Návrh do `decisions.md` (čeká na formulaci autora): (1) sebemodifikace jen přes `reflect_apply.py` po explicitním schválení jednoho nálezu, nikdy agentem přímo; (2) skript destiluje, quality judgements dělá výhradně LLM.
**Jak vrátit zpět:** `crontab -e` a smazat dva řádky `reflect`; `rm -rf ~/.nanobot/workspace/skills/reflect ~/.nanobot/workspace/reflect ~/.nanobot/workspace/results/2026-09-01_reflect.md`; lokálně `git revert` commitů `4aff500`, `1a8bc2a`, `7780841`, `ecc7d03`, `1bb5f4d`. Nic destruktivního neproběhlo — skill zatím jen četl a zapisoval do vlastního sandboxu.
## 2026-09-02 06:05 — skill `plan`: odstranění češtiny ze `SKILL.md`
**Cíl:** V `skills/plan/SKILL.md` nesmí zůstat jediné české slovo — konvence projektu je, že skilly pro nanobota jsou EN-only.
**Co jsem zkusil:** Nejdřív diff serverové verze proti lokální (`ssh cat` + `diff`) — byly **identické**, takže se nic ze serveru nezahazovalo. Pak dvě chirurgické editace: (1) nadpisy v šabloně plánu `## Kontext / ## Postup / ## Ověření` → `## Context / ## Steps / ## Verification`; (2) český literál schvalovací otázky (`Plán uložen do … Schvaluješ? Mám ho vykonat teď?`) nahrazen anglickou **instrukcí** místo doslovného textu — lokalizaci už pokrývá stávající pravidlo *"Respond in the user's language"*.
**Co fungovalo a proč:** `grep -nP '[ěščřžýáíéúůťďň…]|Kontext|Postup|Schvaluj|teď'` vrací prázdno. Nasazeno `rsync`em do `~/.nanobot/workspace/skills/plan/`, vlastník `nanobot:nanobot`, po nasazení server ↔ lokál `IDENTICAL`. Restart služby nebyl potřeba — skilly se čtou při každém tahu.
**Důsledek:** Nanobot bude nově psát plány do `workspace/plans/` s anglickými nadpisy i pro česky mluvícího uživatele. Tělo plánu si model dál lokalizuje, mění se jen struktura.
**Jak vrátit zpět:** `git revert` commitu se změnou skillu + znovu `rsync` na server.
## 2026-09-02 06:10 — skill `project`: deterministický zápis do `memory.md`, přepis skillu, oprava dat
**Cíl:** Review skillu `/project` proti reálným datům, která pod ním na serveru vznikla (`projects/{chata,life,proxmox,radio1}`), a oprava toho, co v praxi nedrží.
**Co review našlo (doloženo v datech, ne odvozeno z textu skillu):**
1. **Vymyšlená data** — `chata/memory.md` měl dva záznamy datované `2026-09-14`, přitom byly zapsány 2026-09-01 (mtime 10:51). Skill předepisoval formát `- YYYY-MM-DD:`, ale neříkal, odkud datum vzít — a model má přitom `Current Time` v runtime kontextu.
2. **Slepený řádek** — `proxmox/memory.md` obsahoval `…jako další VM/kontejner.- 2026-09-01: Rozhodnuto —` na jednom řádku. `chata/memory.md` neměl koncový newline.
3. **`state.md` se nikdy nezaložil** — `chata` 0 B při 2 KB memory. Pravidlo *„offer to draft it"* je příliš měkké a nespustí se.
4. **Mrtvé pravidlo o Dreamu** — *„Dream must not touch `workspace/projects/`"* žilo v těle skillu, které Dream nikdy nečte, a upstream to už vynucuje kódem: `build_dream_tools()` (`nanobot/agent/memory.py:641`) dává Dreamu Write/Edit/ApplyPatch s `allowed_dir = workspace/skills` + tři memory soubory. Do `projects/` se nedostane. **Nekryté je riziko opačné** — Dream smí přepisovat samotný skill, a serverový `SKILL.md` byl 2026-09-01 13:15 skutečně změněn mimo repo.
**Co jsem zkusil a co fungovalo:**
- **Nejdřív dotažen server → repo** (commit `3c388ba`), aby byl přepis čitelný v diffu.
- **`skills/project/scripts/project_cli.py`** (`activate` / `log` / `list` / `new`) přebírá datum ze systémových hodin a newline hygienu. Text jde na **stdin quoted heredocem** (`<<'NOTE'`) — shell obsah neinterpretuje, takže `„"`, `'` i `"` projdou doslova. Ověřeno reálným zápisem.
- **16 pytest testů** lokálně zelených; na serveru se testy nespouští (konvence).
- **Data opravena** po odsouhlasení uživatelem: data `09-14` → `09-01`, rozdělen slepený řádek, doplněn newline, `chata/state.md` sepsán z historie (dřevo + otevřená otázka zazimování). Záznam o zazimování zůstal i v `memory.md` — append-only invariant se neporušil.
**Rozpočet velikosti — návrh, který padl:** Původně jsem navrhoval prahy 8 000 / 12 000 znaků odvozené z okna 65 536 tokenů. **Bylo to postavené na špatném čísle:** `agents.defaults.contextWindowTokens: 65536` preset přebíjí a default `glm53` má 976 000. `proxmox` (11 278 znaků ≈ 9 400 tokenů při 1,2 znaku/token) tedy zabírá ~1 % okna. Uživatel návrh zamítl s tím, že projekt nemá ztrácet zadaná data — správně. Zůstalo jediné reálné omezení `maxToolResultChars: 16000`, které je ale **omezením čtení, ne ukládání**: `activate` nad ním vypustí z *výstupu* nejstarší záznamy a ukáže cestu k plnému logu; soubor na disku se nemění (ověřeno na 33 790znakovém souboru — výstup 14 305 znaků, velikost souboru beze změny).
**Chyba v mém vlastním review:** tvrdil jsem, že `chata/memory.md` má „4 záznamy na 3 řádcích". `wc -l` počítá newliny, ne řádky — soubor měl 4 řádky a jen mu chyběl koncový newline. Slepený řádek byl reálně jen v `proxmox`.
**Co zbývá:**
- Reálný smoke test přes Telegram/WebUI (aktivace projektu, zápis poznámky s dnešním datem, „vypiš projekty", dotaz na detail z `chata`).
- Návrh do `decisions.md` (čeká na formulaci autora): (1) zápis do `memory.md` výhradně přes `project_cli.py log`; (2) projektová data nemají strop ani konsolidaci — velikost se řeší jen na straně čtení; (3) `artifacts/` vzniká líně při prvním artefaktu.
- `radio1` (prázdné `memory.md`/`state.md` od 23. 7.) — uživatel rozhodl nechat být.
**Jak vrátit zpět:** Zálohy dat jsou na serveru v `workspace/backup/projects-2026-09-02/` (`chata-memory.md`, `chata-state.md`, `proxmox-memory.md`) — `cp` zpět na místo. Skill: `git revert 39b3fb3 3c388ba` + `rsync` na server. Restart služby netřeba, skilly se čtou při každém tahu.
## 2026-09-02 09:05 — reflect: klouzavé okno, audit počtů a záznam rozhodnutí
**Cíl.** Uživatel se zeptal, jestli jsou nálezy z dávkované analýzy věrohodné a jak by se
změnily nad celou historií. Kontrola provozu odhalila tři nezávislé problémy plus jeden,
na který se doptal později (záznam rozhodnutí).
**Co jsem zjistil (data z běhů 1.2. 9.).**
- `cursor: 2026-05-29`, session sahají do `2026-09-02` → 3 běhy × 1 dávka = **56 z 356
session (16 %)**, a všechny z nejstarších 4 dnů korpusu. Backlog 19 dávek při ~1 dávce/noc
neklesal. Všech 8 rozhodnutých nálezů tedy popisovalo chování z konce května.
- Dávkování samo nálezy nezkreslilo — každý běh byl **jedna** dávka. Zkreslilo je okno.
- `f09a7`: `occurrences: 4`, `sessions_affected: 5` — aritmeticky nemožné. Počty jsou
nekontrolované self-reporty modelu a `merge_findings` je jen sčítá.
- `ff77b`: tvrdí 18× a nese 2 důkazy — fold přepisoval `evidence`, ale `occurrences` sčítal.
- Audit zaznamenával **výsledek, ne rozhodnutí**: u zamítnutí chybí důvod (4 z 8 zamítnuto),
`uprav:` přepsalo `patch.new_text` a zahodilo návrh modelu, `přeskoč` nezanechalo nic.
**Co jsem zkusil a co funguje.**
1. **Klouzavé okno** (`--window-days`, default 21): `since = max(cursor, now - okno)`.
Cursor je podlaha (nic dvakrát), okno strop (starý backlog se přeskočí natrvalo).
Backlog se nedohání — rozhodnutí uživatele, archeologie z května hodnotu nemá.
2. **Ostrý `--all` odmítnut** — znovu čte spočítané session a `merge_findings` jim sečte
`occurrences` do existujícího záznamu. Je to nafukovač počtů; zůstává pro `--dry-run`.
3. **Clamp počtů** ve validátoru: `sessions_affected ≤ min(occurrences, session_count)`.
Clamp, ne odmítnutí — retry stojí celý tah.
4. **`_fold_evidence()`** — při foldu se důkazy kumulují (nejnovější první, dedup, cap 6),
takže kumulativní počet jde ověřit. U regrese se nekumulují (mísily by před/po opravou).
5. **Prompt**: model dostal informaci, že vidí jen výsek, a smí hlásit i jediný výskyt
**známého** vzoru. Tím se zavřela slepá skvrna — vzor s frekvencí ~1×/dávka se dřív
nikdy nepojmenoval (prompt to zakazoval) → nikdy nespočítal → práh nepřelezl.
6. **Záznam rozhodnutí** v `reflect_apply.py`: `--reject` vyžaduje `--reason` (povinně —
je to jediná zpětná vazba na kvalitu analýzy), nová akce `--skip` s počítadlem,
`patch` zůstává návrhem modelu a uživatelova verze jde do `applied.new_text`
(`APPLIED-EDITED` v logu). `SKILL.md` má sekci *Decision history*.
7. **Report a Telegram** nesou okno a nemlčí o nezpracovaném zbytku — dřív se při 0 nálezech
vracel prázdný string, což je přesně důvod, proč hladovění cursoru tři noci nikdo neviděl.
Testy: 140 prošlo (`tests/`), ruff clean. Nasazeno rsyncem, vlastník `nanobot:nanobot`.
**Ostrý běh 09:0809:31 — ověřeno na živých datech.**
```text
Zpracováno 74 session ve 6 dávkách. Nálezů: 29 (23 k review, 6 sledovaných).
Okno: od 2026-08-12, dávek 6/6.
Známé vzory: 100,0 výskytu / 100 session (minule 57,7).
```
- Okno v hlavičce reportu i ve `state.json` (`window_from`, `batches_total`), cursor
přeskočil z `2026-05-29` na `2026-09-02T06:22` — backlog je minutý, jak bylo rozhodnuto.
- **Kumulace důkazů funguje**: `f2b3d retry-without-diagnosis` má 67 výskytů a nese 6 důkazů
(cap `MAX_EVIDENCE`) posbíraných napříč dávkami; `f999d` z 2. dávky se do něj složil.
- **Žádný nový nález není nekoherentní** (`sessions_affected ≤ occurrences` všude).
Jediný nekoherentní záznam ve store je starý `f09a7` (4×/5 session) — clamp platí na
nově parsované nálezy, historii zpětně nepřepisuje.
- Guardy `reflect_apply.py` odmítly `--reject` bez `--reason`, `--reason` bez `--reject`
i `--skip --reject`, aniž by na store sáhly.
**Korekce mé vlastní obavy z dimenzování.** Z dry-runu jsem usoudil, že 6 dávek se do
20min deadline nevejde. Neplatí: dávka trvá **~4 min**, celé okno 23 min, a deadline pustil
i šestou. Je to ale těsné (kontrola deadlinu padla na ~20 min u indexu 5), takže jedna
pomalejší dávka běh zastaví a zbytek nechá na další noc — což Telegram ohlásí. Zvýšení
`DEFAULT_DEADLINE_MINUTES` na ~40 min je proto drobná pojistka, ne nutnost.
**Co zbývá.** 23 open nálezů k review a **ani jeden nemá patch** — potvrzuje otevřenou
položku o stropu „read at most 2 files". Regrese na session starších než oprava z 1. 9.
se pořád hlásí falešně (okno začíná 12. 8., patche jsou z 1. 9.).
**Jak to vrátit zpět.** Skripty: `git revert` v tomto repu + rsync. Stav na serveru:
`/tmp/state.json.bak` a `/tmp/findings.jsonl.bak` (záloha před ostrým během).
## 2026-09-02 10:05 — reflect: celý skill do angličtiny
**Cíl.** `SKILL.md` a Python skripty skillu `reflect` mají být EN-only (konvence
z `CLAUDE.md`), včetně textů, které skript generuje.
**Co jsem zkusil.**
- `SKILL.md`: ukázkový výstup nálezu, tabulka rozhodnutí a odkazy na ně v textu byly česky.
Klíčová slova v tabulce jsou teď `ok` / `apply`, `edit:`, `skip`, `reject`, `stop`
+ věta, že se přijímají ekvivalenty v jazyce, kterým uživatel píše. Pod ukázkou přibyla
věta, že popisky se píšou v jazyce uživatele.
- `reflect_auto.py`: report (`Zpracováno…`, `Okno:`, `Známé vzory:`, `**Výskyt/Důkazy/Návrh**`,
`REGRESE`, `poprvé/naposledy`, `Nic k hlášení`), Telegram hlášky (`nálezů k review`,
`Napiš /reflect`, timeout) a `ReflectError("model nedostupný")` → anglicky.
- `reflect_distill.py`: hlavička destilátu (`N zpráv`) a řádek statistik do stderr.
- Testy: asserty na tyto texty + česká fixture data. Ponechán jen znak `` v testu
neescapovaných uvozovek — to je předmět testu, ne jazyk.
- Vedlejší úklid: `_decimal()` existovala jen kvůli desetinné čárce; v anglickém reportu
by `3,2 occurrences` četlo špatně, takže tečka a helper inlinovaný do `_rate_line`.
**Co fungovalo a proč.** `pytest tests -q` → **140 passed**. Před nasazením ověřeno, že
server == HEAD u `SKILL.md`, `README.md` i všech tří skriptů (nikdo do nich mimo nás
nesáhl), pak `rsync` celého adresáře, vlastník `nanobot:nanobot` sedí. Workspace na serveru
jsem necommitoval — je trvale dirty, jak je tam zvykem.
**Co zbývá.** `README.md` zůstává česky — je to dokumentace psaná uživateli, mimo dohodnutý
rozsah („SKILL.md a python skripty"). Nejbližší noční běh pošle Telegram i report anglicky.
**Jak to vrátit zpět.** `git revert 2375d76` + rsync skillu na server.
## 2026-09-02 10:15 — reflect: druhé kolo oprav (set-patch, dedup, hradlo na regrese)
**Cíl.** Vykonat [plans/reflect-verohodnost.md](plans/reflect-verohodnost.md): složení
patche při review má být jedno validované volání skriptu, počty v reportu mají odpovídat
store, a příznak regrese má znamenat „vrátilo se to **po** opravě".
**Co jsem zkusil.**
- `reflect_apply.py`: nová akce `--set-patch <json>` (`{file, old_text, new_text}`).
Pořadí je celý smysl: kandidát `{**record, "patch": novy}` projde existující
`check_patch()` (status `open`, cesta uvnitř workspace, právě jeden výskyt `old_text`,
„nemění nic") a **teprve pak** se zapíše `patch` + `patch_drafted_at` a `DRAFTED` do
auditu. Vypíše rovnou i diff, takže nenásleduje druhé volání `--check`. Vytažen helper
`_diff()` (sdílí ho `--check`) a `_parse_patch_file()`. Přepis existujícího patche
povolen — guard je status `open`, ne absence patche.
- `reflect_auto.py` bod 2: `merged` je `dict[str, Finding]` klíčovaný `pattern` místo
seznamu, do kterého se po každé dávce přičítalo. Vzor nalezený v 5 dávkách byl v reportu
5× (report z 2. 9.: **29 nadpisů proti 13 vzorům ve store**) a stejně nafouknutý šel do
`stats` i do Telegramu.
- `reflect_auto.py` bod 3: nové derivované pole `Finding.last_seen` = `max(evidence[].when)`
přes tvary `^\d{4}-\d{2}-\d{2}` (prvních 10 znaků, nedatumové hodnoty zahozené), fallback
`created`. Dvě použití: `regression_of` se nastaví jen když `last_seen > applied.at[:10]`,
jinak `stale_after_fix` → status `watch` (ne `open`, ne REGRESE); a `_seen_line()` hlásí
`last_seen` místo `created`, takže „naposledy" přestalo být datum přepsání záznamu.
Report navíc značí `— STALE` nález, jehož `last_seen` je před `window_from` běhu.
- `SKILL.md`: do STOP gate 4 doplněn **zákaz ruční editace `reflect/findings.jsonl`**
(chybějící věta, kvůli které agent legálně sáhl do store), postup `--set-patch` místo
„propose the exact old_text/new_text yourself", řazení v *1. Load* na severity →
`last_seen` → `occurrences` (s fallbackem na `created` u starých záznamů) a značení
zastaralých nálezů. `README.md`: `DRAFTED` v tabulce rozhodnutí, co znamená „zastaralý",
regrese jen po opravě, záruka o zápisu do store.
- Testy: +26 (celkem **166 passed**, ruff čistý). Klíčové: neaplikovatelný i nejednoznačný
`--set-patch` nechá záznam **bez patche** (ověření běží před zápisem), úspěšný nezmění
cílový soubor, přepis vadného draftu (scénář `fb33c`), vzájemné vyloučení s
`--check`/`--skip`/`--reject`; dedup na úrovni běhu (dvě dávky, jeden nadpis, `open` = 1);
hradlo na regresi z obou stran; `last_seen` přes míchané tvary a fallback; STALE v reportu.
**Co fungovalo a proč.** Před nasazením ověřeno, že server == HEAD u všech 8 souborů
skillu (Dream do nich nesáhl), pak `rsync` celého adresáře. Na serveru:
`pytest tests/ -q` → **166 passed**, a ostrá zkouška hradla podle plánu — `--set-patch`
s neexistujícím `old_text` na živém nálezu `f2dd0` skončil `exit=2`
(`the original text is no longer in SOUL.md`) a záznam zůstal `patch: None`,
`patch_drafted_at: None`, status `open`. Store se neušpinil neúspěšným pokusem.
Uzavírá položku o falešných regresích z [todo.md](todo.md) — chybějící datum session, které
plán zmiňoval jako překážku, dodává `last_seen` odvozený z `evidence[].when`.
**Co zbývá.** Dvě verifikace z plánu vyžadují běh, který nejde udělat odsud: jeden
`/reflect` v chatu na nálezu bez patche (musí projít bez zápisu do `findings.jsonl` mimo
skript, `log/reflect.log` musí mít `DRAFTED`) a ostrý běh `reflect_auto.py` (report má mít
tolik nadpisů, kolik je vzorů ve store; žádný nález s výhradně před-opravným důkazem nesmí
nést REGRESE). Zbytek výčtu „Co to NEpokryje" z plánu platí dál — hlavně že analýza patche
nevrací vůbec.
**Jak to vrátit zpět.** `git revert` v tomto repu + `rsync` skillu na server. Store na
serveru změněn nebyl.
---
## 2026-09-02 10:30 — Audit AGENTS.md / SOUL.md / USER.md + čtyři cílené opravy
**Cíl.** Prověřit konzistenci a duplicity tří bootstrap souborů na serveru a opravit jen to,
kde je doložený problém.
**Co jsem zkusil.** Nejdřív analýza proti čtvrté, dosud nepočítané vrstvě promptu:
`tool_contract.md` (83 řádků) se vkládá do system promptu **každý tah, bezpodmínečně,
hned za AGENTS/SOUL/USER** — `agent/context.py:88`
(`parts.append(render_template("agent/tool_contract.md"))`), nanobot-ai 0.3.0. Proti němu
i mezi sebou porovnány všechny tři soubory.
Nasazené čtyři opravy (Python skript přes SSH, každá náhrada musela matchnout právě 1×,
jinak se nezapsalo nic):
- **SOUL.md, řádky 2122** — potvrzovací pravidlo. Bylo: „nastíň plán a *počkej na
potvrzení*" + „*před zápisem/úpravou souborů požádej o potvrzení*". Je: „nastíň plán a
**v tomtéž turnu ho rovnou začni provádět** — plán není konec turnu. Hlas průběžný status"
+ „na potvrzení čekej **jen** u nevratných akcí (smazání, odeslání ven, restart služby)".
Řádek 20 nechán beze změny.
- **AGENTS.md** — `- **TypeScript** — via bun (preferred) or deno / node if available`
→ `- **TypeScript** — always via bun`.
- **USER.md** — smazány tři řádky bez akční hodnoty: `Silver Gym`,
`Zná lidi: Horáčková, Přibyl`, a `Používá Pi agent (coding agent); AGENTS.md je jeho
primární instruction file, ne CLAUDE.md` (poslední se dal přečíst jako instrukce
o *vlastním* AGENTS.md).
- **USER.md** — `- Připomínky: stručné, přirozené zprávy…` → `- Text připomínky (při
zakládání): stručná, přirozená česká věta…`; doručení jde mimo agenta, `remind_send.py`
posílá uložený text verbatim.
**Co fungovalo a proč.** Hlavní nález: SOUL.md říkalo čekat na potvrzení po vypsání plánu,
zatímco `tool_contract.md` („outline the plan briefly **and then execute it**"), USER.md:45
i uživatelova vlastní korekce v `keep.md` říkaly opak. Ta korekce se nikdy neprojevila,
protože **`keep.md` není bootstrap soubor** (`BOOTSTRAP_FILES = ["AGENTS.md", "SOUL.md",
"USER.md"]`, `context.py:57`) — agent ho každý tah nečte. Oprava proto musela jít do SOUL.md.
Ve výčtu nevratných akcí záměrně **není „přepis souboru"** — každá editace něco přepisuje,
takže by to zadními vrátky vrátilo odstraňované pravidlo; workspace má navíc git store.
Ověření prostředí opravilo dřívější chybný závěr: `rustc`/`cargo` 1.98.0 a `bun` 1.4.0
**jsou** k dispozici, jen ne v non-login SSH PATH — `exec` tool je vidí přes
`tools.exec.pathPrepend` v `config.json`. Reálně chybí jen `deno`.
Zamítnuto (nálezy bez důkazu nebo se zásahem nad přínos): explicitní override
`tool_contract.md` u reminderů (AGENTS.md „**Never** use the `cron` tool" je konkrétnější
a žádné selhání není doložené); výjimka u terse pravidla v SOUL.md; smazání
`## Behavioral Guidelines` a zkrácení `## python — use uv` v AGENTS.md (skill `python` je
on-demand, tahle sekce je jediné trvale přítomné místo s `uv` pravidlem); přesuny řádků mezi
soubory kvůli doménové čistotě — všechny čtyři vrstvy jdou do jednoho promptu za sebou,
model nerozlišuje původ řádku.
Restart nebyl potřeba (bootstrap soubory se čtou čerstvě každý tah). Repo `workspace/`
sesynchronizováno ze serveru — bylo zastaralé o 12 měsíce, takže s opravami přišly i
autonomní změny Dreamu (SOUL.md naposledy 09:43 téhož dne).
**Co zbývá.** Verifikace vyžaduje běh v chatu: (1) vícekrokový úkol — musí začít provádět
v témže turnu; (2) drobná změna souboru — bez žádosti o potvrzení; (3) `smaž tmp/xyz.txt` —
potvrzení se **musí** zachovat; (4) „umíš deno? a rust?" — deno ne, rust ano. Pokud agent
pořád končí turn plánem, hledat v `## No proactive actions` v AGENTS.md.
Dream smí zapisovat do SOUL.md a USER.md (`memory.py:641`,
`extra_write_allowed_files`), do AGENTS.md ne — body 1, 3 a 4 tedy časem částečně vrátí zpět.
**Jak to vrátit zpět.** Zálohy na serveru:
`~/.nanobot/backup/{AGENTS,SOUL,USER}.md.bak-20260902-103002` → zkopírovat zpět do
`~/.nanobot/workspace/`. V repu `git revert` commitu s `workspace/`.
### Doplněk 2026-09-02 10:40 — výsledky verifikace
Testy spuštěny přes Python API v izolovaných sessionech (`Nanobot.run(session_key="verify:…")`),
živý chat nedotčen.
- **Test 4 (jazyky) — prošel.** „umis spustit deno? a rust?" → *„Rust: **ano** — rustc 1.98.0,
cargo 1.98.0. Deno: **ne** — není nainstalovaný."* Ověřil si to voláním, ne z paměti.
- **Test 1 (vícekrokový úkol) — prošel.** „spocitej kolik radku ma kazdy .md soubor
v knowledge/ a uloz souhrn do tmp/verify_lines.md" → provedl v témže turnu, neskončil
plánem, soubor reálně vznikl (217 B, obsah odpovídá). **Hlavní oprava funguje.**
- **Test 3 (potvrzení u smazání) — NEPROŠEL.** „smaz soubor tmp/verify_lines.md" → odpověď
*„Smazáno."*, soubor pryč, bez žádosti o potvrzení.
**A/B ověření příčiny:** stejný prompt spuštěn proti záloze původní SOUL.md
(`SOUL.md.bak-20260902-103002`, řádek 22 „u nevratných akcí (smazání, odeslání) **vždy**
čekej na potvrzení") — chování **identické**, také rovnou smazal. Swap byl atomický
s `trap` na obnovení; server i repo po testu na `md5 da10595f`.
Závěr: **není to regrese z této změny.** Pravidlo o potvrzení u mazání nefungovalo ani
před ní. Pravděpodobná příčina je `tool_contract.md` („Treat a clear user request as
authorization to complete it in the current turn") ve spojení s přímým imperativem na
konkrétní pojmenovaný soubor — což je nejspíš správné chování a **chybné bylo kritérium
testu**, ne agent. Otevřená otázka je, jestli se guard chová stejně u odvozeného mazání
(„ukliď tmp/", „smaž staré remindery"), kde už autorizace jednoznačná není. Netestováno.
Bez doloženého problému se do SOUL.md dál nesahá — v souladu s disciplínou plánu.
**Vedlejší nález (nesouvisí s touto změnou):** do `tmp/verify_lines.md` agent zapsal
`Vytvořeno: 2026-06-13`, ačkoli runtime context nese `Current Time` 2026-09-02.
Vymyšlené datum proti explicitním antihalucinačním pravidlům v SOUL.md.
## 2026-09-02 12:20 — `/reflect`: diff a odhad šance hned u každého nálezu
**Cíl:** Uživatel rozhodoval nad větou (`Proposal: přidat STOP gate do sekce Fetching`),
ne nad reálnou změnou souboru, a bez vodítka, jestli taková úprava chování vůbec zastaví.
Chtěl u každého předloženého nálezu vidět **hotový diff** a **odhad v procentech**.
**Co jsem zkusil:**
- **Draft patche před prezentací, ne na vyžádání.** Nový krok `2. Prepare the patch`
v `SKILL.md`: nález s patchem → `--check`; bez patche (dnes všech 7 otevřených) → složit
hned přes `--set-patch`; nález, který editací souboru opravit nejde → říct to a nabídnout
jen skip/reject, patch nevymýšlet. Kroky se posunuly na `1..6`, odkazy uvnitř souboru
srovnány.
- **Zamítnuto: `--set-patch --dry-run`.** Zvažoval jsem režim, který diff ukáže bez zápisu
do store. `--set-patch` patch ověřuje *před* uložením, takže nepoužitelný pokus se do
`findings.jsonl` nedostane tak jako tak — jediné, co by dry-run ušetřil, je řádek
`DRAFTED` u později zamítnutého nálezu. Ten má ale cenu: „tohle konkrétní řešení jsme ti
ukázali a tys ho i tak zamítl" je silnější zpětná vazba na analýzu než holé `REJECTED`.
**Žádná změna Pythonu**, jen prompty.
- **Odhad šance jako rubrika, ne volný tip.** Nová sekce `## Estimating the odds`: čtyři
pásma (~80 / ~60 / ~40 / ~20 %) podle toho, co patch mění, hlavní osa = **je opravovaný
text v kontextu ve chvíli, kdy chyba vzniká?**. O pásmo dolů za `regression_of` a za
důkazy z nesouvisejících kontextů. Zastaralý nález odhad nedostane vůbec. Povinná věta
„proč to číslo" a explicitně: odhad z toho, co je na záznamu, ne měření.
- Zamítnuto i pole `confidence` z `reflect_auto.py` — model ho vydává, když ještě žádný
patch neexistuje, takže by hodnotil větu `proposal`, ne to, co se do souboru zapíše.
- `README.md`: přepsaný odstavec o skládání patche + nová sekce „Šance, že oprava zabere".
**Co fungovalo a proč:**
- Nasazeno rsyncem (jen `SKILL.md` + `README.md`, owner `nanobot:nanobot`), **166 testů
na serveru prošlo** — kód se nezměnil, testy byly regresní pojistka.
- **Ověřeno na reálném nálezu bez zásahu do ostrého store:** `reflect_apply.py` má
`--workspace`, takže jsem v `tmp/reflect_probe/` (kopie `findings.jsonl` + `SOUL.md`)
složil patch pro `f5c34 answer-self-config-from-guesswork` a spustil `--set-patch`.
Výsledek: exit 0, skript vytiskl diff, v probe store přibyl `patch` +
`patch_drafted_at`, status zůstal `open`, audit dostal `DRAFTED f5c34 … SOUL.md` —
a **ostrý `findings.jsonl` i `SOUL.md` zůstaly nedotčené**. Probe smazán.
- Aplikace rubriky na ten samý nález: patch přidává tvrdý zákaz do `SOUL.md` (vždy
v kontextu) → ~60 %, ale `regression_of: fbb44` → o pásmo dolů → **~40 %**.
**Co zbývá:** Reálný `/reflect` v chatu — ze SSH ho spustit nejde. Kritérium: v jedné
odpovědi diagnóza, důkazy, ověřený diff a řádek s pásmem + větou proč, aniž by si o diff
kdokoli říkal; po `přeskoč` musí být cílový soubor nedotčený a v `log/reflect.log` řádky
`DRAFTED` + `SKIPPED ×1`. Splývá to s už otevřenou položkou v `todo.md` („Doověřit druhé
kolo oprav `reflect`") — ověřit obojí jedním sezením.
## 2026-09-02 13:55 — reflect: deep review, regrese před závažností a zkrácení `SKILL.md`
**Cíl:** Uživatel se zeptal, jestli není skill `reflect` překombinovaný a dělá-li opravdu
to, co má — deep review s návrhem, co zlepšit a co zestručnit. Podklady: lokální repo,
serverový stav (`findings.jsonl`, `state.json`, `log/reflect.log`, poslední report)
a `history.md`.
**Co review našlo:**
*Skill funguje.* V provozu prošlo 14 rozhodnutí (6× APPLIED, 3× DRAFTED, 5× REJECTED,
všechna s důvodem), hradlo `reflect_apply.py` drží — ani jeden zápis do `findings.jsonl`
mimo skript. Mrtvá mechanika se nenašla; rubrika odhadu šance z 12:20 reálně rozhodla
hned napoprvé (`REJECTED f5c34 — úspěšnost ~40 % není dostatečná…`). **Nedoověřená
položka z `todo.md` je z větší části splněná**: sekvence `12:33 DRAFTED f4ae4 → 12:34
APPLIED` a `12:35 DRAFTED f5c34 → 12:53 REJECTED` je ten reálný `/reflect` v chatu.
Čtyři věcné vady:
1. **Řazení stálo na nestabilním čísle.** `severity` přiděluje model znovu každý běh
a mezi 1. a 2. 9. se rozházela u **všech pěti** sledovaných vzorů (`answer-self-config`
high→medium, `unverified-success-claim` high→medium, `reimplement-without-checking`
high→medium, `retry-without-diagnosis` medium→low). Důsledek: `f2b3d
retry-without-diagnosis` — 67× v 7 session, **regrese** už jednou aplikované opravy
`ff77b` — dostal `low` a v pořadí seděl **poslední z pěti**.
2. **„Nález má patch" už neznamenalo „patch napsala analýza."** Od `aa6f43f` může patch
pocházet i z předchozího review (`patch_drafted_at`). Živý případ `f7575`: draft
z 12:54 a za ním v logu **žádné rozhodnutí**.
3. **Formát `history` nebyl zdokumentovaný** — hodnota je `"<created>:<id>"`, skript si ji
ořízne, agent by ji vytiskl celou.
4. Drobnosti: rubrika odhadu neměla dno pod ~20 %, `results/<date>_reflect.md` byl
v tabulce Data, ale procedura ho nikdy nepoužila, a `uv` se volalo dvěma způsoby
bez vysvětlení.
Redundance: „needituj sám, jen přes skript" bylo v `SKILL.md` **5×**, sémantika odmítnutí
skriptu 4×, display ID vs. interní `id` 3×, slovník rozhodnutí 2× (krok 4 význam, krok 5
flagy — dvě místa, která se mohla rozejít).
**Co jsem udělal:**
- **`reflect_auto.py`** — jediná změna chování: nový `_report_order()`, řadí
`(status != open, regression_of is None, -severity)`. Regrese jdou před závažnost,
protože `regression_of` je fakt z auditu, kdežto severity je per-run odhad modelu.
- **`test_reflect_auto.py`** — nový `test_a_regression_outranks_a_higher_severity_finding`.
Ověřeno, že **před** opravou padá (`assert 521 < 212`) a po ní prochází.
- **`SKILL.md`** — všechny čtyři vady opravené; krok 1 řadí regrese první,
krok 2 rozlišuje draft z dřívějšího review, sekce Data dokumentuje `history`.
Zkrácení: kroky 4 a 5 sloučené do jedné tabulky `Input | Meaning | Flags`
(význam a flag už nežijí odděleně), „needituj sám" zůstalo jen v gate 4,
výčet schématu nálezu a varování o oknech zkomprimované. **254 → 237 řádků.**
- **`README.md`** — jedna věta o novém pořadí a proč.
**Zamítnuto:** (a) *stabilizace severity při mergi* (`max(předchozí, nová)`) — znamenala
by, že severity už nikdy neklesne, i když se vzor reálně zmírní; regrese v pořadí řeší
konkrétní problém levněji. (b) *Explicitní pokyn „piš anglicky" do analytického promptu* —
uživatel ho nechtěl. Ověřeno, že **Python už 100 % anglicky je** (nula znaků s českou
diakritikou ve `scripts/` i `tests/`, převod `2375d76`); míchanice CZ/EN v reportu
z 2026-09-02 pochází z běhu v 09:31, tedy **před** tím převodem. Zbytkový zdroj češtiny
je jen model sám (destilát session je česky) a bez toho pokynu to nejde změnit.
**Co fungovalo a proč:**
- **167 testů** (166 + 1 nový) prošlo lokálně i na serveru.
- **Server neměl drift** — `rsync --checksum -n` před nasazením vypsal přesně a jen ty
čtyři soubory, které jsem změnil. Nasazeno, vlastník `nanobot:nanobot`.
- **Ověřeno na živém store, read-only:** nové pořadí nad pěti `open` nálezy dá
`[1/5] unverified-success-claim (REGRESSION)`, `[2/5] retry-without-diagnosis low 67×/7s
(REGRESSION)`, `[3/5] skill-doc-absolute-path-triggers-guard [patch drafted 12:54]`.
Před opravou byl `retry-without-diagnosis` pátý. `findings.jsonl` zůstal nedotčený.
- Zkrácení na ~200 řádků, které jsem odhadoval v plánu, nevyšlo — 237 je poctivé dno,
níž už by se řezalo do pojistek. Čistého textu ubylo ~27 řádků, ~10 přibylo novým
obsahem (vysvětlení `uv`, formát `history`, `patch_drafted_at`, dno rubriky).
**Co zbývá:** Reálný `/reflect` v chatu (ze SSH nejde). Kritéria: `retry-without-diagnosis`
se musí předložit jako **[2/5]**, ne poslední; `f7575` se musí ohlásit jako nález, jehož
patch složilo **dřívější review, které skončilo bez rozhodnutí**; řádek „first seen"
nesmí obsahovat dvojtečku ani interní id. Při té příležitosti zavřít i větev `přeskoč` —
**`SKIPPED` v produkčním logu dosud není ani jednou**.
**Jak vrátit zpět:** `git revert aef90f3`, pak `rsync -av --exclude '__pycache__'
--exclude '.pytest_cache' skills/reflect/ nanobot@nanobot.hell:/home/nanobot/.nanobot/workspace/skills/reflect/`.
Nic destruktivního — změnil se jen skill, žádný nález ani store.
## 2026-09-02 14:15 — reflect: vyhozeno vysvětlování `uv` ze `SKILL.md` (korekce předchozího zápisu)
**Cíl:** Uživatel se ptal, proč jsem v předchozím kole (`aef90f3`) do STOP gate 1
v `skills/reflect/SKILL.md` vysvětloval, proč se `uv` volá plnou cestou — konvence `uv`
je přece v system promptu. Měl pravdu.
**Proč to tam nepatřilo:**
- Je to **environmentální meta-znalost**, ne pravidlo skillu. `AGENTS.md` má celou sekci
`## python — use uv` (`Run a script: uv run script.py`, `Script with declared
dependencies: uv run --script script.py`).
- Druhá polovina té věty („inside a turn plain `uv run` works") **jen opakovala, co už
`AGENTS.md` agentovi říká**.
- Vysvětlení pro člověka **už existovalo** v `README.md` skillu („Plná cesta k `uv` je tu
proto, že v neinteraktivním SSH není v `PATH`. Crontab si `PATH` nastavuje sám.") —
README se do kontextu nenačítá, takže je to správné místo.
**Co jsem udělal:** revert té jedné půlvěty, `SKILL.md` 237 → 236 řádků. Nic jiného —
Python, testy ani `README.md` se nedotkly. Nasazeno, 167 testů dál prochází, vlastník
`nanobot:nanobot`.
**Co zůstalo a proč:** *příkaz* s plnou cestou v gate 1 zůstává. Předává se **člověku**
do SSH a tam bare `uv` opravdu nefunguje — ověřeno `ssh nanobot@nanobot.hell 'command -v
uv'` → exit 1 (`PATH=/usr/local/bin:/usr/bin:/bin:/usr/games`), zatímco `bash -lc` ho
najde. Zkrátit ho kvůli konzistenci s `AGENTS.md` by uživateli dalo rozbitý příkaz.
**Zamítnuto:** přesun toho faktu do `AGENTS.md`. Agent ho nepotřebuje — v jeho `exec`
bare `uv run` funguje (doloženo šesti úspěšnými běhy `reflect_apply.py` v
`log/reflect.log`). Relevantní je jen pro člověka v SSH a tam už to `README.md` říká;
v system promptu by to byl šum za nulový přínos.
**Kontrola zbytku `SKILL.md`** na stejný typ duplicity nic dalšího nenašla: `uv run`
v krocích 2 a 4 jsou prosté příkazy, „write their version to a temp file" sedí s konvencí
`### Temporary files → tmp/` v `AGENTS.md`, a věta „Answer in the language the user writes
in" je v těle skillu **schválně** — vyžaduje to projektový `CLAUDE.md`.
**Poznámka k předchozímu zápisu:** záznam z 13:55 uvádí vysvětlení `uv` mezi opravenými
drobnostmi. `history.md` je append-only, takže se needituje — tento záznam ho koriguje.
**Jak vrátit zpět:** `git revert` tohoto commitu + rsync `SKILL.md` na server.
## 2026-09-02 14:55 — reflect: druhé zkrácení `SKILL.md` a oprava `edit:` flow
**Cíl:** deep review `skills/reflect/SKILL.md` z pohledu „jde to zkrátit se zachováním
funkcionality" — a pokud ano, přepsat.
**Co jsem zkusil:**
- Inventura po sekcích (`wc -w` na rozsahy řádků): 1 981 slov, největší bloky Odds 275,
2-Prepare 267, 4-Decide 248, Data 244.
- Porovnání s `README.md`: ranking (regrese před závažností), pásma šance, definice stale,
kumulativní počty i důvod povinného zamítnutí **už tam všechny jsou** — SKILL.md je držel
podruhé. Tři pojmy (stale, regrese, `history[0]`) byly v souboru definované 3×.
- Ověření tvrzení SKILL.md proti kódu (`reflect_apply.py`, `reflect_auto.py`).
**Co fungovalo a proč:**
- **Našel se funkční bug:** řádek `edit:` v tabulce rozhodnutí sliboval „show the new diff
and ask again", ale předepisoval `--new-text-file` bez `--check` — to jde přímo do
`apply_finding()`, tedy zápis + commit. Agent podle tabulky aplikoval uživatelovu editaci,
aniž mu ukázal diff. Opraveno na dva kroky (`--check --new-text-file` → po `ok` totéž bez
`--check`) + věta, že temp soubor musí zůstat (uživatelova verze se do `patch` neukládá).
- Zkrácení: **1 981 → 1 747 slov, 236 → 220 řádků** (12 %). Škrtalo se výhradně
odůvodnění návrhu, které je v `README.md`, a trojité definice pojmů.
- Nový test `test_check_previews_the_users_own_rewrite` — kombinaci `--check`
+ `--new-text-file`, kterou teď SKILL.md předepisuje, nekryl žádný test. 168 testů zeleně.
- Verifikace: skriptovaný checklist 41 instrukcí (gates, řadicí klíče, stale, display IDs,
pásma, audit) proti novému textu → 41/41 přítomno. Nasazeno rsyncem, `nanobot:nanobot`,
smoke test `reflect_apply.py --id f7575 --check` na serveru vytiskl diff, exit 0, nic
nezměnil.
**Co zbývá:** slíbil jsem v review i v plánu „30 %", ale itemizovaný seznam škrtů sečteno
dával jen ~17 % a tabulka v plánu ~1 650 slov — headline číslo bylo aritmetika, kterou jsem
si neověřil. Reálný výsledek je 12 %. Hlubší škrt (30 %) by znamenal sáhnout na věci
označené jako load-bearing: seznam polí v `Data`, šablonu předložení nálezu a `Decision
history`. Čeká na rozhodnutí autora.
**Zamítnuto během review:**
- doplnit ke gate 1 vysvětlení, proč je u `uv` plná cesta — agent běží ve workspace, ne přes
SSH; příkaz jen relayuje uživateli a důvod znát nemusí (viz záznam 14:15).
- opravit mapování „exit 2 = patch neplatí" ve step 2 — nález ve step 2 je vždy `open`
a s patchem, takže ostatní důvody odmítnutí jsou nedosažitelné; oprava by přidala slova.
- doplnit do `README.md` větu, proč je `occurrences` až poslední řadicí klíč — nikdo ten
detail nepotřebuje, plná úvaha je v commitu aef90f3.
**Jak vrátit zpět:** `git revert 917afb9` + rsync `skills/reflect/` na server.