Deep Dive into QMD: OpenClaw's Memory Search System
QMD (Query Memory Database) is OpenClaw's built-in search system for agent memory. It enables agents to search through conversation logs, notes, and documents quickly and relevantly. Understanding QMD unlocks powerful memory capabilities.
This guide covers how QMD works, when to use BM25 vs vector search, indexing strategies, and how to query memory effectively.
What Is QMD?
QMD indexes your agent's memory files and makes them searchable. Instead of grep or file scanning, QMD provides:
- Full-text search via BM25 algorithm
- Semantic search via vector embeddings (optional)
- Fast retrieval from thousands of documents
- Ranked results by relevance
Think of it as your agent's personal search engine.
BM25 vs Vector Search
BM25 (Best Match 25)
How it works:
- Tokenizes text into words
- Scores documents based on term frequency and rarity
- No embeddings or neural networks required
- Fast and lightweight
Best for:
- Exact keyword matches
- Technical terms, names, IDs
- Code search
- Low-resource environments
Example: Finding all mentions of "SearXNG" in memory
Vector Search
How it works:
- Converts text to embeddings (numerical vectors)
- Finds semantically similar documents
- Requires embedding model (OpenAI, Sentence Transformers)
- More resource-intensive
Best for:
- Semantic similarity ("how to debug" finds "troubleshooting tips")
- Conceptual queries
- Multilingual search
- When exact keywords aren't known
Example: Finding documents about "error handling" even if they say "exception management"
Which to Choose?
Use BM25 when:
- You have specific keywords
- Memory fits in RAM easily
- CPU doesn't support vector ops efficiently
- You value speed over semantic understanding
Use Vector Search when:
- Queries are conceptual
- You need semantic similarity
- You have GPU or modern CPU
- Precision matters more than speed
Hybrid approach: Use both and merge results
Configuration
QMD config lives in ~/.openclaw/openclaw.json:
{
"qmd": {
"enabled": true,
"indexPath": "~/.openclaw/qmd",
"collections": [
{
"name": "memory",
"paths": ["~/clawd/memory/*.md"],
"indexer": "bm25",
"updateInterval": 3600
}
]
}
}
BM25 Configuration
{
"indexer": "bm25",
"bm25": {
"k1": 1.5,
"b": 0.75,
"tokenizerLanguage": "english"
}
}
Parameters:
k1: Term frequency saturation (higher = more weight to repeated terms)b: Document length normalization (0 = no normalization, 1 = full)tokenizerLanguage: Language for stemming and stop words
Vector Search Configuration
{
"indexer": "vector",
"vector": {
"model": "openai:text-embedding-3-small",
"dimensions": 1536,
"chunkSize": 512,
"chunkOverlap": 50
}
}
Parameters:
model: Embedding model to usedimensions: Vector sizechunkSize: Max tokens per chunkchunkOverlap: Tokens shared between chunks
Indexing
Initial Index
Create the index:
openclaw qmd index --collection memory
This processes all files matching the collection's paths and builds the search index.
Incremental Updates
QMD tracks file modifications and only re-indexes changed files:
openclaw qmd update --collection memory
Run this periodically (cron job recommended).
Full Re-Index
If index gets corrupted or you change config:
openclaw qmd reindex --collection memory
Auto-Update
Enable automatic updates:
{
"collections": [
{
"name": "memory",
"updateInterval": 3600,
"autoUpdate": true
}
]
}
QMD will re-index every hour (3600 seconds).
Querying
Basic Query
openclaw qmd search "SearXNG integration"
Returns ranked results with snippets.
Programmatic Query
import { qmd } from "openclaw";
const results = await qmd.search({
collection: "memory",
query: "bug in payment system",
limit: 10
});
for (const result of results) {
console.log(`[${result.score.toFixed(2)}] ${result.file}`);
console.log(result.snippet);
console.log("");
}
Advanced Query Options
const results = await qmd.search({
collection: "memory",
query: "authentication error",
limit: 20,
minScore: 0.5,
filter: {
dateRange: {
start: "2026-01-01",
end: "2026-02-01"
},
filePattern: "*.md"
},
highlight: true,
snippetLength: 200
});
Use Cases
Use Case 1: Bug Pattern Analysis
Find all past bugs similar to current issue:
async function findSimilarBugs(errorMessage: string) {
const results = await qmd.search({
collection: "memory",
query: errorMessage,
filter: { filePattern: "*bug*.md" },
limit: 5
});
const patterns = results.map(r => ({
date: r.metadata.date,
description: r.snippet,
file: r.file,
relevance: r.score
}));
return patterns;
}
Use Case 2: Context Retrieval
Get relevant context before answering a question:
async function getRelevantContext(question: string) {
const results = await qmd.search({
collection: "memory",
query: question,
limit: 5
});
const context = results
.map(r => r.content)
.join("\n\n---\n\n");
return {
question,
context,
sources: results.map(r => r.file)
};
}
Use Case 3: Research Compilation
Find everything related to a topic:
async function compileResearch(topic: string) {
const results = await qmd.search({
collection: "memory",
query: topic,
limit: 50,
minScore: 0.3
});
const byDate = results.sort((a, b) =>
new Date(b.metadata.date) - new Date(a.metadata.date)
);
const markdown = byDate.map(r => `
## ${r.metadata.date} - ${r.file}
${r.content}
`).join("\n");
await fs.writeFile(`./research/${topic}.md`, markdown);
}
Best Practices
1. Organize Memory Files
Use consistent naming:
memory/
├── 2026-02-01.md
├── 2026-02-02.md
├── projects/
│ ├── project-a.md
│ └── project-b.md
└── research/
├── searxng.md
└── trello.md
2. Add Metadata to Files
Include frontmatter:
---
date: 2026-02-15
tags: [bug, authentication, urgent]
project: payment-system
---
# Daily Log
...
QMD can index and filter by metadata.
3. Regular Re-Indexing
Set up a cron job:
# Re-index every 6 hours
0 */6 * * * openclaw qmd update --collection memory
4. Monitor Index Health
Check index status:
openclaw qmd status --collection memory
Shows:
- Total documents indexed
- Index size
- Last update time
- Stale documents count
5. Optimize Queries
Bad: Vague queries
qmd.search({ query: "stuff" })
Good: Specific keywords
qmd.search({ query: "OAuth authentication token refresh" })
Troubleshooting
Index is Stale
Symptom: Search doesn't return recent files
Fix:
openclaw qmd update --collection memory
Search is Slow
Symptom: Queries take >1 second
Fixes:
limit in queriesNo Results Found
Symptom: Query returns empty results
Checks:
openclaw qmd statusminScore thresholdVector Search Crashes
Symptom: CPU/RAM maxes out, process crashes
Fixes:
chunkSizeAdvanced Topics
Custom Tokenizers
For specialized vocabulary:
{
"bm25": {
"tokenizerLanguage": "english",
"customStopWords": ["agent", "openclaw"],
"preserveCase": false,
"stemming": true
}
}
Multi-Collection Search
Search across multiple collections:
const results = await qmd.searchAll({
query: "deployment error",
collections: ["memory", "logs", "docs"],
limit: 10
});
Result Caching
Cache frequent queries:
const cache = new Map();
async function cachedSearch(query: string) {
if (cache.has(query)) {
return cache.get(query);
}
const results = await qmd.search({ query });
cache.set(query, results);
return results;
}
Performance Tuning
For BM25
{
"bm25": {
"maxDocuments": 100000,
"memoryLimit": "512MB",
"cacheSize": 1000
}
}
For Vector Search
{
"vector": {
"batchSize": 32,
"maxConcurrent": 4,
"useGPU": false
}
}
Wrapping Up
QMD transforms agent memory from static files into searchable knowledge. Whether using BM25 for speed or vectors for semantics, effective indexing and querying unlock your agent's past conversations.
Start with BM25. Index your memory directory. Run some searches. See what works. Then tune for your specific needs.
Your agent's memory is only valuable if it can be found.