Files
nanobot-runtime/skills/note/SKILL.md
2026-09-11 15:17:08 +02:00

11 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 ….

Never pass the text on the command line. Not as an argument, not in a heredoc, not through a pipe — always write_file it to tmp/ and pass the path. The exec safety guard scans the raw command string and has no shell parser, so quoting does not help: a colon right after a letter that follows a diacritic parses as a Windows drive path, and a note like Cíl: koupit mléko blocks the whole command with path outside working dir. Since capture takes the user's input verbatim, this would hit real notes, not edge cases.

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, in two steps — write_file the raw input to tmp/note-capture.md, then: uv run skills/note/scripts/note_capture.py --file tmp/note-capture.md [--channel <ch>] [--chat-id <id>] Pass the input as-is — do not reformulate or strip URLs here. See Never pass the text on the command line below for why it goes through a file.
  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. write_file the raw input to tmp/note-capture.md, then uv run skills/note/scripts/note_capture.py --file tmp/note-capture.md [--channel <ch>] [--chat-id <id>] (the extra write is what keeps the capture from being blocked — see below; it is still one fast turn, so this mode stays non-blocking)
  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.
  • No such file from capture → the write_file step was skipped or the path is wrong; write tmp/note-capture.md first, never fall back to passing the text as an argument.
  • 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.