diff --git a/.gitignore b/.gitignore index fddf53d..26fe730 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,9 @@ -/* -!memory/ -!SOUL.md -!USER.md -!memory/MEMORY.md -!.gitignore - -!skills/ -!scripts/ -!plans/ -!results/ -!keep/ -!HEARTBEAT.md -!develop/ -!knowledge/ -!notes/ -!projects/ +db/ +sessions/ +log/ +__pycache__/ +tmp/ +backup/ +tasks/ +.nanobot/ +*.bak diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..470f3c1 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,34 @@ +# Agent Instructions + +## Scheduled Reminders + +**Personal reminders for the user** (notifications about tasks they need to do) → use the `/remind` skill → stored in `reminder.yaml`. Never use the `cron` tool for these. + +**Background agent tasks** (run a script, check something, autonomous action) → use the built-in `cron` tool directly. + +Test: *Who is the recipient?* User gets notified → `reminder.yaml` via `/remind` skill. Agent executes something → `cron` tool. + +**Do NOT just write reminders to MEMORY.md** — that won't trigger actual notifications. + +## Heartbeat Tasks + +`HEARTBEAT.md` is checked on the configured heartbeat interval. Use file tools to manage periodic tasks: + +- **Add**: `edit_file` to append new tasks +- **Remove**: `edit_file` to delete completed tasks +- **Rewrite**: `write_file` to replace all tasks + +When the user asks for a recurring/periodic task, update `HEARTBEAT.md` instead of creating a one-time cron reminder. + +## Databases (SQLite) + +Always store SQLite databases under `db/*.sqlite` (relative to the workspace root). +Never use `/tmp/`, hardcoded absolute paths, or in-memory databases for persistent data. + +## How you were extended & tuned + +`develop/` records how this instance was extended and tuned — skills, config, service work — with verified facts and a change log you can learn from (not the upstream code). See `develop/README.md`; read on demand. + +## Knowledge base + +`knowledge/` holds verified facts and measured values you can draw on when answering (e.g. notes on the models available to you). See `knowledge/README.md` for what's there; read on demand. diff --git a/TOOLS.md b/TOOLS.md new file mode 100644 index 0000000..5485247 --- /dev/null +++ b/TOOLS.md @@ -0,0 +1,59 @@ +# Tool Usage Notes + +Tool signatures are provided automatically via function calling. +This file documents non-obvious constraints and usage patterns. + +## exec — Safety Limits + +- Commands have a configurable timeout (default 60s) +- Dangerous commands are blocked (rm -rf, format, dd, shutdown, etc.) +- Output is truncated at 10,000 characters +- `restrictToWorkspace` config can limit file access to the workspace + +## grep — Content Search + +- Use `grep` to search file contents inside the workspace +- Default behavior returns only matching file paths (`output_mode="files_with_matches"`) +- Supports optional `glob` filtering (e.g. `glob="*.py"`) plus `context_before` / `context_after` +- Supports `type="py"`, `type="ts"`, `type="md"` and similar shorthand filters +- Use `fixed_strings=true` for literal keywords containing regex characters +- Use `output_mode="files_with_matches"` to get only matching file paths +- Use `output_mode="count"` to size a search before reading full matches +- Use `head_limit` and `offset` to page across results +- Prefer this over `exec` for code and history searches +- Binary or oversized files may be skipped to keep results readable + +## cron — Background Agent Tasks + +- Use `cron` only for **background agent tasks** (scripts, checks, autonomous actions). +- For **personal reminders to the user**, use the `/remind` skill instead (`reminder.yaml`). +- Do not call `nanobot cron` via `exec` — use the built-in `cron` tool. + +## python — use uv + +For all Python code (scripts, snippets, one-liners, tools) use `uv`, +not `python` / `python3` / `pip` directly. + +- Run a script: `uv run script.py` (not `python script.py`) +- One-liner / snippet: `uv run --with python -c '...'` +- Script with declared dependencies: `uv run --script script.py` + (PEP 723 header inside the file) +- Add a project dependency: `uv add ` (not `pip install`) +- Remove: `uv remove ` +- Sync environment: `uv sync` +- Run a tool: `uv run pytest`, `uv run ruff`, `uv run mypy`, … +- REPL: `uv run python` + +Do not use `pip`, `pip-tools`, `poetry`, `conda`, or the system `python`. +Reason: isolated, reproducible environments with no system-level side +effects, faster resolves, no "works on my machine" surprises. + +## log/reminder.log — doručené připomínky + +Odeslané připomínky se logují do `log/reminder.log` (append-only, formát +`YYYY-MM-DDTHH:MM:SS `, Prague time, bez timezone suffixu). Posílá je +**systémový cron uživatele nanobot** (`skills/remind/scripts/remind_send.py`) +přímo přes Telegram, mimo agenta. Když se uživatel ptá na minulé/dnešní +připomínky („připomněl jsi mi…?", „co dnes přišlo?"), přečti tento soubor. +Vedle něj v `log/reminder_cron.log` se zachytává stdout/stderr crontabu — +za normálního běhu prázdný, plní se jen při pádech skriptu. diff --git a/cron/jobs.json b/cron/jobs.json new file mode 100644 index 0000000..cc9b3a4 --- /dev/null +++ b/cron/jobs.json @@ -0,0 +1,423 @@ +{ + "version": 1, + "jobs": [ + { + "id": "42a84295", + "name": "nanobot-version-check", + "enabled": true, + "schedule": { + "kind": "cron", + "atMs": null, + "everyMs": null, + "expr": "0 8 * * *", + "tz": "Europe/Prague" + }, + "payload": { + "kind": "agent_turn", + "message": "Run the nanobot version check script at /home/nanobot/.nanobot/workspace/scripts/check_nanobot_version.sh, then check if any of PyPI/GitHub/Docker Hub versions differ from current 0.2.0. If a newer version is found, send a Telegram notification to the user: \"New nanobot version available: X.Y.Z (current: 0.2.0). Update with: docker pull smanx/nanobot:X.Y.Z or pip install nanobot-ai==X.Y.Z\"", + "deliver": true, + "channel": "telegram", + "to": "8826147089", + "channelMeta": { + "message_id": 30, + "user_id": 8826147089, + "username": null, + "first_name": "Martin", + "is_group": false, + "message_thread_id": null, + "is_forum": false, + "reply_to_message_id": null, + "_wants_stream": true + }, + "sessionKey": "telegram:8826147089" + }, + "state": { + "nextRunAtMs": 1781071200000, + "lastRunAtMs": 1780984800002, + "lastStatus": "ok", + "lastError": null, + "runHistory": [ + { + "runAtMs": 1779948000001, + "status": "ok", + "durationMs": 37408, + "error": null + }, + { + "runAtMs": 1780120800001, + "status": "ok", + "durationMs": 14277, + "error": null + }, + { + "runAtMs": 1780207200002, + "status": "ok", + "durationMs": 9226, + "error": null + }, + { + "runAtMs": 1780293600002, + "status": "ok", + "durationMs": 18462, + "error": null + }, + { + "runAtMs": 1780380000001, + "status": "ok", + "durationMs": 17065, + "error": null + }, + { + "runAtMs": 1780466400002, + "status": "ok", + "durationMs": 12513, + "error": null + }, + { + "runAtMs": 1780552800002, + "status": "ok", + "durationMs": 14408, + "error": null + }, + { + "runAtMs": 1780639200002, + "status": "ok", + "durationMs": 8434, + "error": null + }, + { + "runAtMs": 1780725600001, + "status": "ok", + "durationMs": 20082, + "error": null + }, + { + "runAtMs": 1780812000002, + "status": "ok", + "durationMs": 15949, + "error": null + }, + { + "runAtMs": 1780898400002, + "status": "ok", + "durationMs": 13551, + "error": null + }, + { + "runAtMs": 1780984800002, + "status": "ok", + "durationMs": 13813, + "error": null + } + ] + }, + "createdAtMs": 1779872578080, + "updatedAtMs": 1780984813815, + "deleteAfterRun": false + }, + { + "id": "dream", + "name": "dream", + "enabled": true, + "schedule": { + "kind": "every", + "atMs": null, + "everyMs": 7200000, + "expr": null, + "tz": null + }, + "payload": { + "kind": "system_event", + "message": "", + "deliver": false, + "channel": null, + "to": null, + "channelMeta": {}, + "sessionKey": null + }, + "state": { + "nextRunAtMs": 1781073212281, + "lastRunAtMs": 1781066012279, + "lastStatus": "ok", + "lastError": null, + "runHistory": [ + { + "runAtMs": 1780928715855, + "status": "ok", + "durationMs": 2, + "error": null + }, + { + "runAtMs": 1780935915858, + "status": "ok", + "durationMs": 2, + "error": null + }, + { + "runAtMs": 1780943115862, + "status": "ok", + "durationMs": 2, + "error": null + }, + { + "runAtMs": 1780950315865, + "status": "ok", + "durationMs": 2, + "error": null + }, + { + "runAtMs": 1780957515869, + "status": "ok", + "durationMs": 2, + "error": null + }, + { + "runAtMs": 1780964715872, + "status": "ok", + "durationMs": 2, + "error": null + }, + { + "runAtMs": 1780971915875, + "status": "ok", + "durationMs": 2, + "error": null + }, + { + "runAtMs": 1780979115879, + "status": "ok", + "durationMs": 2, + "error": null + }, + { + "runAtMs": 1780986315884, + "status": "ok", + "durationMs": 2, + "error": null + }, + { + "runAtMs": 1780993515887, + "status": "ok", + "durationMs": 2, + "error": null + }, + { + "runAtMs": 1781000715891, + "status": "ok", + "durationMs": 1, + "error": null + }, + { + "runAtMs": 1781007915894, + "status": "ok", + "durationMs": 2, + "error": null + }, + { + "runAtMs": 1781015115897, + "status": "ok", + "durationMs": 2, + "error": null + }, + { + "runAtMs": 1781022315900, + "status": "ok", + "durationMs": 2, + "error": null + }, + { + "runAtMs": 1781029515903, + "status": "ok", + "durationMs": 2, + "error": null + }, + { + "runAtMs": 1781036715907, + "status": "ok", + "durationMs": 496360, + "error": null + }, + { + "runAtMs": 1781044412268, + "status": "ok", + "durationMs": 2, + "error": null + }, + { + "runAtMs": 1781051612271, + "status": "ok", + "durationMs": 2, + "error": null + }, + { + "runAtMs": 1781058812275, + "status": "ok", + "durationMs": 2, + "error": null + }, + { + "runAtMs": 1781066012279, + "status": "ok", + "durationMs": 2, + "error": null + } + ] + }, + "createdAtMs": 1780892715821, + "updatedAtMs": 1781066012281, + "deleteAfterRun": false + }, + { + "id": "heartbeat", + "name": "heartbeat", + "enabled": true, + "schedule": { + "kind": "every", + "atMs": null, + "everyMs": 1800000, + "expr": null, + "tz": "Europe/Prague" + }, + "payload": { + "kind": "system_event", + "message": "", + "deliver": false, + "channel": null, + "to": null, + "channelMeta": {}, + "sessionKey": null + }, + "state": { + "nextRunAtMs": 1781069612302, + "lastRunAtMs": 1781067812302, + "lastStatus": "ok", + "lastError": null, + "runHistory": [ + { + "runAtMs": 1781033115995, + "status": "ok", + "durationMs": 0, + "error": null + }, + { + "runAtMs": 1781034915997, + "status": "ok", + "durationMs": 1, + "error": null + }, + { + "runAtMs": 1781037212270, + "status": "ok", + "durationMs": 1, + "error": null + }, + { + "runAtMs": 1781039012273, + "status": "ok", + "durationMs": 0, + "error": null + }, + { + "runAtMs": 1781040812275, + "status": "ok", + "durationMs": 0, + "error": null + }, + { + "runAtMs": 1781042612276, + "status": "ok", + "durationMs": 0, + "error": null + }, + { + "runAtMs": 1781044412277, + "status": "ok", + "durationMs": 0, + "error": null + }, + { + "runAtMs": 1781046212279, + "status": "ok", + "durationMs": 1, + "error": null + }, + { + "runAtMs": 1781048012282, + "status": "ok", + "durationMs": 0, + "error": null + }, + { + "runAtMs": 1781049812285, + "status": "ok", + "durationMs": 0, + "error": null + }, + { + "runAtMs": 1781051612286, + "status": "ok", + "durationMs": 0, + "error": null + }, + { + "runAtMs": 1781053412287, + "status": "ok", + "durationMs": 1, + "error": null + }, + { + "runAtMs": 1781055212291, + "status": "ok", + "durationMs": 0, + "error": null + }, + { + "runAtMs": 1781057012293, + "status": "ok", + "durationMs": 0, + "error": null + }, + { + "runAtMs": 1781058812293, + "status": "ok", + "durationMs": 1, + "error": null + }, + { + "runAtMs": 1781060612296, + "status": "ok", + "durationMs": 1, + "error": null + }, + { + "runAtMs": 1781062412298, + "status": "ok", + "durationMs": 1, + "error": null + }, + { + "runAtMs": 1781064212299, + "status": "ok", + "durationMs": 1, + "error": null + }, + { + "runAtMs": 1781066012300, + "status": "ok", + "durationMs": 0, + "error": null + }, + { + "runAtMs": 1781067812302, + "status": "ok", + "durationMs": 0, + "error": null + } + ] + }, + "createdAtMs": 1780892715832, + "updatedAtMs": 1781067812302, + "deleteAfterRun": false + } + ] +} \ No newline at end of file diff --git a/cron/jobs.json.bak-1779911421 b/cron/jobs.json.bak-1779911421 new file mode 100644 index 0000000..1d5af71 --- /dev/null +++ b/cron/jobs.json.bak-1779911421 @@ -0,0 +1,236 @@ +{ + "version": 1, + "jobs": [ + { + "id": "42a84295", + "name": "nanobot-version-check", + "enabled": true, + "schedule": { + "kind": "cron", + "atMs": null, + "everyMs": null, + "expr": "0 8 * * *", + "tz": "Europe/Prague" + }, + "payload": { + "kind": "agent_turn", + "message": "Run the nanobot version check script at /home/nanobot/.nanobot/workspace/scripts/check_nanobot_version.sh, then check if any of PyPI/GitHub/Docker Hub versions differ from current 0.2.0. If a newer version is found, send a Telegram notification to the user: \"New nanobot version available: X.Y.Z (current: 0.2.0). Update with: docker pull smanx/nanobot:X.Y.Z or pip install nanobot-ai==X.Y.Z\"", + "deliver": true, + "channel": "telegram", + "to": "8826147089", + "channelMeta": { + "message_id": 30, + "user_id": 8826147089, + "username": null, + "first_name": "Martin", + "is_group": false, + "message_thread_id": null, + "is_forum": false, + "reply_to_message_id": null, + "_wants_stream": true + }, + "sessionKey": "telegram:8826147089" + }, + "state": { + "nextRunAtMs": 1779948000000, + "lastRunAtMs": null, + "lastStatus": null, + "lastError": null, + "runHistory": [] + }, + "createdAtMs": 1779872578080, + "updatedAtMs": 1779872578080, + "deleteAfterRun": false + }, + { + "id": "1a7bb634", + "name": "remind-check", + "enabled": true, + "schedule": { + "kind": "cron", + "atMs": null, + "everyMs": null, + "expr": "* * * * *", + "tz": "Europe/Prague" + }, + "payload": { + "kind": "agent_turn", + "message": "Run /home/nanobot/.nanobot/workspace/skills/remind/scripts/remind_check.py via exec tool (/home/nanobot/.local/bin/uv run). If the output is EMPTY (no lines printed), do NOT send any message — exit silently. Only if there is non-empty output, forward each line as a Telegram notification to the user.", + "deliver": true, + "channel": "telegram", + "to": "8826147089", + "channelMeta": {}, + "sessionKey": "telegram:8826147089" + }, + "state": { + "nextRunAtMs": 1779911340000, + "lastRunAtMs": 1779910800001, + "lastStatus": "ok", + "lastError": null, + "runHistory": [ + { + "runAtMs": 1779906120001, + "status": "ok", + "durationMs": 102857, + "error": null + }, + { + "runAtMs": 1779906240001, + "status": "ok", + "durationMs": 510361, + "error": null + }, + { + "runAtMs": 1779906780003, + "status": "ok", + "durationMs": 17453, + "error": null + }, + { + "runAtMs": 1779907022917, + "status": "ok", + "durationMs": 122021, + "error": null + }, + { + "runAtMs": 1779907200001, + "status": "ok", + "durationMs": 434016, + "error": null + }, + { + "runAtMs": 1779907680003, + "status": "ok", + "durationMs": 425365, + "error": null + }, + { + "runAtMs": 1779908160002, + "status": "ok", + "durationMs": 23054, + "error": null + }, + { + "runAtMs": 1779908220001, + "status": "ok", + "durationMs": 386611, + "error": null + }, + { + "runAtMs": 1779908640002, + "status": "ok", + "durationMs": 453205, + "error": null + }, + { + "runAtMs": 1779909120002, + "status": "ok", + "durationMs": 102348, + "error": null + }, + { + "runAtMs": 1779909240002, + "status": "ok", + "durationMs": 49995, + "error": null + }, + { + "runAtMs": 1779909300003, + "status": "ok", + "durationMs": 207256, + "error": null + }, + { + "runAtMs": 1779909540002, + "status": "ok", + "durationMs": 136616, + "error": null + }, + { + "runAtMs": 1779909720001, + "status": "ok", + "durationMs": 9602, + "error": null + }, + { + "runAtMs": 1779909780002, + "status": "ok", + "durationMs": 129043, + "error": null + }, + { + "runAtMs": 1779909960001, + "status": "ok", + "durationMs": 58836, + "error": null + }, + { + "runAtMs": 1779910020001, + "status": "ok", + "durationMs": 172213, + "error": null + }, + { + "runAtMs": 1779910200001, + "status": "ok", + "durationMs": 99259, + "error": null + }, + { + "runAtMs": 1779910320002, + "status": "ok", + "durationMs": 467494, + "error": null + }, + { + "runAtMs": 1779910800001, + "status": "ok", + "durationMs": 484056, + "error": null + } + ] + }, + "createdAtMs": 1779888543665, + "updatedAtMs": 1779911284057, + "deleteAfterRun": false + }, + { + "id": "dream", + "name": "dream", + "enabled": true, + "schedule": { + "kind": "every", + "atMs": null, + "everyMs": 7200000, + "expr": null, + "tz": null + }, + "payload": { + "kind": "system_event", + "message": "", + "deliver": false, + "channel": null, + "to": null, + "channelMeta": {}, + "sessionKey": null + }, + "state": { + "nextRunAtMs": 1779914222916, + "lastRunAtMs": 1779906806699, + "lastStatus": "ok", + "lastError": null, + "runHistory": [ + { + "runAtMs": 1779906806699, + "status": "ok", + "durationMs": 216217, + "error": null + } + ] + }, + "createdAtMs": 1779899606686, + "updatedAtMs": 1779907022916, + "deleteAfterRun": false + } + ] +} \ No newline at end of file diff --git a/keep.md b/keep.md new file mode 100644 index 0000000..ab8ff10 --- /dev/null +++ b/keep.md @@ -0,0 +1,2 @@ +- Podcast "Máš na míň" — moderátor Vašek Matějovský spolumoderuje s Radarem (Michal Vrátný, zakladatel Železné koule) +- Při studiu 3+ souborů z git repozitáře (typicky GitHub) naklonuj do tmp/ a zkoumej lokálně — ale jen pokud repo není obrovské (desítky MB OK, stovky MB+ už ne) diff --git a/plans/2026-06-07_todo-unification-plan.md b/plans/2026-06-07_todo-unification-plan.md new file mode 100644 index 0000000..4bb8cd1 --- /dev/null +++ b/plans/2026-06-07_todo-unification-plan.md @@ -0,0 +1,85 @@ +# Plán: /todo skill a unifikace /note, /remind, /keep + +## Cíl +Prozkoumat všechny možné architektonické přístupy k vytvoření `/todo` skillu a případné unifikaci s existujícími skilly `/note`, `/remind`, `/keep`. Žádná implementace dokud plán není schválen. + +## Fáze 1: Brainstorming alternativních přístupů + +### 1.1 Event-sourcing / append-only log +- Místo CRUD v SQLite: každá akce je immutable event (`TaskCreated`, `TaskCompleted`, `TaskDeleted`) +- Aktuální stav se materializuje z event logu +- Výhody: audit trail, undo/redo, distribuovaná synchronizace +- Nevýhody: komplexita, overkill pro osobní asistenta + +### 1.2 File-based s Git history +- Každý úkol = jeden markdown file v `tasks/` directory +- Git commit = historie změn +- Výhody: lidsky čitelné, diff-friendly, Git jako audit trail +- Nevýhody: pomalé listování, race conditions při paralelním zápisu, složité vyhledávání + +### 1.3 SQLite + JSONB flex schema +- Jedna tabulka `items` s `data JSONB` sloupcem +- Každý typ (note, todo, reminder, keep) má vlastní schema uvnitř JSON +- Výhody: extrémní flexibilita, žádné ALTER TABLE migrace +- Nevýhody: ztráta typové bezpečnosti, složité dotazy, SQLite JSONB je relativně nové + +### 1.4 Graph-based (RDF / knowledge graph) +- Úkoly a poznámky jako uzly v grafu, vztahy jako hrany +- "úkol X souvisí s poznámkou Y", "úkol Z závisí na úkolu W" +- Výhody: bohaté vztahy mezi entitami +- Nevýhody: obrovská komplexita, žádný jednoduchý CLI + +### 1.5 Hybrid: SQLite + Markdown export +- Primární storage SQLite (rychlé dotazy, fulltext search) +- Periodický export do markdown (čitelnost, Git backup) +- Výhody: kombinuje výhody obou světů +- Nevýhody: duplicitní data, synchronizační problém + +### 1.6 Plugin architektura +- Jeden core skill `/item` s rozhraním +- `/note`, `/todo`, `/remind`, `/keep` jako pluginy registrující se do core +- Výhody: extensibility, čisté separation of concerns +- Nevýhody: potřebuje plugin API v nanobot frameworku (neexistuje) + +### 1.7 CLI-first s TUI +- Místo chat-based: terminal UI (např. `taskwarrior`, `todo.txt`) +- Nanobot jen wrapper volající externí CLI +- Výhody: battle-tested tools, offline použití +- Nevýhody: závislost na externím toolu, méně integrace s LLM + +## Fáze 2: Hlubší analýza 3 nejslibnějších variant + +Z fáze 1 vybrat 3 varianty pro detailní pro/contra: +- Varianta C z předchozí analýzy (konvergence / thin wrapper) +- Varianta 1.3 (SQLite + JSONB flex schema) +- Varianta 1.5 (hybrid SQLite + Markdown) + +Pro každou variantu: +- Datový model (ER diagram / schema) +- Příkazová struktura (všechny subcommands) +- Migrace z existujících skillů +- Testovací strategie +- Maintenance overhead +- User experience flow + +## Fáze 3: Rozhodovací kritéria a scoring + +Definovat vážená kritéria: +- Jednoduchost implementace (30%) +- Jednoduchost použití (25%) +- Extensibility (15%) +- Data integrity / reliability (15%) +- Performance (10%) +- Aesthetics / "feels right" (5%) + +Každá varianta dostane skóre 1-5 v každém kritériu. + +## Fáze 4: Finální doporučení + +Na základě scoringu vybrat vítěznou variantu. +Popis: proč právě tato, co se stane s existujícími skilly, roadmap implementace. + +## Omezení +- Žádná implementace dokud plán není explicitně schválen +- Analýza musí být kompletní — žádné "to se vyřeší později" +- Každá varianta musí mít konkrétní pro/contra, ne obecné fráze