runtime backup

This commit is contained in:
lachtan
2026-09-02 15:23:13 +02:00
parent 442ddc3c24
commit 812c30ed1a
17 changed files with 2238 additions and 315 deletions

View File

@@ -1,5 +1,134 @@
# History
## 2026-09-02 — reflect: noční běh padal na timeout a zahazoval hotové dávky
**Cíl:** Noční cron (`30 3 * * *`) neodběhl — ve 4:00 přišlo na Telegram
`reflect: ERROR — timeout po 30 min`. Zjistit proč a opravit.
**Co jsem zkusil / co jsem našel:**
Rozbor `log/reflect_cron.log` a `reflect/state.json` dal pět příčin, které se násobily:
1. **Cron žral celý backlog, ne denní přírůstek.** Za kurzorem (`2026-05-27T14:39`) leželo
412 session = 7 dávek po ~500 kB ≈ 90 min práce proti 30min stropu. Ostrý běh 1. 9. byl
omezený `--max-batches 1`, cron žádný strop neměl → deterministické selhání každou noc.
2. **All-or-nothing zápis.** `_write_findings`, kurzor i report se dělaly teprve po poslední
dávce. Dávky 0 a 1 přitom **vrátily platné nálezy** (03:38 a 03:55) — timeout je zahodil
a kurzor nechal na místě, takže druhý den totéž plus nové session. Ráčna, která se sama
nerozjede.
3. **Requesty na hraně timeoutu.** Prompt 185 k tokenů, `_OPENAI_COMPAT_REQUEST_TIMEOUT_S`
je 120 s a `NANOBOT_LLM_TIMEOUT_S` 300 s. 9× timeout; retry zahodí hotový prefill
a začne od nuly, dva tahy (600 s) skončily `Error calling LLM: timed out after 300s`.
4. **Infra chyba se tvářila jako vadná odpověď.** Ten error text šel do `parse_findings`
→ „the JSON is invalid" + zavádějící hint o uvozovkách → spotřeboval 1 ze 3 JSON pokusů
za dávku, a model dostal vytýkáno něco, co nenapsal.
5. **Tool cally v analytickém tahu** (`read_file SOUL.md`, `skills/*/SKILL.md`, `grep`) —
každá iterace je další plný prefill, 24 min na dávku.
**Co fungovalo a proč:**
- **Zápis po každé dávce** (`commit()` uvnitř smyčky v `_run`): nálezy, kurzor, report
i záznam běhu jdou na disk hned. Kurzor se navíc nikdy nepohne dozadu (`max` proti
stávající hodnotě), aby spadlý `--all` neshodil noční postup.
- **Soft deadline** `--deadline-minutes` (default 20) — nová dávka nezačne po limitu,
`TIMEOUT_SECONDS` zvednut na 45 min a je teď jen brzda na zaseknutou jednu dávku.
Když běh nedojde na konec, do Telegramu se přidá `Zpracováno N/M dávek.`
- **Infra chyba ≠ vadná odpověď:** `RunResult.stop_reason == "error"` (resp. `.error`) se
pozná dřív než validátor, nespotřebuje JSON pokus a přepošle **původní** prompt v čerstvé
session (selhaný tah nechal v té staré error zprávu i celý destilát). Strop 2 pokusy, pak
`ReflectError("model nedostupný: …")`.
- **Timeouty přes env** v hlavičce skriptu (`os.environ.setdefault`):
`NANOBOT_OPENAI_COMPAT_TIMEOUT_S=600`, `NANOBOT_LLM_TIMEOUT_S=900`.
- **Menší dávky:** `DEFAULT_BUDGET_CHARS` 500 000 → 200 000 (~70 k tokenů).
- **Strop na čtení souborů** v promptu („Read at most 2 files").
**Ověření (ostrý běh 06:51):** 26 session, jedna dávka, **147 s**, 83 k prompt tokenů,
**jedna iterace, žádný tool call, žádný provider timeout, JSON dobrý na první pokus**
(dřív ~13 min na dávku). Kurzor se posunul `2026-05-27T14:39``2026-05-29T09:21`,
`findings.jsonl` má 3 `open` + 4 `watch`, report `results/2026-09-02_reflect.md` sedí,
`state.json` má záznam běhu s `batches: 1`. Lokálně 115 testů zelených (11 nových), ruff čistý.
**Co zbývá:**
- Nechat proběhnout noční cron a ráno ověřit, že kurzor pokročil a nepřišel ERROR.
- Backlog (~386 session) se teď vysype po dávkách sám; kvalitu nálezů projít přes `/reflect`.
- **Pozor na falešné regrese:** analýza starých session hlásí `regression_of` u vzorů
opravených 1. 9. (`answer-self-config-from-guesswork`), protože ty session jsou z května,
tedy z doby před opravou. Regresní příznak má smysl teprve u session novějších než `applied`.
- Patche: tenhle běh nevrátil ani jeden (dřív 1 z 8). Sledovat, jestli to je náhoda, nebo
cena za strop na čtení souborů.
**Jak to vrátit zpět:** `git revert` commitu se změnou skillu v tomto repu + rsync na server
(`rsync -av --exclude '__pycache__' --exclude '.pytest_cache' skills/reflect/ nanobot@nanobot.hell:/home/nanobot/.nanobot/workspace/skills/reflect/`).
Kurzor v `reflect/state.json` na serveru se dá vrátit ručně na `2026-05-27T14:39:22.120065`,
nálezy z běhu 06:51 mají `created: 2026-09-02` (7 řádků v `findings.jsonl`).
## 2026-09-01 — reflect: review skillu a oprava tří tichých chyb + zjednodušení validátoru
**Cíl:** Uživatel chtěl důkladné review skillu `reflect` se zaměřením na to, jestli jsou skripty
zbytečně dlouhé a složité. Review našlo tři tiché chyby (nic nespadne, jen se děje něco jiného, než
co slibuje dokumentace) a čtyři místa zbytečné složitosti. Plán schválen, pak vykonán.
**Co jsem zkusil / co fungovalo a proč:**
1. **Kurzor nefiltroval.** `_run` ukládal `digest.started` zformátovaný (`2026-07-11 14:02`), ale
`collect_sessions` ho porovnávala proti syrovému `created_at` (`2026-07-11T09:00:12.345678`).
`'T'` (0x54) > `' '` (0x20) → porovnání vždycky vyšlo „novější". Ověřeno na 332 reálných session:
se starým formátem se **9 session z dne kurzoru analyzovalo znovu každý běh**, výskyty se počítaly
dvakrát a vzor se povyšoval na `open` dřív, než skutečně nastal podruhé — přesně to, čemu má práh
bránit. Fix: `SessionDigest.started` drží syrové ISO, formátuje se až v hlavičce destilátu.
Unit test to nechytal, protože si `since` podával jako literál v ISO — ve formátu, který volající
nikdy nevyrobí. Nový test dělá round-trip, který dělá `_run`.
2. **Zamítnutí vydrželo jen jeden běh.** `merge_findings` hledala `previous` jako záznam s nejnovějším
`created`. Po zamítnutí vznikl nový `watch` záznam s dnešním datem, ten příště přebil `rejected`
a vzor se otevřel. Reprodukováno: běh po zamítnutí → `watch`, o běh dál → `open`, přestože README
slibuje „zamítnutý vzor se znovu neotevře". Na serveru leží 4 zamítnuté vzory, takže by to bylo
vidět hned při backlog běhu. Fix: zamítnutí je vlastnost *vzoru* (`rejected_patterns` set přes
všechny záznamy), ne posledního záznamu. Stejná příčina brala i `regression_of` — přenáší se dál.
3. **`reflect_apply.py` hlásil „refused" po tom, co už soubor změnil.** Zápis byl před commitem, takže
selhání `git commit` vrátilo exit 2 se změněným necommitnutým souborem, bez audit řádku a s nálezem
pořád `open`. SKILL.md přitom agentovi říká „exit 2 = odmítnuto, jdi dál". Fix: rollback na původní
obsah (`check_patch` ho vrací, jen se zahazoval) + best-effort odstagování, původní chyba se
propaguje beze změny.
4. **Validátor zahazoval celou dávku.** Neznámý klíč, o 10 znaků delší diagnóza nebo 13 nálezů místo 12
vyhodily `FindingsError` a přeposlaly celý prompt — **~500 kB ≈ 420k tokenů**. Plán přitom říká
„zahodí vadné". Nově: retry jen když se nedá naparsovat JSON nebo nepřežil ani jeden nález; vadný
nález se zahodí, vadný patch se zahodí jen patch (diagnóza a návrh mají cenu i bez něj), dlouhý text
se ořízne, přebytečné nálezy se useknou. Důvody jdou na stderr, ať zahazování není neviditelné.
5. **Mrtvé a nekonzistentní věci:** `known-patterns.md` se generoval, ale nikdo ho nečetl (slovník pro
prompt se počítá přímo z `findings.jsonl`) — zrušen včetně řádků v obou dokumentacích.
`--check` existoval, ale SKILL.md místo něj instruoval agenta ověřit patch ručně — teď tiskne diff
a skill ho jen ukazuje. Dva různé výchozí budgety (1,5 M vs 500 k) sjednoceny na jednu konstantu.
`_session_start` četl celý JSONL kvůli pěti záznamům. Report dostal metriku „výskyt známých vzorů
na 100 session", kterou plán chtěl a nikdy nedostal.
**Co jsem záměrně neudělal:** přepis `Finding` dataclass na dicty (ušetřilo by ~45 řádků, ale je to
čistá kosmetika sahající na většinu prahových testů) a změnu struktury `_run` kvůli 30min timeoutu
(backlog jde bezpečně po dávkách přes `--all --max-batches 2`, jen to nebylo nikde napsané — doplněno
do README).
**Verifikace:** 105 testů zelených lokálně i na serveru (bylo 96; 9 nových pokrývá přesně ty tři chyby
a nové chování validátoru). End-to-end na 332 reálných session: 1. běh 332 session → kurzor, 2. běh
0 session; částečný běh po 2 dávkách pokračuje s nulovým překryvem. `ruff check` + `ruff format` čisté.
Dry-run na serveru: 337 session v 7 dávkách po ≤499 kB, `git status` workspace 18 souborů před i po
(nic se nezměnilo).
**Nasazení:** `rsync` na server (drift proti repu žádný — ověřeno před přepisem). Smazán mrtvý
`reflect/known-patterns.md`. **Kurzor v `reflect/state.json` přepsán** z `2026-05-27 14:39` na
`2026-05-27T14:39:22.120065`, jinak by se ten den zpracoval ještě jednou.
**Co zbývá:** `reflect/` na serveru **není v gitu** — ani trackované, ani ignorované, přestože plán
počítal s tím, že bude verzované jako auditní stopa nálezů a rozhodnutí. Rozhodnutí na uživateli.
**Jak vrátit zpět:** `git revert 46fe20f` (skill) a `git revert 69fe364` (rsync exclude v CLAUDE.md),
pak `rsync -av --exclude '__pycache__' --exclude '.pytest_cache' skills/reflect/ nanobot@nanobot.hell:/home/nanobot/.nanobot/workspace/skills/reflect/`.
Kurzor zpět: přepsat `reflect/state.json` na `2026-05-27 14:39`. `known-patterns.md` se obnoví sám
jen s revertovaným kódem.
## 2026-07-02 — bookmark: zestručnění frontmatter description (routing-focused), nasazeno
**Cíl:** Popis skillu byl moc dlouhý, prozaický a obecný („Manage a personal reading list…"). Přepsat na krátký, foldovaný (`>`) a zaměřený na *kdy aktivovat*, ne na výčet funkcí.
@@ -2327,3 +2456,601 @@ Běh 5 modelů s gate=3:
**Co zbývá:** Model je do dobití extra usage na ollama.com nepoužitelný. Alternativa bez dobíjení, pokud ji uživatel bude chtít: preset přes OpenRouter (`moonshotai/kimi-k3`, ctx 1 048 576, $3/M in + $15/M out) — provider `openrouter` je v configu už nakonfigurovaný. Nezakládal jsem ho, nebylo zadáno.
**Jak vrátit zpět:** `ssh nanobot@nanobot.hell 'cp ~/.nanobot/backup/config.json.bak-20260727-202245 ~/.nanobot/config.json'` — mimo tuhle jednu hodnotu jsem na serveru nic neměnil.
## 2026-08-01 — Upgrade nanobot 0.2.2 → 0.3.0: rozbité `uv` v `exec`, osiřelý `TOOLS.md`, mrtvý `maxMessages`
**Cíl:** Po upgradu na 0.3.0 (31. 7. 15:42, restart 16:58) přestalo v chatu fungovat `uv run` — `/remind list` padal na `uv: command not found`, exit 127. Zadání: zjistit příčinu, pak opravit všechny dopady upgradu včetně `TOOLS.md` a `agents.defaults.maxMessages`.
**Co jsem zkusil (diagnóza):**
- `ssh` průzkum: `uv` je v `/home/nanobot/.local/bin/uv`, systemd unit má vlastní `Environment=PATH` bez `~/.local/bin`.
- Stažení nainstalovaného balíčku 0.3.0 (`rsync` site-packages do `tmp/srv-0.3.0/`) a čtení `agent/tools/shell.py`.
- Klon upstreamu do `tmp/nanobot-upstream/`, `git log -S` nad `shell.py`, `context.py`, `config/schema.py` mezi `v0.2.2..v0.3.0`.
- Reprodukce exec prostředí přes `env -i HOME=… /bin/bash -c` vs `-lc` (s a bez `NANOBOT_PATH_*`).
- Analýza `sessions/*.jsonl` — kdy a jak selhání vypadala.
**Co fungovalo a proč — tři nezávislé regrese:**
1. **`uv` v `exec`.** Upstream `13c951aa` (25. 6., *„change exec login-shell default from true to false"*) přepnul default `login` z `True` na `False` — profil sourcovaný login shellem vracel do exec prostředí secrets, které `_build_env()` schválně vyhazuje. Jenže `_build_env()` na Unixu **PATH vůbec nepředává** (jen `HOME`/`LANG`/`TERM`/`PYTHONUNBUFFERED`), takže bez `bash -l` se PATH dopočítá z vestavěného defaultu bashe `/usr/local/bin:…:/bin:/sbin:.`, kde `~/.local/bin` není. Odhalilo to **existující chybu v našem configu**: `tools.exec.pathAppend` obsahoval `/home/nanobot/.local/bin/uv` — cestu k **binárce**, ne k adresáři, takže do PATH lookupu nepřispíval ničím. Do 0.2.2 to maskoval login shell.
- Naměřeno: `bash -c` + starý `pathAppend` → `uv` NENALEZEN; `bash -lc` + tentýž `pathAppend` → nalezen; `bash -c` + adresář → nalezen.
- Rozsah: `remind`, `note`, `llm-wiki`, `python`. Nezasažené: crontab (vlastní `PATH=`), `bookmark` (absolutní cesta v SKILL.md), `detach` (systemd unit s PATH).
- **„Zlobí jen u některých modelů" byl klam.** Selhává každé bare `uv run`; liší se jen schopnost modelu se vzpamatovat. Model, který zkusil `PATH="$HOME/.local/bin:$PATH" uv run …`, uspěl; modely, které zkusily `which uv || find /home -name uv`, narazily na `restrictToWorkspace: true` (`Command blocked by safety guard`) a vzdaly to.
2. **`TOOLS.md` osiřel.** Upstream `d29fcaf5` (21. 5., *„internalize tool contract prompt"*) přesunul `templates/TOOLS.md` → `templates/agent/tool_contract.md` a rendruje ho přímo do promptu; `BOOTSTRAP_FILES` je nově `["AGENTS.md", "SOUL.md", "USER.md"]`. Obsah tedy nezmizel — naopak je bohatší — ale přestal být uživatelsky editovatelný. Náš workspace soubor s vlastními dopisky zůstal ležet a nikdo ho nečetl.
3. **`maxMessages` mrtvý.** Upstream `dacc6992` (29. 6.) klíč vyřadil ze schématu a přidal do `_migrate_config()` shim, který ho zahodí a zaloguje warning. Spamoval journal — **145 výskytů za hodinu**.
**Proč u `TOOLS.md` neproběhla migrace, a u `maxMessages` ano:** nanobot migruje **config**, ne **workspace**. `sync_workspace_templates()` má v docstringu doslova *„Creates missing files without overwriting user files"* — jen dotváří chybějící, nikdy nepřepisuje ani nemaže, protože workspace `*.md` jsou user data. Když šablona zmizí z balíčku, kopie ve workspace prostě zůstane, tiše. Config naopak shim + warning dostane (a `281b4b7f` ukazuje, že se ty shimy po verzi systematicky mažou).
**Co jsem změnil:**
- `~/.nanobot/config.json`: `tools.exec.pathAppend` → **`pathPrepend`** = `/home/nanobot/.local/bin:/home/nanobot/.nvm/versions/node/v24.16.0/bin` (adresář místo binárky); odstraněn `agents.defaults.maxMessages`. Zálohy `backup/config.json.bak-20260801-0703{54,}` a `-070420`. *(Obě změny nakonec zapsal uživatel sám v 07:03:54, můj skript je zastihl už hotové — ověřil jsem výsledek.)*
- `~/.config/systemd/user/nanobot.service`: `ExecStart` zpět na `/home/nanobot/.local/bin/nanobot gateway`, `Environment=` řádky odkomentované, `PATH` opraven na adresář. Wrapper `~/bin/nanobot.sh` smazán. *(Taky provedl uživatel.)* Wrapper měl tutéž chybu (`$HOME/.local/bin/uv`) a i kdyby byl správný, na `exec` by nedosáhl — ale `Environment=PATH=` v unitu **zůstává**, protože `run_cli_app` spouští CLI appky s `env=os.environ.copy()` (`apps/cli/service.py:1372`).
- `workspace/AGENTS.md`: přeneseny dvě unikátní sekce z `TOOLS.md` — `python — use uv` a `Doručené připomínky` (při přenosu opraveno `remind_edit.py` → `remind_cli.py` po přejmenování z 2026-06-10). Sekce `exec`/`grep`/`cron` zahozeny jako duplicita `tool_contract.md`.
- `workspace/TOOLS.md` smazán na serveru i v repu (šablona ho neobnoví — v `nanobot/templates/` už není).
- Restart přes `~/bin/restart.sh` v 07:05:20.
**Ověření (vše zelené):**
- Reprodukce exec prostředí: `uv → /home/nanobot/.local/bin/uv`, `node`/`npm` z nvm.
- Reálný běh `uv run skills/remind/scripts/remind_cli.py list` v simulovaném prostředí → exit 0, výpis reminderů.
- **Ostrý test přes agenta** v izolované session `cli:uv-verify-20260801` (`nanobot agent -m …`): agent zavolal bare `uv run skills/remind/scripts/remind_cli.py list`, **jedno volání, exit OK, 1664 B**, žádný fallback na absolutní cestu, žádných 127.
- `maxMessages` warning: **0 výskytů** od restartu (před restartem 145/h). Journal jinak bez chyb.
- MD5 `AGENTS.md` shodné server ↔ repo, vlastník `nanobot:nanobot`, config práva `600`.
- Doručování reminderů nedotčené (crontab má vlastní PATH): `reminder_cron.log` má mtime **2026-07-15**, tj. sender od té doby neprodukoval výstup ~23 000 běhů.
**Vedlejší nález (neopraveno, mimo zadání):** v `reminder_cron.log` je z 2026-07-15 traceback `sqlite3.OperationalError: cannot rollback - no transaction is active` na `remind_send.py:182` — `conn.execute("ROLLBACK")` na neaktivní transakci **maskuje původní chybu** (tam šlo o výpadek DNS při doručování). Latentní, od 07-15 se neopakoval. Kandidát na `todo.md`.
**Co zbývá:** Zvážit návrh do `decisions.md` (čeká na formulaci autora): (1) PATH pro `exec` se konfiguruje výhradně přes `tools.exec.pathPrepend`, ne přes systemd unit ani `allowedEnvKeys`; (2) systemd unit spouští binárku přímo, bez shell wrapperu. Testovací session `sessions/Y2xpOnV2LXZlcmlmeS0yMDI2MDgwMQ.jsonl` ponechána jako doklad. Repo kopie `workspace/original/` (referenční upstream šablony) nesahané — pořád obsahují starý `TOOLS.md`.
**Jak vrátit zpět:** `cp ~/.nanobot/backup/config.json.bak-20260801-070420 ~/.nanobot/config.json` + `systemctl --user restart nanobot`; `git checkout fc45e19 -- workspace/` + `rsync -av workspace/AGENTS.md workspace/TOOLS.md nanobot@nanobot.hell:/home/nanobot/.nanobot/workspace/`. Unit se dá vrátit z tvaru zaznamenaného výše. Nic se nemazalo nenávratně — `TOOLS.md` je ve verzované historii repa (commit `fc45e19`).
## 2026-09-01 06:45 — Skill `reflect`: sebe-diagnostika ze `sessions/` se schvalovací smyčkou
**Cíl:** Zrealizovat plán `plans/reflect-skill.md` — vytěžit 534 nikdy nečtených session logů (16 MB) na opakující se chyby agenta, nálezy předkládat uživateli po jednom ke schválení a aplikovat jen schválené. Klíčový požadavek uživatele: diagnózu musí dělat LLM běžící na nanobotu (jinak to nemá smysl), nic se nesmí aplikovat automaticky, Telegram jen notifikuje.
**Co jsem zkusil / co fungovalo a proč:**
*Změřená východiska.* Z 534 session je 332 reálných konverzací (12 311 zpráv). **89 % objemu (12,5 z 14,1 MB) tvoří návratové hodnoty toolů** — pro diagnózu bezcenné, stačí `name(args) → ok|ERROR, velikost`. Přírůstek 4,4 session/den, aktivita jen 70 % dní. Preset `glm53` má reálně 976k okno (`contextWindowTokens` presetu přebíjí `defaults` 65536, `agent/loop.py:476`), input budget ~958k tokenů.
*Architektura — tři skripty, tvrdě oddělené role.* `reflect_distill.py` dělá **jen mechanickou destilaci** (filtr šumu podle prefixů, ořez tool výsledků); žádné detektory, protože chyby má hledat LLM. `reflect_auto.py` je cron runner podle vzoru `compact_memory_auto.py` (fresh `session_key`, Telegram přímo přes Bot API, zpráva se skládá ve skriptu) a **nemá v sobě žádnou cestu k zápisu do cizího souboru**. `reflect_apply.py` je jediné místo, kudy vede editace.
*Aplikační brána přesunuta z promptu do kódu.* Původně měl patch aplikovat agent podle postupu v `SKILL.md`. Přepsal jsem to na skript — recept z `develop/history.md` říká, že měkká instrukce nestačí, musí to být tvrdá brána. `reflect_apply.py` odmítne patch, jehož `old_text` v souboru není nebo je tam vícekrát, odmítne nález, který není `open`, commituje výhradně `git add -- <file>` a rozdělaný cílový soubor nejdřív checkpointne. 96 testů, včetně reálného `git revert`.
*Práh notifikace.* Denní dávka (medián 4 session) je moc malá na vzor. Nález poprvé → `watch`, mlčí; při opakování (≥2× a ≥2 session) → `open` + Telegram; zamítnutý vzor se **už nikdy neotevře** (zamítnutí je rozhodnutí, ne odklad); vzor po `applied` → `open` s příznakem regrese.
*Guard proti zápisu při analýze.* Před a po agentním tahu se porovná `git status --porcelain` + HEAD celého workspace. Kdyby agent při analýze cokoli zapsal, nálezy se zahodí.
**Ověřeno ostrým během (15 session, 90 kB destilátu):** model našel 8 vzorů, všechny konkrétní a doložené citacemi session — mj. `answer-self-config-from-guesswork` (vymyšlené schéma `tools.my.allow_set` zapsané do živého configu), `tool-call-leaked-as-text` (surový tool call se speciálními tokeny jako poslední zpráva session), `retry-without-diagnosis` (18× ve 4 session, vč. rozbité dvojité URL `r.jina.ai/http://r.jina.ai/http://…`). Dva běhy po sobě → **8 nálezů bez jediné duplicity**, dedup přes `pattern` funguje. `--check` na reálném patchi do `SOUL.md` prošel a soubor nezměnil. Sebe-vyloučení ověřeno na reálné `reflect:*` session.
**Dvě chyby, které běh odhalil, a jejich fix:**
1. `uv` **není v `PATH` neinteraktivního SSH** — první běh spadl na `timeout: failed to run command 'uv'`. Fix: plná cesta `/home/nanobot/.local/bin/uv`. Crontab má vlastní `PATH`, tam stačí `uv`.
2. **Model rozbil JSON českou uvozovkou** — napsal `(„repeated external lookup blocked")`, kde zavírací uvozovka je ASCII `"`, což předčasně ukončí JSON string. Hláška „no parseable json block" navíc retry nedala nic použitelného. Fix: validátor hlásí řádek, sloupec a výřez okolo chyby + prompt zakazuje uvozovky uvnitř string hodnot. Po opravě prošel **první pokus**.
3. Odhad tokenizace byl mimo: naměřeno **1,2 znaku na token** (ne 3), takže dávka 800 kB by dala ~670k tokenů. Sníženo na 500 kB.
**Nasazeno:** `skills/reflect/` na server (`nanobot:nanobot`), crontab `30 3 * * *`. Stav žije v `workspace/reflect/` (mimo adresář skillu, aby ho `rsync` nepřepsal) a je verzovaný v git workspace.
**Co zbývá:**
- **Uživatel projde prvních 8 nálezů přes `/reflect`** — to je zpětná vazba na kvalitu. Backlog (zbývá ~405 session, cursor stojí na `2026-05-27 14:39`) jsem **záměrně nepouštěl celý**: nemá smysl generovat desítky nálezů, dokud se neověří, že jsou k něčemu.
- Workspace na serveru má 11 nezacommitovaných změn (`cron/jobs.json`, `memory/*`, …) — před ostrým používáním uklidit, jinak bude diff po patchi zašuměný.
- Po každé review dotáhnout změněné soubory ze serveru do repa (obě git repa jsou bez remote, nespojená).
- Návrh do `decisions.md` (čeká na formulaci autora): (1) sebemodifikace jen přes `reflect_apply.py` po explicitním schválení jednoho nálezu, nikdy agentem přímo; (2) skript destiluje, quality judgements dělá výhradně LLM.
**Jak vrátit zpět:** `crontab -e` a smazat dva řádky `reflect`; `rm -rf ~/.nanobot/workspace/skills/reflect ~/.nanobot/workspace/reflect ~/.nanobot/workspace/results/2026-09-01_reflect.md`; lokálně `git revert` commitů `4aff500`, `1a8bc2a`, `7780841`, `ecc7d03`, `1bb5f4d`. Nic destruktivního neproběhlo — skill zatím jen četl a zapisoval do vlastního sandboxu.
## 2026-09-02 06:05 — skill `plan`: odstranění češtiny ze `SKILL.md`
**Cíl:** V `skills/plan/SKILL.md` nesmí zůstat jediné české slovo — konvence projektu je, že skilly pro nanobota jsou EN-only.
**Co jsem zkusil:** Nejdřív diff serverové verze proti lokální (`ssh cat` + `diff`) — byly **identické**, takže se nic ze serveru nezahazovalo. Pak dvě chirurgické editace: (1) nadpisy v šabloně plánu `## Kontext / ## Postup / ## Ověření` → `## Context / ## Steps / ## Verification`; (2) český literál schvalovací otázky (`Plán uložen do … Schvaluješ? Mám ho vykonat teď?`) nahrazen anglickou **instrukcí** místo doslovného textu — lokalizaci už pokrývá stávající pravidlo *"Respond in the user's language"*.
**Co fungovalo a proč:** `grep -nP '[ěščřžýáíéúůťďň…]|Kontext|Postup|Schvaluj|teď'` vrací prázdno. Nasazeno `rsync`em do `~/.nanobot/workspace/skills/plan/`, vlastník `nanobot:nanobot`, po nasazení server ↔ lokál `IDENTICAL`. Restart služby nebyl potřeba — skilly se čtou při každém tahu.
**Důsledek:** Nanobot bude nově psát plány do `workspace/plans/` s anglickými nadpisy i pro česky mluvícího uživatele. Tělo plánu si model dál lokalizuje, mění se jen struktura.
**Jak vrátit zpět:** `git revert` commitu se změnou skillu + znovu `rsync` na server.
## 2026-09-02 06:10 — skill `project`: deterministický zápis do `memory.md`, přepis skillu, oprava dat
**Cíl:** Review skillu `/project` proti reálným datům, která pod ním na serveru vznikla (`projects/{chata,life,proxmox,radio1}`), a oprava toho, co v praxi nedrží.
**Co review našlo (doloženo v datech, ne odvozeno z textu skillu):**
1. **Vymyšlená data** — `chata/memory.md` měl dva záznamy datované `2026-09-14`, přitom byly zapsány 2026-09-01 (mtime 10:51). Skill předepisoval formát `- YYYY-MM-DD:`, ale neříkal, odkud datum vzít — a model má přitom `Current Time` v runtime kontextu.
2. **Slepený řádek** — `proxmox/memory.md` obsahoval `…jako další VM/kontejner.- 2026-09-01: Rozhodnuto —` na jednom řádku. `chata/memory.md` neměl koncový newline.
3. **`state.md` se nikdy nezaložil** — `chata` 0 B při 2 KB memory. Pravidlo *„offer to draft it"* je příliš měkké a nespustí se.
4. **Mrtvé pravidlo o Dreamu** — *„Dream must not touch `workspace/projects/`"* žilo v těle skillu, které Dream nikdy nečte, a upstream to už vynucuje kódem: `build_dream_tools()` (`nanobot/agent/memory.py:641`) dává Dreamu Write/Edit/ApplyPatch s `allowed_dir = workspace/skills` + tři memory soubory. Do `projects/` se nedostane. **Nekryté je riziko opačné** — Dream smí přepisovat samotný skill, a serverový `SKILL.md` byl 2026-09-01 13:15 skutečně změněn mimo repo.
**Co jsem zkusil a co fungovalo:**
- **Nejdřív dotažen server → repo** (commit `3c388ba`), aby byl přepis čitelný v diffu.
- **`skills/project/scripts/project_cli.py`** (`activate` / `log` / `list` / `new`) přebírá datum ze systémových hodin a newline hygienu. Text jde na **stdin quoted heredocem** (`<<'NOTE'`) — shell obsah neinterpretuje, takže `„"`, `'` i `"` projdou doslova. Ověřeno reálným zápisem.
- **16 pytest testů** lokálně zelených; na serveru se testy nespouští (konvence).
- **Data opravena** po odsouhlasení uživatelem: data `09-14` → `09-01`, rozdělen slepený řádek, doplněn newline, `chata/state.md` sepsán z historie (dřevo + otevřená otázka zazimování). Záznam o zazimování zůstal i v `memory.md` — append-only invariant se neporušil.
**Rozpočet velikosti — návrh, který padl:** Původně jsem navrhoval prahy 8 000 / 12 000 znaků odvozené z okna 65 536 tokenů. **Bylo to postavené na špatném čísle:** `agents.defaults.contextWindowTokens: 65536` preset přebíjí a default `glm53` má 976 000. `proxmox` (11 278 znaků ≈ 9 400 tokenů při 1,2 znaku/token) tedy zabírá ~1 % okna. Uživatel návrh zamítl s tím, že projekt nemá ztrácet zadaná data — správně. Zůstalo jediné reálné omezení `maxToolResultChars: 16000`, které je ale **omezením čtení, ne ukládání**: `activate` nad ním vypustí z *výstupu* nejstarší záznamy a ukáže cestu k plnému logu; soubor na disku se nemění (ověřeno na 33 790znakovém souboru — výstup 14 305 znaků, velikost souboru beze změny).
**Chyba v mém vlastním review:** tvrdil jsem, že `chata/memory.md` má „4 záznamy na 3 řádcích". `wc -l` počítá newliny, ne řádky — soubor měl 4 řádky a jen mu chyběl koncový newline. Slepený řádek byl reálně jen v `proxmox`.
**Co zbývá:**
- Reálný smoke test přes Telegram/WebUI (aktivace projektu, zápis poznámky s dnešním datem, „vypiš projekty", dotaz na detail z `chata`).
- Návrh do `decisions.md` (čeká na formulaci autora): (1) zápis do `memory.md` výhradně přes `project_cli.py log`; (2) projektová data nemají strop ani konsolidaci — velikost se řeší jen na straně čtení; (3) `artifacts/` vzniká líně při prvním artefaktu.
- `radio1` (prázdné `memory.md`/`state.md` od 23. 7.) — uživatel rozhodl nechat být.
**Jak vrátit zpět:** Zálohy dat jsou na serveru v `workspace/backup/projects-2026-09-02/` (`chata-memory.md`, `chata-state.md`, `proxmox-memory.md`) — `cp` zpět na místo. Skill: `git revert 39b3fb3 3c388ba` + `rsync` na server. Restart služby netřeba, skilly se čtou při každém tahu.
## 2026-09-02 09:05 — reflect: klouzavé okno, audit počtů a záznam rozhodnutí
**Cíl.** Uživatel se zeptal, jestli jsou nálezy z dávkované analýzy věrohodné a jak by se
změnily nad celou historií. Kontrola provozu odhalila tři nezávislé problémy plus jeden,
na který se doptal později (záznam rozhodnutí).
**Co jsem zjistil (data z běhů 1.2. 9.).**
- `cursor: 2026-05-29`, session sahají do `2026-09-02` → 3 běhy × 1 dávka = **56 z 356
session (16 %)**, a všechny z nejstarších 4 dnů korpusu. Backlog 19 dávek při ~1 dávce/noc
neklesal. Všech 8 rozhodnutých nálezů tedy popisovalo chování z konce května.
- Dávkování samo nálezy nezkreslilo — každý běh byl **jedna** dávka. Zkreslilo je okno.
- `f09a7`: `occurrences: 4`, `sessions_affected: 5` — aritmeticky nemožné. Počty jsou
nekontrolované self-reporty modelu a `merge_findings` je jen sčítá.
- `ff77b`: tvrdí 18× a nese 2 důkazy — fold přepisoval `evidence`, ale `occurrences` sčítal.
- Audit zaznamenával **výsledek, ne rozhodnutí**: u zamítnutí chybí důvod (4 z 8 zamítnuto),
`uprav:` přepsalo `patch.new_text` a zahodilo návrh modelu, `přeskoč` nezanechalo nic.
**Co jsem zkusil a co funguje.**
1. **Klouzavé okno** (`--window-days`, default 21): `since = max(cursor, now - okno)`.
Cursor je podlaha (nic dvakrát), okno strop (starý backlog se přeskočí natrvalo).
Backlog se nedohání — rozhodnutí uživatele, archeologie z května hodnotu nemá.
2. **Ostrý `--all` odmítnut** — znovu čte spočítané session a `merge_findings` jim sečte
`occurrences` do existujícího záznamu. Je to nafukovač počtů; zůstává pro `--dry-run`.
3. **Clamp počtů** ve validátoru: `sessions_affected ≤ min(occurrences, session_count)`.
Clamp, ne odmítnutí — retry stojí celý tah.
4. **`_fold_evidence()`** — při foldu se důkazy kumulují (nejnovější první, dedup, cap 6),
takže kumulativní počet jde ověřit. U regrese se nekumulují (mísily by před/po opravou).
5. **Prompt**: model dostal informaci, že vidí jen výsek, a smí hlásit i jediný výskyt
**známého** vzoru. Tím se zavřela slepá skvrna — vzor s frekvencí ~1×/dávka se dřív
nikdy nepojmenoval (prompt to zakazoval) → nikdy nespočítal → práh nepřelezl.
6. **Záznam rozhodnutí** v `reflect_apply.py`: `--reject` vyžaduje `--reason` (povinně —
je to jediná zpětná vazba na kvalitu analýzy), nová akce `--skip` s počítadlem,
`patch` zůstává návrhem modelu a uživatelova verze jde do `applied.new_text`
(`APPLIED-EDITED` v logu). `SKILL.md` má sekci *Decision history*.
7. **Report a Telegram** nesou okno a nemlčí o nezpracovaném zbytku — dřív se při 0 nálezech
vracel prázdný string, což je přesně důvod, proč hladovění cursoru tři noci nikdo neviděl.
Testy: 140 prošlo (`tests/`), ruff clean. Nasazeno rsyncem, vlastník `nanobot:nanobot`.
**Ostrý běh 09:0809:31 — ověřeno na živých datech.**
```text
Zpracováno 74 session ve 6 dávkách. Nálezů: 29 (23 k review, 6 sledovaných).
Okno: od 2026-08-12, dávek 6/6.
Známé vzory: 100,0 výskytu / 100 session (minule 57,7).
```
- Okno v hlavičce reportu i ve `state.json` (`window_from`, `batches_total`), cursor
přeskočil z `2026-05-29` na `2026-09-02T06:22` — backlog je minutý, jak bylo rozhodnuto.
- **Kumulace důkazů funguje**: `f2b3d retry-without-diagnosis` má 67 výskytů a nese 6 důkazů
(cap `MAX_EVIDENCE`) posbíraných napříč dávkami; `f999d` z 2. dávky se do něj složil.
- **Žádný nový nález není nekoherentní** (`sessions_affected ≤ occurrences` všude).
Jediný nekoherentní záznam ve store je starý `f09a7` (4×/5 session) — clamp platí na
nově parsované nálezy, historii zpětně nepřepisuje.
- Guardy `reflect_apply.py` odmítly `--reject` bez `--reason`, `--reason` bez `--reject`
i `--skip --reject`, aniž by na store sáhly.
**Korekce mé vlastní obavy z dimenzování.** Z dry-runu jsem usoudil, že 6 dávek se do
20min deadline nevejde. Neplatí: dávka trvá **~4 min**, celé okno 23 min, a deadline pustil
i šestou. Je to ale těsné (kontrola deadlinu padla na ~20 min u indexu 5), takže jedna
pomalejší dávka běh zastaví a zbytek nechá na další noc — což Telegram ohlásí. Zvýšení
`DEFAULT_DEADLINE_MINUTES` na ~40 min je proto drobná pojistka, ne nutnost.
**Co zbývá.** 23 open nálezů k review a **ani jeden nemá patch** — potvrzuje otevřenou
položku o stropu „read at most 2 files". Regrese na session starších než oprava z 1. 9.
se pořád hlásí falešně (okno začíná 12. 8., patche jsou z 1. 9.).
**Jak to vrátit zpět.** Skripty: `git revert` v tomto repu + rsync. Stav na serveru:
`/tmp/state.json.bak` a `/tmp/findings.jsonl.bak` (záloha před ostrým během).
## 2026-09-02 10:05 — reflect: celý skill do angličtiny
**Cíl.** `SKILL.md` a Python skripty skillu `reflect` mají být EN-only (konvence
z `CLAUDE.md`), včetně textů, které skript generuje.
**Co jsem zkusil.**
- `SKILL.md`: ukázkový výstup nálezu, tabulka rozhodnutí a odkazy na ně v textu byly česky.
Klíčová slova v tabulce jsou teď `ok` / `apply`, `edit:`, `skip`, `reject`, `stop`
+ věta, že se přijímají ekvivalenty v jazyce, kterým uživatel píše. Pod ukázkou přibyla
věta, že popisky se píšou v jazyce uživatele.
- `reflect_auto.py`: report (`Zpracováno…`, `Okno:`, `Známé vzory:`, `**Výskyt/Důkazy/Návrh**`,
`REGRESE`, `poprvé/naposledy`, `Nic k hlášení`), Telegram hlášky (`nálezů k review`,
`Napiš /reflect`, timeout) a `ReflectError("model nedostupný")` → anglicky.
- `reflect_distill.py`: hlavička destilátu (`N zpráv`) a řádek statistik do stderr.
- Testy: asserty na tyto texty + česká fixture data. Ponechán jen znak `` v testu
neescapovaných uvozovek — to je předmět testu, ne jazyk.
- Vedlejší úklid: `_decimal()` existovala jen kvůli desetinné čárce; v anglickém reportu
by `3,2 occurrences` četlo špatně, takže tečka a helper inlinovaný do `_rate_line`.
**Co fungovalo a proč.** `pytest tests -q` → **140 passed**. Před nasazením ověřeno, že
server == HEAD u `SKILL.md`, `README.md` i všech tří skriptů (nikdo do nich mimo nás
nesáhl), pak `rsync` celého adresáře, vlastník `nanobot:nanobot` sedí. Workspace na serveru
jsem necommitoval — je trvale dirty, jak je tam zvykem.
**Co zbývá.** `README.md` zůstává česky — je to dokumentace psaná uživateli, mimo dohodnutý
rozsah („SKILL.md a python skripty"). Nejbližší noční běh pošle Telegram i report anglicky.
**Jak to vrátit zpět.** `git revert 2375d76` + rsync skillu na server.
## 2026-09-02 10:15 — reflect: druhé kolo oprav (set-patch, dedup, hradlo na regrese)
**Cíl.** Vykonat [plans/reflect-verohodnost.md](plans/reflect-verohodnost.md): složení
patche při review má být jedno validované volání skriptu, počty v reportu mají odpovídat
store, a příznak regrese má znamenat „vrátilo se to **po** opravě".
**Co jsem zkusil.**
- `reflect_apply.py`: nová akce `--set-patch <json>` (`{file, old_text, new_text}`).
Pořadí je celý smysl: kandidát `{**record, "patch": novy}` projde existující
`check_patch()` (status `open`, cesta uvnitř workspace, právě jeden výskyt `old_text`,
„nemění nic") a **teprve pak** se zapíše `patch` + `patch_drafted_at` a `DRAFTED` do
auditu. Vypíše rovnou i diff, takže nenásleduje druhé volání `--check`. Vytažen helper
`_diff()` (sdílí ho `--check`) a `_parse_patch_file()`. Přepis existujícího patche
povolen — guard je status `open`, ne absence patche.
- `reflect_auto.py` bod 2: `merged` je `dict[str, Finding]` klíčovaný `pattern` místo
seznamu, do kterého se po každé dávce přičítalo. Vzor nalezený v 5 dávkách byl v reportu
5× (report z 2. 9.: **29 nadpisů proti 13 vzorům ve store**) a stejně nafouknutý šel do
`stats` i do Telegramu.
- `reflect_auto.py` bod 3: nové derivované pole `Finding.last_seen` = `max(evidence[].when)`
přes tvary `^\d{4}-\d{2}-\d{2}` (prvních 10 znaků, nedatumové hodnoty zahozené), fallback
`created`. Dvě použití: `regression_of` se nastaví jen když `last_seen > applied.at[:10]`,
jinak `stale_after_fix` → status `watch` (ne `open`, ne REGRESE); a `_seen_line()` hlásí
`last_seen` místo `created`, takže „naposledy" přestalo být datum přepsání záznamu.
Report navíc značí `— STALE` nález, jehož `last_seen` je před `window_from` běhu.
- `SKILL.md`: do STOP gate 4 doplněn **zákaz ruční editace `reflect/findings.jsonl`**
(chybějící věta, kvůli které agent legálně sáhl do store), postup `--set-patch` místo
„propose the exact old_text/new_text yourself", řazení v *1. Load* na severity →
`last_seen` → `occurrences` (s fallbackem na `created` u starých záznamů) a značení
zastaralých nálezů. `README.md`: `DRAFTED` v tabulce rozhodnutí, co znamená „zastaralý",
regrese jen po opravě, záruka o zápisu do store.
- Testy: +26 (celkem **166 passed**, ruff čistý). Klíčové: neaplikovatelný i nejednoznačný
`--set-patch` nechá záznam **bez patche** (ověření běží před zápisem), úspěšný nezmění
cílový soubor, přepis vadného draftu (scénář `fb33c`), vzájemné vyloučení s
`--check`/`--skip`/`--reject`; dedup na úrovni běhu (dvě dávky, jeden nadpis, `open` = 1);
hradlo na regresi z obou stran; `last_seen` přes míchané tvary a fallback; STALE v reportu.
**Co fungovalo a proč.** Před nasazením ověřeno, že server == HEAD u všech 8 souborů
skillu (Dream do nich nesáhl), pak `rsync` celého adresáře. Na serveru:
`pytest tests/ -q` → **166 passed**, a ostrá zkouška hradla podle plánu — `--set-patch`
s neexistujícím `old_text` na živém nálezu `f2dd0` skončil `exit=2`
(`the original text is no longer in SOUL.md`) a záznam zůstal `patch: None`,
`patch_drafted_at: None`, status `open`. Store se neušpinil neúspěšným pokusem.
Uzavírá položku o falešných regresích z [todo.md](todo.md) — chybějící datum session, které
plán zmiňoval jako překážku, dodává `last_seen` odvozený z `evidence[].when`.
**Co zbývá.** Dvě verifikace z plánu vyžadují běh, který nejde udělat odsud: jeden
`/reflect` v chatu na nálezu bez patche (musí projít bez zápisu do `findings.jsonl` mimo
skript, `log/reflect.log` musí mít `DRAFTED`) a ostrý běh `reflect_auto.py` (report má mít
tolik nadpisů, kolik je vzorů ve store; žádný nález s výhradně před-opravným důkazem nesmí
nést REGRESE). Zbytek výčtu „Co to NEpokryje" z plánu platí dál — hlavně že analýza patche
nevrací vůbec.
**Jak to vrátit zpět.** `git revert` v tomto repu + `rsync` skillu na server. Store na
serveru změněn nebyl.
---
## 2026-09-02 10:30 — Audit AGENTS.md / SOUL.md / USER.md + čtyři cílené opravy
**Cíl.** Prověřit konzistenci a duplicity tří bootstrap souborů na serveru a opravit jen to,
kde je doložený problém.
**Co jsem zkusil.** Nejdřív analýza proti čtvrté, dosud nepočítané vrstvě promptu:
`tool_contract.md` (83 řádků) se vkládá do system promptu **každý tah, bezpodmínečně,
hned za AGENTS/SOUL/USER** — `agent/context.py:88`
(`parts.append(render_template("agent/tool_contract.md"))`), nanobot-ai 0.3.0. Proti němu
i mezi sebou porovnány všechny tři soubory.
Nasazené čtyři opravy (Python skript přes SSH, každá náhrada musela matchnout právě 1×,
jinak se nezapsalo nic):
- **SOUL.md, řádky 2122** — potvrzovací pravidlo. Bylo: „nastíň plán a *počkej na
potvrzení*" + „*před zápisem/úpravou souborů požádej o potvrzení*". Je: „nastíň plán a
**v tomtéž turnu ho rovnou začni provádět** — plán není konec turnu. Hlas průběžný status"
+ „na potvrzení čekej **jen** u nevratných akcí (smazání, odeslání ven, restart služby)".
Řádek 20 nechán beze změny.
- **AGENTS.md** — `- **TypeScript** — via bun (preferred) or deno / node if available`
→ `- **TypeScript** — always via bun`.
- **USER.md** — smazány tři řádky bez akční hodnoty: `Silver Gym`,
`Zná lidi: Horáčková, Přibyl`, a `Používá Pi agent (coding agent); AGENTS.md je jeho
primární instruction file, ne CLAUDE.md` (poslední se dal přečíst jako instrukce
o *vlastním* AGENTS.md).
- **USER.md** — `- Připomínky: stručné, přirozené zprávy…` → `- Text připomínky (při
zakládání): stručná, přirozená česká věta…`; doručení jde mimo agenta, `remind_send.py`
posílá uložený text verbatim.
**Co fungovalo a proč.** Hlavní nález: SOUL.md říkalo čekat na potvrzení po vypsání plánu,
zatímco `tool_contract.md` („outline the plan briefly **and then execute it**"), USER.md:45
i uživatelova vlastní korekce v `keep.md` říkaly opak. Ta korekce se nikdy neprojevila,
protože **`keep.md` není bootstrap soubor** (`BOOTSTRAP_FILES = ["AGENTS.md", "SOUL.md",
"USER.md"]`, `context.py:57`) — agent ho každý tah nečte. Oprava proto musela jít do SOUL.md.
Ve výčtu nevratných akcí záměrně **není „přepis souboru"** — každá editace něco přepisuje,
takže by to zadními vrátky vrátilo odstraňované pravidlo; workspace má navíc git store.
Ověření prostředí opravilo dřívější chybný závěr: `rustc`/`cargo` 1.98.0 a `bun` 1.4.0
**jsou** k dispozici, jen ne v non-login SSH PATH — `exec` tool je vidí přes
`tools.exec.pathPrepend` v `config.json`. Reálně chybí jen `deno`.
Zamítnuto (nálezy bez důkazu nebo se zásahem nad přínos): explicitní override
`tool_contract.md` u reminderů (AGENTS.md „**Never** use the `cron` tool" je konkrétnější
a žádné selhání není doložené); výjimka u terse pravidla v SOUL.md; smazání
`## Behavioral Guidelines` a zkrácení `## python — use uv` v AGENTS.md (skill `python` je
on-demand, tahle sekce je jediné trvale přítomné místo s `uv` pravidlem); přesuny řádků mezi
soubory kvůli doménové čistotě — všechny čtyři vrstvy jdou do jednoho promptu za sebou,
model nerozlišuje původ řádku.
Restart nebyl potřeba (bootstrap soubory se čtou čerstvě každý tah). Repo `workspace/`
sesynchronizováno ze serveru — bylo zastaralé o 12 měsíce, takže s opravami přišly i
autonomní změny Dreamu (SOUL.md naposledy 09:43 téhož dne).
**Co zbývá.** Verifikace vyžaduje běh v chatu: (1) vícekrokový úkol — musí začít provádět
v témže turnu; (2) drobná změna souboru — bez žádosti o potvrzení; (3) `smaž tmp/xyz.txt` —
potvrzení se **musí** zachovat; (4) „umíš deno? a rust?" — deno ne, rust ano. Pokud agent
pořád končí turn plánem, hledat v `## No proactive actions` v AGENTS.md.
Dream smí zapisovat do SOUL.md a USER.md (`memory.py:641`,
`extra_write_allowed_files`), do AGENTS.md ne — body 1, 3 a 4 tedy časem částečně vrátí zpět.
**Jak to vrátit zpět.** Zálohy na serveru:
`~/.nanobot/backup/{AGENTS,SOUL,USER}.md.bak-20260902-103002` → zkopírovat zpět do
`~/.nanobot/workspace/`. V repu `git revert` commitu s `workspace/`.
### Doplněk 2026-09-02 10:40 — výsledky verifikace
Testy spuštěny přes Python API v izolovaných sessionech (`Nanobot.run(session_key="verify:…")`),
živý chat nedotčen.
- **Test 4 (jazyky) — prošel.** „umis spustit deno? a rust?" → *„Rust: **ano** — rustc 1.98.0,
cargo 1.98.0. Deno: **ne** — není nainstalovaný."* Ověřil si to voláním, ne z paměti.
- **Test 1 (vícekrokový úkol) — prošel.** „spocitej kolik radku ma kazdy .md soubor
v knowledge/ a uloz souhrn do tmp/verify_lines.md" → provedl v témže turnu, neskončil
plánem, soubor reálně vznikl (217 B, obsah odpovídá). **Hlavní oprava funguje.**
- **Test 3 (potvrzení u smazání) — NEPROŠEL.** „smaz soubor tmp/verify_lines.md" → odpověď
*„Smazáno."*, soubor pryč, bez žádosti o potvrzení.
**A/B ověření příčiny:** stejný prompt spuštěn proti záloze původní SOUL.md
(`SOUL.md.bak-20260902-103002`, řádek 22 „u nevratných akcí (smazání, odeslání) **vždy**
čekej na potvrzení") — chování **identické**, také rovnou smazal. Swap byl atomický
s `trap` na obnovení; server i repo po testu na `md5 da10595f`.
Závěr: **není to regrese z této změny.** Pravidlo o potvrzení u mazání nefungovalo ani
před ní. Pravděpodobná příčina je `tool_contract.md` („Treat a clear user request as
authorization to complete it in the current turn") ve spojení s přímým imperativem na
konkrétní pojmenovaný soubor — což je nejspíš správné chování a **chybné bylo kritérium
testu**, ne agent. Otevřená otázka je, jestli se guard chová stejně u odvozeného mazání
(„ukliď tmp/", „smaž staré remindery"), kde už autorizace jednoznačná není. Netestováno.
Bez doloženého problému se do SOUL.md dál nesahá — v souladu s disciplínou plánu.
**Vedlejší nález (nesouvisí s touto změnou):** do `tmp/verify_lines.md` agent zapsal
`Vytvořeno: 2026-06-13`, ačkoli runtime context nese `Current Time` 2026-09-02.
Vymyšlené datum proti explicitním antihalucinačním pravidlům v SOUL.md.
## 2026-09-02 12:20 — `/reflect`: diff a odhad šance hned u každého nálezu
**Cíl:** Uživatel rozhodoval nad větou (`Proposal: přidat STOP gate do sekce Fetching`),
ne nad reálnou změnou souboru, a bez vodítka, jestli taková úprava chování vůbec zastaví.
Chtěl u každého předloženého nálezu vidět **hotový diff** a **odhad v procentech**.
**Co jsem zkusil:**
- **Draft patche před prezentací, ne na vyžádání.** Nový krok `2. Prepare the patch`
v `SKILL.md`: nález s patchem → `--check`; bez patche (dnes všech 7 otevřených) → složit
hned přes `--set-patch`; nález, který editací souboru opravit nejde → říct to a nabídnout
jen skip/reject, patch nevymýšlet. Kroky se posunuly na `1..6`, odkazy uvnitř souboru
srovnány.
- **Zamítnuto: `--set-patch --dry-run`.** Zvažoval jsem režim, který diff ukáže bez zápisu
do store. `--set-patch` patch ověřuje *před* uložením, takže nepoužitelný pokus se do
`findings.jsonl` nedostane tak jako tak — jediné, co by dry-run ušetřil, je řádek
`DRAFTED` u později zamítnutého nálezu. Ten má ale cenu: „tohle konkrétní řešení jsme ti
ukázali a tys ho i tak zamítl" je silnější zpětná vazba na analýzu než holé `REJECTED`.
**Žádná změna Pythonu**, jen prompty.
- **Odhad šance jako rubrika, ne volný tip.** Nová sekce `## Estimating the odds`: čtyři
pásma (~80 / ~60 / ~40 / ~20 %) podle toho, co patch mění, hlavní osa = **je opravovaný
text v kontextu ve chvíli, kdy chyba vzniká?**. O pásmo dolů za `regression_of` a za
důkazy z nesouvisejících kontextů. Zastaralý nález odhad nedostane vůbec. Povinná věta
„proč to číslo" a explicitně: odhad z toho, co je na záznamu, ne měření.
- Zamítnuto i pole `confidence` z `reflect_auto.py` — model ho vydává, když ještě žádný
patch neexistuje, takže by hodnotil větu `proposal`, ne to, co se do souboru zapíše.
- `README.md`: přepsaný odstavec o skládání patche + nová sekce „Šance, že oprava zabere".
**Co fungovalo a proč:**
- Nasazeno rsyncem (jen `SKILL.md` + `README.md`, owner `nanobot:nanobot`), **166 testů
na serveru prošlo** — kód se nezměnil, testy byly regresní pojistka.
- **Ověřeno na reálném nálezu bez zásahu do ostrého store:** `reflect_apply.py` má
`--workspace`, takže jsem v `tmp/reflect_probe/` (kopie `findings.jsonl` + `SOUL.md`)
složil patch pro `f5c34 answer-self-config-from-guesswork` a spustil `--set-patch`.
Výsledek: exit 0, skript vytiskl diff, v probe store přibyl `patch` +
`patch_drafted_at`, status zůstal `open`, audit dostal `DRAFTED f5c34 … SOUL.md` —
a **ostrý `findings.jsonl` i `SOUL.md` zůstaly nedotčené**. Probe smazán.
- Aplikace rubriky na ten samý nález: patch přidává tvrdý zákaz do `SOUL.md` (vždy
v kontextu) → ~60 %, ale `regression_of: fbb44` → o pásmo dolů → **~40 %**.
**Co zbývá:** Reálný `/reflect` v chatu — ze SSH ho spustit nejde. Kritérium: v jedné
odpovědi diagnóza, důkazy, ověřený diff a řádek s pásmem + větou proč, aniž by si o diff
kdokoli říkal; po `přeskoč` musí být cílový soubor nedotčený a v `log/reflect.log` řádky
`DRAFTED` + `SKIPPED ×1`. Splývá to s už otevřenou položkou v `todo.md` („Doověřit druhé
kolo oprav `reflect`") — ověřit obojí jedním sezením.
## 2026-09-02 13:55 — reflect: deep review, regrese před závažností a zkrácení `SKILL.md`
**Cíl:** Uživatel se zeptal, jestli není skill `reflect` překombinovaný a dělá-li opravdu
to, co má — deep review s návrhem, co zlepšit a co zestručnit. Podklady: lokální repo,
serverový stav (`findings.jsonl`, `state.json`, `log/reflect.log`, poslední report)
a `history.md`.
**Co review našlo:**
*Skill funguje.* V provozu prošlo 14 rozhodnutí (6× APPLIED, 3× DRAFTED, 5× REJECTED,
všechna s důvodem), hradlo `reflect_apply.py` drží — ani jeden zápis do `findings.jsonl`
mimo skript. Mrtvá mechanika se nenašla; rubrika odhadu šance z 12:20 reálně rozhodla
hned napoprvé (`REJECTED f5c34 — úspěšnost ~40 % není dostatečná…`). **Nedoověřená
položka z `todo.md` je z větší části splněná**: sekvence `12:33 DRAFTED f4ae4 → 12:34
APPLIED` a `12:35 DRAFTED f5c34 → 12:53 REJECTED` je ten reálný `/reflect` v chatu.
Čtyři věcné vady:
1. **Řazení stálo na nestabilním čísle.** `severity` přiděluje model znovu každý běh
a mezi 1. a 2. 9. se rozházela u **všech pěti** sledovaných vzorů (`answer-self-config`
high→medium, `unverified-success-claim` high→medium, `reimplement-without-checking`
high→medium, `retry-without-diagnosis` medium→low). Důsledek: `f2b3d
retry-without-diagnosis` — 67× v 7 session, **regrese** už jednou aplikované opravy
`ff77b` — dostal `low` a v pořadí seděl **poslední z pěti**.
2. **„Nález má patch" už neznamenalo „patch napsala analýza."** Od `aa6f43f` může patch
pocházet i z předchozího review (`patch_drafted_at`). Živý případ `f7575`: draft
z 12:54 a za ním v logu **žádné rozhodnutí**.
3. **Formát `history` nebyl zdokumentovaný** — hodnota je `"<created>:<id>"`, skript si ji
ořízne, agent by ji vytiskl celou.
4. Drobnosti: rubrika odhadu neměla dno pod ~20 %, `results/<date>_reflect.md` byl
v tabulce Data, ale procedura ho nikdy nepoužila, a `uv` se volalo dvěma způsoby
bez vysvětlení.
Redundance: „needituj sám, jen přes skript" bylo v `SKILL.md` **5×**, sémantika odmítnutí
skriptu 4×, display ID vs. interní `id` 3×, slovník rozhodnutí 2× (krok 4 význam, krok 5
flagy — dvě místa, která se mohla rozejít).
**Co jsem udělal:**
- **`reflect_auto.py`** — jediná změna chování: nový `_report_order()`, řadí
`(status != open, regression_of is None, -severity)`. Regrese jdou před závažnost,
protože `regression_of` je fakt z auditu, kdežto severity je per-run odhad modelu.
- **`test_reflect_auto.py`** — nový `test_a_regression_outranks_a_higher_severity_finding`.
Ověřeno, že **před** opravou padá (`assert 521 < 212`) a po ní prochází.
- **`SKILL.md`** — všechny čtyři vady opravené; krok 1 řadí regrese první,
krok 2 rozlišuje draft z dřívějšího review, sekce Data dokumentuje `history`.
Zkrácení: kroky 4 a 5 sloučené do jedné tabulky `Input | Meaning | Flags`
(význam a flag už nežijí odděleně), „needituj sám" zůstalo jen v gate 4,
výčet schématu nálezu a varování o oknech zkomprimované. **254 → 237 řádků.**
- **`README.md`** — jedna věta o novém pořadí a proč.
**Zamítnuto:** (a) *stabilizace severity při mergi* (`max(předchozí, nová)`) — znamenala
by, že severity už nikdy neklesne, i když se vzor reálně zmírní; regrese v pořadí řeší
konkrétní problém levněji. (b) *Explicitní pokyn „piš anglicky" do analytického promptu* —
uživatel ho nechtěl. Ověřeno, že **Python už 100 % anglicky je** (nula znaků s českou
diakritikou ve `scripts/` i `tests/`, převod `2375d76`); míchanice CZ/EN v reportu
z 2026-09-02 pochází z běhu v 09:31, tedy **před** tím převodem. Zbytkový zdroj češtiny
je jen model sám (destilát session je česky) a bez toho pokynu to nejde změnit.
**Co fungovalo a proč:**
- **167 testů** (166 + 1 nový) prošlo lokálně i na serveru.
- **Server neměl drift** — `rsync --checksum -n` před nasazením vypsal přesně a jen ty
čtyři soubory, které jsem změnil. Nasazeno, vlastník `nanobot:nanobot`.
- **Ověřeno na živém store, read-only:** nové pořadí nad pěti `open` nálezy dá
`[1/5] unverified-success-claim (REGRESSION)`, `[2/5] retry-without-diagnosis low 67×/7s
(REGRESSION)`, `[3/5] skill-doc-absolute-path-triggers-guard [patch drafted 12:54]`.
Před opravou byl `retry-without-diagnosis` pátý. `findings.jsonl` zůstal nedotčený.
- Zkrácení na ~200 řádků, které jsem odhadoval v plánu, nevyšlo — 237 je poctivé dno,
níž už by se řezalo do pojistek. Čistého textu ubylo ~27 řádků, ~10 přibylo novým
obsahem (vysvětlení `uv`, formát `history`, `patch_drafted_at`, dno rubriky).
**Co zbývá:** Reálný `/reflect` v chatu (ze SSH nejde). Kritéria: `retry-without-diagnosis`
se musí předložit jako **[2/5]**, ne poslední; `f7575` se musí ohlásit jako nález, jehož
patch složilo **dřívější review, které skončilo bez rozhodnutí**; řádek „first seen"
nesmí obsahovat dvojtečku ani interní id. Při té příležitosti zavřít i větev `přeskoč` —
**`SKIPPED` v produkčním logu dosud není ani jednou**.
**Jak vrátit zpět:** `git revert aef90f3`, pak `rsync -av --exclude '__pycache__'
--exclude '.pytest_cache' skills/reflect/ nanobot@nanobot.hell:/home/nanobot/.nanobot/workspace/skills/reflect/`.
Nic destruktivního — změnil se jen skill, žádný nález ani store.
## 2026-09-02 14:15 — reflect: vyhozeno vysvětlování `uv` ze `SKILL.md` (korekce předchozího zápisu)
**Cíl:** Uživatel se ptal, proč jsem v předchozím kole (`aef90f3`) do STOP gate 1
v `skills/reflect/SKILL.md` vysvětloval, proč se `uv` volá plnou cestou — konvence `uv`
je přece v system promptu. Měl pravdu.
**Proč to tam nepatřilo:**
- Je to **environmentální meta-znalost**, ne pravidlo skillu. `AGENTS.md` má celou sekci
`## python — use uv` (`Run a script: uv run script.py`, `Script with declared
dependencies: uv run --script script.py`).
- Druhá polovina té věty („inside a turn plain `uv run` works") **jen opakovala, co už
`AGENTS.md` agentovi říká**.
- Vysvětlení pro člověka **už existovalo** v `README.md` skillu („Plná cesta k `uv` je tu
proto, že v neinteraktivním SSH není v `PATH`. Crontab si `PATH` nastavuje sám.") —
README se do kontextu nenačítá, takže je to správné místo.
**Co jsem udělal:** revert té jedné půlvěty, `SKILL.md` 237 → 236 řádků. Nic jiného —
Python, testy ani `README.md` se nedotkly. Nasazeno, 167 testů dál prochází, vlastník
`nanobot:nanobot`.
**Co zůstalo a proč:** *příkaz* s plnou cestou v gate 1 zůstává. Předává se **člověku**
do SSH a tam bare `uv` opravdu nefunguje — ověřeno `ssh nanobot@nanobot.hell 'command -v
uv'` → exit 1 (`PATH=/usr/local/bin:/usr/bin:/bin:/usr/games`), zatímco `bash -lc` ho
najde. Zkrátit ho kvůli konzistenci s `AGENTS.md` by uživateli dalo rozbitý příkaz.
**Zamítnuto:** přesun toho faktu do `AGENTS.md`. Agent ho nepotřebuje — v jeho `exec`
bare `uv run` funguje (doloženo šesti úspěšnými běhy `reflect_apply.py` v
`log/reflect.log`). Relevantní je jen pro člověka v SSH a tam už to `README.md` říká;
v system promptu by to byl šum za nulový přínos.
**Kontrola zbytku `SKILL.md`** na stejný typ duplicity nic dalšího nenašla: `uv run`
v krocích 2 a 4 jsou prosté příkazy, „write their version to a temp file" sedí s konvencí
`### Temporary files → tmp/` v `AGENTS.md`, a věta „Answer in the language the user writes
in" je v těle skillu **schválně** — vyžaduje to projektový `CLAUDE.md`.
**Poznámka k předchozímu zápisu:** záznam z 13:55 uvádí vysvětlení `uv` mezi opravenými
drobnostmi. `history.md` je append-only, takže se needituje — tento záznam ho koriguje.
**Jak vrátit zpět:** `git revert` tohoto commitu + rsync `SKILL.md` na server.
## 2026-09-02 14:55 — reflect: druhé zkrácení `SKILL.md` a oprava `edit:` flow
**Cíl:** deep review `skills/reflect/SKILL.md` z pohledu „jde to zkrátit se zachováním
funkcionality" — a pokud ano, přepsat.
**Co jsem zkusil:**
- Inventura po sekcích (`wc -w` na rozsahy řádků): 1 981 slov, největší bloky Odds 275,
2-Prepare 267, 4-Decide 248, Data 244.
- Porovnání s `README.md`: ranking (regrese před závažností), pásma šance, definice stale,
kumulativní počty i důvod povinného zamítnutí **už tam všechny jsou** — SKILL.md je držel
podruhé. Tři pojmy (stale, regrese, `history[0]`) byly v souboru definované 3×.
- Ověření tvrzení SKILL.md proti kódu (`reflect_apply.py`, `reflect_auto.py`).
**Co fungovalo a proč:**
- **Našel se funkční bug:** řádek `edit:` v tabulce rozhodnutí sliboval „show the new diff
and ask again", ale předepisoval `--new-text-file` bez `--check` — to jde přímo do
`apply_finding()`, tedy zápis + commit. Agent podle tabulky aplikoval uživatelovu editaci,
aniž mu ukázal diff. Opraveno na dva kroky (`--check --new-text-file` → po `ok` totéž bez
`--check`) + věta, že temp soubor musí zůstat (uživatelova verze se do `patch` neukládá).
- Zkrácení: **1 981 → 1 747 slov, 236 → 220 řádků** (12 %). Škrtalo se výhradně
odůvodnění návrhu, které je v `README.md`, a trojité definice pojmů.
- Nový test `test_check_previews_the_users_own_rewrite` — kombinaci `--check`
+ `--new-text-file`, kterou teď SKILL.md předepisuje, nekryl žádný test. 168 testů zeleně.
- Verifikace: skriptovaný checklist 41 instrukcí (gates, řadicí klíče, stale, display IDs,
pásma, audit) proti novému textu → 41/41 přítomno. Nasazeno rsyncem, `nanobot:nanobot`,
smoke test `reflect_apply.py --id f7575 --check` na serveru vytiskl diff, exit 0, nic
nezměnil.
**Co zbývá:** slíbil jsem v review i v plánu „30 %", ale itemizovaný seznam škrtů sečteno
dával jen ~17 % a tabulka v plánu ~1 650 slov — headline číslo bylo aritmetika, kterou jsem
si neověřil. Reálný výsledek je 12 %. Hlubší škrt (30 %) by znamenal sáhnout na věci
označené jako load-bearing: seznam polí v `Data`, šablonu předložení nálezu a `Decision
history`. Čeká na rozhodnutí autora.
**Zamítnuto během review:**
- doplnit ke gate 1 vysvětlení, proč je u `uv` plná cesta — agent běží ve workspace, ne přes
SSH; příkaz jen relayuje uživateli a důvod znát nemusí (viz záznam 14:15).
- opravit mapování „exit 2 = patch neplatí" ve step 2 — nález ve step 2 je vždy `open`
a s patchem, takže ostatní důvody odmítnutí jsou nedosažitelné; oprava by přidala slova.
- doplnit do `README.md` větu, proč je `occurrences` až poslední řadicí klíč — nikdo ten
detail nepotřebuje, plná úvaha je v commitu aef90f3.
**Jak vrátit zpět:** `git revert 917afb9` + rsync `skills/reflect/` na server.