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.pywrites the raw input verbatim intonotes/inbox/(atomic) plus one line tolog/note.log. No reformulation, no reading ofnotes.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.md — never 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.
- Read
Channel/Chat IDfrom the runtime context if present. - 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. - Run the Compile workflow (below) inline: acquire the lock, process
notes/inbox/, file intonotes/notes.md, move the source tonotes/done/(ornotes/hard/). - Commit the change (see Versioning below): via
execrungit add notes/ && git commit -m "note: <short summary of what landed>". - 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.
- Read
Channel/Chat IDfrom the runtime context if present. uv run skills/note/scripts/note_capture.py --text "<raw input>" [--channel <ch>] [--chat-id <id>]- 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:
- 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 editnotes.mdat once. - 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
webtool (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/incompletemarker. Whole articles: summarize the key points. - File it under the right thematic
##section ofnotes/notes.md. Create a new section if none fits. Use a surgicalstr_replace/append — never rewrite the whole document. - Move the source out of
inbox/immediately: tonotes/done/if anything usable was filed (paywall markers count as filed — they are the breadcrumb); tonotes/hard/if nothing usable could be extracted. Move right after each file so a crash mid-batch re-processes at most one.
- 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 …".
- Read the whole
notes/notes.md— that single file is the knowledge base. Do not readinbox/,done/, orhard/(those are the raw pipeline, not the KB). - Answer from it. If the topic is not covered, say so plainly — do not confabulate.
- 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.md — no 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):
- Read
notes/notes.mdto find the matching note(s). That file is the only place a note lives — do not searchdb/, do not runsqlite3, do not readinbox//done//hard/. If nothing matches, say so. If the target is ambiguous or several entries match, list the candidates and ask which one. - Show the user the exact verbatim line(s)/section you would remove (for an edit: the
before→after), 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"…):
- Remove/change it in
notes/notes.mdwith a surgicalstr_replace(never rewrite the whole document). Touchnotes/notes.mdonly — do NOT delete or move anything innotes/done/; those raw-capture breadcrumbs are internal plumbing, not a second copy of the note. - Commit (see Versioning): via
execrungit 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 viaexec(git add notes/ && git commit -m "note: …"). One commit per operation. - Cron drain:
note_compile.pycommits deterministically after the batch — you do not commit inside the cronDRAIN_GOALrun. - Never
git add -A(Dream owns the rest of the workspace); stage onlynotes/. The.compile.lockis gitignored, sogit add notes/never stages it.
Edge cases
/notewith no content → ask what to note.- Empty / whitespace-only input →
note_capture.pyexits 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.mdduring acroncapture, stop.