runtime
This commit is contained in:
@@ -1,5 +1,134 @@
|
||||
# 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
|
||||
@@ -3455,3 +3584,278 @@ 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`.
|
||||
|
||||
Reference in New Issue
Block a user