QMD Semantic Search Setup
QMD Semantic Search Setup
Section titled “QMD Semantic Search Setup”QMD adds semantic search to the Obsidian vault. Instead of keyword matching, it understands meaning — so searching “how do practitioners progress through forms” finds relevant content even if those exact words don’t appear together.
It runs locally, indexes markdown files into vectors, and integrates with Claude Code via MCP.
How It Works
Section titled “How It Works”qmd embedscans all.mdfiles in the vault and builds a local vector indexqmd search "query" -c baseworks-kbreturns semantically ranked results- Claude Code connects to QMD via MCP server (
qmd mcp), giving it vault search during sessions - A nightly job re-embeds new/changed files automatically
Current Deployment
Section titled “Current Deployment”| Machine | Status | Collection | Nightly Reindex |
|---|---|---|---|
| Patrick’s Mac | Active | baseworks-kb → ~/Obsidian/baseworks-kb-shared-brain/ | launchd, 3 AM |
| VPS (baseworks-agents) | Active | baseworks-kb → /srv/baseworks/knowledge-base/ | cron, 3 AM |
| Asia’s Mac | Setup needed | See instructions below | launchd, 3 AM |
Setup Instructions (Asia’s Mac)
Section titled “Setup Instructions (Asia’s Mac)”1. Install QMD
Section titled “1. Install QMD”npm install -g @tobilu/qmdVerify:
qmd --versionShould show 1.0.7 or later.
2. Create the collection
Section titled “2. Create the collection”qmd add baseworks-kb ~/Obsidian/baseworks-kb-shared-brain/ --pattern "**/*.md"3. Add context description
Section titled “3. Add context description”qmd context baseworks-kb / "Baseworks knowledge base: movement methodology, educational programs, practice platform, Forms, Movement Principles, voice guides, agent system documentation, session summaries, and organizational notes for Patrick and Asia."4. Build the initial index
Section titled “4. Build the initial index”This downloads ~2 GB of models on first run, then indexes all markdown files. Takes 5–15 minutes depending on hardware.
qmd embed5. Test it
Section titled “5. Test it”qmd search "what are Movement Principles" -c baseworks-kbYou should see ranked results from primer transcripts and related documents.
6. Add QMD MCP to Claude Code
Section titled “6. Add QMD MCP to Claude Code”Edit ~/.claude/settings.json and add the qmd entry under mcpServers:
{ "mcpServers": { "qmd": { "command": "qmd", "args": ["mcp"] } }}If the file already has other MCP servers, add qmd alongside them. If the file doesn’t exist, create it with the content above.
7. Set up nightly reindex
Section titled “7. Set up nightly reindex”Create the file ~/Library/LaunchAgents/com.baseworks.qmd-reindex.plist:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict> <key>Label</key> <string>com.baseworks.qmd-reindex</string> <key>ProgramArguments</key> <array> <string>/opt/homebrew/bin/qmd</string> <string>embed</string> </array> <key>StartCalendarInterval</key> <dict> <key>Hour</key> <integer>3</integer> <key>Minute</key> <integer>0</integer> </dict> <key>StandardOutPath</key> <string>/tmp/qmd-reindex.log</string> <key>StandardErrorPath</key> <string>/tmp/qmd-reindex-error.log</string></dict></plist>Note: If QMD installed to a different path than /opt/homebrew/bin/qmd, check with which qmd and update the path.
Then load it:
launchctl load ~/Library/LaunchAgents/com.baseworks.qmd-reindex.plistThis runs qmd embed at 3 AM every night. On macOS, launchd catches up on missed schedules when the Mac wakes — so if the laptop was closed at 3 AM, it runs on the next wake.
8. Verify
Section titled “8. Verify”# Check launchd is loadedlaunchctl list | grep qmd
# Check QMD statusqmd statusExcluding Notes from Search (no_embed)
Section titled “Excluding Notes from Search (no_embed)”To prevent a note from being indexed in the vector database, add this to its frontmatter:
---no_embed: true---Both no_embed: true and no_embed: "true" are accepted.
What it does: After each embed run, a wrapper script finds all notes with this flag and sets them to inactive in the SQLite index. They are excluded from all qmd query, qmd search, and qmd vsearch results. The note remains directly readable by path — it just won’t surface in queries.
Use case: Outdated or draft content you want to keep in the vault for reference (e.g., old copy being used as a “before” example) but don’t want accidentally influencing search-driven decisions.
How it works (technical): The nightly reindex job calls ~/scripts/qmd-embed-filtered.sh instead of qmd embed directly. This script runs qmd embed, then sets active = 0 in ~/.cache/qmd/index.sqlite for any tagged notes.
Manual run:
~/scripts/qmd-embed-filtered.shScript location on each machine:
| Machine | Script path | Vault path in script |
|---|---|---|
| Asia’s Mac Mini | ~/scripts/qmd-embed-filtered.sh | ~/Obsidian/baseworks-kb-shared-brain/ |
| Asia’s MacBook Air | ~/scripts/qmd-embed-filtered.sh | same |
| Patrick’s Mac | ~/scripts/qmd-embed-filtered.sh | same |
| VPS | ~/scripts/qmd-embed-filtered.sh | /srv/baseworks/knowledge-base/ |
The script content is the same on all machines except the VAULT path on VPS. Check which qmd on each machine — the qmd binary path may differ and the script uses $PATH so this is usually handled automatically.
Script contents (save as ~/scripts/qmd-embed-filtered.sh, then chmod +x it):
#!/bin/bash# Runs qmd embed, then excludes any notes tagged with `no_embed: true` in frontmatter.
set -euo pipefail
VAULT="${HOME}/Obsidian/baseworks-kb-shared-brain"# VPS: change VAULT to /srv/baseworks/knowledge-base/DB="${HOME}/.cache/qmd/index.sqlite"COLLECTION="baseworks-kb"LOG_PREFIX="[qmd-embed-filtered]"
echo "${LOG_PREFIX} Starting at $(date)"
qmd embed
echo "${LOG_PREFIX} Embed complete. Applying no_embed exclusions..."
COUNT=0EXCLUDED_FILES=()
while IFS= read -r filepath; do frontmatter=$(awk '/^---/{count++; if(count==2) exit} {print}' "$filepath") if ! echo "$frontmatter" | grep -qE '^no_embed: ("true"|true)'; then continue fi relpath="${filepath#${VAULT}/}" sqlite3 "$DB" "UPDATE documents SET active = 0 WHERE collection = '${COLLECTION}' AND path = '${relpath}';" EXCLUDED_FILES+=("$relpath") COUNT=$((COUNT + 1))done < <(grep -rlE '^no_embed: ("true"|true)' "$VAULT" --include="*.md" 2>/dev/null || true)
if [ "$COUNT" -gt 0 ]; then echo "${LOG_PREFIX} Excluded ${COUNT} file(s) from search index:" for f in "${EXCLUDED_FILES[@]}"; do echo " - $f"; doneelse echo "${LOG_PREFIX} No files with no_embed: true found."fi
echo "${LOG_PREFIX} Done at $(date)"Update the launchd plist (Mac) to call the script:
<key>ProgramArguments</key><array> <string>/bin/bash</string> <string>/Users/YOUR_USERNAME/scripts/qmd-embed-filtered.sh</string></array>Update the cron job (VPS) — replace qmd embed with:
bash /home/YOUR_USER/scripts/qmd-embed-filtered.shUseful Commands
Section titled “Useful Commands”# Full status (files indexed, vectors, models)qmd status
# Search the vaultqmd search "query text here" -c baseworks-kb
# Re-embed everything (manual)qmd embed
# Check reindex logscat /tmp/qmd-reindex.logRelated
Section titled “Related”- 2026-03-03-Session-Review — Session where QMD was set up
- Claude-Code-VPS-Setup — VPS infrastructure