# reflect — jak to funguje Skill hledá v mých vlastních session logách **opakující se chyby**, pojmenuje je a navrhne opravu. Nálezy pak procházíš ty, jeden po druhém, a rozhoduješ, co se použije. **Sám od sebe nikdy nic nezmění.** ## Dva režimy ```text ANALÝZA — denně 03:30, bez tebe REVIEW — jen když napíšeš /reflect cron → reflect_auto.py ├─ vezme 1 otevřený nález ├─ destiluje session z okna ├─ ukáže diagnózu, důkazy, návrh, diff ├─ LLM tah → nálezy ├─ čeká na tvoje rozhodnutí ├─ zapíše do findings.jsonl ├─ aplikuje → git commit → audit └─ Telegram (jen když je co) └─ další nález NEEDITUJE NIC edituje jen to, co schválíš ``` ## Nálezy jsou o nedávném chování, ne o celé historii Běh se dívá jen na **posledních 21 dní** (`--window-days`). Není to úspora — je to podmínka, aby nález něco znamenal: korpus má 3,5 měsíce a jedna dávka 200 kB, takže dohánění celé historie po jedné dávce za noc znamenalo, že cursor tři noci stál na konci května a nálezy z něj se předkládaly jako aktuální. Dvě hranice, každá jinak: | | Co dělá | |---|---| | **cursor** | podlaha — co se jednou analyzovalo, se neanalyzuje znovu (jinak by se zdvojily počty) | | **okno** | strop — co je starší než 21 dní, se přeskočí a cursor to mine natrvalo | Kdybys někdy potřeboval archeologii, vrať `cursor` ve `state.json` a spusť `--window-days 0`. ## Proč tě to neotravuje každý den Denně přibude jen pár session — v nich se vzor nepozná, jedna chyba je náhoda. Proto: | Kdy | Status | Telegram | |---|---|---| | vzor poprvé, 1 výskyt | `watch` | ne, jen se počítá | | vzor podruhé (≥2× a ve ≥2 session) | `open` | ano | | vzor už jednou opravený se vrátí **po** opravě | `open` + **regrese** | ano | | vzor už jednou opravený, ale důkazy jsou starší než oprava | `watch` | ne | | vzor jsi zamítl | `watch` napořád | ne, už nikdy | Zamítnutí je rozhodnutí, ne odklad — zamítnutý vzor se znovu neotevře. ## Co ti /reflect nabídne | Napíšeš | Stane se | |---|---| | `ok` / `aplikuj` | patch se použije a commitne | | `uprav: ` | přepíšeš návrh vlastními slovy, ukáže se nový diff | | `přeskoč` | nález zůstane otevřený na příště | | `zamítni` | nález se zavře natrvalo | | `konec` | konec review | Nález, který od analýzy patch nedostal (většina), si ho složí až při review: agent napíše návrh do JSON a nechá ho ověřit (`--set-patch`). Skript ho **nejdřív ověří proti souboru a teprve pak uloží**, takže nepoužitelný pokus ve `findings.jsonl` nezůstane — a agent do store nesahá vůbec. Pak ti ukáže diff a čeká na `ok` jako u každého jiného patche. Vždy jen **jeden** nález najednou. Nálezy se číslují `1..N` podle pořadí, ne podle interního id. ## Kde co leží | Cesta | Co | |---|---| | `reflect/findings.jsonl` | všechny nálezy, jeden JSON na řádek | | `reflect/state.json` | kam se došlo (cursor), použité okno + statistiky běhů | | `results/_reflect.md` | plný report jednoho běhu + okno, které pokryl, a kolik výskytů na 100 session mají už známé vzory | | `log/reflect.log` | audit — každé rozhodnutí, i zamítnutí a přeskočení | **„Naposledy" u nálezu je nejnovější datum v důkazech**, ne datum, kdy se záznam naposledy přepsal. Když je starší než okno posledního běhu, je nález **zastaralý**: analýza tam už nedohlédne, takže se sám nikdy neobnoví — spíš než patch si zaslouží zamítnutí. `/reflect` ti to u něj řekne. **Počty v nálezu (`7× ve 4 session`) jsou kumulativní součet napříč běhy** — sečtené z toho, co model napočítal v jednotlivých oknech, ne měření nad celým korpusem. Skript u nich hlídá jen to, co hlídat umí: vzor nemůže zasáhnout víc session, než kolikrát nastal, ani víc, než kolik jich v dávce vůbec bylo. ## Co se o tvém rozhodnutí zapíše | Rozhodnutí | Do `findings.jsonl` | Do `log/reflect.log` | |---|---|---| | složení patche | `patch` + `patch_drafted_at` (= složeno při review, ne modelem) | `DRAFTED [vzor] ` | | `ok` | `applied: {at, sha, file}`, `patch` = návrh modelu | `APPLIED [vzor] ` | | `uprav:` | navíc `applied.new_text` = tvoje verze (návrh modelu zůstává v `patch`) | `APPLIED-EDITED …` | | `zamítni` | `rejected: {at, reason}` — **důvod je povinný** | `REJECTED [vzor] — ` | | `přeskoč` | `skipped: {count, last}`, status zůstává `open` | `SKIPPED [vzor] ×N` | Povinný důvod u zamítnutí není otravování: je to jediná zpětná vazba na kvalitu analýzy. Z prvních osmi nálezů jsi čtyři zamítl — bez důvodů se z toho čísla nedá poznat, co v analytickém promptu změnit. Na dotaz „co jsem už rozhodl" ti to `/reflect` vypíše z logu včetně revert příkazu. ## Jak vrátit změnu zpět Workspace je git, takže každá aplikovaná oprava je samostatný commit: ```bash git revert ``` SHA najdeš v `log/reflect.log` nebo u nálezu ve `findings.jsonl` (`applied.sha`). Změna žije **jen na serveru** — do trackovacího repa (`src/nanobot`) se musí dotáhnout zvlášť, jinak ji příští `rsync` skillu přepíše zpátky. ## Ruční spuštění ```bash cd ~/.nanobot/workspace # normální běh (to dělá cron) ~/.local/bin/uv run --script skills/reflect/scripts/reflect_auto.py # jiné okno než výchozích 21 dní ~/.local/bin/uv run --script skills/reflect/scripts/reflect_auto.py --window-days 7 # jen prompty do tmp/, nevolat model; s --all přes celý korpus ~/.local/bin/uv run --script skills/reflect/scripts/reflect_auto.py --dry-run --all # jak velký je korpus a na kolik dávek vyjde (nevolá model) ~/.local/bin/uv run --script skills/reflect/scripts/reflect_distill.py --stats > /dev/null ``` `--all` ignoruje cursor, takže by znovu přečetl už spočítané session a **nafoukl jim počty**. V ostrém běhu ho skript odmítne — je jen na `--dry-run`. **Přerušený běh o hotovou práci nepřijde.** Nálezy i cursor se zapisují po každé dávce a nová dávka nezačne po 20 minutách (`--deadline-minutes`). S 21denním oknem to vyjde zpravidla na jednu dávku, takže se tenhle strop ani neuplatní; kdyby zbyla nezpracovaná dávka, Telegram to řekne i když nálezy nejsou žádné. Plná cesta k `uv` je tu proto, že v neinteraktivním SSH není v `PATH`. Crontab si `PATH` nastavuje sám, takže tam stačí `uv run …`. ## Záruky Nejsou to sliby v promptu, ale kód: - **Analýza (`reflect_auto.py`) nemá v sobě žádnou cestu k zápisu do cizího souboru.** Navíc se před a po tahu porovná `git status` celého workspace — kdyby agent přesto něco zapsal, nálezy se zahodí. - **Editaci dělá výhradně `reflect_apply.py`, vždy jeden nález.** Agent soubor needituje sám. Skript odmítne patch, jehož původní text v souboru není nebo je tam vícekrát — nehádá, kam patřil. Odmítne i nález, který není `open` (tj. nebyl ti předložen). - **Do `findings.jsonl` píše taky jen `reflect_apply.py`.** I patch složený při review jde přes něj (`--set-patch`) a projde stejnou kontrolou; agent auditní stopu needituje. - **Commituje se jen ten jeden dotčený soubor** (`git add -- `), nikdy `git add -A`. Rozdělaná práce Dreamu a jiných skillů se do commitu nedostane; když je rozdělaný přímo ten soubor, udělá se nejdřív checkpoint commit, aby byl revert přesný. - Skill vynechává vlastní session, takže neanalyzuje sám sebe. - **Každé rozhodnutí nechá záznam**, i to, které nic nezmění: zamítnutí s důvodem, přeskočení s počítadlem, `uprav:` odděleně od původního návrhu modelu. Ověřeno testy v `tests/` — včetně toho, že `git revert` vrátí soubor do původního stavu.