Files
nanobot-runtime/skills/reflect/README.md
2026-09-02 10:36:37 +02:00

7.9 KiB
Raw Blame History

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

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: <text> 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/<datum>_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 <id> [vzor] <soubor>
ok applied: {at, sha, file}, patch = návrh modelu APPLIED <id> [vzor] <soubor> — <sha>
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 <id> [vzor] — <důvod>
přeskoč skipped: {count, last}, status zůstává open SKIPPED <id> [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:

git revert <sha>

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í

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 -- <file>), 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.