| name | meeting-notes |
| description | Search your Obsidian meeting-notes vault (e.g. transcribed meetings with summary / topics / decisions / action-item notes) for what was said, decided, or assigned in past meetings, using local semantic search (QMD — runs fully on-device).
Use whenever the user refers to past meetings or asks you to consult their notes — e.g. "check the latest meeting notes", "what did we decide about X", "look at my notes on Y", "didn't we discuss this in a meeting / standup / sync / offsite", "who owns the action item for Z", "pull up the notes from the demo call". Matches by meaning, not just keywords, and returns the real notes with their dates and paths so answers can be cited. |
Meeting notes (Obsidian + QMD)
Search the local meetings QMD collection — your meeting-notes vault — retrieve
the actual notes, and answer with citations. Everything runs on-device; nothing
leaves the machine. The vault location is not hardcoded: it's set once via
qmd collection add … --name meetings (see Setup & configuration) and
auto-discovered from there, so this skill is shareable as-is.
Agent-agnostic paths. Throughout, <skill-dir> means the folder this skill
is installed in — where this SKILL.md and qmd-link.sh live. Substitute your
agent's actual location when calling the helper: Claude Code prints
Base directory for this skill: <dir> on load; otherwise it's the install path,
e.g. ~/.claude/skills/meeting-notes, ./.claude/skills/meeting-notes, or
.agents/skills/meeting-notes (Codex). The qmd, search, and open steps are
plain shell and work under any agent.
How to run: pick a model, then delegate
First use on a machine: if qmd collection show meetings errors, the
collection isn't set up — do Setup & configuration (bottom) first; you
bootstrap it, the user doesn't run commands by hand.
Other agents: the delegation + model routing below use Claude Code's
subagent tool and Claude model names. On an agent without subagents or
model-switching (e.g. Codex), skip delegation and just run steps 1–4
inline — identical workflow and output, minus the model tiering.
Run the search in a subagent (the Task/Agent tool), not inline — a
subagent works in a small, fresh context and on a model you choose, which is
faster than looping in the big main conversation. Pick the subagent's model
from the user's wording:
| User signals… | Example cues | model |
|---|
| nothing special (default) | — | sonnet |
| speed | "quick", "fast", "be fast", "just quickly", "speed matters" | haiku |
| accuracy / care | "accurate", "precise", "be precise", "thorough", "careful", "double-check" | opus |
Match the intent, not only these exact words. If the request mixes cues
(e.g. "quick but accurate"), accuracy wins → opus.
Then spawn the subagent with that model, and match the prompt's strictness
to the model — a speed cue means "do less", not just "use a faster model" (a
fast model that takes 10 tool calls is slower than a careful one that takes 3,
because tool-call count dominates wall-clock).
Haiku (speed) → send this self-contained, minimal prompt — no skill read, no
links, fewest possible tool calls:
Answer a question from a local Obsidian meeting-notes vault via the QMD CLI
(installed, indexed, models cached locally). Be minimal — fewest tool calls:
qmd update >/dev/null 2>&1 && qmd embed >/dev/null 2>&1
- ONE structured query, fields authored by you:
qmd query $'intent: …\nlex: …\nvec: …' -n 8
- ONE batched retrieve of the 2–4 best hits in a single call:
qmd get "#id1"; qmd get "#id2"; qmd get "#id3"
- Answer concisely. End with a numbered Sources list, one line per cited
note:
N. <meeting name + date + part> — <its qmd://meetings/… path>. No
obsidian links, no extra searches.
QUESTION: <the user's question>
Sonnet (default) / Opus (accuracy) → send this self-contained prompt — same
discipline as the fast path, plus clickable links and (for Opus) a verification
pass. Still capped:
Answer a question from a local Obsidian meeting-notes vault via the QMD CLI
(installed, indexed, models cached locally). Precision comes from careful
reading, not many searches — keep total tool calls to ~5–7.
qmd update >/dev/null 2>&1 && qmd embed >/dev/null 2>&1
- ONE structured query, fields authored by you:
qmd query $'intent: …\nlex: …\nvec: …' -n 10. (Opus only: you may run AT
MOST one more, solely to fill a specific gap.)
- Retrieve AT MOST 4 notes, batched in ONE call. For long notes use a line
window —
qmd get "#id:FROM:COUNT" — don't dump whole files.
- (Opus) Verify each claim against the retrieved text before asserting it.
- Answer concisely. End with a numbered Sources list, one line per cited
note:
N. <meeting name + date + part> — <its qmd://meetings/… path>. Do
NOT format obsidian links — they aren't clickable in this terminal; the user
opens notes on request (handled by the orchestrator, not this subagent).
QUESTION: <the user's question>
Either way, relay the subagent's answer faithfully — don't re-summarize it.
(For a trivial follow-up about results already in this conversation, you may
answer inline.)
Opening a source. obsidian:// links are NOT clickable here — Claude Code's
renderer colors them but won't open them (even raw OSC 8). So when the user says
"open N" or names a note, resolve it and launch Obsidian yourself:
URI=$(bash <skill-dir>/qmd-link.sh "<that note's qmd://… path>")
open "$URI"
open uses macOS's URL handler, so it works regardless of terminal. Offer this
("say open N and I'll open it in Obsidian") rather than presenting dead links.
If the helper prints UNRESOLVED, cite the qmd:// path as text instead.
⚠️ Never tell the subagent to "be exhaustive", "read everything",
"consult several meetings", or "prioritise completeness over speed", and never
lift the caps above — that uncaps the loop and a single query can run for many
minutes. "Precise / accurate" means a better model + careful reading within
the caps, not more tool calls.
1. Refresh the index first
New or edited meetings aren't searchable until indexed (the vault syncs via
iCloud, so new notes appear often):
qmd update && qmd embed
update re-indexes changed files (mtime-based, fast); embed only vectorizes
what's new. Cheap when nothing changed. If qmd is missing or errors, see
Setup at the bottom.
2. Search — author the query yourself
Default to structured qmd query (hybrid BM25 + vector + reranking). Do not
just paste the user's sentence — you know the goal and the domain vocabulary
better than the built-in expander, so write the fields deliberately. Use
ANSI-C $'...' quoting so the \n become real newlines:
qmd query $'intent: <what to find AND what to avoid>\nlex: <exact terms, names, titles, jargon you expect in the notes>\nvec: <the same idea paraphrased in natural, source-like language>\nhyde: <one sentence describing the ideal note/answer>' -n 8
- Always write
intent:. Almost always add lex: + vec:. Add hyde: when it helps.
- For an exact name, title, code symbol, or rare phrase, prefer BM25 instead:
qmd search "<phrase>" -n 8.
3. Retrieve the full source — snippets are only leads
Results print as qmd://meetings/<path>:line #docid with a title, score, and a
short snippet. get takes a #docid or a path; multi-get takes paths or
globs, not docids (a glob like one meeting's *.md, or a comma-separated path
list "pathA,pathB"). To pull several hits by docid, run qmd get per docid.
Latency matters — most of the wall-clock is LLM round-trips, not QMD. Two
rules keep it fast:
-
Batch all retrieval into ONE call. After searching you already know which
hits you want — fetch them together in a single bash invocation instead of one
call per note. Each extra call is a whole extra model round-trip over the
growing context (seconds); the qmd gets themselves are near-instant. Run
several qmd get in one command, or one multi-get glob/list:
qmd get "#id1"; qmd get "#id2"; qmd get "#id3"
qmd multi-get "<meeting-folder>/*.md"
-
Fetch only what you'll cite, and only the lines you need. Don't pull whole
long notes when the snippet already located the passage. get accepts a line
window — qmd get "#docid:FROM:COUNT" (e.g. qmd get "#id1:60:15") — so
grab the relevant slice. Smaller output = cheaper prefill on every later step.
Prefer the focused Decisions-*.md / Summary-*.md parts over dumping every
Notes/Topics file.
4. Answer + sources you can open on request
End the answer with a numbered Sources list — plain text, one line per cited
note: meeting name + date + part, plus its qmd://meetings/… path. Prefer the
focused Decisions / Summary / Action items parts.
Don't format the qmd:// paths as links — they aren't clickable here. Opening is
on request: tell the user they can say "open N", and open it for them when
they do (see Opening a source above).
Notes
- Collection
meetings is the only one, so every result is already scoped to
meetings — no extra filtering needed.
- Fully local: models in
~/.cache/qmd/models, index in
~/.cache/qmd/index.sqlite.
- Widen scope later (e.g. add All Hands):
qmd collection add "<path>" --name <name> then qmd embed.
Setup & configuration (per machine)
You (the agent) do this by reading these instructions — the user doesn't run
commands by hand. When qmd collection show meetings fails, bootstrap it:
- Find the vault. Search common Obsidian locations and confirm the exact
meetings folder with the user before indexing — e.g.
~/Library/Mobile Documents/iCloud~md~obsidian/Documents/*/, ~/Documents,
~/Obsidian, or any directory containing a .obsidian/ folder.
- Install + index. Warn the user the first
embed downloads ~2GB of local
models, then run:
npm install -g @tobilu/qmd
qmd collection add "<confirmed meetings folder>" --name meetings
qmd embed
- If
qmd errors with "Could not locate the bindings file" (native module vs
Node version), rebuild it and retry:
( cd "$(npm root -g)/@tobilu/qmd/node_modules/better-sqlite3" && npm run build-release )
The meetings folder is the only machine-specific value, and it lives only in
the QMD collection — search and qmd-link.sh auto-discover it via
qmd collection show meetings. Override without re-indexing using
QMD_MEETINGS_ROOT (and QMD_MEETINGS_COLLECTION, default meetings).