3862 lines
368 KiB
Markdown
3862 lines
368 KiB
Markdown
# History
|
||
|
||
## 2026-09-11 — AGENTS.md: pravidlo o exec guardu globálně + srovnání skillu `note`
|
||
|
||
**Cíl:** Navázat na opravu skillu `project` (záznam níže) a zapsat pravidlo o exec
|
||
guardu na místo, kde pomůže všem skillům, ne jen tomu, kde se zrovna projevilo.
|
||
|
||
**Co jsem zkusil / co jsem našel:**
|
||
|
||
Nejdřív jsem zjišťoval, který soubor nanobot vůbec načítá do system promptu.
|
||
`build_system_prompt()` (`agent/context.py:70–121`) skládá prompt z
|
||
`BOOTSTRAP_FILES = ["AGENTS.md", "SOUL.md", "USER.md"]`. Z nich je **`AGENTS.md` jediný
|
||
user-ownovaný** — `SOUL.md`, `USER.md` i `memory/MEMORY.md` přepisuje Dream, bundled
|
||
`templates/` přepíše upgrade balíčku.
|
||
|
||
Při čtení `AGENTS.md` vyšlo najevo, že **tentýž bug už tam byl zdokumentovaný dvakrát
|
||
izolovaně**: sekce `## Git commit timestamps` říká, že `date '+%Y-%m-%d %H:%M:%S'` guard
|
||
blokuje (ověřeno — token `H:%M:%S`, `H` po `%`), a sekce `## exec Tool` řešila chybějící
|
||
workspace path. Ani jednou se nepojmenovala společná příčina. Třetí výskyt byl živý:
|
||
`note_capture.py --text "Cíl: koupit mléko"` → blok (token `l:`), přičemž
|
||
`note/SKILL.md` explicitně nařizuje „Pass the input **as-is**". Poznámka uživatele
|
||
začínající `Cíl:` tedy tiše selhávala — a capture bere vstup doslova, takže to nebyl
|
||
okrajový případ, ale běžná česká formulace.
|
||
|
||
**Co fungovalo a proč:**
|
||
|
||
- **`workspace/AGENTS.md`** — nejdřív dotažena autoritativní serverová verze do repa
|
||
samostatným commitem (lokální kopie byla zastaralá a server je zdroj pravdy), teprve
|
||
pak přidáno pravidlo do sekce `## exec Tool`: text nikdy do command stringu, vždy
|
||
`write_file` do `tmp/` + předání cesty. Sekce `## Git commit timestamps` ponechána —
|
||
jmenuje konkrétní skript a je jen instancí obecného pravidla.
|
||
- **`skills/note`** — `--text` nahrazen za `--file <path>` (fail fast na chybějící
|
||
soubor), obě volací místa v SKILL.md převedena na dvoukrok, přidána sekce *Never pass
|
||
the text on the command line*. Testy 19 → 22.
|
||
|
||
Ověřeno na serveru proti **nainstalovanému** guardu:
|
||
|
||
```text
|
||
PROJDE NOVY --file -> []
|
||
BLOKOVAN STARY --text -> ['l:', 'r:']
|
||
```
|
||
|
||
End-to-end: `write_file` textu `Cíl: koupit mléko. Závěr: zítra.` + `--file` → soubor
|
||
v `notes/inbox/` s korektním frontmatterem, exit 0. Inbox pak uklizen, **`log/note.log`
|
||
ne** — je to append-only audit log a mazat ho by porušilo přesně to pravidlo, které
|
||
tahle práce kodifikuje. Zůstal tam jeden testovací CAPTURE řádek z 15:09.
|
||
|
||
Ověřeno i to, že se `AGENTS.md` do promptu opravdu dostává (liší se od
|
||
`templates/AGENTS.md`; při shodě by ho `context.py:179–182` tiše vynechalo).
|
||
|
||
**Co zbývá:**
|
||
|
||
- `bookmark` (český článek v heredocu) a `remind` (`edit --text`) mají pořád stejnou
|
||
expozici — vědomě mimo scope, vedeno v `knowledge.md`.
|
||
- `AGENTS.md` se bere z `project_root` aktuálního tahu (`context.py:163`), takže
|
||
v session scoped do `tmp/<x>` se root verze nenačte. Pro chat workflow to nevadí;
|
||
univerzální varianta by byl skill s `always: true`.
|
||
- Regex v nanobotu zůstává rozbitý; tohle je workaround, ne fix.
|
||
|
||
**Jak to vrátit zpět:** `git revert` příslušných commitů + rsync `workspace/AGENTS.md`
|
||
a `skills/note/` na server. Nic se nemazalo kromě vlastních testovacích artefaktů.
|
||
|
||
## 2026-09-11 — project: zápis do memory.md padal na exec guardu, převeden na --file
|
||
|
||
**Cíl:** Agent na serveru nedokázal zapsat záznam do `projects/life/memory.md` —
|
||
tři pokusy po sobě spadly na `Command blocked by safety guard (path outside
|
||
working dir)`. Zjistit proč a opravit skill, aby se to neopakovalo.
|
||
|
||
**Co jsem zkusil / co jsem našel:**
|
||
|
||
Stažena session `002f2196` z `~/.nanobot/webui/`. Pořadí selhání: quoted heredoc
|
||
→ `printf | pipe` → `--text`, všechny se stejnou chybou; uspěl až `write_file` do
|
||
`tmp/` + `log life < tmp/soubor`. Agent si to ve vlastním shrnutí vyložil jako
|
||
„nedeterministický blok na datu a tečkách" — **to je mylné**.
|
||
|
||
Skutečná příčina, ověřená spuštěním nainstalovaného guardu
|
||
(`/home/nanobot/.local/share/uv/tools/nanobot-ai/…/nanobot/agent/tools/shell.py`)
|
||
na reálných příkazech: `ExecTool._extract_absolute_paths()` hledá windowsové cesty
|
||
regexem s **ASCII-only negativním lookbehindem** `(?<![A-Za-z])[A-Za-z]:…`. Znak
|
||
s diakritikou před ASCII písmenem lookbehind neutne, takže `Cíl:` → token `l:`,
|
||
`Závěr:`/`směr:` → `r:`. `Path("r:").resolve()` se rozvine vůči cwd démona
|
||
(`/home/nanobot`, ne vůči workdiru příkazu) → `/home/nanobot/r:` → mimo workspace
|
||
→ blok. Guard navíc jede nad **raw command stringem bez shell parseru**, takže
|
||
nerozliší argument od obsahu heredocu — proto padly všechny tři varianty stejně.
|
||
Blokovaný text obsahoval `Diagnóza/směr:`; `artifacts/fyzio-cviky-kycele.md` ani
|
||
lomítko samo o sobě vinné nebyly.
|
||
|
||
Kontrolní vzorek proti nainstalovanému guardu (`--file` vs. staré varianty):
|
||
|
||
```text
|
||
PROJDE NOVY --file -> []
|
||
BLOKOVAN STARY heredoc -> ['l:', 'r:', 'r:']
|
||
BLOKOVAN STARY --text -> ['l:', 'r:', 'r:']
|
||
BLOKOVAN STARY printf|pipe -> ['l:', 'r:', 'r:']
|
||
```
|
||
|
||
**Co fungovalo a proč:**
|
||
|
||
Oprava není „jiný způsob citace", ale **text se nikdy nesmí dostat do command
|
||
stringu**. Konkrétně:
|
||
|
||
- `project_cli.py`: `--text` odstraněn, přidán `--file <path>` (fail fast na
|
||
neexistující soubor). Stdin zůstal jako fallback. Flag místo shell redirectu
|
||
proto, že celý failure mode byl model improvizující shell syntax.
|
||
- `SKILL.md`: sekce „Writing to memory.md" přepsána na dvoukrok `write_file` do
|
||
`tmp/` + `log <slug> --file …`, s vysvětlením **proč**, ať to nikdo
|
||
neoptimalizuje zpátky na heredoc.
|
||
- `SKILL.md` Rules: zákaz testovacích zápisů do ostrého projektu — agent si při
|
||
diagnostice zanesl do append-only `memory.md` dvě test položky a pak je ručně
|
||
odstranil, čímž porušil „never rewrite history". Od toho je `new <scratch-slug>`.
|
||
- Testy: 15 → 20. Sekce `log` převedena na fixture `entry_file`, takže testuje
|
||
reálnou cestu přes soubor. Přibyl regresní test s `Cíl:`/`Závěr:`, test na
|
||
chybějící soubor a pojistka, že `--text` v argparse skončí `SystemExit 2`.
|
||
|
||
Před nasazením ověřena shoda serveru s repem (`rsync` + `diff -r` → lišil se jen
|
||
`__pycache__`). Nasazeno rsyncem, vlastník `nanobot:nanobot` ověřen, restart
|
||
služby není potřeba. End-to-end test proti scratch projektu `guard-test` (ne proti
|
||
`life`) s textem `Cíl: … Diagnóza/směr: … Závěr: …` prošel, exit 0; scratch
|
||
projekt i `tmp/guard-test.md` pak smazány.
|
||
|
||
**Co zbývá:**
|
||
|
||
Guard v nanobotu je pořád rozbitý a **stejnou expozici mají další skilly** —
|
||
`note` (`--text "<raw input>"` se syrovým českým vstupem uživatele, nejrizikovější),
|
||
`bookmark` (český článek v heredocu) a `remind` (`edit --text`). Vědomě mimo scope
|
||
této opravy, zapsáno do `knowledge.md` jako známý dluh. Plošný fix by znamenal
|
||
unicode-aware lookbehind v upstreamu (resp. ve forku `nanobot-custom-commands`).
|
||
|
||
**Jak to vrátit zpět:** `git revert` commitu se skillem + rsync `skills/project/`
|
||
na server. Žádná data se nemazala kromě scratch projektu `guard-test`.
|
||
|
||
## 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, 2–4 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 ~20–30 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,15–0,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 5–15 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 5–15 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 2–5), 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 3–5).
|
||
|
||
**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 400–7 400 ms na prvních 4 příkladech — přibližně 3–8× horší. Skutečná cold performance je cca 4–5 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 (3–4 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 1–7 (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 4–7 přečíslovány na 5–8.
|
||
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 ~28–32k přes nanobot agent loop), ~1–1,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é 20–60 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 0–3, 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 ~2–7 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ý (~6–7 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 3–4× MÉNĚ tokenů** (~430 vs ~1480). To je hlavní důkaz „lenosti" minimaxu.
|
||
- **Generace tok/s:** minimax **~95–120 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ý (1200–1700 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:36–38 `.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 1–25, cron 1–6, random 1–9). 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` má `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ů (Po–Ne), 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 15–100 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` ř. 615–634), 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` ř. 615–634 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 700–1300 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 165–207 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:08–09: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 21–22** — 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 1–2 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.
|
||
|
||
---
|
||
|
||
## 2026-09-04 06:50 — Přeuklizení `keep.md`: rozdělení obsahu do správných úložišť
|
||
|
||
**Cíl:** `keep.md` na serveru měl 8 položek, ale skill ho definuje pro trvalé osobní
|
||
fakty / preference / rozhodnutí. Zjistit, co tam nepatří, přesunout do správného úložiště
|
||
a zabránit opakování driftu.
|
||
|
||
**Co jsem zkusil:**
|
||
|
||
- Triáž všech 8 řádků proti popisu skillů `keep` / `note` / `project` a proti `AGENTS.md`.
|
||
Verdikt: 4× `notes.md`, 1× `AGENTS.md`, 1× `projects/proxmox/state.md`, 1× smazat, 1× nechat.
|
||
- Sync server → repo **jako první krok** (`skills/keep/SKILL.md` a `workspace/AGENTS.md`) —
|
||
server byl u obou napřed, viz níže.
|
||
- `notes.md`: stáhnout rsyncem, upravit lokálně Pythonem s unikátní kotvou + `assert`,
|
||
nahrát zpět, commit `note:` **jen** `git add notes/` v serverovém workspace repu.
|
||
Stejný postup pro `AGENTS.md`, `projects/proxmox/state.md` a `keep.md`.
|
||
- Do `skills/keep/SKILL.md` vložena sekce `## Triage — before writing anything` před
|
||
`## Write protocol` + jeden řádek do `## Rules`. Nasazeno rsyncem celého adresáře.
|
||
|
||
**Co fungovalo a proč:**
|
||
|
||
- **Klíčové zjištění:** `AGENTS.md` → `## Explicit user details` říká *„Explicit user facts
|
||
are stored in `keep.md`. **Read at every turn**"*. `keep.md` je tedy v kontextu každého
|
||
tahu — proto tam pravidla pro agenta (#3, #6) čte model jako fakta o uživateli místo jako
|
||
instrukce, a věci k vyzkoušení obcházejí `/note`, kde je uživatel hledá. To je důvod
|
||
přesunu, ne estetika.
|
||
- **Konflikt nalezen:** položka „git repo naklonuj do `tmp/`" byla v přímém rozporu
|
||
s konvencí `AGENTS.md` *„Always clone repos into `workspace/src/<repo-name>`"*.
|
||
Rozhodnutí uživatele: platí `AGENTS.md` → položka smazána, `AGENTS.md` beze změny.
|
||
- **Server byl napřed před repem** (potvrzuje pravidlo „nejdřív stáhni, pak přepiš"):
|
||
serverový `keep/SKILL.md` měl zostřený `description` (`NOT collecting notes … (that is
|
||
note)`), a serverovému `AGENTS.md` chyběly v lokálním mirroru **dvě sekce**
|
||
(`## Explicit user details`, odstavec o safety-guard blocku). Slepý rsync repo→server
|
||
by obojí zahodil.
|
||
- Nové pravidlo v `AGENTS.md` (bod 5, vícekrokový úkol = plán a rovnou provedení) muselo
|
||
dostat explicitní hranici vůči existující sekci `## No proactive actions`, jinak by si
|
||
konkurovaly: *„find out / investigate / check" zůstává information-only*.
|
||
- Příčina driftu byla ve skillu, ne v jednotlivých zápisech — `keep` neměl žádný triage
|
||
krok, šel rovnou od „přeformuluj" k „zapiš".
|
||
|
||
**Co zbývá:**
|
||
|
||
- Funkční test triage gate (poslat přes Telegram `/keep <pravidlo pro agenta>` a ověřit,
|
||
že skill odmítne zapsat a odkáže na `AGENTS.md`) — vyžaduje živý provoz, neověřeno.
|
||
- V `notes.md` sekci `## Claude Code / AI Agents` leží druhá skupina odrážek („Komunikace
|
||
v češtině", „Informace hledej … i v dokumentaci online", „Pokud si nejsi jistý, ptej se")
|
||
— to jsou pravidla pro agenta zabloudilá do `notes.md`, stejná třída chyby jako v keep.
|
||
Mimo zadání, neřešeno.
|
||
- Změněné soubory v serverovém workspace repu (`keep.md`, `AGENTS.md`, `projects/`) jsem
|
||
**necommitoval** — byly modified už před zásahem (Dream/reflect vlastní zbytek workspace),
|
||
commituje se tam jen `notes/` přes `note` skill. Ponecháno ve stavu, v jakém repo běží.
|
||
|
||
**Jak vrátit zpět:**
|
||
|
||
- `keep.md` (není commitnutý, původní obsah doslovně):
|
||
|
||
```
|
||
- Podcast "Máš na míň" — moderátor Vašek Matějovský spolumoderuje s Radarem (Michal Vrátný, zakladatel Železné koule)
|
||
- Chce mít filmy uložené lokálně na disku — streaming služby mění podmínky a dostupnost, chce mít obsah trvale k dispozici po zaplacení
|
||
- Při studiu 3+ souborů z git repozitáře (typicky GitHub) naklonuj do tmp/ a zkoumej lokálně — ale jen pokud repo není obrovské (desítky MB OK, stovky MB+ už ne)
|
||
- Otestovat s Claude Code plugin llm-wiki-plugin (https://github.com/praneybehl/llm-wiki-plugin)
|
||
- Vyzkoušet LibreChat
|
||
- Vícekrokový úkol/research: po vypsání plánu v témže turnu rovnou začněn provádět (plán ≠ konec turnu) a hlásit průběžný status — pouhé vypsání plánu a ukončení turnu vypadá jako dokončená odpověď, uživatel pak zbytečně čeká
|
||
- Chce absolvovat kurz/workshop práce se dřevem se základními domácími nástroji; k tomu YouTube videa na teorii: typy dřeva, typy vrutů, typy bitů, moření, lakování, broušení, spojování vruty, lepení
|
||
- Hlavní server proxmox.hell: Dell OptiPlex 9020 Mini Tower, Intel Core i7-4790 @ 3.60GHz, 32GB RAM, GeForce RTX 4060 Ti 16GB, KINGSTON SKC3000D2048G 2TB NVMe, KINGSTON SUV5004 56RI 480GB SSD
|
||
```
|
||
|
||
- `notes.md`: `ssh nanobot@nanobot.hell 'cd ~/.nanobot/workspace && git revert f136f50'`
|
||
- `AGENTS.md` / `projects/proxmox/state.md`: `git checkout -- AGENTS.md projects/proxmox/state.md` v serverovém workspace repu (pozor: vrátí i případné cizí necommitnuté změny).
|
||
- `skills/keep/`: `git revert a96a5c6` v tomto repu + rsync `skills/keep/` na server.
|
||
|
||
---
|
||
|
||
## 2026-09-04 07:20 — `keep` skill: description podle oficiálního formátu + přenositelnost
|
||
|
||
**Cíl:** Uživatel reklamoval `description` u `skills/keep/SKILL.md` — čeština uvnitř a špatně
|
||
napsaná proti tomu, jak má `description` vypadat. V průběhu doplnil druhou vadu: absolutní
|
||
cesta v těle skillu ho dělá nepřenositelným.
|
||
|
||
**Co jsem zkusil:**
|
||
|
||
1. Porovnal serverovou verzi s repem (`ssh nanobot@nanobot.hell cat …`) — **identické**,
|
||
žádný drift od Dreamu k dotažení.
|
||
2. Dostudoval dokumentaci. `nanobot.wiki` (0.3.0) frontmatter skillů **nedokumentuje** —
|
||
`/docs/0.3.0/` nemá stránku o skillech, `use-nanobot/concepts` je zmiňuje jen jako obsah
|
||
workspace. WebFetch na wiki vrací 403, curl s UA projde. Autoritou je proto
|
||
[Anthropic Skill authoring best practices](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices)
|
||
(formát je identický) + naše zjištění ze zdrojáku v `knowledge.md`.
|
||
3. Změřil současnou verzi proti pravidlům: ✗ CZ triggery (EN-only + ověřeno, že
|
||
dvojjazyčné nic nepřidají), ✗ otevírá noun-phrase definicí místo slovesné fráze,
|
||
✗ obsahuje *jak* funguje (dedup/compaction/cesta), ✗ interní architektura
|
||
(MEMORY.md / Dream), ✗ duplicita, ~ chybí odlišení od `/project`.
|
||
Třetí osoba porušená nebyla.
|
||
4. Přepsal `description` do oficiálního tvaru `<co dělá>. Use when <triggery>.` (385 znaků,
|
||
limit 1 024), opravil poslední češtinu v těle (`"udělej compaction"` → `the user asks
|
||
for compaction`) a absolutní cestu → `workspace/keep.md`.
|
||
5. Nasadil rsyncem, ověřil vlastníka.
|
||
|
||
**Co fungovalo a proč:**
|
||
|
||
- Grepy čisté: žádná čeština, žádné `/home/nanobot` v `skills/keep/SKILL.md`.
|
||
- **Funkční test routingu:** `nanobot agent -m "zapamatuj si, že testovací server pro
|
||
staging je stag-07"` → odpověď `Kept: …`, záznam v `keep.md`. Skill se tedy trefil
|
||
i bez českých triggerů — potvrzuje ověřený předpoklad z `/plan` (2026-05-31), že
|
||
dvojjazyčné triggery jsou zbytečný token cost.
|
||
- Testovací záznam z `keep.md` smazán (záloha `/tmp/keep.md.bak` na serveru).
|
||
- `nanobot` binárka není v PATH non-login shellu — volat `~/.local/bin/nanobot`
|
||
(stejná gotcha jako u `uv`).
|
||
|
||
**Co zbývá:**
|
||
|
||
- `skills/plan/SKILL.md:45` má stejnou absolutní cestu, přitom řádky 69 a 75 v témže
|
||
souboru už jsou relativní. `bookmark` má absolutní cesty ve spouštěcích příkazech —
|
||
tam opodstatněné (PATH gotcha u `uv`). Neřešeno, mimo scope zadání.
|
||
- Ostatní skilly nebyly proti oficiálnímu tvaru `description` proměřeny.
|
||
|
||
**Jak vrátit zpět:** `git revert 18ab382` + rsync `skills/keep/` na server.
|
||
|
||
---
|
||
|
||
## 2026-09-09 20:20 — Final wiki: zhodnocení draftu hybrid RAG a přepis do `final-wiki-hybrid-rag.md`
|
||
|
||
**Cíl:** Zhodnotit serverový draft `plans/notes-search-hybrid-rag.md` (hybrid RAG index nad
|
||
uživatelovými git poznámkami), rozhodnout, jestli by to tak šlo, a přepsat do finálního plánu.
|
||
|
||
**Co jsem zkusil:**
|
||
|
||
1. Přečetl draft a proměřil prostředí místo věření odhadům v něm:
|
||
- Ollama na `nvidia.hell` dosažitelná; `qwen3-embedding` tam zpočátku nebyl (uživatel dodal).
|
||
- Cold load modelu 1,81 s vs. warm 0,043 s; throughput 8,9 chunk/s po jednom vs.
|
||
**108 chunk/s v batchi 32**.
|
||
- Cosine nad 10⁵×1024 v BLOB+numpy: **594 ms** = read 382 + pack 190 + **dot jen 22**.
|
||
Přes `.npy` + `mmap_mode='r'` 35 ms.
|
||
- FTS5: `remove_diacritics 1`+ foldí diakritiku, `záloh*` pokryje českou flexi.
|
||
- Korpus workspace: 234 md mimo `tmp/` → ~800 chunků (draft počítal 10⁴–10⁵).
|
||
2. Ověřil `sqlite-vec` smoke testem celého navrženého schématu (`chunks` + `files` +
|
||
`chunks_fts` + triggery + `vec_chunks`) — včetně toho, na čem D3 stálo.
|
||
3. Ověřil model na reálném obsahu: cross-jazyk retrieval test + vliv instruct prefixu.
|
||
4. Přepsal plán do `final-wiki-hybrid-rag.md`, nasadil na server i do repa, starý draft
|
||
označil `> Superseded by`.
|
||
|
||
**Co fungovalo a proč:**
|
||
|
||
- **Díra v návrhu draftu**: RRF slučoval file-level BM25 s chunk-level cosine. Rank-merge dvou
|
||
různých jednotek nemá definovaný význam. Fix: `chunks` je jediná retrieval jednotka, FTS5 je
|
||
external-content nad ní, `vec0` má `rowid = chunks.id`, RRF slučuje `chunks.id`. Ověřeno.
|
||
- **`vec0` obstálo ve všech kritických testech**: `DELETE`/re-`INSERT`/`UPDATE` po `rowid`,
|
||
transakční `ROLLBACK`, `distance_metric=cosine`, `vec0` v témže souboru jako běžné tabulky.
|
||
KNN k=20: **19 ms @ 10k, 212 ms @ 100k**. Kdyby `DELETE` nešel, D3 by padlo a vracel by se BLOB.
|
||
- **Zdůvodnění D3 v draftu bylo špatné** — stavěl `sqlite-vec` jako „indexovaný KNN" (ANN).
|
||
Není; dokumentovaná cesta je průchod (lineární 19 → 212 ms to potvrzuje). Skutečná výhoda je,
|
||
že skenuje v C a odřízne těch 96 % Python režie.
|
||
- **Ollama Cloud embeddingy neexistují** — 18 cloud modelů, žádný s capability `embedding`;
|
||
`/api/embed` na cloud modelu vrací `unauthorized`, zatímco `/api/generate` na tomtéž projde
|
||
(takže to není o autorizaci). Katalog na ollama.com potvrzuje: všech 12 embedding modelů
|
||
je jen ke stažení.
|
||
- **D1 potvrzeno měřením**, ne argumentem: dotaz „jak snížit elektroodpad ze stavebnic" →
|
||
BM25 **1/5**, embeddingy **3/5**; lexikální dotaz naopak BM25 4/5. Obě poloviny si vydělávají.
|
||
- **`exec` timeout 60 s** vs. plný index ~77 s → indexace nesmí běžet v tahu agenta. V draftu to
|
||
nebylo; uživatel zvolil offline cron s lockem, čímž `fetch-on-query` z draftu vypadl.
|
||
- **Whitelist místo blacklistu** (na dotaz uživatele): ve workspace je 234 md mimo `tmp/` a
|
||
použitelných ~99 — zbytek `.venv/` 27, `cml/` 39, `skills/` 32, `backup/` 20, `tasks/` 16;
|
||
`tmp/` drží dalších 135. Blacklist by musel trvale pokrývat právě adresář určený k balastu.
|
||
Rozhodující je ale směr selhání: u blacklistu nový adresář *tiše vstoupí* do indexu.
|
||
- DDL vytažené přímo z hotového dokumentu spuštěno proti `:memory:` — prošlo, a FTS5 i `vec0`
|
||
vrátily shodné `rowid`.
|
||
|
||
**Co jsem měl špatně (korekce vlastních tvrzení):**
|
||
|
||
- Tvrdil jsem, že vynechání **instruct prefixu** stojí víc než přechod na menší model. Test na
|
||
178 chunkách to vyvrátil: recall@5 s prefixem i bez něj **identický** (5/5 vs 5/5, 3/5 vs 3/5),
|
||
similarita s prefixem dokonce nižší. Prefix zlepšil jen top-1 nejtěžšího dotazu. V plánu
|
||
zůstává (je zdarma), ale zdůvodnění je teď „bitová identita indexace a dotazu", ne kritičnost.
|
||
- Cold load jsem extrapoloval na 4–6 s; naměřeno **1,81 s**. `keep_alive: -1` je tedy výhodný,
|
||
ne nutný.
|
||
- Dvě chyby v mých vlastních test skriptech, obě mě zdržely: chybějící
|
||
`isolation_level=None` (→ „cannot start a transaction within a transaction") a bufferovaný
|
||
stdout Pythonu, kvůli kterému 9 minut běžící skript neukázal, že visí na síťovém čtení.
|
||
|
||
**Nálezy pro implementaci:**
|
||
|
||
- **`ON DELETE CASCADE` z `files` uklidí `chunks` i FTS5 (trigger se na kaskádě spustí), ale
|
||
řádek ve `vec_chunks` osiří** — `vec0` není cílem foreign key. Indexer musí mazat `vec_chunks`
|
||
explicitně. Je to v plánu jako regresní test; jinak by index při mazání souborů tiše hnil.
|
||
- `keep_alive` musí být **číslo** `-1` (nebo `"24h"`); string `"-1"` vrací HTTP 400.
|
||
|
||
**Co zbývá:**
|
||
|
||
- **Seznam rep** (URL + kam mirrorovat) — jediná věc, kterou musí doplnit autor; bez toho
|
||
nelze naplnit `config/notes-search.yaml`.
|
||
- Zopakovat cross-jazyk benchmark na reálných repech (proběhl na workspace obsahu).
|
||
- Návrh 6 vět do `decisions.md` je v plánu — **čeká na souhlas autora**, nezapsáno.
|
||
- Mimo scope a jen hlášeno: `llm-wiki` řeší autor sám (zazálohuje a smaže);
|
||
`wiki-compile` existuje jen na serveru a v žádném commitu (drift, pravděpodobně Dream).
|
||
|
||
**Jak vrátit zpět:** `git revert 36259d7`; na serveru smazat
|
||
`plans/final-wiki-hybrid-rag.md` a odstranit vložený `> **Superseded by` řádek (druhý řádek)
|
||
z `plans/notes-search-hybrid-rag.md`.
|
||
|
||
---
|
||
|
||
## 2026-09-09 — Oprava plánu final-wiki-hybrid-rag po plan-review
|
||
|
||
**Cíl:** Projet `final-wiki-hybrid-rag.md` skrz `/lab:plan-review` a opravit, co review najde,
|
||
aby byl plán implementovatelný bez hádání.
|
||
|
||
**Co jsem zkusil:**
|
||
|
||
Review vrátila verdikt „vrátit k přepracování": 5 blokujících nálezů, 7 k doplnění, 3 poznámky.
|
||
Všechny kotvy, které plán uváděl jako existující, jsem ověřil a všechny sedí
|
||
(`remind/scripts/db.py` symboly, `note_capture.py:25`, `_ascii_fold`, `.compile.lock`,
|
||
`llm-wiki/scripts/wiki_search.py`, `develop/history.md` + `knowledge.md` na serveru, crontab
|
||
vzor, `db/` v `.gitignore`). Plán nebyl superseded — skill neexistoval.
|
||
|
||
Pět blokujících nálezů:
|
||
|
||
1. Vrstva 2 (TOC) byla vyhlášená, ale sync ji negeneroval, schéma pro ni nemělo tabulku
|
||
a Verifikace ji netestovala.
|
||
2. `git clone --mirror` je **bare** repo → vrstva 1 (`rg`) nemá working tree, co grepovat,
|
||
a chunker neměl řečeno, jak se z mirroru dostane obsah souboru.
|
||
3. Chunky s `embedded_at IS NULL` se nikdy nedobraly: krok 3 syncu končí „nic se nezměnilo
|
||
→ exit 0", jenže po degradovaném běhu se soubory nezměnily. Verifikace bod 4 přitom
|
||
tvrdila opak a partial index `idx_chunks_pending` nikdo nečetl.
|
||
4. Skupinové klíče v YAML (`sources.git.*`, `sources.nanobot.*`) nešly namapovat na
|
||
`CHECK(kind IN ('git','workspace'))` — `nanobot` by CHECK odmítl.
|
||
5. RRF neměl jediný parametr: chybělo `k` pro KNN, počet kandidátů z BM25, RRF konstanta,
|
||
výstupní top-k i váhy.
|
||
|
||
**Co fungovalo a proč:**
|
||
|
||
Před opravou jsem si nechal od autora rozhodnout adresářovou strukturu a jméno skillu, protože
|
||
na tom visí deploy cesta, cron řádek i testy — a pak dvě věci **naměřil místo hádání**, když
|
||
padla otázka „jak se to bude chovat, když do `include` dám i `*.py` a `*.cs`":
|
||
|
||
- markdown-it-py nad Python souborem: komentář `# TODO: fix this` se parsuje jako **ATX
|
||
heading h1**. Chunker by řezal soubor v komentářích a stavěl breadcrumb
|
||
`foo.py > TODO: fix this` — ne horší kvalita, ale nesmysl.
|
||
- FTS5 `unicode61`: `send`/`async` uvnitř `SendAsync` → **miss** (camelCase se nedělí);
|
||
`migrate` i `_migrate` → oba hit, nerozlišitelné. `tokenchars '_'` problém jen převrátí
|
||
(`get_db` hit, `get` miss). Jediný použitelný tokenizer je `trigram` — a ten je vlastnost
|
||
**tabulky**, ne řádku, takže smíšený korpus = druhá FTS tabulka a dvojí dotaz.
|
||
|
||
Tohle rozhodlo D15 (index zůstává md-only) měřením, ne názorem. Náhradou je, že grep vrstva
|
||
jede přes celý klon včetně kódu — soubory už na disku leží, takže to stojí nula.
|
||
|
||
Dvě zjednodušení nálezy zrušila místo záplatování: **plochá konfigurace** s explicitním `kind`
|
||
(padá nález 4) a **sloučení `workspace` + `develop`** do jednoho zdroje (stejný kořen, stejný
|
||
kind, nebyl důvod je mít dva).
|
||
|
||
Autor dodal URL obou rep, ověřeno z `nanobot.hell`: obě dostupná klíčem uživatele `nanobot`,
|
||
obě `HEAD → refs/heads/master`. Otevřená otázka 1 tím padla.
|
||
|
||
**Nálezy pro implementaci:**
|
||
|
||
- Workspace na serveru **je git repo** a `wiki/` v jeho `.gitignore` chybí. Nasazení musí
|
||
přidat `wiki/*` + `!wiki/config.yaml`, jinak se do gitu commitne index (komprimovaná kopie
|
||
osobního obsahu) i klony. Původní plán se opíral o to, že `db/` je gitignorované —
|
||
přesunem pod `wiki/` ta záruka zmizela.
|
||
- `note_compile.py:85-108` má stale-lock reclaim (mrtvý PID nebo stáří > 30 min). Plán měl
|
||
jen holý `O_EXCL`, což by po pádu procesu navždy umlčelo minutový cron bez jediného řádku
|
||
v logu. Převzato doslova.
|
||
- Aritmetika v plánu byla špatně: 10⁴ chunků / 108 chunk/s je ~93 s, ne 77 s. Závěr
|
||
(> 60 s `exec` timeout) drží.
|
||
|
||
**Co zbývá:**
|
||
|
||
- **Pustit `/lab:plan-review` znovu** — skill se nedá vyvolat přes `Skill` tool
|
||
(`disable-model-invocation`), musí ho spustit autor. Cíl: 0 blokujících nálezů.
|
||
- Návrh 4 vět do `decisions.md` — čeká na souhlas autora, nezapsáno: kořen runtime dat
|
||
`workspace/wiki/`, non-bare klon místo `--mirror`, plochá konfigurace s explicitním `kind`,
|
||
index výhradně nad markdownem.
|
||
- Benchmark modelu na uživatelských datech zůstává jako výstup etapy 6.
|
||
|
||
**Jak vrátit zpět:** `git revert 43688c5`. Na server se nesahalo.
|
||
|
||
## 2026-09-09 22:10 — implementace a nasazení skillu `wiki` (hybrid RAG index)
|
||
|
||
**Cíl:** Vykonat plán [plans/final-wiki-hybrid-rag.md](plans/final-wiki-hybrid-rag.md),
|
||
etapy 1–7 včetně nasazení na server.
|
||
|
||
**Co jsem zkusil / co fungovalo a proč:**
|
||
|
||
- **Etapy 1–6 implementované a otestované lokálně** (`skills/wiki/`, commit `aecf597`):
|
||
87 testů, `ruff` i `ty` čisté. Pokrývají body 1–6 a 8 z Verifikace v plánu.
|
||
- **Etapa 7 nasazena** na `nanobot.hell`: rsync skillu, `wiki/config.yaml`,
|
||
`.gitignore` (`wiki/*` + `!wiki/config.yaml`), první plný index a minutový cron.
|
||
Plný index: **152 souborů, 601 chunků, 601 vektorů, 0 pending za 45 s**
|
||
(index 55/123, travel 5/9, workspace 92/469).
|
||
- **Dotaz end-to-end 53–62 ms** (cíl plánu byl pod 1 s). Rozpad odpovídá plánu:
|
||
embed dotazu ~55 ms, KNN 2–5 ms, BM25 < 1 ms, RRF zanedbatelné.
|
||
- **Odchylky od plánu (mechanické, ne věcné):** moduly nesou prefix `wiki_`, protože
|
||
`db.py`/`store.py` kolidují s `remind` a `wiki_search.py` s `llm-wiki` ve `ty.toml`
|
||
`extra-paths` (ploché jmenné prostory, vyhrává první cesta). Přidány dva moduly nad
|
||
pětici z plánu — `wiki_config.py` (layout + rozsah) a `wiki_embed.py` (Ollama klient
|
||
+ `meta` guard), protože obě vstupní body je potřebují a guard **musí** být identický
|
||
na obou stranách. `numpy` z dependency setu vypuštěno — `sqlite_vec.serialize_float32`
|
||
stačí, normalizace je jeden `math.sqrt`.
|
||
- **Reálné selhání sítě ověřeno neplánovaně:** z mého stroje nemá SSH klíč právo na
|
||
`travel-notes.git`, takže první lokální běh doslova předvedl bod 8 — `WARN`, zdroj
|
||
přeskočen, `index` dojel. Na serveru (klíč uživatele `nanobot`) projde.
|
||
|
||
**Nalezené a opravené vady (moje, ne plánu):**
|
||
|
||
1. **Sekce obsahující jen svůj nadpis vytvářela prázdný chunk.** U běžného tvaru
|
||
`# Titul` → `## Sekce` by šum lezl skoro do každého dokumentu. Fix: taková sekce se
|
||
zahodí, nadpis se do indexu dostane přes breadcrumb potomků a `files.headings`.
|
||
2. **Víceřádkový git stderr v logu.** Trvale nedostupný zdroj sype WARN každou minutu;
|
||
šestiřádkový stderr = tisíce řádků denně. Fix: `_one_line()` + strop 300 znaků.
|
||
Plán mluví o „řádku WARN" — teď to řádek skutečně je.
|
||
3. **Coverage report hlásil i `wiki/`** (klony v `wiki/remote/` obsahují md).
|
||
Strukturálně nikdy nekandidát, ne rozhodnutí k revizi → z reportu vyřazen.
|
||
|
||
**Otevřená otázka č. 1 plánu (benchmark modelu) — uzavřena.** 12 parafrázových dotazů
|
||
s jednoznačným cílovým souborem nad reálnými 123 chunky repa `index`:
|
||
|
||
| Varianta | recall@5 | recall@10 | MRR |
|
||
|---|---|---|---|
|
||
| BM25 sám | 5/12 | 5/12 | 0,257 |
|
||
| qwen3-embedding:0.6b + instruct prefix | 4/12 | **8/12** | 0,261 |
|
||
| qwen3-embedding:0.6b bez prefixu | 2/12 | 3/12 | 0,204 |
|
||
| mxbai-embed-large | 6/12 | 6/12 | 0,261 |
|
||
| nomic-embed-text | 3/12 | 4/12 | 0,170 |
|
||
| **RRF hybrid** | 6/12 | 6/12 | **0,367** |
|
||
|
||
D1 i D2 platí: hybrid má lepší MRR než obě poloviny samostatně, `nomic` je nejhorší
|
||
(plán ho zamítal správně). V top-10 najde cíl **jen vektory u 3 dotazů, jen BM25 u 0** —
|
||
vektorová polovina na tomhle korpusu lexikální pokrytím dominuje, BM25 ale dává lepší
|
||
top-1 rank když trefí. Čtyři dotazy nenašel nikdo, protože cílové soubory jsou stuby
|
||
(50–483 B, většinou shell příkazy) — chyba mých labelů, ne retrievalu; na odpověďorelných
|
||
dotazech je hybrid 6/8.
|
||
|
||
**Co nesouhlasí s plánem:**
|
||
|
||
- **Instruct prefix NENÍ „nekritická vlastnost"** (plán, Query kontrakt). Naměřeno
|
||
recall@10 **8/12 s prefixem vs. 3/12 bez** na uživatelských datech. Plán měřil
|
||
5/5 vs 5/5 na 178 chuncích jiného korpusu. Prefix je load-bearing; je zapsán
|
||
v `meta.query_prefix` a jeho změna vyžaduje `--full`.
|
||
- **D6 („prefix wildcard pokryje českou flexi") platí jen na půl.** Prokázáno na
|
||
serveru: `cestu*` → 0 chunků v `travel/packaging-list.md`, `cesty*` → 3, `cest*` → 4.
|
||
Wildcard je **prefixový**, takže pomůže jen když je dotazové slovo prefixem tvaru
|
||
v dokumentu; „cestu" prefixem „cesty" není. Plán to dokládal dotazem `záloh*`, což
|
||
je už samotný **kmen**, ne skloněné slovo. Důsledek: na dotaz „co si vzít na cestu
|
||
do zahraničí" se doslovný seznam věcí na cestu nedostal ani do top 10.
|
||
|
||
**Co zbývá:** viz [todo.md](todo.md) — rozhodnout o dotazové vrstvě u české flexe
|
||
a návrh 4 vět do `decisions.md` (čeká na souhlas autora, nezapsáno).
|
||
|
||
**Jak vrátit zpět:** na serveru `crontab ~/.nanobot/backup/crontab.bak-20260909-220814`,
|
||
pak `rm -rf ~/.nanobot/workspace/wiki ~/.nanobot/workspace/skills/wiki` a vrátit řádky
|
||
`wiki/*` + `!wiki/config.yaml` z `.gitignore`. Lokálně `git revert aecf597`.
|
||
|
||
## 2026-09-09 23:25 — README ke skillu `wiki` + oprava prázdných titulků
|
||
|
||
**Cíl:** Doplnit ke skillu `wiki` lidsky psaný `README.md` (mentální model, proč je co tak,
|
||
operátorský runbook), protože vysvětlení bylo rozeseté po `history.md`, result dokumentu
|
||
a konverzaci. `SKILL.md` je záměrně EN instrukce pro agenta — tohle do něj nepatří.
|
||
|
||
**Co jsem zkusil / co fungovalo a proč:**
|
||
|
||
- **`skills/wiki/README.md`** (214 řádků), česky, druhá osoba, ve stylu existujících
|
||
`skills/reflect/README.md` a `skills/compact-memory/README.md` — což jsou jediné dva
|
||
README v repu, takže konvence se dala odečíst přímo z nich. Sekce: tři vrstvy, offline
|
||
indexace a proč, co se v tiku děje, proč hybrid, česká flexe, index jako nápověda,
|
||
rozsah indexace, kde co leží, ruční spuštění, hlášky, ověření.
|
||
- **Ze `SKILL.md` na README záměrně nevede odkaz.** Ani `reflect`, ani `compact-memory` ho
|
||
nemají; README je pro člověka a nesmí stát agenta kontext. (`detach/architecture.md`
|
||
odkaz má, ale to je anglický dokument jiného žánru.)
|
||
- **Každé tvrzení v README ověřeno proti kódu**, ne napsáno z hlavy: hlášky doslova
|
||
grepem z `wiki_search.py`/`wiki_embed.py`, cesty vyhodnocením konstant z `wiki_config.py`,
|
||
flagy z `parse_args()`. Příkazy ze sekce „Ruční spuštění" spuštěny naostro na serveru
|
||
včetně `--source` s neexistujícím id (exit 1, `WARN unknown source`).
|
||
|
||
**Nalezená a opravená vada — `files.title` byl NULL u všech 152 souborů.**
|
||
|
||
Vyšlo to při ověřování příkladu z README: `toc` tiskl prázdný sloupec s titulkem pro
|
||
**celý korpus**, přitom plán ho ukazuje jako podstatnou část výstupu. Příčina: `title` se
|
||
bral výhradně z frontmatteru `title:`, ale uživatelovy poznámky nesou titulek jako `# H1`.
|
||
|
||
Fix: fallback na první nadpis. Po `--full` je pokrytí **117/152** (`index` 55/55,
|
||
`travel` 5/5, `workspace` 57/92); zbylých 35 jsou raw zachyty `/note` v `notes/done/`
|
||
bez jediného H1–H3 nadpisu — tam není z čeho titulek vzít a slug v názvu souboru ho nese.
|
||
|
||
Podstatné rozhodnutí u toho fixu: fallback plní **jen katalog, ne breadcrumb root**.
|
||
Kdyby šel do rootu, přepsal by se text všech chunků (breadcrumb je součástí embedovaného
|
||
textu) a musel by se bumpnout `chunker_version` s vynuceným `--full` u každého uživatele.
|
||
Takhle je to změna jednoho sloupce v `files`.
|
||
|
||
**Vedlejší nález:** padl existující test `test_malformed_frontmatter_stays_body`. Nebyla to
|
||
regrese — u rozbitého frontmatteru (`---\ntitle: [unclosed\n---`) udělá markdown-it z toho
|
||
řádku **setext nadpis**, takže ho fallback vezme jako titulek. Frontmatter se opravdu
|
||
nespotřeboval (tagy zůstaly prázdné, text zůstal v těle), jen test tvrdil něco jiného,
|
||
než byl jeho záměr. Přepsán na to, co má tvrdit.
|
||
|
||
**Co zbývá:** nic z tohoto zásahu. Otevřené položky skillu `wiki` v `todo.md` (česká flexe,
|
||
návrh do `decisions.md`) se nemění.
|
||
|
||
**Jak vrátit zpět:** `git revert 3adc2ef`, pak na serveru rsync skillu a
|
||
`wiki_sync.py --full` (titulky se vrátí na NULL). Index se tím nerozbije — `title` je
|
||
jen sloupec v katalogu, retrieval na něm nestojí.
|
||
|
||
---
|
||
|
||
## 2026-09-15 06:20 — Ollama usage poller: revize serverového plánu + nasazení sběru
|
||
|
||
**Cíl:** Uživatel chtěl dotáhnout serverový plán `plans/ollama-usage-poller.md`
|
||
(napsal si ho nanobot sám 2026-09-14) do repa a probrat, jak ho realizovat a kde
|
||
má slabiny. Záměr plánu: minutový cron → SQLite, aby šlo zpětně zjistit, kolik
|
||
která session stála.
|
||
|
||
**Co jsem zkusil:** Nejdřív ověření předpokladů plánu proti reálnému serveru
|
||
(ne proti tomu, co plán tvrdí). Nálezy, kvůli kterým se návrh měnil:
|
||
|
||
- **`memory/history.jsonl` nemá tokeny.** Plán na něm stavěl budoucí atribuci
|
||
(„per-request timestamps, token counts, session ids"). Reálně obsahuje jen
|
||
`{cursor, timestamp, content}` (+ `session_key` u 59 ze 430 řádků) — je to
|
||
destilát paměti z Dreamu. `sessions/*.jsonl` má `role/content/timestamp/
|
||
tool_calls/reasoning_content/latency_ms`, **taky bez tokenů a bez modelu**.
|
||
Jediný zdroj tokenů je `LLM usage:` v journalu, což je DEBUG a `-v` už v
|
||
`ExecStart` **není** (poslední takový řádek je z 2026-05-27) — `knowledge.md`
|
||
tvrdila opak, opraveno.
|
||
- **`limits.*.usage` má rozlišení 0,1 %** (API vrací 3 desetinná místa).
|
||
Plánovaný report `Δsession %, Δweekly %` per dvojice vzorků sliboval přesnost,
|
||
kterou data nemají → osa reportu přepnuta na `request_count` per model.
|
||
- **`models` je pole `[{name, request_count}]`**, ne slovník jak tvrdil komentář
|
||
ve schématu → kanonizace na setříděný dict, jinak by se zapisovalo každou minutu.
|
||
- **Umístění `workspace/scripts/`** odporovalo vzoru — všech 6 existujících cron
|
||
entry míří do `skills/<name>/scripts/` → přesunuto do `skills/usage/scripts/`,
|
||
čímž odpadl i `sys.path` hack kolem reuse `load_api_key`.
|
||
- **Write-on-change nerozliší klid od výpadku** → doplněna tabulka
|
||
`meta(last_ts, last_status)`, přepisovaná při každém běhu včetně chybového.
|
||
- Ukládá se i `session.models` (plán si nechával jen `weekly`).
|
||
|
||
**Co fungovalo a proč:** Skill `usage` dotažen do repa (dosud tam vůbec nebyl),
|
||
přidány `ollama_usage_poll.py` + `ollama_usage_report.py` (stdlib only, EN),
|
||
sekce „Continuous sampling" do `SKILL.md`, rsync na server (`nanobot:nanobot`),
|
||
cron entry `* * * * *`. Ověřeno:
|
||
|
||
1. Dva běhy po sobě → `samples` má 1 řádek, `meta.last_ts` se posunulo oběma
|
||
(04:19:46 → 04:19:53). Write-on-change funguje.
|
||
2. `OLLAMA_API_KEY=bogus` → exit 0, `meta.last_status = 'http_401'`,
|
||
žádný nový řádek. Cron se tím nezanáší.
|
||
3. Logika reportu ověřena na syntetické DB ve scratchpadu: hranice session okna
|
||
(pokles `session_usage` i součtu `session_models`), mezera v datech,
|
||
per-model delty, souhrn.
|
||
4. Cron potvrzen (`meta.last_ts` se posouvá bez ručního spuštění,
|
||
`log/ollama_usage_cron.log` prázdný).
|
||
|
||
**Co zbývá:** Atribuce na sessions — přesunuto do `todo.md`, až bude pár dní dat.
|
||
Vedlejší přínos, který plán nepojmenoval: minutová řada ověří/vyvrátí domněnku
|
||
ve `skills/usage/SKILL.md`, že session okno je kalendářní hodina
|
||
(`until_next_full_hour` je odhad, ne údaj z API).
|
||
|
||
**Jak vrátit zpět:** `crontab -e` a smazat entry `ollama-usage`;
|
||
`rm ~/.nanobot/workspace/db/ollama_usage.sqlite`;
|
||
`rm ~/.nanobot/workspace/skills/usage/scripts/ollama_usage_{poll,report}.py`
|
||
a rsync `SKILL.md` z gitu před commitem. Lokálně `git revert`.
|
||
|
||
---
|
||
|
||
## 2026-09-15 10:05 — Session okno: fixní 5h blok, fáze kalibrovaná z DB
|
||
|
||
**Cíl:** Skill `/usage` hlásil „Session: … resets in 2 minutes", zatímco web
|
||
ukazoval 4 hodiny. Zjistit proč a dát tomu odhad, který se dá věřit.
|
||
|
||
**Co jsem zkusil:**
|
||
|
||
1. Porovnání serveru s repem — server běžel se starou verzí z 14. 9.
|
||
(`until_next_full_hour()`). **Oprava z předchozí session se nikdy nenasadila**
|
||
a ležela necommitnutá v repu. To je celé vysvětlení „2 minut" (běželo 06:58 UTC).
|
||
2. Data z `db/ollama_usage.sqlite`: session spadla z `0.077`/21 req na `0.0`/`{}`
|
||
mezi vzorky 04:28:01 a 06:00:01 → **fixní blok, ne rolling okno** (rolling by
|
||
se rozpadal postupně).
|
||
3. Kalibrace proti webu (uživatel hlásil, co vidí): ~06:58 „4 hodiny",
|
||
~07:05 „3 hodiny", session **2,3 %** — procento přesně sedí s API, takže
|
||
dashboard a `/api/usage` čtou totéž.
|
||
4. Délka okna z primárního zdroje — [transparent-pricing](https://ollama.com/blog/transparent-pricing):
|
||
nové plány zrušily „5-hour or weekly limits", tenhle klíč je má → **5 h**.
|
||
5. Původní návrh přidával do polleru heartbeat, aby šlo mezeru v `samples`
|
||
odlišit od výpadku. **Uživatel ho zamítl** — DB má zůstat čistě změnová.
|
||
Ukázalo se, že je zbytečný (viz níže).
|
||
|
||
**Co fungovalo a proč:** Mřížka je periodická, takže každý pozorovaný reset
|
||
omezuje **tutéž fázi**. Jedno pozorování ohraničí hranici intervalem
|
||
`(poslední vzorek před, první vzorek po]`; průnik těch intervalů **modulo 300
|
||
minut** fázi dopočítá. Dvě vlastnosti, proč to heartbeat nepotřebuje:
|
||
|
||
- Ohraničení platí, i když poller neběžel — **výpadek interval jen rozšíří**,
|
||
nikdy nezpůsobí špatný závěr. Není tedy co dokazovat.
|
||
- Když je délka bloku špatně, průnik se **vyprázdní** místo tiché chyby.
|
||
|
||
Dokud je kandidátů víc než `MAX_SPREAD`, reset se nevypisuje — dnešní data dávají
|
||
93 z 300, takže skill zatím hlásí holé `Session: 2.4 %`. To je správný výstup.
|
||
|
||
Nález při psaní výpisu mřížky: **5 h nedělí 24 h**, takže „mřížka 00/05/10/15/20
|
||
UTC" nemůže být stálá — buď startuje denně (s kratším posledním blokem), nebo
|
||
plave. Obě hypotézy jsou zatím konzistentní s daty a průnik je rozliší; report
|
||
proto tiskne jen **příští hranici**, ne seznam denních.
|
||
|
||
Časy ve výstupu jsou v lokální zóně serveru (`.astimezone()`, nic
|
||
nehardcodováno), ale výpočet zůstává v UTC — v lokálním čase by ho přechod na
|
||
zimní čas 25. 10. posunul o hodinu a zahodil kalibraci.
|
||
|
||
Ověřeno: 19 kontrol na syntetických datech (zúžení průniku, prázdný průnik,
|
||
bracket ≥ 300 min, drift přes den, rozestup přes DST), proti reálné DB
|
||
(93 kandidátů → `None`), formátování pod `TZ=Europe/Prague`/`UTC`/`America/New_York`,
|
||
a po nasazení přímo na serveru včetně běhu polleru. Uzavírá položku
|
||
z `todo.md` o ověření domněnky o kalendářní hodině.
|
||
|
||
**Co zbývá:** Rozhodující pozorování — příští hranice ve **12:00 nebo 13:00
|
||
CEST**. Podle toho, jak blízko ní bude poslední aktivita, se fáze zúží a skill
|
||
začne reset hlásit sám. Pak porovnat s webem.
|
||
|
||
**Jak vrátit zpět:** `git revert ff9ac3e` + rsync `skills/usage/` na server.
|
||
|
||
## 2026-09-15 10:15 — Session okno se kotví requestem, ne mřížkou (oprava modelu)
|
||
|
||
**Cíl:** Uživatel namítl dvě věci k předchozímu záznamu: (a) mlčení místo odhadu
|
||
mu nepomáhá, „lepší něco než nic"; (b) **hranice přece nemusí být pevná — nemůže
|
||
se začít počítat od prvního dotazu po pauze?**
|
||
|
||
**Co jsem zkusil:** Bod (b) je testovatelný, tak jsem šel do dat dřív, než jsem
|
||
dodělal (a). Z `sessions/*.jsonl` jsem vytáhl timestampy assistant zpráv a
|
||
poskládal bloky aktivity oddělené pauzami. Rekonstrukce (UTC):
|
||
|
||
| Okno | Start | Konec |
|
||
|---|---|---|
|
||
| W2 | 00:00 (první request po vypršení W1) | 05:00 |
|
||
| W3 | 06:00 (první request po vypršení W2) | 11:00 |
|
||
|
||
**Co fungovalo a proč:** Rozhodl **chybějící zápis v 05:00**. Kdyby existovala
|
||
pevná mřížka, usage by se v 05:00 vynulovala a poller (běží každou minutu) by
|
||
změnu zapsal. Místo toho se nezměnilo nic až do 06:00, kdy přišel první request
|
||
po 93minutové pauze. Vzorek `0.0`/`{}` v 06:00:01 tedy **není okamžik resetu, ale
|
||
okamžik startu nového okna**. Pevná mřížka by navíc vyžadovala, aby ten request
|
||
padl na hranici náhodou (~1:93).
|
||
|
||
Počty requestů to nerozhodly (23 v 00:00–05:00 vs 19 v 01:00–06:00 proti
|
||
hlášeným 21) — assistant zprávy nejsou 1:1 s LLM requesty.
|
||
|
||
Model přepsán na **poslední pozorovaný přechod + 5 h**. Vedlejší efekt: je to
|
||
výrazně jednodušší než průnik kandidátních fází modulo 300 min, který jsem
|
||
nasadil o hodinu dřív, a **hlásí reset okamžitě** — čímž vyřešil i bod (a).
|
||
Server teď říká `Session: 2.5 %, resets 13:00 CEST (in 2 h 47 min)`.
|
||
|
||
Za konec okna se nic neextrapoluje: po vypršení výstup řekne, že další okno
|
||
začne dalším requestem. Report dostal `Rollover gap:` řádky jako průběžný test
|
||
modelu — rozestup delší než blok potvrzuje kotvení requestem, rozestup přesně
|
||
roven bloku přes dlouhou nečinnost by ukazoval zpět na pevnou mřížku.
|
||
|
||
Ověřeno 15 kontrolami na syntetické DB (kotva, víc přechodů, chybějící DB,
|
||
vypršelé okno, okno přes přechod na zimní čas, formátování v UTC i CEST),
|
||
proti reálné DB a po nasazení na serveru.
|
||
|
||
**Poučení:** Předchozí záznam (09:xx) tvrdil „fixní blok" a stavěl na tom celou
|
||
kalibrační mašinerii. Hypotézu „start prvním requestem" jsem měl v úvahách na
|
||
začátku, ale zahodil ji bez testu — přitom šla ověřit ze `sessions/*.jsonl`,
|
||
které jsem měl celou dobu k dispozici. Past, která to držela naživu: crony
|
||
běží na celé hodiny, takže hranice *vypadaly* jako mřížka.
|
||
|
||
**Jak vrátit zpět:** `git revert 9f39d29` + rsync `skills/usage/` na server.
|
||
Předchozí (mřížkový) model je `ff9ac3e`.
|
||
|
||
## 2026-09-15 10:40 — Review a zeštíhlení skillu `usage`
|
||
|
||
**Cíl:** Projít `skills/usage/SKILL.md` — jestli splňuje náležitosti skillu a jestli
|
||
v něm nejsou zbytečnosti. Plus odpovědět, k čemu je v DB tabulka `meta`.
|
||
|
||
**Co jsem zkusil:** Přečetl SKILL.md, všechny tři skripty, `knowledge.md`, porovnal
|
||
serverovou verzi s lokální (server == HEAD, žádný drift). Review podle
|
||
`core:skill-authoring`.
|
||
|
||
**Co fungovalo a proč:**
|
||
|
||
- **Zbytečnosti.** Sekce „Reset times" byla ze ~40 řádků z větší části forenzní důkaz
|
||
— *jak* jsme model session okna naměřili. 1:1 duplikát `knowledge.md` („Ollama session
|
||
okno"), history 2026-09-15 10:15 a docstringů `window_rollovers()` / `print_window()`.
|
||
V SKILL.md se platí kontextem při každém načtení a agentovi neříká, co má dělat —
|
||
vyhozeno, důkazy zůstávají v tracking souborech a v kódu. Pravidlo „žádné modely na
|
||
Session/Weekly řádcích" bylo řečené třikrát, falzifikační odstavec o `Rollover gap:`
|
||
dvakrát. 117 → 96 řádků.
|
||
- **Nepřesnosti vůči skriptům** (všechny opraveny): `print_session()` má tři větve,
|
||
dokumentovaná byla jedna (chybělo „window expired…" a samotné procento); Weekly řádek
|
||
slíbený jako „resets in Y days", ale `fmt_delta_short()` pod 24 h tiskne hodiny;
|
||
„report ends with `Session window:`" — ve skutečnosti končí `Last poll:`; zdroj klíče
|
||
je env **pak** `workspace/.env`, ne jen `.env`.
|
||
- **Tabulka `meta` — odpověď na otázku.** Zapisuje se do ní (`record_poll()` na všech
|
||
třech cestách pollu: `ok`, `http_<kód>`, `unreachable`), ale je to **jeden řádek**
|
||
(`id = 1`), ne log. Čte ji jen `print_poll_status()` → `Last poll: <ts> (status)`.
|
||
Smysl: `samples` se plní jen při změně, takže bez `meta` nejde odlišit „nic se
|
||
nezměnilo" od „poller je mrtvý / API vrací 401". Zůstává; zapisovat vzorek při každém
|
||
pollu bylo dřív zamítnuto (knowledge „Zamítnuto: heartbeat v usage polleru"), `meta`
|
||
je právě ta zmenšená náhrada. Opraven ale **popis** v SKILL.md i v docstringu polleru
|
||
— tvrdil „records every poll, so a gap can be told apart from a failed poll", což
|
||
naznačuje historii; přepsaný řádek rozsoudí jen liveness *teď*.
|
||
- **Trigger fráze** ukotveny na „ollama" (`ollama usage/quota/credits/limits`, `how much
|
||
of the ollama plan is left`, `ollama usage report`). `"usage history"` zrušeno a obecné
|
||
`"quota"` zamítnuto uživatelem — netriggerovalo by, tahalo by skill na cizí dotazy.
|
||
- **Verifikace na serveru:** `ollama_usage.py` → `Session: 2.8 %, resets 13:00 CEST
|
||
(in 2 h 22 min)` / `Weekly: 16.2 %, resets in 5 days` + Models sekce — sedí se šablonou
|
||
řádek po řádku. Report končí `Last poll: 2026-09-15T08:37:01+00:00 (ok)`, `meta` má
|
||
jeden řádek s čerstvým timestampem. Verifikace odhalila i poslední nepřesnost: při
|
||
jediném rolloveru v historii report žádné `Rollover gap:` řádky netiskne
|
||
(`itertools.pairwise`) — formulace upravena na „from the second recorded rollover on".
|
||
Nasazeno rsyncem, md5 lokál == server, vlastník `nanobot:nanobot`, bez restartu služby.
|
||
|
||
**Co zbývá:** Nic. Commit `a7a725e`.
|
||
|
||
## 2026-09-16 07:20 — Fix po přejmenování presetů `glm53`→`glm`, `kimi27`→`kimi`
|
||
|
||
**Cíl:** Uživatel přejmenoval aliasy modelů v serverovém `config.json`. Rozbily se dvě
|
||
noční cron úlohy (`KeyError: "model_preset 'kimi27' not found"` z compact-memory,
|
||
`'glm53'` z reflectu). Vrátit je do provozu a najít i místa, kde je rozbití tiché.
|
||
|
||
**Co jsem zkusil / co jsem našel:**
|
||
|
||
Dva paralelní skeny — lokální repo a server. Aktuální stav configu: 11 presetů, default
|
||
`glm` (= `glm-5.3:cloud`), fallback `kimi` (= `kimi-k2.7-code:cloud`). Obě přejmenování
|
||
jsou 1:1 na stejný model string, takže oprava je mechanická a joby poběží na tomtéž
|
||
modelu jako dřív.
|
||
|
||
*Proč to nechytila validace:* `Config._validate_model_preset` (nanobot `config/schema.py`,
|
||
model_validator mode="after") ověřuje při startu **jen** `agents.defaults.modelPreset` a
|
||
`fallbackModels`. Obojí uživatel přejmenoval správně, takže gateway nastartovala bez
|
||
chyby. Preset předaný ad-hoc do `Nanobot.from_config(model_preset=...)` — což dělají
|
||
právě oba cron skripty — neprochází žádnou statickou kontrolou a spadne až za běhu
|
||
v `Config.resolve_preset()`.
|
||
|
||
*Tichá část (to, co „není vidět"):* serverový `~/.nanobot/workspace/knowledge/models.md`
|
||
je tabulka presetů, podle které si agent vybírá `/model`. Obsahovala neplatné aliasy —
|
||
a navíc **neodpovídala configu ani před přejmenováním**: `glm` měl model id
|
||
`glm-5.3-flash` (reálně `glm-5.3`, flash je samostatný preset `flash`), překlep `glmi51`
|
||
místo `glm51`, chyběly presety `flash`, `sonnet`, `haiku`, `gemini-flash(-lite)`, a řádek
|
||
„Kódování: `kimi27` (fallback: `kimi`)" byl po přejmenování sebereferenční nesmysl.
|
||
Uživatel rozhodl srovnat celou tabulku s realitou a sjednotit daily driver i těžké úlohy
|
||
na `glm` (glm-5.3); eskalační sekce „Kdy přepnout na glm53" tím ztratila smysl a je
|
||
nahrazena „Kdy sáhnout po `flash`" (multimodální vstup — `glm` je text-only — a levné
|
||
rutinní tahy).
|
||
|
||
*Co rozbité nebylo:* `skills/detach/` (resolvuje presety dynamicky přes
|
||
`load_preset_names()`/`resolve_preset()`), `skills/usage/`, `cron/jobs.json`
|
||
(dream/heartbeat/version-check preset nenastavují, `dream.modelOverride` je `null`),
|
||
systemd unity, `workspace/USER.md` (`Preset: glm` je dál platné), fronty
|
||
`tasks/{inbox,new,running}`. Komentáře v `reflect_auto.py:49` a `reflect_distill.py:55`
|
||
zmiňují **model** `glm-5.3:cloud`, ne preset — zůstaly.
|
||
|
||
**Co fungovalo a proč:**
|
||
|
||
1. Nejdřív `rsync` serverového `knowledge/models.md` do repa vlastním commitem (`3d17bf8`) —
|
||
lokální verze byla z 2026-06 a úplně jiný soubor, slepý přepis by serverovou verzi
|
||
(přepsanou 2026-08-29) zahodil. Pravidlo „server je zdroj pravdy" se vyplatilo.
|
||
2. `MODEL_PRESET` opraven na `"glm"` / `"kimi"`, `compact-memory/README.md` srovnán,
|
||
`knowledge/models.md` přepsán proti reálnému `modelPresets` (commit `dcb66d5`).
|
||
3. Nasazeno rsyncem celých adresářů skillů + `knowledge/models.md`, vlastník
|
||
`nanobot:nanobot`, bez restartu služby.
|
||
4. Verifikace: `Nanobot.from_config(model_preset=...)` pro `glm` i `kimi` proběhne a loguje
|
||
přepnutí na `glm-5.3:cloud` / `kimi-k2.7-code:cloud` (přesně ta cesta, co padala);
|
||
grep starých aliasů přes `skills/`, `knowledge/` a `config.json` je prázdný; reflect
|
||
`--dry-run --max-batches 1` exit 0; lokálně 219 testů zelených.
|
||
5. Grep zpočátku hlásil zbylý výskyt v `skills/reflect/scripts/__pycache__/*.pyc` —
|
||
zastaralý bytecode ze 2. 9., smazán (Python si ho vygeneruje znovu).
|
||
|
||
**Gotcha pro příště:** `~/.local/bin/uv` plnou cestou — v neinteraktivním SSH není `uv`
|
||
v `PATH` (`bash: uv: command not found`).
|
||
|
||
**Co zbývá:**
|
||
|
||
- E2E potvrzení nočního běhu (02:00 compact-memory, 03:30 reflect) — v logu nesmí být
|
||
`KeyError` a má přijít Telegram report. `compact_memory_auto.py` nemá dry-run a mutuje
|
||
`memory/MEMORY.md`, takže ruční spuštění jsem nedělal.
|
||
- Mimo rozsah, hlášeno uživateli: `src/MiniLoop/config.json` je commitnutý v gitu včetně
|
||
živého OpenRouter API klíče v plaintextu (řádek v `src/MiniLoop/.gitignore` je
|
||
zakomentovaný). Vyžaduje rotaci klíče a odstranění z historie.
|
||
- Automatický validátor presetů uživatel zamítl — Telegram alert z nočního běhu je jako
|
||
detekce dostatečný. Postup ruční kontroly zapsán do `knowledge.md`.
|