Skills memory-qmd
QMD memory backend — BM25 + vector search over indexed code and docs via the qmd CLI.
git clone https://github.com/openclaw/skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/openclaw/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/apptah/openclaw-memory-stack/skills/memory-qmd" ~/.claude/skills/openclaw-skills-memory-qmd && rm -rf "$T"
T=$(mktemp -d) && git clone --depth=1 https://github.com/openclaw/skills "$T" && mkdir -p ~/.openclaw/skills && cp -r "$T/skills/apptah/openclaw-memory-stack/skills/memory-qmd" ~/.openclaw/skills/openclaw-skills-memory-qmd && rm -rf "$T"
skills/apptah/openclaw-memory-stack/skills/memory-qmd/SKILL.mdQMD — SKILL.md
Overview
QMD is a BM25 + vector search engine for code and documentation. It indexes files into named collections using glob patterns and stores them in a local SQLite database with FTS5 full-text search. QMD supports three search modes: BM25 keyword search for exact symbol/name lookups, vector semantic search for concept-based queries, and hybrid search that combines both. It is the most versatile general-purpose memory backend in the OpenClaw Memory Stack, ideal for code search, documentation lookup, and finding specific symbols or behavioral patterns across a codebase.
Tier: Starter — included in the $49 package. Note: requires Bun runtime and SQLite FTS5 extension (Bun's built-in SQLite includes FTS5, so no separate installation is needed).
Prerequisites
| Dependency | Required | Notes |
|---|---|---|
| Bun | Yes | JavaScript/TypeScript runtime. Not universally installed — users must install separately. |
| SQLite FTS5 | Yes | Full-text search extension. Bun's built-in SQLite driver includes FTS5 by default. |
CLI | Yes | Installed via . |
Bun is not as ubiquitous as Node.js. If the user does not have Bun installed,
setup.sh will fail with exit code 2 and provide installation instructions.
Configuration
Configuration is stored in
config.json alongside this file. Key settings:
: Location of the SQLite index (index_path
)~/.cache/qmd/index.sqlite
: Default search mode (search.default_mode
for hybrid)query
: Available search modes with their minimum score thresholdssearch.modes
: Normalization formula for raw BM25 scoresrelevance
Per-project collections are configured via the
qmd CLI, not through config.json.
Usage
Store
QMD indexes existing files on disk — "storing" means adding files to a collection via glob patterns, then generating vector embeddings.
Step 1 — Create a collection:
qmd collection add <name> --pattern "**/*.md" --path /project/path
Step 2 — Generate embeddings:
qmd embed <name>
Step 3 — (Optional) Add context descriptions:
qmd context add -c <name> "This collection contains API documentation for the auth module"
Updating after file changes:
qmd update <name>
Collections can use any glob pattern:
**/*.swift, src/**/*.ts, docs/**/*.md, etc.
Retrieve
Retrieve a specific indexed document by its QMD URI:
qmd get qmd://<collection>/path/to/file
Retrieve multiple documents in one call:
qmd multi_get qmd://<collection>/file1,qmd://<collection>/file2
Use
multi_get when you need 3 or more files — never call get repeatedly.
Search
QMD provides three search modes. Mode selection is critical for result quality.
Mode Selection Guide
| Signal | Mode | Command | minScore | When to use |
|---|---|---|---|---|
| Exact name, symbol, error string | | | 0.1 | Function names, class names, error messages |
| Concept, behavior, "how does X work" | | | 0.3 | Understanding behavior, finding related code |
| Unclear, broad, first-time exploration | | | 0.3 | Cross-cutting concerns, initial exploration |
Quick rule: Know the exact word? Use
search. Describing behavior? Use vsearch. Not sure? Use query.
Search Examples
# BM25 keyword — find a specific function qmd search "handleAuthCallback" -c myproject --minScore 0.1 # Vector semantic — find code related to a concept qmd vsearch "how does the payment flow handle retries" -c myproject --minScore 0.3 # Hybrid — broad exploration qmd query "error handling middleware" -c myproject --minScore 0.3
Result Handling
- < 2 results above minScore: Broaden the search — try
mode, drop collection filter, or rephrasequery - 2-5 results: Ideal. Use
to read the top hitsqmd get - > 5 results: Consider narrowing with a more specific query or higher minScore
Always specify
-c collection when the project has per-layer collections.
Interface Contract
Input
— Maps tostore(key, content, metadata?)
+qmd collection addqmd embed
— Maps toretrieve(query, options?)qmd get qmd://<collection>/path
— Maps tosearch(pattern, scope?)qmd search|vsearch|query "pattern" -c scope
Output Format
All backends return the same JSON structure:
{ "query_echo": "original query string", "results": [ { "content": "matched file content or excerpt", "relevance": 0.82, "source": "qmd", "timestamp": "2026-03-10T14:30:00Z" } ], "result_count": 2, "status": "success", "error_message": null, "error_code": null, "backend_duration_ms": 230, "normalized_relevance": 0.82, "backend": "qmd" }
Failure Codes
| Code | Meaning |
|---|---|
| Bun or qmd not installed, or SQLite index missing |
| Exceeded 5s (router-measured) |
| Query succeeded but no matches above minScore |
| Collection exists but index is stale or incomplete |
| Internal error (see error_message) |
Relevance Normalization
BM25 raw scores are inherently low (typically 0.1-0.3 for good results) due to camelCase tokenization in code. This is normal, not a sign of poor results.
Normalization formula:
normalized = min(raw * 3, 1.0)
| Raw BM25 Score | Normalized Score | Interpretation |
|---|---|---|
| 0.10 | 0.30 | Weak match |
| 0.20 | 0.60 | Good match |
| 0.33+ | 1.00 | Strong match (capped) |
Vector search (
vsearch) scores are already in 0.0-1.0 range and do not need normalization.
Router Integration
- Normalized relevance >= 0.4 -> "good enough", no fallback needed
- Normalized relevance < 0.4 or status = empty -> trigger fallback to next backend
- status = error -> immediate fallback, log error
Limitations
- Bun dependency: Bun is not as widely installed as Node.js. Users must install it separately, which adds friction.
- BM25 struggles with semantic queries: Keyword search cannot understand meaning — "authentication flow" will not match "login process" unless both terms appear in the text.
- Vector embedding startup cost: First-time
on a large collection can take minutes. Subsequent updates are incremental.qmd embed - Index staleness: After file changes, the index must be updated with
. If the user forgets, search results may be outdated or incomplete.qmd update - camelCase tokenization: BM25 scores are naturally low because camelCase identifiers are split into tokens. A minScore of 0.1 is appropriate for
mode — do not set it higher or you will miss valid results.search - No real-time indexing: Unlike some backends, QMD does not watch for file changes. Reindexing is manual or hook-driven.
Tier
Starter — included in the $49 OpenClaw Memory Stack package. Requires Bun runtime (free, open source) and no paid API keys. The only barrier to entry is installing Bun.