Files
nanobot-runtime/skills/note/SKILL.md
2026-07-22 12:32:02 +02:00

9.6 KiB

name, description
name description
note Capture notes, texts, URLs, or whole articles into a personal knowledge base and answer questions against it. Triggers on "note X", "/note cron X", "search my notes for X", "delete/edit the note about X", "forget X". For filing reference material to search later — not a short durable fact/preference to just remember, and not a bare URL saved only to read later with no filing.

Note

A personal capture-to-knowledge-base skill. The user throws in notes, texts, URLs, or whole articles from any channel; each input is captured raw, then reformulated and filed into one structured markdown document (notes/notes.md) organized into thematic sections that the LLM owns and grows. Search = load the whole document and answer from it.

Architecture — read this first

Two-stage pipeline, one shared compile step:

  • Capture (always, instant, dumb). note_capture.py writes the raw input verbatim into notes/inbox/ (atomic) plus one line to log/note.log. No reformulation, no reading of notes.md, no fetching. This is all capture ever does.
  • Compile (reformulate + file into notes/notes.md). Runs either inline in the immediate mode, or in the background cron drain. Same workflow either way.

Storage layout under the workspace root (fixed locations):

notes/
├── notes.md        ← THE structured doc (thematic ## sections, LLM-owned)
├── inbox/          ← pending captures (one file each); compile drains this
├── done/           ← successfully compiled captures (sibling of inbox/)
├── hard/           ← held back: paywalled / unreadable / ambiguous — for manual review
└── .compile.lock   ← concurrency lock shared by inline compile and cron drain
log/note.log        ← append-only audit of every capture

No database — ever. Notes live only in notes/notes.md (prose) plus the notes/ pipeline dirs above. There is no SQLite/DB backend. To find a note, grep or read notes/notes.mdnever search db/, never run sqlite3, never create or open any .sqlite/.db file. (The AGENTS.md "store SQLite under db/" convention does not apply to notes — that is for other skills.) An older version of this skill used a database; it is gone. If you catch yourself opening a DB, stop — the answer is in notes/notes.md.

Separate store. notes/ is not agent memory: keep it distinct from keep, MEMORY.md, and the llm-wiki store (cml/). Never cross-read or cross-write between them. The Dream processor must not touch notes/.

Run scripts with uv run, workspace-relative paths (exec runs from the workspace root, not the skill dir): uv run skills/note/scripts/<script>.py ….

Language. This skill body is English; always reply to the user in the user's own language.

/note <text> — capture and file NOW (default)

The default: file the note into the knowledge base immediately, in this turn.

  1. Read Channel / Chat ID from the runtime context if present.
  2. Capture: uv run skills/note/scripts/note_capture.py --text "<raw input>" [--channel <ch>] [--chat-id <id>] Pass the input as-is — do not reformulate or strip URLs here.
  3. Run the Compile workflow (below) inline: acquire the lock, process notes/inbox/, file into notes/notes.md, move the source to notes/done/ (or notes/hard/).
  4. Commit the change (see Versioning below): via exec run git add notes/ && git commit -m "note: <short summary of what landed>".
  5. Confirm to the user which section it landed under, and quote the exact text that was filed (the reformulated fact(s), verbatim as written into notes.md — not a re-summary of it), in their language.

This blocks the turn for a while (reads the whole doc; a URL/article adds a fetch). If the user is firing off many notes quickly, suggest /note cron.

/note cron <text> — deferred capture

Capture only; let the background cron file it later. Fast, non-blocking.

  1. Read Channel / Chat ID from the runtime context if present.
  2. uv run skills/note/scripts/note_capture.py --text "<raw input>" [--channel <ch>] [--chat-id <id>]
  3. Confirm in one short line (e.g. "captured — I'll file it in the background") and STOP the turn. Forbidden here: reformulating, reading notes/notes.md, running any compile step, taking the lock. If you catch yourself about to read the doc, you are compiling inline — stop and just capture.

Compile workflow (shared: inline immediate mode + cron drain)

The cron (note_compile.py) invokes this via a drain goal; immediate mode runs it inline. Either way:

  1. Take the lock. Create notes/.compile.lock (skip if a live one exists — another compile is running; try again later). The cron script handles this itself; inline mode must respect it so an inline merge and a cron drain never edit notes.md at once.
  2. For each file in notes/inbox/:
    • Reformulate the body into a terse fact (or a few). One concept per entry; drop filler; preserve the input language — never translate. Split if too complex. If the input is Czech typed without diacritics (e.g. "kdyz uz to psal bez hacku"), restore correct diacritics as part of reformulation. Leave already-accented text and non-Czech text untouched — never add diacritics where none belong.
    • URLs: extract every URL from the body (0..N). Fetch each with the web tool (Jina Reader — returns clean markdown, handles JS and soft paywalls). If a URL is paywalled / login-gated / truncated / unreadable (login/subscribe/metered content, very short output, HTTP 401/403): do not fabricate a summary — write just the URL + any available title + a ⚠ paywall/incomplete marker. Whole articles: summarize the key points.
    • File it under the right thematic ## section of notes/notes.md. Create a new section if none fits. Use a surgical str_replace/append — never rewrite the whole document.
    • Move the source out of inbox/ immediately: to notes/done/ if anything usable was filed (paywall markers count as filed — they are the breadcrumb); to notes/hard/ if nothing usable could be extracted. Move right after each file so a crash mid-batch re-processes at most one.
  3. Release the lock (the cron script does this in finally).

Never assert content you could not read. When unsure, hedge or mark it.

/note search <query> / /note find <query> — query

Also triggered by "what do I have on …?", "find in my notes …".

  1. Read the whole notes/notes.md — that single file is the knowledge base. Do not read inbox/, done/, or hard/ (those are the raw pipeline, not the KB).
  2. Answer from it. If the topic is not covered, say so plainly — do not confabulate.
  3. Read-only: never modify the document in a search turn.

/note delete <query> / /note edit <query> — remove or change a note

Also triggered by "delete/remove the note about X", "forget X", "edit/update the note about X". Notes are prose in notes/notes.mdno IDs, no DB. The user names a note by describing it; you find it by reading the document.

This is a hard-gated TWO-TURN flow. NEVER delete or edit in the same turn as the request — showing is not doing.

Turn 1 — locate and confirm (absolutely NO mutation):

  1. Read notes/notes.md to find the matching note(s). That file is the only place a note lives — do not search db/, do not run sqlite3, do not read inbox//done// hard/. If nothing matches, say so. If the target is ambiguous or several entries match, list the candidates and ask which one.
  2. Show the user the exact verbatim line(s)/section you would remove (for an edit: the beforeafter), and ask them to confirm in plain words. Then STOP the turn. Forbidden this turn: str_replace/edit_file, rm, git, or any other mutation.

Turn 2 — only after the user explicitly confirms ("yes", "confirmed", "delete it"…):

  1. Remove/change it in notes/notes.md with a surgical str_replace (never rewrite the whole document). Touch notes/notes.md only — do NOT delete or move anything in notes/done/; those raw-capture breadcrumbs are internal plumbing, not a second copy of the note.
  2. Commit (see Versioning): via exec run git add notes/ && git commit -m "note: delete <short desc>" (edit → note: edit …).

Exception: a capture still pending (not yet compiled, a file in notes/inbox/) never reached the KB — you may cancel it directly with exec: rm notes/inbox/<file> (commit only if it was already tracked).

Versioning (git)

notes/ lives inside the workspace git repo. The Dream processor never touches it, so this skill is the only thing that commits notes/ — do it after every change to notes/notes.md:

  • Inline /note <text> and delete/edit: commit in the same turn via exec (git add notes/ && git commit -m "note: …"). One commit per operation.
  • Cron drain: note_compile.py commits deterministically after the batch — you do not commit inside the cron DRAIN_GOAL run.
  • Never git add -A (Dream owns the rest of the workspace); stage only notes/. The .compile.lock is gitignored, so git add notes/ never stages it.

Edge cases

  • /note with no content → ask what to note.
  • Empty / whitespace-only input → note_capture.py exits non-zero; ask for real content.
  • The compile step, not capture, decides sections and does all fetching. If you ever find yourself reformulating or reading notes.md during a cron capture, stop.