70 KiB
Knowledge
Ověřená fakta o vnitřním fungování nanobota. Stručně, s případným odkazem na zdroj pokud je to oprvdu podstatné.
uv na serveru není v non-interaktivním SSH PATH
ssh nanobot@nanobot.hell "uv run ..." selže s uv: command not found — non-login shell nemá ~/.local/bin v PATH. Plná cesta je /home/nanobot/.local/bin/uv. Cron skilly to obcházejí shebangem #!/usr/bin/env -S uv run --script. Zdroj: nasazení remind upcoming 2026-06-10 (history.md).
Kdy je a není potřeba restart nanobot.service
Restart NENÍ potřeba:
| Soubor | Proč |
|---|---|
~/.nanobot/workspace/cron/jobs.json |
Cron service volá _load_store() při každém ticku — soubor se načte znovu automaticky |
~/.nanobot/workspace/reminder.yaml |
Čte ho remind_check.py jako subprocess; každé spuštění čte čerstvě |
Skripty v workspace/skills/ |
Exec tool je spouští jako subprocess pokaždé znovu |
~/.nanobot/config.json — providers a modelPresets |
_refresh_provider_snapshot() volá load_config() před každým agentem tahem; /model přepínač funguje okamžitě |
Restart JE potřeba:
| Soubor / změna | Proč |
|---|---|
~/.nanobot/config.json — channels, tools, MCP servery, workspace |
Tyto sekce se předávají do AgentLoop.from_config() jednou při startu |
~/.config/systemd/user/nanobot.service |
Po změně: daemon-reload + restart |
Zdroj: nanobot/agent/loop.py:_refresh_provider_snapshot(), nanobot/cron/service.py:_load_store(), nanobot/providers/factory.py:load_provider_snapshot()
Restart nanobot.service jako root
systemctl --user restart nanobot.service jako root selže — user bus není dostupný bez správných env proměnných. Správný postup:
su - nanobot -s /bin/bash -c '
XDG_RUNTIME_DIR=/run/user/$(id -u nanobot)
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u nanobot)/bus
systemctl --user restart nanobot.service
'
Pozor: kill -HUP <pid> na gateway proces nanobot nezrestartuje — proces se ukončí a systemd ho nenaskočí zpět (není to watchdog). Místo HUP vždy používej systemctl --user restart.
Cron joby nanobota: jobs.json
Naplánované joby jsou v /home/nanobot/.nanobot/workspace/cron/jobs.json. Struktura: pole jobs, každý má id, schedule (kind=cron/every/at s expr/every_ms/at_ms, volitelně tz), payload (kind=agent_turn, message, channel, to, channelMeta, deliver), volitelně deleteAfterRun (true pro at joby = jednorázové).
Změna se projeví bez restartu — cron service volá _load_store() při každém ticku (nanobot/cron/service.py:394), jobs.json se čte čerstvě. Hot reload tedy funguje out-of-box.
Editace: Python in-place editor přes SSH, např.:
ssh root@nanobot.hell "python3 -c \"
import json; from pathlib import Path
p = Path('/home/nanobot/.nanobot/workspace/cron/jobs.json')
data = json.loads(p.read_text())
# ... uprav data ...
p.write_text(json.dumps(data, ensure_ascii=False, indent=2))
\""
Cron job s LLM agentem je nespolehlivý pro "pošli jen když něco je"
Nanobot cron job vždy běží přes agenta (agent.process_direct) — neagentní typ jobu neexistuje. Dva problémy v cestě prázdného výstupu:
- Prompt je obalený natvrdo v kódu.
nanobot/cli/commands.py:on_cron_jobpřilepí předpayload.messagefixní"The scheduled time has arrived. Deliver this reminder to the user now…". Tvoje "exit silently" instrukce je s tím v konfliktu → agent improvizuje meta-odpověď ("Output was empty…"). evaluate_responseje fail-open.nanobot/utils/evaluator.pyrozhoduje o doručení druhým LLM callem; při chybě / chybějícím tool-callu vracíTrue(doruč). Slabší modely často"no tool call returned, defaulting to notify"→ meta-odpověď propadne na Telegram. Proto únik jen "sem tam" a pokaždé jinak formulovaný.
Zamítnuto: pouhá úprava promptu na "exit silently" (nestačí — viz body 1+2).
Fix: doručování úplně mimo agenta — viz /remind skill níže (system crontab + přímé Bot API).
Plný rozbor: history 2026-05-27 "Spam Output was empty".
Workspace vzniká při prvním spuštění agenta
~/.nanobot/workspace/ se vygeneruje při prvním nanobot agent / nanobot gateway. Obsahuje AGENTS.md, USER.md, SOUL.md, HEARTBEAT.md, TOOLS.md, memory/, git store.
Co se auto-loaduje do system promptu (verze 0.2.0)
Každý tah ContextBuilder skládá system prompt z těchto zdrojů (žádná cache, fresh read_text()):
- Bootstrap files v rootu
~/.nanobot/workspace/:AGENTS.md,SOUL.md,USER.md,TOOLS.md. Po editaci není potřeba restart service — změna platí od příští zprávy.- Zdroj:
nanobot/agent/context.py:25(BOOTSTRAP_FILES),context.py:156(_load_bootstrap_files).
- Zdroj:
memory/MEMORY.md— hardcoded cesta vMemoryStore. Žádný jiný soubor vmemory/se NEčte (ani.bak, ani user-vytvořené.md).history.jsonlkonzumuje výhradně Dream procesor.- Zdroj:
nanobot/agent/memory.py:55(memory_file = memory_dir / "MEMORY.md"),memory.py:205,229.
- Zdroj:
- Skilly s
metadata.always: trueve frontmatteruworkspace/skills/<name>/SKILL.md— přesSkillsLoader.get_always_skills(). Ostatní skilly se nahrávají on-demand, ne do system promptu.- Zdroj:
nanobot/agent/skills.py:203.
- Zdroj:
HEARTBEAT.md není v system promptu každého tahu — má vlastní mechanismus přes heartbeat/service.py, čte se jen na heartbeat tick (default 30 min).
Důsledek: Když agent v chatu vytvoří soubor v memory/ mimo MEMORY.md (např. memory/film_policy.md), tváří se jako že si pravidlo „uložil", ale agent ho v dalším tahu neuvidí. Místo toho ho musí jít do bootstrap souboru — viz následující sekce.
K čemu slouží jednotlivé workspace soubory
| Soubor | Doména | Co tam patří | Co tam nepatří |
|---|---|---|---|
SOUL.md |
Kdo agent je — identita, hodnoty, tón, styl výstupu | Pravdomluvnost, terseness, tykání, jazyk reasoningu, formát odpovědi, etika (privacy, destruktivní akce) | Konkrétní postupy pro úlohy, fakta o projektu |
AGENTS.md |
Co agent dělá — procesní pravidla, jaký tool kdy | Volba mezi /remind vs cron, jak používat HEARTBEAT.md, varování typu „nepiš reminder do MEMORY.md" |
Identita, hodnoty, fakta o uživateli |
USER.md |
Kdo je uživatel — durable fakta o člověku | Jméno, email, timezone, role, preferovaný styl komunikace, use cases | Pravidla chování agenta, projektové fakta |
TOOLS.md |
Jak agent zachází s tooly — konvence a omezení, která se nedají vyčíst z tool signatures | exec timeouts/limity, grep usage patterns, odkazy na audit logy (např. log/reminder.log) |
Globální chování (to je SOUL), procesní pravidla (to je AGENTS) |
memory/MEMORY.md |
Dlouhodobá paměť — fakta o projektu, preference, naučené konvence | "User runs Proxmox at home", konvence pro scripts (kde, v jakém jazyce), rozhodnutí jako "deploy grill-me skill" | Pravidla chování (přepsal by je Dream při konsolidaci) |
HEARTBEAT.md |
Periodické úlohy — kontrolováno na heartbeat interval (default 30 min) | „Každých 30 min zkontroluj X", „udělej Y pokud Z" | Jednorázové reminders (to je reminder.yaml přes /remind) |
Test umístění (rozhoduj podle otázky, ne podle obsahu pravidla): „Mění to kdo jsem (SOUL) / co dělám (AGENTS) / kdo je uživatel (USER) / jak používám tool (TOOLS) / co vím o projektu (MEMORY) / co dělám pravidelně (HEARTBEAT)?"
Zdroj: upstream nanobot/templates/{AGENTS,SOUL,USER}.md (header docstrings), nanobot/agent/context.py, nanobot/agent/memory.py, nanobot/heartbeat/service.py.
Ollama provider potřebuje /v1 suffix v apiBase
Nanobot volá OpenAI-kompatibilní /v1/chat/completions, ne Ollama-native /api/chat. V configu musí být apiBase: http://host:11434/v1 — bez /v1 vrací Ollama 404.
docs/configuration.md to v příkladu (http://localhost:11434) neuvádí — je to zavádějící.
modelPresets = jeden agent, víc modelů
Nanobot nepodporuje víc pojmenovaných agentů. Místo toho má modelPresets — pojmenované dvojice (provider, model), mezi kterými se přepíná za běhu příkazem /model <preset> v chatu (Telegram i WebUI). Default je agents.defaults.modelPreset.
Gateway s websocket.host: 0.0.0.0 bez tokenu odmítne start
Bezpečnostní pojistka — pokud má WebSocket channel host: 0.0.0.0 (bind všech rozhraní), vyžaduje vyplněný token. Jinak gateway selže při startu.
Porty gateway
| Port | Co tam je |
|---|---|
| 8765 | WebUI HTML (SPA) + WebSocket auth endpoint na stejném portu |
| 18790 | Gateway health endpoint (/health → {"status":"ok"}) |
CLI chat mód: nanobot agent
Interaktivní konverzace s agentem přímo v terminálu se spouští příkazem nanobot agent. Je to stejný agent jako přes Telegram/WebUI a sahá do stejného ~/.nanobot/workspace/ (sdílí paměť, bootstrap soubory i git store). Fungují v něm i slash-příkazy (/model <preset>, /restart, /history, /status, /goal, …).
Přehled CLI módů: nanobot onboard (setup wizard), nanobot agent (chat v terminálu), nanobot gateway (WebSocket gateway pro WebUI/Telegram).
Zdroj: upstream HKUDS/nanobot Quick Start („3. Chat: nanobot agent").
/model bez argumentu vypíše dostupné presety
V chatu (CLI nanobot agent / Telegram / WebUI) napsání samotného /model (bez argumentu) vypíše status: aktuální model, aktuální preset a seznam dostupných presetů. /model <preset> přepne. Stejný seznam se ukáže i při pokusu přepnout na neexistující preset.
Seznam ukazuje nakonfigurované modelPresets z ~/.nanobot/config.json, ne katalog modelů, co provider reálně nabízí (na to viz Ollama …/api/tags, OpenRouter …/api/v1/models). OpenAI-kompatibilní endpoint /v1/models vrací taktéž jen presety.
Zdroj: nanobot/command/builtin.py (cmd_model, _model_command_status).
Telegram bot commands — /new resetuje session, /restart ne
V Telegramu jsou slash-příkazy zaregistrované jako BotCommand (objeví se v menu po stisku / v inputu). Nejsou to volné texty pro agenta — regex router (_forward_command) je posílá rovnou do AgentLoop, agent je v promptu nevidí.
| Příkaz | Co dělá |
|---|---|
/new |
Reset session. Zruší aktivní task, vyprázdní zprávy v sessionu, snapshot pošle do Consolidatoru na archivaci na pozadí. Tohle je „clear context" před novou diskuzí. |
/restart |
Restartuje bota (proces), ne session — po restartu konverzace pokračuje. Slouží k načtení nové konfigurace, ne k čistění kontextu. |
/stop |
Zruší aktuálně běžící task, kontext nechá. |
/history |
Vypíše posledních N zpráv (read-only). |
/status, /goal, /pairing, /model, /dream, /dream_log, /dream_restore, /help |
Ostatní registrované commands. |
Pozn. k aliasům: Telegram nepovoluje pomlčku v command jménu, takže /dream_log a /dream_restore jsou aliasy — handler je interně přemapuje na kanonické /dream-log a /dream-restore (_normalize_telegram_command).
Zdroj: nanobot/channels/telegram.py:258-326 (BotCommand registrace, regex router, alias normalizace), nanobot/command/builtin.py:199 (cmd_new — session.clear() + background consolidator.archive(snapshot)).
/remind skill — architektura a gotchas
Ú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.
Nesahej na to přes cron tool: nikdy nevytvářet remind-check job v cron/jobs.json. Doručování řeší crontab mimo nanobot.
Nevytvářet ani agentní delivery skill (např. deliver-reminder-notifications, který by exec-em volal nějaký remind_check.py). Žádný takový skript v remind/scripts/ není — je tam jen remind_send.py volaný cronem. Migrace na deterministické doručování ho udělala zbytečným. Pokud takový skill ve workspace/skills/ najdeš, je to mrtvý zbytek a smaž ho.
Telegram: /remind text je bot command, nedojde k agentovi jako text. Psát přirozeně: připomeň mi..., nastav připomínku...
Read-back doručených („co dnes přišlo?") = subcommand delivered, ne čtení logu. remind_cli.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.
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_cli.py (deterministické CLI, SQLite transakce + validace cron/at/random), nikdy přímý DB nebo edit_file. Volat uv run skills/remind/scripts/remind_cli.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). Oprava/změna textu = edit --id <n> --text "…" (id z list), NIKDY remove+add (ztratil by se delivery history a změnilo id) — SKILL.md k tomu agenta explicitně navádí. Plný výčet flagů žije v argparse, ne v SKILL.md (hybrid od 2026-06-10): remind_cli.py <cmd> --help (rename z remind_edit.py, history 2026-06-10).
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_cli.py jde přes stejný compute_fire_times. (random_times.py taky drží sdílené minutes_to_hhmm — dřív duplikované v cli i send.) Testy: uv run --with pytest pytest skills/remind/tests/. Návrh: plans/remind-random-time.md.
Týdenní náhodný režim (period='week', od 2026-06-15): --random-times-per-week N (mutually exclusive s --random-times-per-day) rozprostře N odpálení přes týden Po–Ne na N různých náhodných dnů, 1 čas na den uvnitř okna. Engine zůstává per-day volaný — _weekly_fire_times spočítá celý týdenní plán deterministicky ze seedu f"{pondělí}|{text}|week" (ne ze dne) a vrátí jen fires daného dne, takže daemon i forecast nemění iterační logiku. Sloupec schedule_random.period ('day'/'week', default 'day') + idempotentní _migrate(conn) v db.get_db (PRAGMA-guarded ALTER TABLE ADD COLUMN, protože init_db na živou DB nesahá). count > dostupné dny se clampuje (hraniční týden u from/until), count > kapacita filtru raisuje. Detaily: history 2026-06-15.
Display IDs (autonomní serverová feature, do repa dotaženo 2026-06-15): list/upcoming ukazují 1-based pozici mezi aktivními remindery (#1, #2…, počítáno on-the-fly v _active_display_order), ne interní DB id. --id i chybové hlášky ({"error":"no match","display_id":n}, ambiguous s display_id) pracují s display ID. Přečíslovává se po každém remove → při nejistotě vždy nejdřív list. Interní id se uživateli nikdy neukazuje. (Objeveno při syncu před weekly deployem — feature byla na serveru, ne v repu; pravděpodobně Dream procesor. history 2026-06-15.)
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.
DB_PATH path (gotcha): V remind_send.py/remind_cli.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 = id (SQLite autoincrement). text slouží jako lidský klíč pro --keyword (substring match); jednoznačný výběr přes --id. Dedup odpalů je per reminder_id+schedule_id. (Historie: per-entry ID bylo nad YAML zvažováno a zavrženo — history 2026-06-02; migrace na SQLite ho zavedla nativně.)
Aktivní texty jsou unikátní (guard od 2026-07-06). add i edit --text odmítnou duplicitní text aktivního reminderu (find_active_by_exact_text, casefold vč. diakritiky) chybou {"error":"duplicate text", display_id, hint} (exit 1). Důvod: agent občas rozdělil „jeden text, víc časů" do víc add volání místo jednoho add s opakovanými --at/--cron (doloženo: UFO burger id=46/47). Jeden text = jeden reminder s víc schedule. --id/ambiguous disambiguace zůstává — substring keyword může být nejednoznačný napříč různými texty a historické dupy (před guardem) v DB pořád jsou. Plný záznam: history 2026-07-06.
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)
Modely se přidávají jako položky do modelPresets v ~/.nanobot/config.json na serveru nanobot.hell (uživatel nanobot).
Kroky:
-
Ověř dostupnost u providera. Pro Ollama:
curl http://nvidia.hell:11434/api/tagsa zkontroluj, že název modelu (přesně, včetně:cloudsuffixu) je v seznamu. Pro OpenRouter:curl https://openrouter.ai/api/v1/models. -
Edituj config in-place přes Python (zachová ostatní klíče včetně secrets):
ssh nanobot@nanobot.hell 'python3 -c " import json, pathlib p = pathlib.Path.home() / \".nanobot/config.json\" c = json.loads(p.read_text()) c[\"modelPresets\"][\"<preset-name>\"] = {\"provider\": \"<ollama|openrouter>\", \"model\": \"<model-id>\"} p.write_text(json.dumps(c, indent=2)) "' -
Restartuj službu, aby gateway preset načetla:
ssh nanobot@nanobot.hell 'XDG_RUNTIME_DIR=/run/user/1000 systemctl --user restart nanobot.service' -
V chatu (Telegram/WebUI) přepneš příkazem
/model <preset-name>.
Konvence pojmenování presetů: <model-zkratka>-<provider> (např. kimi-k2.6-openrouter, glm-5.1-ollama). Suffix providera je důležitý — uživatel chce v názvu vidět, odkud model jede.
Ollama gotcha: providers.ollama.apiBase musí končit /v1 (http://nvidia.hell:11434/v1) — viz [[Ollama provider potřebuje /v1 suffix v apiBase]].
Logování: gateway -v/--verbose, agent --logs
Nanobot defaultně vypíná vlastní logy (logger.disable("nanobot")), proto v běžném výstupu nic není. Zapínají se podle příkazu:
nanobot gateway -v/--verbose→ INFO+DEBUG do stderr (u nás přes systemd do journalu). Pokrývá i WebUI — WebUI je jenwebsocketchannel uvnitř gateway procesu (port 8765), není to samostatná služba, takže žádný separátní přepínač pro WebUI neexistuje.nanobot agent --logs/--no-logs→ runtime log přímo v interaktivním CLI chatu. Jiný flag než gateway, nejsou zaměnitelné.
Žádná env proměnná ani config klíč pro log level mimo tyhle flagy neexistuje.
Nasazení u nás: -v přidáno do ExecStart v ~/.config/systemd/user/nanobot.service na nanobot.hell. Logy živě: ssh nanobot@nanobot.hell 'journalctl --user -u nanobot.service -f --no-pager'.
Co -v ukáže v jednom tahu (ověřeno na WebUI zprávě):
Processing message from <channel>:<id>: <text>— příchozí zpráva- stavy agentního tahu:
RESTORE → COMPACT → COMMAND → BUILD → RUN → SAVE → RESPOND(každý s časem) Tool call: <nástroj>({...args...})— volání toolu i s argumenty (INFO)LLM usage: prompt=… completion=… cached=…— spotřeba tokenů každé iterace agentní smyčkyResponse to <channel>:<id>: <text>— finální odpověď
Co se NEloguje: tělo tool výsledku (stdout), plné LLM zprávy ani thinking. Thinking jde samostatným kanálem do klienta (WebUI), ne do journalu. -v je serverová záležitost — ve WebUI se nic nezmění.
Pozor: -v zapíná INFO+DEBUG globálně, takže v journalu jsou i heartbeat/cron/dream tahy.
Startup taky vypíše užitečné: Registered N tools: [...] (výčet dostupných toolů) a Runtime model switched … <model> (aktivní preset).
Srovnání modelů pro nanobot (cloud inference)
Hodnoceno pro mix: agentní úlohy (tool use, Dream, skilly) + rychlost + Python. Platí pro cloud Ollama i OpenRouter — hardwarové podmínky jsou srovnatelné. Provider-agnostic pohled (předpokládá dostupnost rychlé inference).
Za podmínky Ollama Cloud (žádný rychlý provider) to upřesňuje
models.md— tam rozhoduje latence, takže pro interaktivní vrstvu vede GLM-5.1, ne Kimi. Tahle tabulka amodels.mdse nerozcházejí v datech, jen v východisku: provider-agnostic vs. fixní Ollama Cloud.
| Pořadí | Model | Proč |
|---|---|---|
| 1 | Kimi K2 (kimi-k2.6-*) |
Jediný explicitně trénovaný na agentní úlohy a tool use; MoE ~32B aktivních params = rychlý |
| 2 | Qwen 3.6+ (qwen-3.6-plus-openrouter) |
Pravděpodobně Qwen3 235B-A22B (~22B aktivních = nejrychlejší v seznamu); top coding, silné instruction following |
| 3 | DeepSeek V3.2 (deepseek-v3.2-ollama) |
Nejlepší Python, nejsilnější instruction following; ~37B aktivních; ideální pro Dream |
| 4 | Qwen 3.5 (qwen3.5-ollama) |
Solidní záloha, dobrý coding, rychlý |
| 5 | GLM-5.1 (glm-5.1-ollama) |
Dobrý model, ale za Kimi/Qwen/DeepSeek na všech osách |
| 6–7 | MiniMax M2 (obě varianty) | Nejméně prověřený pro agentic workload; rezerva pro speciální případy |
Prakticky: primary model → kimi-k2.6; Dream (pokud chceš jiný preset) → deepseek-v3.2 nebo qwen-3.6-plus.
Jak funguje nanobot skill systém
Skill = složka ~/.nanobot/workspace/skills/<name>/ se souborem SKILL.md (YAML frontmatter s name + description, tělo markdown instrukce). Bootstrap soubory se čtou při každém tahu bez restartu. Žádný install příkaz neexistuje — skill se vytvoří ručně (nebo ho Dream vytvoří sám).
Clawhub.ai / OpenClaw je jiný ekosystém, nemá s nanobotem nic společného. Skilly odtud je třeba manuálně adaptovat.
Claude Code skilly jsou přímo přenositelné. Anthropic Skills format (SKILL.md s YAML frontmatter name+description + markdown tělo) je identický s nanobot skill formátem. Stačí zkopírovat složku skills/<name>/ ze zdroje (např. plugin .claude-plugin/skills/<name>/) do ~/.nanobot/workspace/skills/<name>/ — žádná konverze. Manifest .claude-plugin/plugin.json se neinstaluje, je Claude-Code-specific. Pozor jen na (a) reference na Claude-Code tooly v těle skillu (TodoWrite, ExitPlanMode, AskUserQuestion apod. v nanobotovi neexistují), (b) prompt-injection v markdown těle — nanobot čte skill jako součást system contextu. Ověřeno: nasazen grill-me z mattpocock pluginu (history 2026-05-28 "Pilot mattpocock skillu grill-me").
Zdroj: nanobot/agent/skills/, ContextBuilder._load_bootstrap_files()
Skill description — k čemu reálně slouží (progressive loading)
Pole description ve frontmatteru non-always skillu je routing signál, ne kontext „jak skill funguje". Při sestavování system promptu se každý non-always skill vykreslí jako jeden řádek v seznamu: - **<name>** — <description> \cesta/k/SKILL.md`. Tělo SKILL.md se načte **až on-demand**, když si agent skill sám přečte přesread_file`. Důsledky:
descriptionje jediná info o skillu v promptu, dokud agent nečte tělo → patří tam jen kdy/proč skill spustit (trigger fráze, odlišení od příbuzných skillů), ne jak funguje.descriptionse nezkracuje (_get_skill_descriptionvrací text doslova) a je v promptu každý tah u všech skillů → trvalý token cost. Drž stručně, routing-orientovaně. Detailní postup patří do těla.- Always skilly (
metadata.nanobot.always: true):descriptionse ignoruje úplně, do promptu se eager vkládá celé tělo (bez frontmatteru). Druhý vysvětlující odstavec vdescriptionje u nich čistý šum.
Co tedy patří do description: jen kdy/proč skill spustit — krátká věta o účelu + trigger fráze + případné odlišení od příbuzného skillu. Nepatří tam jak skill funguje (to do těla, čte se on-demand) ani detailní postup. Triggery nemusí být dvojjazyčné — model rozpozná záměr napříč jazyky, takže explicitní CZ varianty nic nepřidají, jen prodlužují řádek (ověřeno na /plan, 2026-05-31).
Zdroj: nanobot/agent/skills.py:111-159 (build_skills_summary, _get_skill_description), skills.py:94-109 (load_skills_for_context, always skilly), nanobot/agent/context.py:87-95.
Dream procesor — automatické self-improvement
Nanobot má vestavěný Dream procesor (agent/memory.py:Dream) který běží každé 2 hodiny. Jde o dvou-fázový LLM pipeline nad history.jsonl:
- Fáze 1: Plain LLM call analyzuje historii, hledá fakta (
[MEMORY]/[USER]/[SOUL]), kandidáty na smazání ([FILE-REMOVE]), opakující se workflow ([SKILL]) - Fáze 2: AgentRunner s
read_file/edit_file/write_filetools provede chirurgické editace; umí sám vytvářet nové skilly (skills/<name>/SKILL.md)
Dream řeší: deuplikaci, detekci stale obsahu (git blame age na řádcích MEMORY.md), automatické git commity po změnách. Cursor v .dream_cursor zabraňuje přepracování.
Důsledek: Self-improving-agent skilly z jiných ekosystémů jsou z velké části redundantní — Dream pokrývá jejich core funkcionalitu nativně. Přidaná hodnota by byl jen okamžitý strukturovaný error log (ERR-YYYYMMDD-XXX formát) — Dream čeká 2h.
Zdroj: nanobot/agent/memory.py:Dream, prompt templates agent/dream_phase1.md, agent/dream_phase2.md
Non-interactive nanobot CLI: streamuje chaoticky, Python API vrací čistý string
nanobot agent --message "..." --session "..." projede agent loop, výstup
ale streamuje rozkouskovaně přes stdout (✻ prefixované delty
reasoning/progress, finální response.content až na úplném konci). I při
--no-markdown a pipe (| cat) jde streaming dál. Postprocesovat by bylo
křehké.
Pro programatické použití (daemon, skript) jdi přes Python API:
import asyncio
from nanobot import Nanobot
bot = Nanobot.from_config()
result = await bot.run("prompt", session_key="my:session")
# result.content je čistý string, žádné streamovací nečistoty
Interpreter s import nanobot: /home/nanobot/.local/share/uv/tools/nanobot-ai/bin/python.
Loguru jde na stderr (lze odchytit nebo přesměrovat). Nanobot.run interně volá
AgentLoop.process_direct bez cron preamble — to je jen v on_cron_job callbacku.
Zdroj: nanobot/cli/commands.py:1204-1231 (CLI), nanobot/nanobot.py:71-102 (Nanobot.run).
Agent vidí Channel a Chat ID v runtime contextu zprávy
ContextBuilder každý tah příchozí zprávy obaluje runtime context blokem,
ve kterém je Channel: <name> a Chat ID: <id> (kromě Current Time a
volitelně Sender ID). Skill nebo prompt si je tedy může přečíst —
nemusí mít vlastní tool ani contextvars přístup.
Channel: telegram
Chat ID: 8826147089
V CLI / SDK session bez channel kontextu se blok nezobrazí (Chat ID
chybí). Skill na to musí umět reagovat (např. detach v takovém
případě nabídne synchronní vykonání).
Zdroj: nanobot/agent/context.py:123-139 (ContextBuilder._build_runtime_context).
Cron preamble je hardcoded — pro non-reminder background úlohy obejít
nanobot/cli/commands.py:891-897 (on_cron_job) obaluje payload natvrdo:
The scheduled time has arrived. Deliver this reminder to the user now,
as a brief and natural message in their language. Speak directly to them —
do not narrate progress, summarize, include user IDs, or add status reports
like 'Done' or 'Reminded'.
Reminder: <payload.message>
Pro reminders je to správné chování. Pro background úlohy (deep research, ingest, multi-step research) je to v přímém rozporu — agent má provést úkol, zapsat výsledek do souboru, vrátit informativní větu. Preamble ho stáhne do meta-statusu.
Cesta okolo: zahodit cron tool i at jednorázové joby, orchestraci řešit
externím daemonem mimo agent loop — viz "Detach skill" níže. Stejný pattern
už používá /remind (viz "Cron job s LLM agentem je nespolehlivý…" výše).
Detach skill — background úlohy přes externí daemon (mimo agent loop)
Architektura podobná /remind — orchestrace mimo agent loop, žádný cron preamble.
Tok:
- Skill
detachv chatu →exec skills/detach/scripts/create-task.py --goal … --slug … --channel … --chat-id …. Skript vygeneruje timestamp + frontmatter, zajistí fronty (mkdir -p), atomicky zapíše dotasks/tmp/a přesune dotasks/inbox/(atomický rename, partial-write race neexistuje). Agent dělá jen LLM části (přeformulovat goal, vybrat slug, přečíst channel/chat_id z runtime contextu) — žádný ručníwrite_file/mv/date. - Systemd user unit
tasks-daemon.path(DirectoryNotEmpty=…/tasks/inbox) přes inotify spustítasks-daemon.service(Type=oneshot). tasks-daemon.py(Python, shebang na uv venv interpreter) projede inbox:mv → running/, parse frontmatter (chat_idpovinný), zavoláNanobot.from_config().run(goal, session_key=f"detach:<stem>")s 45-min timeoutem, appendne## Resultsekci,mv → done/nebofailed/, pošle Telegram zprávu přes Bot API (urllib + token z~/.nanobot/config.json["channels"]["telegram"]["token"]).
Volba modelu pro task (od 2026-06-07): Detach umí task spustit na explicitně zvoleném presetu (background = latence nebolí, vyplatí se silnější model). Uživatel model jen zmíní ve větě („na kimi") → agent předá token jako create-task.py --model "<token>" → skript ho při captue fuzzy-resolvne proti config.json (resolve_preset: exact case-insensitive → unikátní substring; jinak KeyError se seznamem, exit 1, fail-fast v chatu) a uloží přesný preset do frontmatteru model:. Bez --model jede default (agents.defaults.modelPreset). Daemon přečte fm["model"] a před run() přepne bot._loop.set_model_preset(preset) — stejný switch jako /model v chatu (ověřeno e2e s nainstalovaným balíčkem, history 2026-06-07). Gotcha: klíč presetů je v serverovém config.json na disku snake_case model_presets (ne camelCase modelPresets), zatímco agents.defaults.modelPreset je camelCase — load_preset_names() proto čte oba tvary.
Soubory:
~/.nanobot/workspace/skills/detach/SKILL.md— definice + triggery (EN-only)~/.nanobot/workspace/skills/detach/scripts/tasks_common.py— sdílené čisté helpery (TASKS, FILENAME_RE, parse_frontmatter, parse_kv, format_, build_task_), importují ho ostatní skripty~/.nanobot/workspace/skills/detach/scripts/create-task.py— capture skript (frontmatter + atomický tmp→inbox)~/.nanobot/workspace/skills/detach/scripts/{list-tasks,read-task}.py— list / read subactions~/.nanobot/workspace/skills/detach/tests/— pytest čisté logiky (lokálně v repu, ne na serveru)~/.nanobot/workspace/skills/detach/scripts/tasks-daemon.py— daemon~/.nanobot/workspace/skills/detach/systemd/tasks-daemon.{path,service}— user systemd unity (symlinkované do~/.config/systemd/user/)~/.nanobot/workspace/tasks/{tmp,inbox,running,done,failed}/— fronty~/.nanobot/workspace/log/tasks-daemon.{log,stdout.log,stderr.log}— append-only logy
Souběh: systemd serializuje (Type=oneshot se nespustí podruhé, dokud první běh trvá; level-triggered .path ho restartne po doběhu pokud inbox stále není prázdný). Žádný flock není potřeba.
Notifikační target — Telegram s fallback chat_id (single-user setup): Skill v frontmatteru zapíše channel + chat_id z runtime contextu (Channel: telegram → numeric ID, Channel: websocket → session UUID, atd.). Daemon resolve_telegram_chat_id(fm):
- pokud
channel == "telegram"→ použijchat_idz frontmatteru (multi-user ready) - jinak → čti
channels.telegram.allowFrom[0]z~/.nanobot/config.json
Tím Telegram vždy doručí, i když úkol přišel z WebUI / CLI. Daemon log: NOTIFY chat=<id> source=<frontmatter|fallback>. Bez tohoto fallbacku selhával Telegram Bot API s HTTP 400 pro non-telegram channel (history 2026-05-28 18:37).
Subactions list a read: detach skill umí i číst zpět hotové úkoly. „výsledky?" → markdown tabulka tasks/{running,done,failed}/. „výsledek " → read_file přes match v done/+failed/, předlož # Result sekci. Identifier match: slug substring (*foo*), timestamp fragment (*T175451*), nebo prázdný = nejnovější.
Zdroj: skills/detach/ v tracking repu, history 2026-05-28 „Skill detach + daemon" + iterace #2 + iterace #3.
uv-native invokace (iterace #3): Shebang přepnut na #!/usr/bin/env -S uv run --script s PEP 723 inline metadata (requires-python = ">=3.11", dependencies = ["nanobot-ai"]). uv run --script samo vytvoří/cachuje izolované venv — skript přežije uv tool uninstall/install i přesun na jiný stroj. První spuštění po PEP 723 změně trvá ~5-10s (budování venv), další jsou instantní (cache v ~/.cache/uv/). Systemd user unit musí mít Environment=PATH=%h/.local/bin:/usr/bin:/bin, jinak uv v PATH chybí.
Detach notifikace do origin kanálu (WebUI/CLI) — záměrně nepodporováno
Daemon notifikuje jen Telegram (přes Bot API, deterministicky). Když task přišel z WebUI nebo CLI, do toho kanálu se notifikace nepošle — uživatel si výsledek vyzvedne přes výsledek <slug> (detach subaction read).
Architektonický důvod: WebSocket spojení vlastní gateway proces; daemon je samostatný systemd oneshot. Nanobot nemá HTTP endpoint pro vstřikování zpráv do WS sessions (nanobot/channels/websocket.py:673-782 — všechny /api/sessions/... jsou read-only). Sdílí jen filesystem, žádné IPC.
Zvážené a zamítnuté možnosti:
- Samostatný
Nanobot.run()jen kvůli notifikaci — LLM jako IPC proxy. Pomalé (10–30 s), drahé, nedeterministické (model může prompt překroutit nebomessagetool nezavolat). Stejná třída problému jako Cron job s LLM agentem je nespolehlivý. - Přibalit
messagetool call k existujícímu agent turnu tasku — žádný extra LLM call, ale stále LLM-mediated; nepokrývá timeout/exception (agent se k toolu nedostane). - Patch upstream + nový HTTP endpoint na gatewayi — čisté řešení (daemon dělá prostý POST, žádný LLM), ale udržovat patch napříč upgrady
nanobot-ai. Pokud někdy ano, místo jenanobot/channels/websocket.py(přidat handler vedle stávajících/api/sessions/..., vytvořitOutboundMessage(channel="websocket", chat_id=..., content=...)abus.publish_outbound(msg)).
Rozhodnutí 2026-05-29: status quo — Telegram fallback stačí, výsledek <slug> je dokumentovaný způsob pro WebUI/CLI.
Skill exec běží z workspace rootu, ne ze skill adresáře
Když skill volá exec bez explicitního working_dir, příkaz běží s CWD = workspace root (~/.nanobot/workspace), ne v adresáři skillu. Cesty na skripty skillu proto musí být buď workspace-relativní (skills/<name>/scripts/x.py) nebo absolutní — skill-dir-relativní scripts/x.py se rozbije (resolvuje na workspace/scripts/x.py).
Zdroj: upstream nanobot/agent/tools/shell.py:148 (working_dir=ctx.workspace) + :370 (cwd = working_dir or workspace_root). Pozn.: remind SKILL.md používá workspace-relativní skills/remind/scripts/remind_cli.py (korektní); detach analogicky skills/detach/scripts/….
Python skripty na serveru — uv-native pattern (PEP 723)
Preferovaný způsob pro libovolný stand-alone Python skript v ~/.nanobot/workspace/:
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = ["nanobot-ai", "requests", ...]
# ///
uv run --script vytvoří a cachuje izolované venv per skript (~/.cache/uv/). Skript přežije uv tool uninstall/install, upgrade Pythonu i přesun stroje — bez přímé cesty do ~/.local/share/uv/tools/<tool>/bin/python. První spuštění po vytvoření hlavičky trvá ~5-10s (build venv), další jsou instantní.
Gotcha pro user systemd: unit musí mít explicitní PATH, jinak shebang uv nenajde:
[Service]
Environment=PATH=%h/.local/bin:/usr/bin:/bin
ExecStart=%h/path/to/script.py
Bez Environment=PATH selže s /usr/bin/env: 'uv': No such file or directory. Aplikace pravidla na všechny budoucí user systemd unity spouštějící uv skripty (nejen detach).
Zdroj: PEP 723, uv docs uv run --script, ověřeno deployem detach skillu iterace #3.
Systemd .path unit s DirectoryNotEmpty= — event-driven workspace daemon
Pattern pro libovolný daemon, který má reagovat na soubory v workspace bez polling:
# tasks-daemon.path
[Path]
DirectoryNotEmpty=%h/.nanobot/workspace/tasks/inbox
Unit=tasks-daemon.service
[Install]
WantedBy=paths.target
# tasks-daemon.service
[Service]
Type=oneshot
ExecStart=%h/path/to/daemon.py
%h = user home. .path unit je jen watcher (přes inotify), reálnou akci dělá .service. Level-triggered: dokud kondice DirectoryNotEmpty= platí, systemd po každém doběhnutí service spustí novou instanci. Daemon by měl drenovat celý inbox v jednom běhu (sériově).
Install: systemctl --user enable --now <unit>.path. Lingering musí být zapnutý (loginctl enable-linger nanobot), jinak user units po odhlášení padnou. Pro reminders se to nepoužívá — ty mají cron výrazy, .path není vhodný (kondice se nemění minutu po minutě). Pro file-driven queue (jako detach) ano.
Gotcha — level-triggered .path + startup crash = permanentní latch: Když oneshot daemon spadne ve startup fázi (před vyprázdněním inboxu), inbox zůstane neprázdný → .path ho hned znovu spustí → další pád → … Na manager defaultu (StartLimitIntervalSec=10s, Burst=5) to za <2 s narazí na rate-limit a systemd zalatchuje .service i .path do failed (unit-start-limit-hit). Z toho se sám nezotaví — nutný systemctl --user reset-failed <unit>.service <unit>.path + restart <unit>.path. (Stalo se 7.6., když daemon padal na NameError.)
Hardening (ověřeno, nasazeno na tasks-daemon): v .service přidat
[Unit]
StartLimitIntervalSec=1800
StartLimitBurst=20
[Service]
Restart=on-failure
RestartSec=60
Restart=on-failure + RestartSec dá delay mezi pokusy (nezávisle na .path retriggeru); čistý exit 0 (inbox vyprázdněn) ani SIGTERM od systemd nerestartují. Širší okno (30min/20) zajistí, že se latch po posunu okna sám pustí dál. man systemd.service: pro Type=oneshot jsou zakázané jen Restart=always/on-success, on-failure je povolený.
Zdroj: man systemd.path + man systemd.service, ověřeno smoke testem před deployem detach skillu; latch+hardening history 2026-06-07 19:33.
nvm je shell funkce, ne binárka
nvm je definován jako bash funkce v .bashrc — není to spustitelný soubor. Proto ho systemd service nevidí, ani když má správně nastavenou PATH s nvm node cestou.
| Příkaz | Typ | Dostupný v systemd service? |
|---|---|---|
node, npm, npx |
skutečné binárky v .nvm/.../bin/ |
ano, pokud je PATH nastavena explicitně |
nvm |
shell funkce v .bashrc |
ne nikdy — .bashrc se nesourcuje |
Pro správu verzí Node.js z shellu → přihlásit se jako nanobot a volat nvm interaktivně. Z agenta nebo daemonu → volat node/npx přímo (fungují přes PATH).
Skill /keep — explicit immediate memory
On-demand skill pro okamžitou explicitní paměť. Uživatel řekne „keep X" → agent reformuluje na terse fact → zapíše jako bullet do workspace/keep.md. Bez datumů. Dedup, compaction při >150 řádcích.
Persistent awareness: keep.md není v BOOTSTRAP_FILES (ty jsou hardcoded). Trvalé povědomí zajišťuje krátká reference ## workspace/keep.md na konci USER.md (auto-loadovaný každý tah). Skill je tedy čistě write endpoint — neplýtvá context window každou session.
Kde žije: workspace/keep.md v rootu workspace (vedle USER.md, MEMORY.md). Edituje ho výhradně /keep skill; ostatní agent paths smí číst. Odděleno od Dream / MEMORY.md — Dream o keep.md neví, needituje ho.
Dedup pokrývá keep.md i MEMORY.md: Write protocol (krok 4) před appendem přečte workspace/memory/MEMORY.md a pokud tam je sémanticky podobný fakt (Dream ho mohl destilovat), upozorní uživatele a defaultně přeskočí. MEMORY.md je read-only — /keep do něj nikdy nezapisuje.
Ukládá i why, ne jen what (od 2026-06-06): Krok 2 Write protokolu rozlišuje typ záznamu — plain fakt (alergie, deploy window, jméno) jde bez důvodu; rozhodnutí / preference / dead-end dostane důvod inline na stejném řádku (<fakt> — because <terse why>). Pokud je vstup rozhodnutí/dead-end bez uvedeného důvodu, model se jednou doptá na why (decline/self-evident → uloží bez něj). Záměrně úzká varianta Claude memory.md vzoru, který why přidává jen u feedback/project, ne u reference/faktu. Žádné Why: bloky ani few-shot příklady — silné Ollama Cloud / OpenRouter modely zvládnou hranici fakt-vs-rozhodnutí zero-shot. Plný kontext: history.md 2026-06-06.
Gotcha — BOOTSTRAP_FILES jsou hardcoded: nanobot/agent/context.py:25 má BOOTSTRAP_FILES = ["AGENTS.md", "SOUL.md", "USER.md", "TOOLS.md"] — nelze přidat vlastní soubor bez patche. Vše, co má být vidět každý tah bez on-demand loadingu, musí být reference v existujícím bootstrap souboru (USER.md, SOUL.md, …).
Skill /note — osobní znalostní báze (capture → compile, od 2026-07-01)
Přepsáno z SQLite row-store na capture → compile pipeline (vzor llm-wiki/detach, ale lehčí: jeden dokument, žádný index/graph/lint). Plná historie: history 2026-07-01. Plán: plans/note-prepis.md.
- Úložiště (bez DB):
notes/notes.md= JEDEN strukturovaný dokument s tematickými sekcemi (##), které řídí LLM. Fronta zachytů = soubory vnotes/inbox/, po zpracování →notes/done/, zadržené (paywall/nečitelné) →notes/hard/. Audit každého zachytu vlog/note.log. - Dual-mode:
/note <text>= okamžitě (capture + inline compile v témže tahu, default);/note cron <text>= odloženě (jen capture, zpracuje cron). Oba volajínote_capture.py(dumb atomic zápis) + sdílený compile workflow v SKILL.md + sdílenýnotes/.compile.lock. - Compile:
note_compile.py(systémový crontab uživatelenanobot, každou minutu) — levný fs pre-check bez importu nanobota, lockfile,Nanobot.from_config().run(DRAIN_GOAL)(process_direct bez cron preamble). Reformuluje na terse fakta, URL stáhne přeswebtool + detekce paywallu, zařadí do sekce, přesune zdroj dodone//hard/. - Search:
/note search|find <dotaz>→ načti celýnotes.md, odpověz (read-only). Mazání = editace dokumentu (žádné display ID, žádné tagy). - Odděleno od
/keep,MEMORY.md, Dream, llm-wiki (cml/). Kolize triggerů skeep: NL „poznamenej si/ulož si …" může spadnout dokeep(obě to claimují) — spolehlivý je explicitní/noteprefix; description note skillu odlišen („NOT durable facts → keep"). Starýnote.py/note.sqlitevyřazeny (note.sqlitevbackup/note-retired/).
Nanobot MÁ web tool (search + Jina Reader fetch) — TOOLS.md ho nezmiňuje
Config tools.web (server config.json) má enable: true s search (provider: duckduckgo, maxResults 5) a fetch (useJinaReader: true). Agent tedy umí stáhnout URL přes web tool — Jina Reader vrací čistý markdown a zvládne JS i měkké paywally. workspace/TOOLS.md (dokumentace toolů) web neuvádí (jen exec/grep/cron) — je neúplný; zdroj pravdy je config.json. Pro fetch URL ve skillu preferuj web tool; exec+curl (-o file kvůli 10k/60s limitu exec) je fallback. Ověřeno při přepisu /note (DT Glass URL reálně stažena, history 2026-07-01).
MCP servery v nanobotu — skrytá tokenová zátěž
Každý nakonfigurovaný MCP server přidává do system promptu svůj tool schema popis. Pro sqlite MCP to jsou ~1–2k tokenů, a to každý tah — bez ohledu na to, jestli tool vůbec použiješ.
U menších modelů s omezeným kontextovým oknem (typicky cloud MoE modely s efektivními ~32B params) je to zbytečné plýtvání. Přitom přímá alternativa (CLI sqlite3 přes exec, nebo Python sqlite3 stdlib přes uv run) tuto zátěž nemá a pro 95 % use-cases je dostatečná.
Pravidlo: MCP server zapojit jen pokud přidaná hodnota nad přímým přístupem výrazně převáží tokenovou cenu. Pro sqlite typicky nepřeváží.
Reasoning stream (✻) na konzoli — channels.showReasoning
Řádky prefixované ✻, streamované token po tokenu (✻ The, ✻ user wants, …) v nanobot agent CLI chatu nejsou debug ani chyba — je to reasoning/thinking stream modelu. Řídí ho jediný config klíč channels.show_reasoning (default true, camelCase alias showReasoning).
Vypnout: channels.showReasoning = false v ~/.nanobot/config.json. Sourozenec telegram/websocket uvnitř channels, ne uvnitř konkrétního kanálu.
- Je to globální flag, ne per-channel. Gate čte globální
channels_config.show_reasoning(nanobot/cli/commands.py:345,354), ne per-kanálový config. Nelze vypnout jen pro konzoli a nechat zapnuté ve WebUI — buď všude, nebo nikde. (Trade-off: ve WebUI se reasoning hodí při ladění „proč něco jde/nejde".) - Restart: CLI (
nanobot agent) čte config čerstvě při startu → stačí restart sezení. Gateway/WebUI/Telegram dostávajíchannelspřesAgentLoop.from_config()jednou při startu → restart service. - Žádný runtime flag
nanobot agentna to není;--logs/--no-logsřídí jen loguru runtime log, ne reasoning stream. - Příbuzné knoby v témže bloku:
sendProgress(defaulttrue, progress řádky↳),sendToolHints(defaultfalse, tool-call hinty). Vykreslení✻nacommands.py:301.
Zdroj: nanobot/config/schema.py:37-39, nanobot/cli/commands.py:301,345,354. Plný záznam: history 2026-06-01 „Vypnutí reasoning streamu".
Context window presetů: default 65k, přepis přes contextWindowTokens
Nanobot má hardcoded default context_window_tokens = 65_536 pro ModelPresetConfig i AgentDefaults (nanobot/config/schema.py:101,124). Pokud preset v config.json tuto hodnotu nepřepíše, jede model na 65k bez ohledu na to, co reálně umí. Klíč v JSON: contextWindowTokens (Base má alias_generator=to_camel + populate_by_name=True, schema.py:24 → projde camelCase i snake_case). Sourozenec maxTokens (max output) má default jen 8192.
Nastaveno 2026-06-02 per-preset na reálné limity modelů (kimi-k2.6 / qwen3.5 / nemotron-3-super 262144, minimax-m2.7 204800, glm-5.1 196608, deepseek-v4-flash 1048576) + maxTokens 16384. Bez restartu — modelPresets se hot-reloadují (viz sekce „Kdy je a není potřeba restart"). U :cloud modelů hostí kontext Ollama cloud, takže contextWindowTokens reálně rozšíří budget — není to lokální num_ctx žeroucí RAM. Plný záznam: history 2026-06-02.
Důsledky (trade-off, ne čistá výhra):
- + Méně ořezávání/komprese historie → lepší návaznost v dlouhých sezeních. Delší souvislé odpovědi (16k vs 8k output).
- − „Lost in the middle": LLM neudrží kvalitu rovnoměrně přes celý kontext; info zahrabané uprostřed ~200k se vybavuje hůř. Propad je výraznější u slabších MoE modelů (glm/qwen/nemotron) než u špičkových (Kimi K2.6). Roste latence i protečené tokeny úměrně naplnění.
- Při běžném (nízkém) naplnění se kvalita nemění — efekt nastává až když sezení přeroste 65k.
Otevřená otázka (todo.md): nenechat slabším modelům kontext spíš na ~128k? Menší okno může dát lepší kvalitu „per token" než maximální naplnění.
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/bwrapshellem, který přidá extra--ro-bind-tryargumenty před předáním volání dál) — funkční, ale ovlivní všechnabwrapvolá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).
Korekce (2026-07-02): na produkčním nanobot.hell je sandbox VYPNUTÝ, takže pip závislosti přes uv run fungují. Server má v config.json exec blok "sandbox": "" + pathAppend na ~/.local/bin/uv, takže exec běží bez bwrap — stejné prostředí jako plain SSH. Skilly reálně jedou se závislostmi (remind→croniter, llm-wiki→pyyaml) a bookmark/html_to_markdown.py používá trafilatura (v2.1.0 ověřeno naživo: uv run --with trafilatura stáhne a naimportuje). Limity výše platí jen pro nasazení se zapnutým bwrap ("sandbox": "bwrap"), typicky Docker. Zdroj: live test 2026-07-02, viz history.md téhož dne.
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 — 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.
llm-wiki: zaseknutí způsobuje LLM v mechanické smyčce, ne pomalost
Při wiki lint/fix operacích se agent zasekl proto, že dělal mechanickou práci v LLM smyčce (čte stránku → hledá problém → edituje → další stránka). Správné řešení přesouvá tuto práci do deterministického skriptu — LLM rozhoduje jen tam, kde je skutečně potřeba úsudek.
Praktické důsledky pro llm-wiki:
- Lint musí vracet
(soubor, řádek, návrh opravy)— ne jen "9 broken refs" → agent nemusí číst žádnou stránku --fixmód má smysl jen pro mechanické triviality (chybějící frontmatter pole ze šablony) kde není úsudek- Slug collision a frontmatter šablona patří do compile pipeline, ne do lintu ex post
Zamítnuté návrhy ze stejného důvodu: timeout na každý krok, "aspoň jeden edit po každém čtení" (tlačí na editaci), agentní wiki fix subcommand (opět smyčka). Viz todo.md sekce "llm-wiki skill — další zlepšení".
Optimalizovat skilly kvůli tokenům se nevyplatí
Celý blok skillů (~2,5k: skills_section 1,56k + always-skilly 0,95k) je při okně 196k jen ~1,3 % okna. Smazat on-demand skill ušetří jen popis + framing (~40–75 tok/kus) → fakticky neměřitelné. Description neškrtat — je to trigger pro progressive loading (model podle něj pozná, kdy skill načíst); bez něj skill přestane fungovat, ušetříš desítky tokenů a přijdeš o funkčnost. Jediná páka jsou always: true (jdou celým tělem), ale my+memory mají být always. Větší blok jsou tool defs (5,2k, jen vypnutím toolů v configu). Závěr: skilly maž podle užitečnosti, ne kvůli tokenům; reálný strop je contextWindowTokens, ne baseline. Začalo by to dávat smysl až u desítek–stovek skillů nebo velkého těla jako always. Plný rozbor: tokens-explain.md.
maxTokens se počítá dvakrát; prompt caching má 3 háčky
maxTokens jde jednak přímo do API jako strop výstupu (runner.py:621), jednak se odečítá z input budgetu jako rezerva na výstup — u snipu historie i u konsolidace: budget = contextWindowTokens − maxTokens − 1024 (runner.py:1262, memory.py:619). Vyšší maxTokens tedy zmenšuje prostor pro kontext a uspíší konsolidaci → držet skromně (16k OK), u reasoning modelů víc (reasoning tokeny se počítají taky).
Prompt caching nanobot zapíná jen pro providery s supports_prompt_caching=True = openrouter, anthropic, bedrock (registry.py:149,278); ollama a gemini ne → aktivní glm-5.1 přes ollama od nanobota žádné cache breakpointy nedostává. Háčky tam, kde caching jede:
- TTL 5 min — holé
{"type": "ephemeral"}(anthropic_provider.py:400). U sporadického chatu cache mezi tahy obvykle vyprší → platí se plný vstup; write navíc 1,25× base (read 0,1×). - Konsolidace/snip rozbíjí prefix — breakpoint sedí na system +
messages[-2]+ tools (openai_compat:453); jakmile Dream/_snip_historyzmění začátek pole, prefix se invaliduje. - Interakce s kontextem: vyšší
contextWindowTokens= méně časté konsolidace = stabilnější cachovaný prefix → argument cachingem podporuje velké okno, ale jen na cachujících presetech.
MiniLoop — změřená čísla /remind add parseru (PoC)
Samostatný .NET PoC v src/MiniLoop/ (prompt-only parse text→JSON, Microsoft.Extensions.AI nad OpenAI SDK, swap providera přes config). Test test mód protáčí 17 párů × všechny modely souběžně (modely paralelně, příklady uvnitř modelu sekvenčně; NOW fixní 2026-06-03T14:30:00). Souběžnost vůči provideru je omezená maxConcurrency v configu (SemaphoreSlim per provider) — ollama=3 dle kvóty předplatného. Běh 5 modelů s gate=3 (2026-06-03):
| Model | Provider | Úspěšnost | Wall median / avg | Tokeny in / out |
|---|---|---|---|---|
| glm-5.1 | ollama (nvidia.hell) | 17/17 | 1690 / 1927 ms | 21118 / 2896 |
| deepseek-v4-flash | ollama (nvidia.hell) | 17/17 | 4894 / 6118 ms | 21654 / 3487 |
| minimax-m2.7 | ollama (nvidia.hell) | 17/17 | 4815 / 4604 ms | 21969 / 2317 |
| claude-haiku-4.5 | openrouter | 16/17 | 1064 / 1135 ms | 23668 / 691 |
| gpt-5.4-nano | openrouter | 17/17 | 3034 / 4003 ms | 20987 / 553 |
Hrdlo souběhu = kvóta paralelních dotazů providera, ne sdílený výpočet ani počet spojení. Ollama předplatné povoluje max 3 paralelní dotazy (ollama.com/pricing). Když běží víc ollama modelů než 3 naráz, přebytečné dotazy čekají ve frontě a to čekání spadne do wall-clocku (stopky obalují jen HTTP call). Dřív (4 ollama modely bez stropu): glm median 3240 ms, jednotlivá volání qwen až 59 s. Po zavedení maxConcurrency=3 (a redukci na 3 ollama modely, takže strop zatím ani nepřekáží): glm median zpět na 1690 ms, žádné odlehlé hodnoty. SemaphoreSlim slot se navíc získává mimo stopky, takže i kdyby strop překážel, čekání na slot se do měřené latence nezapočte. OpenRouter běží na vlastní infře, strop nemá. Každý model má vlastní OpenAIClient (spojení se nesdílí) — víc spojení by nepomohlo.
Reasoning = pomalé + drahé na out tokeny: deepseek out=3487, minimax out=2317 — proto 5 s. (Dříve zavržený qwen3.5 byl extrém: out=22671 tok ≈ jako input, volání i 59 s — proto vyhozen.) haiku/gpt-nano out 550–700 tok = přímý parse bez reasoningu. glm rychlý (out2,9k, ale median 1,7 s).
FAILy: jediný „FAIL" haiku = false negative v test datech (zkontrolovat pečení vs pečeni; JsonCompare porovnává text přesně, ordinálně). Ostatní modely 17/17. (Z dřívějška: gemma dělala skutečnou chybu data příští pondělí→06-09 místo 06-08; gemma teď v reálném configu není.)
Teze PoC potvrzena: ~1,2k input tokenů na add (vs ~28–32k přes nanobot agent loop, ~30× méně) a ~1 s wall-clock u rychlých modelů (vs ~10 s u /remind list přes agenta). Sedí s odhady z plans/remind-standalone-bot.md. Plný záznam: history 2026-06-03 „MiniLoop paralelizace".
Levné / OSS modely z OpenRouteru (změřeno 2026-06-03)
Test 5 levných OpenRouter modelů (cena $/M tok in/out), gate=3 na ollam(ě) se netýká — vše OpenRouter:
| 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 |
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 |
openai/gpt-oss-120b |
levný | 16/17 | 7238 / 12164 ms | 3769 |
* false negative (slovosled se protáhnout/protáhnout se, resp. pečení/pečeni).
⚠️ 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)
:cloud varianty registrované na nvidia.hell přes POST /api/pull (cloud pointer, žádný GB download):
| Model | Úspěšnost | Wall median / avg | out tok |
|---|---|---|---|
ministral-3:8b-cloud |
15/17 | 1043 / 1169 ms | 653 |
nemotron-3-nano:30b-cloud |
16/17 | 2015 / 2277 ms | 7805 |
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).
| Model | TTFT (medián) | Total wall (medián) | Out tok | End-to-end průtok (out/total) |
|---|---|---|---|---|
| glm-5.1 | ~5,9 s | ~7,5 s | 1200–1730 | ~198 tok/s |
| minimax-m3 | ~6,8 s | ~10,8 s | 420–460 | ~40 tok/s |
minimax-m3 je výrazně línější: TTFT mají srovnatelný (start není problém), ale minimax má ~50 % delší celkovou dobu i přes 3–4× MÉNĚ vygenerovaných tokenů. Čistá generace minimaxu ~95–120 tok/s (streamuje plynule); glm ~5× vyšší end-to-end průtok. Pozn.: glm „1300 tok/s" z post-TTFT okna NEbrat doslovně — cloud buffer flushne dávku, proto měřit out/total. Na interaktivní úkoly je glm-5.1 jednoznačně svižnější. Plný záznam + per-run čísla: history 2026-06-07 „Měření rychlosti glm-5.1 vs minimax-m3".
Širší rozhodovací rozbor (GLM-5.1 vs MiniMax M3 vs Kimi K2.6 — kdy který za podmínky Ollama Cloud, capability cliffs, use-case mřížka): models.md.
Doplněk: minimax-m2.7 vs glm-5.1 (2026-06-07, prokládaně 5 kol)
minimax-m2.7:cloud zmizel z /api/tags (Ollama Cloud ho nahradila m3), ale POST /api/pull ho dotáhne (cloud pointer). Mediány (cloud byl vytížený → absolutní čísla vyšší než ranní m3 měření, ber jen poměr):
| Model | TTFT | Total wall | Out tok | e2e (out/total) |
|---|---|---|---|---|
| glm-5.1 | 15,4 s | 18,3 s | 1558 | ~91 tok/s |
| minimax-m2.7 | 9,2 s | 11,3 s | 351 | ~28 tok/s |
m2.7 má decode ~3× pomalejší než glm (a horší než m3 ~40 tok/s). Nižší wall-clock (11 vs 18 s) je jen díky terseness (~4,5× méně tokenů), ne rychlejším generováním. Pro delší agentní výstupy (tool args, kód) je pomalý decode handicap. Plný záznam: history 2026-06-07 17:51.
minimax-m3 je pro nanobot agenta nepoužitelný (BLOCKED)
Verdikt: nenasazovat minimax-m3 jako agent model. Vedle pomalosti (~40 tok/s end-to-end, viz sekce výše) má fatální slabinu v agentní recovery — neumí přečíst chybovou hlášku toolu a vystoupit ze smyčky.
Konkrétně (detach deep-research ollama-cloud-models-research, 2026-06-07): web_fetch velké stránky se perzistoval do souboru, parsování přes exec blokoval restrictToWorkspace guard, a minimax-m3 místo aby přesunul soubor / použil read_file (guard to doslova radil) opakoval identický blokovaný příkaz s kosmetickými obměnami, prokládal ho triviálními print('ok') sanity-checky (četl failure jako rozbitý interpreter) a jednou vystřelil 10× tentýž grep v jednom tahu → spálil všech 200 maxToolIterations bez výsledku. Stejný úkol s kimi doběhl za ~456 s.
K tomu už dřív známé: tool-result bug + výrazná pomalost. Zkouší se náhrada minimax-m2.7 (starší MiniMax). Pro background deep-research drž GLM-5.1 / Kimi, ne MiniMax. Plný rozbor smyčky: session detach_2026-06-07T170344-ollama-cloud-models-research.jsonl.
/remind: model nevypisuje celou tabulku → mezera v instrukcích, ne bug skriptu
Problém → příčina → fix: Modely (kimi27 i default) po /remind list dostanou kompletní tabulku (CLI exit 0), ale jeden ji scvrkne na počet, druhý si ji přerenderuje po svém. → Příčina: SKILL.md sekce ## Behavioral contract popisovala formát výstupu, ale neříkala „předej kompletní" — modely to čtou jako surová data k vlastnímu formátování. → Fix: odstavec Showing read results (list/upcoming/delivered = user-ready text, vypsat každou položku s #display-id, nesumarizovat). Plný záznam: history 2026-06-15 13:21.
Diagnostika chování nanobot agenta = číst webui session logy. Konverzace (user/assistant/tool turny, včetně reasoning_content a exec výstupů) žijí na serveru ve workspace/sessions/websocket_<id>.jsonl (kopie i v ~/.nanobot/webui/). Pro „proč model udělal X" stáhnout příslušnou session a číst turny — odhalí, že příkaz uspěl a chyba je až v prezentaci.