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

165 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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: <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:
```bash
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í
```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 -- <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.