provozni zaloha
This commit is contained in:
@@ -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