Files
nanobot-runtime/develop/knowledge.md
2026-07-22 12:32:23 +02:00

70 KiB
Raw Blame History

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.jsonproviders 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:

  1. Prompt je obalený natvrdo v kódu. nanobot/cli/commands.py:on_cron_job přilepí před payload.message fixní "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…").
  2. evaluate_response je fail-open. nanobot/utils/evaluator.py rozhoduje 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).
  • memory/MEMORY.md — hardcoded cesta v MemoryStore. Žádný jiný soubor v memory/ se NEčte (ani .bak, ani user-vytvořené .md). history.jsonl konzumuje výhradně Dream procesor.
    • Zdroj: nanobot/agent/memory.py:55 (memory_file = memory_dir / "MEMORY.md"), memory.py:205,229.
  • Skilly s metadata.always: true ve frontmatteru workspace/skills/<name>/SKILL.md — přes SkillsLoader.get_always_skills(). Ostatní skilly se nahrávají on-demand, ne do system promptu.
    • Zdroj: nanobot/agent/skills.py:203.

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_newsession.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 PoNe 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.parent4 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émreminder_fires.schedule_type se pro cron/random zapisoval špatně ('at') a dedup pro ně nefungoval (maskovala jen 60s tolerance). Příčinaschedule_{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 --keywordambiguous 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:

  1. Ověř dostupnost u providera. Pro Ollama: curl http://nvidia.hell:11434/api/tags a zkontroluj, že název modelu (přesně, včetně :cloud suffixu) je v seznamu. Pro OpenRouter: curl https://openrouter.ai/api/v1/models.

  2. 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))
    "'
    
  3. Restartuj službu, aby gateway preset načetla:

    ssh nanobot@nanobot.hell 'XDG_RUNTIME_DIR=/run/user/1000 systemctl --user restart nanobot.service'
    
  4. 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 jen websocket channel 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čky
  • Response 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 a models.md se 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
67 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:

  • description je 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.
  • description se nezkracuje (_get_skill_description vrací 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): description se ignoruje úplně, do promptu se eager vkládá celé tělo (bez frontmatteru). Druhý vysvětlující odstavec v description je 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_file tools 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:

  1. Skill detach v chatu → exec skills/detach/scripts/create-task.py --goal … --slug … --channel … --chat-id …. Skript vygeneruje timestamp + frontmatter, zajistí fronty (mkdir -p), atomicky zapíše do tasks/tmp/ a přesune do tasks/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.
  2. Systemd user unit tasks-daemon.path (DirectoryNotEmpty=…/tasks/inbox) přes inotify spustí tasks-daemon.service (Type=oneshot).
  3. tasks-daemon.py (Python, shebang na uv venv interpreter) projede inbox: mv → running/, parse frontmatter (chat_id povinný), zavolá Nanobot.from_config().run(goal, session_key=f"detach:<stem>") s 45-min timeoutem, appendne ## Result sekci, mv → done/ nebo failed/, 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žij chat_id z 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é (1030 s), drahé, nedeterministické (model může prompt překroutit nebo message tool nezavolat). Stejná třída problému jako Cron job s LLM agentem je nespolehlivý.
  • Přibalit message tool 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 je nanobot/channels/websocket.py (přidat handler vedle stávajících /api/sessions/..., vytvořit OutboundMessage(channel="websocket", chat_id=..., content=...) a bus.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 + RestartSecdelay 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 .bashrcnení 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:25BOOTSTRAP_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 v notes/inbox/, po zpracování → notes/done/, zadržené (paywall/nečitelné) → notes/hard/. Audit každého zachytu v log/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živatele nanobot, 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řes web tool + detekce paywallu, zařadí do sekce, přesune zdroj do done//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ů s keep: NL „poznamenej si/ulož si …" může spadnout do keep (obě to claimují) — spolehlivý je explicitní /note prefix; description note skillu odlišen („NOT durable facts → keep"). Starý note.py/note.sqlite vyřazeny (note.sqlite v backup/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 ~12k 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í channels přes AgentLoop.from_config() jednou při startu → restart service.
  • Žádný runtime flag nanobot agent na to není; --logs/--no-logs řídí jen loguru runtime log, ne reasoning stream.
  • Příbuzné knoby v témže bloku: sendProgress (default true, progress řádky ), sendToolHints (default false, tool-call hinty). Vykreslení na commands.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 restartumodelPresets 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-slimuv 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 nefungujeuv 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).

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
  • --fix mó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 (~4075 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ítekstovek 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:

  1. 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×).
  2. Konsolidace/snip rozbíjí prefix — breakpoint sedí na system + messages[-2] + tools (openai_compat:453); jakmile Dream/_snip_history změní začátek pole, prefix se invaliduje.
  3. 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 550700 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 ~2832k 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 4007 400 ms na prvních 4 příkladech, tj. ~38× horší než výsledek výše. Původní 934 ms zřejmě těžilo z cache providera. Skutečná cold performance je cca 45 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-30breasoning 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, 34 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!), dopoledne09: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 12001730 ~198 tok/s
minimax-m3 ~6,8 s ~10,8 s 420460 ~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 34× MÉNĚ vygenerovaných tokenů. Čistá generace minimaxu ~95120 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.