provozni zaloha
This commit is contained in:
@@ -1,5 +1,26 @@
|
||||
# History
|
||||
|
||||
## 2026-06-07 19:57 — imageGeneration: provider z openrouter na ollama (žádný paid path)
|
||||
|
||||
**Cíl:** `tools.imageGeneration` v serverovém configu odkazoval na `openai/gpt-5.4-image-2` přes provider `openrouter` (placený). Uživatel nechce, aby cokoli kolem obrázků šlo přes openrouter — jen ollama provider / cloud modely.
|
||||
|
||||
**Zjištění:**
|
||||
|
||||
- `openai/...` byl jen default vypnutého toolu (`enabled: false`) — je to OpenRouter naming convention `vendor/model`, ne odkaz na (neexistující) OpenAI provider. Providery nakonfigurované jen tři: `openrouter`, `ollama`, `gemini`.
|
||||
- Nanobot **podporuje** image-gen přes ollama (`OllamaImageGenerationClient` → `POST /api/generate` s `width/height/steps`).
|
||||
- ALE **Ollama Cloud nemá žádný text→image model** — ověřeno z `ollama.com/search?c=cloud` i z `curl nvidia.hell:11434/api/tags`: samé LLM, vision-LLM (obrázky na *vstupu*) a embeddingy, žádný flux/SD/diffusion. Takže image-gen na ollama reálně nepoběží, není na co model namířit.
|
||||
|
||||
**Co jsem udělal:**
|
||||
|
||||
- Záloha: `cp -a ~/.nanobot/config.json ~/.nanobot/config.json.bak-imggen`.
|
||||
- In-place Python edit `~/.nanobot/config.json` → `tools.imageGeneration`: `provider` `openrouter`→`ollama`, `model` `openai/gpt-5.4-image-2`→`""`, `enabled` ponecháno `false`. Config re-parsnut OK.
|
||||
|
||||
**Co fungovalo a proč:** Splňuje záměr — žádná cesta image-gen přes placený openrouter. Tool je `enabled: false`, takže neutrácí; i kdyby se zapnul, generace na ollama selže (žádný image model), nezačne nic platit.
|
||||
|
||||
**Co zbývá / pozn.:** Funkční generování obrázků by vyžadovalo placený provider (gemini/openrouter) nebo lokální diffusion mimo Ollamu. Na ollama to nejde.
|
||||
|
||||
**Jak vrátit zpět:** `cp -a ~/.nanobot/config.json.bak-imggen ~/.nanobot/config.json`.
|
||||
|
||||
## 2026-06-07 19:07 — Přidán model preset `gpt` (gpt-oss:120b-cloud, ollama)
|
||||
|
||||
**Cíl:** Přidat do serverové konfigurace nový model preset `gpt` = `gpt-oss:120b-cloud` přes Ollama.
|
||||
@@ -85,6 +106,40 @@
|
||||
|
||||
---
|
||||
|
||||
## 2026-06-03 — MiniLoop gemini-flash-lite a mistral-small-3.2 cache
|
||||
|
||||
**Cíl:** Otestovat `google/gemini-3.1-flash-lite` (OpenRouter) a ověřit `mistral-small-3.2`.
|
||||
|
||||
**Co jsem zkusil:** `dotnet run --no-build -- test gemini-flash-lite` a `dotnet run --no-build -- test mistral-small-3.2`.
|
||||
|
||||
**Co fungovalo a proč:** `gemini-flash-lite` — 17/17 ok, median **683 ms**, avg 719 ms, out 559 tok. Nejlepší výsledek ze všech dosud měřených OpenRouter modelů; poráží haiku-4.5 (1064 ms), gemma-3-27b (1413 ms) i glm-5.1-ollama (1690 ms). Žádný reasoning, přímý parse.
|
||||
|
||||
**Problém — `mistral-small-3.2` byl dřív naměřen s cache:** Původní výsledek 934 ms median (history 2026-06-03 „MiniLoop levné OSS") zřejmě těžil z cache providera. Opakovaný cold test ukázal 3 400–7 400 ms na prvních 4 příkladech — přibližně 3–8× horší. Skutečná cold performance je cca 4–5 s median. Označeno v knowledge.md.
|
||||
|
||||
**Co zbývá:** ověřit gemini-flash-lite v plném produkčním nasazení (nanobot remind skill), zhodnotit cenu.
|
||||
|
||||
## 2026-06-03 — MiniLoop phi4:latest zamítnut
|
||||
|
||||
**Cíl:** Otestovat lokální `phi4:latest` (14.7B) jako kandidáta pro MiniLoop.
|
||||
|
||||
**Výsledek:** Zamítnut — 15/17, median 1744 ms / avg 1676 ms. Dvě skutečné chyby: (1) `příští pondělí` → `2026-06-05` (čtvrtek) místo `2026-06-08` — horší než codestral, který vrátil neděli; (2) `dopoledne` → window `09:00-12:00` místo `08:00-12:00`. Navíc pomalejší než gemma4:e4b (1744 vs 1136 ms). „Punches above weight" reputace se pro tuto úlohu nepotvrdila.
|
||||
|
||||
## 2026-06-03 — MiniLoop codestral:22b zamítnut
|
||||
|
||||
**Cíl:** Otestovat lokální `codestral:22b` jako kandidáta pro MiniLoop.
|
||||
|
||||
**Výsledek:** Zamítnut — 16/17, median 2483 ms / avg 2621 ms, skutečná chyba data: `příští pondělí` → `2026-06-07` (neděle) místo `2026-06-08` (pondělí). Stejná třída chyby jako ministral-3:8b (weekday aritmetika). Zároveň 2× pomalejší než gemma4:e4b (1136 ms). Coder specializace nepomohla u česky popsaných relativních dat.
|
||||
|
||||
## 2026-06-03 — MiniLoop gemma4:e4b local
|
||||
|
||||
**Cíl:** Otestovat lokální `gemma4:e4b` (8B, nvidia.hell) v MiniLoop — jestli zvládne 17/17 a jaké jsou časy.
|
||||
|
||||
**Co jsem zkusil:** `dotnet run -- test gemma4-local` — jediný model, bez gate (lokální Ollama, žádná kvóta).
|
||||
|
||||
**Co fungovalo a proč:** 17/17, median 1136 ms / avg 1719 ms, out=1530 tok. Žádné chyby data ani cronu. Odlehlé hodnoty (3–4 s) u složitějších vstupů (random times, multiple windows). Výsledek překvapivě silný pro lokální 8B model — lepší median než glm-5.1 cloud (1690 ms) a blízko gemma-3-27b-it OpenRouter (1413 ms). Bez reasoning skluzu (porovnej nemotron-nano: out=7805).
|
||||
|
||||
**Co zbývá:** porovnat s dalšími lokálními modely (codestral:22b, phi4) pokud bude potřeba offline fallback.
|
||||
|
||||
## 2026-06-02 — Fix: dynamická detekce verze nanobotu v check_nanobot_version.py
|
||||
|
||||
**Cíl:** Opravit hardcoded `CURRENT_VERSION = "0.2.0"` v `~/.nanobot/workspace/scripts/check_nanobot_version.py` — po upgradu na 0.2.1 by cron job navždy hlásil 0.2.1 jako "novou verzi".
|
||||
@@ -1418,3 +1473,86 @@ Běh 5 modelů s gate=3:
|
||||
**Jak vrátit zpět:** `git revert` commitu se změnou `tasks-daemon.service` + `rsync` `systemd/` zpět na server + `daemon-reload`. Recovery (A) a smazaný orphan (B) se nevrací.
|
||||
|
||||
**Co zbývá:** End-to-end potvrzení (nový detach task projde `.path` → drain → done) — uživatel ověří interaktivně; daemon na prázdném inboxu už ověřen (`exit=0`).
|
||||
|
||||
---
|
||||
|
||||
## 2026-06-10 07:35 — git cleanup: přestat trackovat gitignored soubory na serveru
|
||||
|
||||
**Cíl:** `.gitignore` v serverovém repu (`~/.nanobot/workspace`) přibyl pozdě — `__pycache__/*.pyc` a `MEMORY.md.bak` se dostaly do gitu dřív, takže je ignore neřešil a pořád byly trackované. Vyndat je z gitu, nechat na disku.
|
||||
|
||||
**Co jsem zkusil:**
|
||||
- `git ls-files -i -c --exclude-standard` → **11 souborů** trackovaných i přes `.gitignore`: 10× `*.pyc` v `__pycache__/` (skills `detach`/`remind`) + `memory/MEMORY.md.bak`.
|
||||
- `git ls-files -i -c --exclude-standard -z | xargs -0 git rm --cached` → odstranění z indexu, soubory na disku zůstaly (ověřeno `ls`).
|
||||
- Commit `f93c1cf` jen s těmito removaly (explicitní pathspec) — repo mělo rozdělanou autonomní práci (Dream procesor: `cron/jobs.json`, `memory/history.jsonl`, skripty `remind/`), té se commit nedotkl. Po commitu `git ls-files -i -c --exclude-standard` = **0**.
|
||||
|
||||
**Co fungovalo a proč:** `.gitignore` ignoruje jen *netrackované* soubory; co už je v indexu, musí ven přes `git rm --cached` (smaže z indexu, nechá na disku). Od teď se nové `__pycache__/` už necommitují.
|
||||
|
||||
**Jak vrátit zpět:** `git revert f93c1cf` (znovu je začne trackovat).
|
||||
|
||||
---
|
||||
|
||||
## 2026-06-10 07:55 — Revize přepsaného remind skillu (YAML → SQLite)
|
||||
|
||||
**Cíl:** Uživatel nechal nanobota přepsat `/remind` skill z `reminder.yaml` na SQLite. Zkontrolovat výsledek, posoudit funkčnost, otestovat, navrhnout změny (vč. textu SKILL.md). Nic neměnit bez souhlasu.
|
||||
|
||||
**Co jsem zkusil:**
|
||||
- Stáhl serverový skill (`rsync`) do `tmp/server-remind/`, přečetl `SKILL.md`, `db.py`, `remind_edit.py`, `remind_send.py`, `random_times.py` + všechny testy.
|
||||
- Ověřil reálný stav serveru: schema `reminders.sqlite` (nové: `days_filter/from_date/until_date`), 14 reálných připomínek migrováno (25 `at`, 6 cron, 9 random), `reminder_fires`=0.
|
||||
- Pustil test suite na serveru: **43 passed**.
|
||||
- Read-only kontrola deployed senderu proti **kopii** prod DB (`REMIND_DB`, bez odeslání) — všech 9 random se spočítá bez výjimky, nic chybně due.
|
||||
- Live test doručení: `remind_edit.py add --at` na +2 min (id=15), ověřeno `reminder_fires` zápis `('at', delivered, 07:54:01)` + DELIVER v logu + reálný příchod na Telegram (potvrdil uživatel), pak `remove --keyword` (soft-delete).
|
||||
|
||||
**Co fungovalo a proč:** Přechod na DB je funkčně nasazený a doručování jede. Nalezené defekty:
|
||||
- **P1 schedule_type collision:** `schedule_{at,cron,random}` mají vlastní AUTOINCREMENT id → překryv (at 1–25, cron 1–6, random 1–9). UNION-ALL inference v `remind_send.main()` označí každý cron/random fire jako `'at'` → `reminder_fires.schedule_type` špatně + dedup pro cron/random nefunkční (maskuje jen 60s tolerance). Fix: každá `_due_*` vrací svůj typ.
|
||||
- **P1 stale bootstrap:** serverové `AGENTS.md` + `TOOLS.md` pořád mluví o `reminder.yaml` a starém formátu `reminder.log` (auto-load každý tah).
|
||||
- **P2 log regrese:** `log_operation` píše UTC + míchá ADD/EDIT/…/DELIVER; `TOOLS.md` „co dnes přišlo“ čeká Prague-time delivery-only.
|
||||
- **P3:** mrtvý `import yaml`/pyyaml dep, ignorovaný sloupec `timezone`, hardcoded `CHAT_ID`, duplicitní text neřešitelný přes keyword (chybí `--id`), `__import__("datetime")`, `--replace-schedules` bez schedule → němá připomínka.
|
||||
- **SKILL.md text:** matoucí „python3 required“, chybí instrukce odpovídat jazykem uživatele a jak řešit read-back doručení.
|
||||
- **Repo desync:** lokální `skills/remind/` je pořád YAML verze + `IMPROVEMENTS_REPORT.md` + `reminder.example.yaml`; `knowledge.md` /remind sekce neplatná.
|
||||
|
||||
**Co zbývá:** Uživatel odsouhlasil rozsah oprav (P1 schedule_type, P1 bootstrap docs, P3 cleanupy, repo+knowledge sync); P2 (reminder_fires dotaz vs. čistý delivery-log) ještě nerozhodnuto.
|
||||
|
||||
**Jak vrátit zpět:** Test připomínka (id=15) už odstraněna (soft-delete). Žádná jiná změna na serveru neproběhla.
|
||||
|
||||
---
|
||||
|
||||
## 2026-06-10 08:10 — Implementace oprav remind skillu (po odsouhlasení)
|
||||
|
||||
**Cíl:** Provést odsouhlasené opravy z revize výše: P1 schedule_type, P1 bootstrap docs, P2 read-back přes reminder_fires, P3 cleanupy, repo+knowledge sync.
|
||||
|
||||
**Co jsem zkusil / udělal:**
|
||||
- **remind_send.py:** každá `_due_{at,cron,random}` vrací `schedule_type`; smazána chybná UNION-ALL inference v `main()`. Odstraněn mrtvý `import yaml` + dep `pyyaml`. `CHAT_ID` → `_telegram_config()` čte `channels.telegram.allowFrom[0]` z configu, fallback konstanta.
|
||||
- **remind_edit.py:** helper `_resolve_one` (výběr přes `--id` nebo `--keyword`, ambiguous vypíše ids); `--id` přidáno k remove/edit/enable/disable; guard na `--replace-schedules` bez nového schedule; nový subcommand `delivered [--since]` (čte `reminder_fires`, Prague time); `from datetime import date` místo `__import__`; dep `croniter` only.
|
||||
- **SKILL.md:** instrukce odpovídat jazykem uživatele, dokumentace `delivered` + `--id` + duplicit, oprava matoucího Environment.
|
||||
- **Testy:** +5 (schedule_type collision, --id disambiguace, resolve vyžaduje id/keyword, replace-schedules guard, delivered) → **48 passed** lokálně i na serveru.
|
||||
- **Nasazení:** ověřeno, že server skill mezitím nikdo nesáhl (diff = jen mé změny), `rsync` na server, owner `nanobot:nanobot`, server pytest 48 OK, smoke `list`+`delivered` proti reálné DB OK. `delivered` ukázal reálné doručení „Panama" 08:02 — potvrdilo, že stará verze zapsala random odpal jako `schedule_type='at'` (P1 bug v praxi).
|
||||
- **Bootstrap:** `AGENTS.md` + `TOOLS.md` na serveru — `reminder.yaml` → SQLite `db/reminders.sqlite`, sekce o `reminder.log` přepsána na `delivered`/`reminder_fires`. Push ověřen.
|
||||
- **Repo:** `skills/remind/` synced z deploye, smazány `reminder.example.yaml` + `IMPROVEMENTS_REPORT.md` (commit `b244c01`). `knowledge.md` /remind sekce přepsána.
|
||||
|
||||
**Co fungovalo a proč:** Skilly se čtou bez restartu (exec subprocess + bootstrap fresh každý tah), takže fix je živý okamžitě. Dedup pro cron/random teď reálně funguje (správný schedule_type), ne jen díky 60s toleranci.
|
||||
|
||||
**Co zbývá:** Jeden historický řádek `reminder_fires` (reminder 7, 08:02) má pořád `schedule_type='at'` z bugu — oprava přes přímý prod UPDATE byla blokována auto-classifierem (neautorizovaný prod write), čeká na svolení uživatele. Kosmetické (znovu se neodpálí). Kandidáti do `decisions.md` (čekají na přeformulování autorem): úložiště = SQLite; read-back i audit = reminder_fires.
|
||||
|
||||
**Jak vrátit zpět:** Skill: `git revert b244c01` + rsync zpět na server. Bootstrap: serverové AGENTS.md/TOOLS.md vrátit na `reminder.yaml` formulaci (DB ale existuje, takže to nedává smysl). DB schema/data beze změny.
|
||||
|
||||
**Dodatek (08:13):** Po svolení uživatele opraven zaseknutý audit řádek — `UPDATE reminder_fires SET schedule_type='random' WHERE reminder_id=7 AND fire_time='2026-06-10T08:02:00'` (1 řádek). Tím je audit konzistentní s fixem.
|
||||
|
||||
---
|
||||
|
||||
## 2026-06-10 08:50 — Code review remind skillu + cleanup (P1+P2+P3)
|
||||
|
||||
**Cíl:** Detailní revize skillu `/remind`, ověřit shodu lokál↔server, navrhnout a po schválení provést zlepšení (čistota, čitelnost, normy).
|
||||
|
||||
**Co jsem zkusil / udělal:**
|
||||
- **Review:** stáhl serverovou verzi, `diff` všech 5 skriptů + SKILL.md → **lokál identický se serverem**. Nálezy rozděleny P1/P2/P3, plán odsouhlasen uživatelem (rozsah: vše; `list` nemá vracet JSON).
|
||||
- **P1 — korektnost:** `cmd_list` vypisoval **pozici** `{idx}.`, ne skutečné DB `id` → agent z `list` četl špatné číslo pro `--id`. Přepsáno na čitelný formát `#<id> text [status]` + odsazené schedule řádky, HH:MM okno, prázdný store → `(no active reminders)`. Helper `_schedule_lines`. SKILL.md popisuje nový formát místo neexistujícího „JSON". `db.log_operation(details: str)` → `str | None` (volá se s `None`).
|
||||
- **P2 — struktura:** `cmd_edit` validace (prázdný text, `_build_random`) přesunuta **před** `BEGIN` (žádná otevřená transakce na early-return). Duplicitní `_parse_window`/`_hhmm_to_minutes` v `remind_edit.py` smazány → reuse `parse_window` z `random_times.py` (publikováno odebráním podtržítka). `_build_random` se volá jen jednou — `_insert_schedules` dostává hotový `random_cfg`.
|
||||
- **P3:** `remind_send._now` → `_now_prague() -> datetime` (konec kolize s `remind_edit._now`, který vrací str); `_telegram_config()` čteno jednou v `main()` + early-return na prázdné `due`; `cmd_delivered` f-string SQL → dvě parametrizované query; `_find_by_keyword` escapuje LIKE wildcardy + `ESCAPE`; shebang obou skriptů → `uv run --script`. Testy: odstraněn nepoužitý `capsys`, `_run_send` korektně zachytává/obnovuje funkce, +3 testy (random `days_filter` e2e, retry po failed fire, `delivered` default „dnes").
|
||||
- **Verifikace:** lokálně **51 passed**. Deploy `rsync` celý adresář, owner `nanobot:nanobot`. Server pytest **51 passed**. Smoke produkční `list` (reálná id, nový formát), `_due_random` dotaz proti reálné DB OK (9 random reminderů). Ověřeno, že produkční schéma `schedule_random` má `days_filter/from_date/until_date`.
|
||||
|
||||
**Co fungovalo a proč:** Skilly se čtou bez restartu. `reminder_cron.log` mtime 07:36 (před deployem) = od deploye crontab sender nezapsal žádnou novou chybu → běží čistě. Staré traceby v logu pochází z dávno mrtvé verze (`_process_reminder`, sloupce `days/start_date/end_date`, `ROLLBACK` v main) — irelevantní.
|
||||
|
||||
**Co zbývá / gotcha:**
|
||||
- **`log_operation` ignoruje `REMIND_DB`** — píše vždy do reálného `workspace/log/reminder.log` přes `__file__`-relativní cestu. Spuštění test suite **na serveru** proto zapsalo 24 fixture řádků (timestamp `2026-06-10T06:47:32`) do reálného logu. **Poučení: testy spouštět jen lokálně.** Úklid logu (odstranit 24 řádků + truncate staré traceby v `reminder_cron.log`) byl blokován auto-classifierem (neautorizovaný prod write) — čeká na svolení uživatele.
|
||||
|
||||
**Jak vrátit zpět:** `git revert <commit>` skillu + rsync předchozí verze na server. DB schema/data beze změny.
|
||||
|
||||
@@ -164,7 +164,7 @@ Zdroj: `nanobot/channels/telegram.py:258-326` (BotCommand registrace, regex rout
|
||||
|
||||
## /remind skill — architektura a gotchas
|
||||
|
||||
Připomínky žijí v `~/.nanobot/workspace/reminder.yaml`. Doručuje je **systémový crontab uživatele nanobot** (každou minutu, `crontab -l`), který spouští `skills/remind/scripts/remind_send.py` přes `uv run`. Skript čte YAML, porovnává cron výrazy / `at` pole s Prague časem, a při shodě posílá **přímo přes Telegram Bot API** (token z `config.json` → `channels.telegram.token`). Žádný agent, žádný LLM. Deduplikace přes `.reminder_state.json` (každý fire 1×), audit do `log/reminder.log` (formát `YYYY-MM-DDTHH:MM:SS <text>`, Prague time bez tz suffixu — čte ho agent na dotaz „co dnes přišlo?"). Vedle něj `log/reminder_cron.log` je čistá zachytávka stdout/stderr crontabu — za zdravého běhu prázdný, plní se jen při pádech skriptu.
|
||||
**Úložiště = SQLite `~/.nanobot/workspace/db/reminders.sqlite`** (dřív `reminder.yaml`; migrace 2026-06-10). Schema: `reminders` (id, text, enabled, timezone, created_at, updated_at, deleted_at) + tři schedule tabulky `schedule_at`/`schedule_cron`/`schedule_random` (FK na reminder, cascade) + `reminder_fires` (audit jednotlivých odpalů: schedule_type, fire_time, delivered_at, status, error_message). Doručuje **systémový crontab uživatele nanobot** (každou minutu), který spouští `skills/remind/scripts/remind_send.py` přes `uv run` — čte DB, porovnává cron / `at` / random s Prague časem, při shodě posílá **přímo přes Telegram Bot API** (token z `config.json`, chat_id z `channels.telegram.allowFrom[0]` s fallback konstantou). Žádný agent, žádný LLM. Deduplikace přes tabulku `reminder_fires` (klíč reminder_id + schedule_id + schedule_type + fire_time, status='delivered'). `log/reminder.log` je teď provozní/debug log **všech operací** (ADD/EDIT/…/DELIVER, UTC) — ne zdroj pravdy pro doručení (viz `delivered` níže); `log/reminder_cron.log` zachytává stdout/stderr crontabu (zdravý běh = prázdný).
|
||||
|
||||
**Proč mimo agenta:** dřív to byl nanobot cron job `remind-check` přes agenta — spamoval "Output was empty" kvůli fail-open evaluatoru (viz výše "Cron job s LLM agentem"). Crontab to obchází deterministicky.
|
||||
|
||||
@@ -174,21 +174,29 @@ Připomínky žijí v `~/.nanobot/workspace/reminder.yaml`. Doručuje je **syst
|
||||
|
||||
**Telegram:** `/remind text` je bot command, nedojde k agentovi jako text. Psát přirozeně: `připomeň mi...`, `nastav připomínku...`
|
||||
|
||||
**Hotový záznam se maže celý** — žádné `done` pole. Cron tool se používá pouze pro background agent úlohy, nikdy pro osobní notifikace uživateli.
|
||||
**Read-back doručených („co dnes přišlo?") = subcommand `delivered`**, ne čtení logu. `remind_edit.py delivered [--since YYYY-MM-DD]` dotáhne z `reminder_fires` jen doručené (`status='delivered'`), default dnes; `fire_time`/`delivered_at` se ukládají **Prague-naive**, takže žádná konverze. `TOOLS.md` na to směruje agenta.
|
||||
|
||||
**Editace `reminder.yaml` — vždy přes `remind_edit.py`:** nikdy `edit_file`/`write_file` přímo. Skript validuje cron výrazy (`croniter.is_valid()`), datetime (`fromisoformat()`), dělá atomický zápis (`.yaml.tmp` → `os.replace()`). Volat jako `uv run /home/nanobot/.nanobot/workspace/skills/remind/scripts/remind_edit.py <subcommand>`. Subcommandy: `list`, `add --text ... --cron ... [--cron ...]`, `add --text ... --at ISO`, `add --text ... --random-times-per-day N --random-window HH:MM-HH:MM [--random-days 1-5] [--random-from DATE] [--random-until DATE]`, `remove --keyword ...`. Výstupy JSON; chyby stderr + non-zero exit.
|
||||
**Soft delete:** `remove` nastaví `deleted_at` (záznam zůstane v DB, jen zmizí z `list` a odpalů); hard delete jen přímým DB zásahem. Cron tool se používá pouze pro background agent úlohy, nikdy pro osobní notifikace uživateli.
|
||||
|
||||
**Editace DB — vždy přes `remind_edit.py`** (deterministické CLI, SQLite transakce + validace cron/`at`/random), nikdy přímý DB nebo `edit_file`. Volat `uv run skills/remind/scripts/remind_edit.py <subcommand>` (workspace-relativní cesta, exec běží z workspace rootu). Subcommandy: `list`, `add --text … (--cron EXPR… | --at ISO… | --random-times-per-day N --random-window HH:MM-HH:MM [--random-days 1-5] [--random-from DATE] [--random-until DATE])`, `edit --keyword|--id [--text …] [--replace-schedules + nové schedule flagy]`, `remove`, `enable`, `disable`, `delivered`. Výběr záznamu přes `--keyword` (substring; ambiguous → vrátí ids) nebo `--id` (přesný). **Mutace vrací JSON** (`{"added": …}` ap.); **`list` vrací čitelný text** — řádek na reminder `#<id> text [enabled|disabled]` + odsazené schedule řádky (prefix `#<id>` je id pro `--id`), prázdný store → `(no active reminders)`. Chyby stderr + non-zero exit. Env `REMIND_DB` přepíše cestu k DB (testy).
|
||||
|
||||
**Gotcha — `log_operation` ignoruje `REMIND_DB`:** audit zápis do `workspace/log/reminder.log` jde přes `__file__`-relativní cestu, **ne** přes `DB_PATH`/`REMIND_DB`. Spuštění test suite **na serveru** proto zapíše fixture texty do reálného `reminder.log` (prod `reminder_fires` zůstává čistá — ta jede přes DB_PATH). **Testy spouštět jen lokálně** (`uv run --with pytest --with croniter pytest skills/remind/tests/`).
|
||||
|
||||
**Náhodný (deterministický) čas (`random` blok):** N× denně v náhodný čas uvnitř okna, ale deterministicky — sdílený modul `scripts/random_times.py` počítá časy ze seedu `f"{datum}|{text}"`, takže sender zůstává bezstavový (počítá se každou minutu znovu). Min. rozestup mezi časy = konstanta `MIN_GAP_MIN` (default 15) v tomtéž modulu. Validace v `remind_edit.py` jde přes stejný `compute_fire_times`. Testy: `uv run --with pytest pytest skills/remind/tests/`. Návrh: [plans/remind-random-time.md](plans/remind-random-time.md).
|
||||
|
||||
**uv path na serveru:** `/home/nanobot/.local/bin/uv` — není v PATH pro root. Spouštět jako `/home/nanobot/.local/bin/uv run script.py`.
|
||||
|
||||
**REMINDER_YAML path (gotcha):** V `remind_send.py` je `Path(__file__).resolve().parent.parent.parent.parent` — **4 levely** nahoru z `.../workspace/skills/remind/scripts/` na workspace root. Se 3 levely vede cesta na `.../workspace/skills/` kde YAML neexistuje a skript tiše skončí (`if not REMINDER_YAML.exists(): return`) bez výstupu i chyby.
|
||||
**DB_PATH path (gotcha):** V `remind_send.py`/`remind_edit.py` je `Path(__file__).resolve().parent.parent.parent.parent` — **4 levely** nahoru z `.../workspace/skills/remind/scripts/` na workspace root, pak `/db/reminders.sqlite`. Se 3 levely míří cesta mimo (`.../workspace/skills/`) a DB se vytvoří/hledá na špatném místě. Override přes env `REMIND_DB`.
|
||||
|
||||
**jobs.json se nepersistuje přes restart agenta:** Změny v `cron/jobs.json` provedené agentem přes `edit_file` tool se mohou ztratit po restartu service (nanobot drží jobs v paměti a přepisuje soubor). Bezpečnější: editovat Python in-place přes SSH + ihned restartovat service.
|
||||
|
||||
**Identita reminderu = `text`; per-entry `id` zvážen a zavržen:** Reminder nemá ID — identitou je `text` na dvou místech: `remove --keyword` (substring match na `text`) a dedup v `remind_send.py` (klíč `sha1(text)[:8]` ve `.reminder_state.json`). Zvažováno přidat unikátní `id` do každého YAML záznamu — **pro běžné použití nepřináší nic** (mazání řídí konverzačně LLM přes `list`→keyword, ID by jen přidalo krok navíc; YAML se stejně needituje ručně). **Jediný reálný zisk = scénář duplicitních textů**, kde dnes mašinérie selhává (viz gotcha níže). Pokud by duplicity byly potřeba: buď interní dedup klíč `sha1(text+schedule)` (nula změn v UX/YAML, vyřeší jen dedup), nebo plné `id` (vyřeší i mazání, ale list-then-remove flow + úpravy example.yaml/testů). Levnější alternativa bez ID: zakázat duplicitní `text` při `add`. Plný rozbor: history 2026-06-02 „Remind skill: per-entry ID".
|
||||
**Identita reminderu = `id` (SQLite autoincrement).** `text` slouží jako lidský klíč pro `--keyword` (substring match), ale nejednoznačné/duplicitní texty se řeší `--id` (z `list` nebo z `ambiguous` chyby, která ids vypíše). Dedup je per `reminder_id`+`schedule_id`, takže i stejné texty se odpalují nezávisle. (Historie: per-entry ID bylo nad YAML zvažováno a zavrženo — history 2026-06-02; migrace na SQLite ho zavedla nativně.)
|
||||
|
||||
**Gotcha — duplicitní text rozbíjí remove i dedup:** Create Workflow v `SKILL.md` duplicity *výslovně připouští* („Ask whether they really want a duplicate"), ale zbytek je neumí: (1) `remove --keyword` na dvou stejných textech vrátí `ambiguous` a nejde je rozlišit — keyword je vždy stejný; (2) dedup klíč `sha1(text)` je pro oba záznamy stejný → `fresh[key]` se v `remind_send.py` přepisuje, takže za určité konstelace časů jeden odpal potlačí druhý. Tj. skill duplicity povolí, ale neumí je ani smazat, ani spolehlivě odpálit.
|
||||
### Vyřešené chyby
|
||||
|
||||
**schedule_type collision (fix 2026-06-10):** *Problém* — `reminder_fires.schedule_type` se pro cron/random zapisoval špatně (`'at'`) a dedup pro ně nefungoval (maskovala jen 60s tolerance). *Příčina* — `schedule_{at,cron,random}` mají každá vlastní AUTOINCREMENT id (překryv 1..N); `remind_send.main()` odvozoval typ přes `SELECT … UNION ALL …` a bral první shodu, vždy `'at'`. *Fix* — každá `_due_*` vrací svůj `schedule_type`, inference smazána. Reálně potvrzeno na prod (reminder 7 „Panama" random odpal zapsán jako `'at'`). Test `test_schedule_type_correct_despite_id_collision`. Plný záznam: history 2026-06-10.
|
||||
|
||||
**Duplicitní text (vyřešeno migrací na SQLite):** Starý YAML systém duplicity neuměl ani smazat (`remove --keyword` → `ambiguous` bez rozlišení), ani spolehlivě odpálit (dedup `sha1(text)` se přepisoval). SQLite to řeší: dedup per `reminder_id` (nezávislý odpal) + `--id` selektor (jednoznačné mazání/edit). Starý rozbor: history 2026-06-02.
|
||||
|
||||
## Postup: přidání nového modelu (preset)
|
||||
|
||||
@@ -578,6 +586,28 @@ Nastaveno 2026-06-02 per-preset na reálné limity modelů (kimi-k2.6 / qwen3.5
|
||||
|
||||
---
|
||||
|
||||
## bwrap sandbox — limity na bare-metal a jak to funguje v Dockeru
|
||||
|
||||
**bwrap bind mounty jsou hardcoded** v `nanobot/agent/tools/sandbox.py` — žádná config volba pro přidání vlastních cest neexistuje. Mountuje se pouze: `/usr` (ro), `/bin`, `/lib`, `/lib64`, `/etc/...` (ro-bind-try), `/tmp` (tmpfs, ephemeral), workspace (rw), media dir (ro). Cesty mimo tyto lokace jsou v sandboxu neviditelné.
|
||||
|
||||
**Zamýšlený deployment je Docker** — base image `ghcr.io/astral-sh/uv:python3.12-bookworm-slim` má `uv` i `python` system-wide pod `/usr`; Node.js 20 se instaluje přes `apt` — taky do `/usr`. V kontejneru tedy vše funguje, protože nástroje jsou tam, kde bwrap mountuje.
|
||||
|
||||
**Na bare-metal to nefunguje** — `uv` je v `~/.local/bin/uv`, Node/npm v `~/.nvm/.../bin/` — oboje mimo bind mounty. `pathAppend` situaci nevyřeší: přidá cestu do PATH, ale bwrap ten adresář do sandboxu vůbec nenabinduje.
|
||||
|
||||
**Zapisovatelné lokace uvnitř sandboxu:** pouze workspace (persistentní) a `/tmp` (smaže se po příkazu). Cachové a datové adresáře `uv` (`~/.cache/uv`, `~/.local/share/uv`) jsou nedostupné — i kdyby byl `uv` system-wide, stahované balíčky by padaly nebo šly do `/tmp` a mizely.
|
||||
|
||||
**Možná řešení (bez modifikace zdrojáků nanobotu):**
|
||||
- Symlinky / kopie binárky do `/usr/local/bin/` (ekvivalent Docker image)
|
||||
- bwrap wrapper skript (nahradí `/usr/bin/bwrap` shellem, který přidá extra `--ro-bind-try` argumenty před předáním volání dál) — funkční, ale ovlivní všechna `bwrap` volání na systému
|
||||
|
||||
**Žádný jiný sandbox backend než `bwrap` neexistuje** — `_BACKENDS = {"bwrap": _bwrap}`, alternativa je jen `"sandbox": ""` (bez sandboxu).
|
||||
|
||||
**Nanobot wiki (nanobot.wiki/docs/0.2.0/) vrací 403** — není veřejně přístupná bez přihlášení.
|
||||
|
||||
Zdroj: `nanobot/agent/tools/sandbox.py`, `Dockerfile` (ověřeno 2026-06-05).
|
||||
|
||||
---
|
||||
|
||||
## Rozpad input contextu (co zabírá tokeny každý tah)
|
||||
|
||||
Detailní naměřený rozpad ~15k baseline input contextu (system prompt po částech + tool definitions 18 toolů) je v samostatném souboru [`tokens-explain.md`](tokens-explain.md) — k 0.2.1, preset glm-5.1. Stručně: ~8,7k system prompt (největší `MEMORY.md`, `skills_section`, `SOUL.md`), ~5,2k tool defs, zbytek session zprávy.
|
||||
@@ -625,7 +655,7 @@ Test 5 levných OpenRouter modelů (cena $/M tok in/out), gate=3 na ollam(ě) se
|
||||
|
||||
| Model | Cena | Úspěšnost | Wall median / avg | out tok |
|
||||
|---|---|---|---|---|
|
||||
| `mistralai/mistral-small-3.2-24b-instruct` | 0.075/0.20 | 17/17 | **934 / 1050 ms** | 610 |
|
||||
| `mistralai/mistral-small-3.2-24b-instruct` | 0.075/0.20 | 17/17 | **934 / 1050 ms** ⚠️ | 610 |
|
||||
| `google/gemma-3-27b-it` | 0.08/0.16 | 17/17 | 1413 / 1611 ms | 608 |
|
||||
| `z-ai/glm-4-32b` | 0.10/0.10 | 16/17* | 1905 / 2072 ms | 519 |
|
||||
| `qwen/qwen3-30b-a3b-instruct-2507` | 0.043/0.17 | 16/17* | 1991 / 1866 ms | 588 |
|
||||
@@ -633,7 +663,19 @@ Test 5 levných OpenRouter modelů (cena $/M tok in/out), gate=3 na ollam(ě) se
|
||||
|
||||
\* false negative (slovosled `se protáhnout`/`protáhnout se`, resp. `pečení`/`pečeni`).
|
||||
|
||||
**Závěr:** **`mistral-small-3.2` má nejlepší poměr ze všech dosud měřených** — 934 ms median (rychlejší než glm-5.1 1690 ms i haiku 1064 ms), 17/17, out 610 tok, cena pakatel. Evropská/česká stopa Mistralu se potvrdila. `gemma-3-27b` těsně za ním. **`gpt-oss-120b` je jediný propadák — reasoning → 7 s a 6× víc out tokenů**, gpt-5.4-nano pokrývá OpenAI rychleji. Potvrzení teze o thinkingu: `qwen3-30b-a3b-instruct` 1991 ms vs cloud `qwen3.5` (thinking) 10852 ms + out=22671 — past byl režim thinking, ne qwen. Plný záznam: history 2026-06-03 „MiniLoop levné OSS".
|
||||
⚠️ **`mistral-small-3.2` — naměřeno s cache.** Opakovaný test (2026-06-03) ukázal reálné časy 3 400–7 400 ms na prvních 4 příkladech, tj. ~3–8× horší než výsledek výše. Původní 934 ms zřejmě těžilo z cache providera. Skutečná cold performance je cca 4–5 s median.
|
||||
|
||||
**Závěr:** `gemma-3-27b` je spolehlivý OpenRouter kandidát (17/17, 1413 ms). `gpt-oss-120b` propadák — reasoning → 7 s a 6× víc out tokenů. Potvrzení teze o thinkingu: `qwen3-30b-a3b-instruct` 1991 ms vs cloud `qwen3.5` (thinking) 10852 ms + out=22671 — past byl režim thinking, ne qwen. Plný záznam: history 2026-06-03 „MiniLoop levné OSS".
|
||||
|
||||
### gemini-flash-lite — nový rekordman OpenRouter (změřeno 2026-06-03)
|
||||
|
||||
`google/gemini-3.1-flash-lite` na OpenRouteru:
|
||||
|
||||
| Model | Cena | Úspěšnost | Wall median / avg | Tokeny in / out |
|
||||
|---|---|---|---|---|
|
||||
| `google/gemini-3.1-flash-lite` | velmi nízká | **17/17** | **683 / 719 ms** | 23285 / 559 |
|
||||
|
||||
**Nejlepší výsledek ze všech dosud měřených modelů** — 683 ms median, 17/17, out pouze 559 tok (přímý parse bez reasoningu). Poráží glm-5.1-ollama (1690 ms), haiku-4.5 (1064 ms) i gemma4:e4b local (1136 ms). Plný záznam: history 2026-06-03 „MiniLoop gemini-flash-lite a mistral-small-3.2 cache".
|
||||
|
||||
### Malé ollama modely — ministral-3, nemotron-3-nano (změřeno 2026-06-03)
|
||||
|
||||
@@ -646,6 +688,27 @@ Test 5 levných OpenRouter modelů (cena $/M tok in/out), gate=3 na ollam(ě) se
|
||||
|
||||
**Ani jeden nepřekonal mistral-small-3.2 — oba zavrženy.** `ministral-3-8b` udělal **skutečnou chybu dne v týdnu** (`každý pátek` → cron `* * 6` sobota místo `* * 5`) — u připomínek vážné, na 8b je to znát; přitom **není ani rychlejší** než mistral-small (1043 vs 934 ms). `nemotron-3-nano-30b` má **reasoning sklony (out=7805 tok, ~12× víc než mistral)**, je 2× pomalejší a jeho jediný FAIL byl rozsekání `1,3,5` na tři cron výrazy (rozvrh ekvivalentní, formát ne). Závěr: pod ~24b instruct (mistral-small, gemma-3-27b) klesá spolehlivost cronu a malé „nano" modely buď chybují, nebo zbytečně reasonují. Plný záznam: history 2026-06-03 „MiniLoop ministral/nemotron-nano".
|
||||
|
||||
### Lokální gemma4:e4b (změřeno 2026-06-03)
|
||||
|
||||
`gemma4:e4b` (8B, 8 GB) — model stažený přímo na nvidia.hell, žádný cloud, žádné náklady:
|
||||
|
||||
| Model | Úspěšnost | Wall median / avg | out tok |
|
||||
|---|---|---|---|
|
||||
| `gemma4:e4b` (lokální) | **17/17** | **1136 / 1719 ms** | 1530 |
|
||||
|
||||
**Překvapivě dobré výsledky pro lokální 8B model.** Median 1136 ms je rychlejší než glm-5.1 cloud (1690 ms) a blízko gemma-3-27b-it na OpenRouteru (1413 ms). Vysoký avg (1719 ms) oproti mediánu (1136 ms) = odlehlé hodnoty u složitějších vstupů (random/multiple times, 3–4 s). Žádné skutečné chyby, žádný reasoning. out=1530 tok je 2,5× více než mistral-small (610), ale bez reasoningu — model prostě verbosněji okomentuje. **Nejlepší dosud změřený čistě lokální model.** Plný záznam: history 2026-06-03 „MiniLoop gemma4:e4b local".
|
||||
|
||||
### Zamítnuté lokální modely
|
||||
|
||||
| Model | Důvod zamítnutí | Median | Úspěšnost |
|
||||
|---|---|---|---|
|
||||
| `phi4:latest` (14.7B) | 2 skutečné chyby: `příští pondělí` → `06-05` (čtvrtek!), `dopoledne` → `09:00` místo `08:00`. Navíc pomalejší než gemma4:e4b | 1744 / 1676 ms | 15/17 |
|
||||
| `codestral:22b` | Pomalý (2483 ms) + skutečná chyba data: `příští pondělí` → `06-07` (neděle) místo `06-08` | 2483 / 2621 ms | 16/17 |
|
||||
| `ministral-3:8b-cloud` | Skutečná chyba weekday v cronu (`pátek` → cron `* * 6` = sobota) | 1043 / 1169 ms | 15/17 |
|
||||
| `nemotron-3-nano:30b-cloud` | Reasoning sklon (out=7805 tok), 2× pomalejší než mistral-small | 2015 / 2277 ms | 16/17 |
|
||||
|
||||
---
|
||||
|
||||
## Rychlost: glm-5.1 vs minimax-m3 (Ollama nativní streaming, 2026-06-07)
|
||||
|
||||
Měřeno přímo proti Ollamě na `nvidia.hell` (stejný endpoint jako nanobot), streaming `/api/chat`, identický `/remind list` prompt, 3 běhy/model. **`:cloud` modely nevracejí sub-durations** (`eval_duration` ap. = `None`) — tok/s nutno měřit přes streaming (TTFT = čas 1. content chunku).
|
||||
|
||||
Reference in New Issue
Block a user