Skip to content

QMD Semantic Search Setup

Created 2026-03-03
Updated 2026-03-12
Status active
Tags agent-systemqmdsemantic-searchsetup

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.


  1. qmd embed scans all .md files in the vault and builds a local vector index
  2. qmd search "query" -c baseworks-kb returns semantically ranked results
  3. Claude Code connects to QMD via MCP server (qmd mcp), giving it vault search during sessions
  4. A nightly job re-embeds new/changed files automatically

MachineStatusCollectionNightly Reindex
Patrick’s MacActivebaseworks-kb~/Obsidian/baseworks-kb-shared-brain/launchd, 3 AM
VPS (baseworks-agents)Activebaseworks-kb/srv/baseworks/knowledge-base/cron, 3 AM
Asia’s MacSetup neededSee instructions belowlaunchd, 3 AM

Terminal window
npm install -g @tobilu/qmd

Verify:

Terminal window
qmd --version

Should show 1.0.7 or later.

Terminal window
qmd add baseworks-kb ~/Obsidian/baseworks-kb-shared-brain/ --pattern "**/*.md"
Terminal window
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."

This downloads ~2 GB of models on first run, then indexes all markdown files. Takes 5–15 minutes depending on hardware.

Terminal window
qmd embed
Terminal window
qmd search "what are Movement Principles" -c baseworks-kb

You should see ranked results from primer transcripts and related documents.

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.

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:

Terminal window
launchctl load ~/Library/LaunchAgents/com.baseworks.qmd-reindex.plist

This 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.

Terminal window
# Check launchd is loaded
launchctl list | grep qmd
# Check QMD status
qmd status

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:

Terminal window
~/scripts/qmd-embed-filtered.sh

Script location on each machine:

MachineScript pathVault 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.shsame
Patrick’s Mac~/scripts/qmd-embed-filtered.shsame
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):

qmd-embed-filtered.sh
#!/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=0
EXCLUDED_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"; done
else
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.sh

Terminal window
# Full status (files indexed, vectors, models)
qmd status
# Search the vault
qmd search "query text here" -c baseworks-kb
# Re-embed everything (manual)
qmd embed
# Check reindex logs
cat /tmp/qmd-reindex.log