QMD Memory Engine: Local-First Search Sidecar for OpenClaw
Learn how QMD enhances OpenClaw with BM25, vector search, reranking, and query expansion. This guide covers setup, prerequisites, and enabling the sidecar for improved recall.
Read this when
- You want to set up QMD as your memory backend
- You want advanced memory features like reranking or extra indexed paths
QMD is a local-first search sidecar that operates alongside OpenClaw. This single binary brings together BM25, vector search, and reranking, and it can process content outside your workspace memory files.
What it adds over builtin
- Reranking and query expansion deliver improved recall.
- Index extra directories - project documentation, team notes, any disk content.
- Index session transcripts - revisit past conversations.
- Fully local - works with the official llama.cpp provider plugin and automatically downloads GGUF models.
- Automatic fallback - when QMD is not reachable, OpenClaw silently reverts to the builtin engine.
Getting started
Prerequisites
- Install QMD:
npm install -g @tobilu/qmdorbun install -g @tobilu/qmd - A SQLite build that supports extensions (
brew install sqliteon macOS). - QMD must reside on the gateway's
PATH. - macOS and Linux function without additional setup. Windows works best through WSL2.
Enable
{
memory: {
backend: "qmd",
},
}
OpenClaw creates a dedicated QMD home under ~/.openclaw/agents/<agentId>/qmd/ and handles the sidecar lifecycle automatically - collections, updates, and embedding tasks are managed for you. It defaults to current QMD collection and MCP query patterns but falls back to alternative collection-pattern flags and older MCP tool names when necessary. During startup reconciliation, stale managed collections are recreated to match their canonical patterns if an older QMD collection with the same name still exists.
How the sidecar works
- OpenClaw builds collections from workspace memory files and configured
memory.qmd.paths. The QMD adapter manages update, embedding, debounce, and timeout logic; these are not user-configurable. - QMD retains ownership of its
index.sqlite, YAML collection configuration, and model downloads within each agent's QMD home - these are external-tool artifacts, not OpenClaw state tables. OpenClaw-managed coordination exists only in SQLite: one shared lease limits embedding work across agents, while one lease per agent database serializes that agent's collection, update, and embed writes. The runtime no longer creates QMD file-lock sidecars.openclaw doctor --fixremoves retired sidecars only after confirming their old process owner is no longer active. Upgrades involve a clean cutover: stop and restart every OpenClaw process sharing the state directory before using the new version. Mixed old and new QMD writers are not supported; the runtime intentionally avoids dual-locking retired sidecars. - The default workspace collection tracks
MEMORY.mdalong with thememory/tree. Lowercasememory.mdis excluded from indexing as a root memory file. - QMD's scanner skips hidden paths and common dependency or build directories including
.git,.cache,node_modules,vendor,dist, andbuild. Gateway startup keeps QMD in a lazy state; the manager initializes only when memory is first accessed. - Searches use the configured
searchMode(default:search; also supportsvsearchandquery).searchoperates as BM25-only, so OpenClaw skips semantic vector readiness checks and embedding maintenance in this mode. If a mode fails, OpenClaw retries withqmd query. - When
searchModeis set toquery, configurememory.qmd.reranktofalseto use QMD's hybrid query path without the reranker (requires QMD 2.1 or newer). OpenClaw passes--no-rerankto the direct QMD CLI path andrerank: falseto QMD's MCP query tool. - With QMD versions that support multi-collection filters, OpenClaw groups collections from the same source into a single QMD search call. Older QMD releases retain the compatible per-collection fallback.
- If QMD fails completely, OpenClaw falls back to the builtin SQLite engine. Repeated chat-turn attempts back off briefly after an open failure to prevent a retry storm from a missing binary or broken sidecar dependency;
openclaw memory statusand one-shot CLI probes still check QMD directly.
Info
The initial query might take a while. QMD automatically downloads GGUF models (roughly 2 GB) for reranking and query expansion during the first
qmd queryexecution.
Search performance and compatibility
OpenClaw ensures the QMD search path works with both recent and legacy QMD installations.
At startup, OpenClaw inspects the installed QMD help text once per manager. When the binary indicates support for multiple collection filters, OpenClaw queries all collections from the same source using a single command:
qmd search "router notes" --json -n 10 -c memory-root-main -c memory-dir-main
This eliminates the need to launch a separate QMD subprocess for each durable memory collection. Session transcript collections remain in their own source group, so combined memory and sessions searches still provide the result diversifier with input from both sources.
Older QMD versions accept only a single collection filter. When OpenClaw identifies such a build, it follows the compatibility path, searching each collection independently before merging and deduplicating the results.
To check the installed contract manually, use:
qmd --help | grep -i collection
Current QMD help mentions targeting one or more collections. Older help typically references only a single collection.
Model overrides
QMD model environment variables pass through unchanged from the gateway process, letting you adjust QMD globally without introducing new OpenClaw configuration:
export QMD_EMBED_MODEL="hf:Qwen/Qwen3-Embedding-0.6B-GGUF/Qwen3-Embedding-0.6B-Q8_0.gguf"
export QMD_RERANK_MODEL="/absolute/path/to/reranker.gguf"
export QMD_GENERATE_MODEL="/absolute/path/to/generator.gguf"
After switching the embedding model, regenerate embeddings so the index aligns with the new vector space.
Indexing extra paths
Point QMD at extra directories to make them searchable:
{
memory: {
backend: "qmd",
qmd: {
paths: [{ name: "docs", path: "~/notes", pattern: "**/*.md" }],
},
},
}
Snippets from additional paths appear as qmd/<collection>/<relative-path> in search results. memory_get recognizes this prefix and retrieves content from the correct collection root.
Indexing session transcripts
Turn on session indexing to recall past conversations. QMD requires both the general memory.search session source and the QMD transcript exporter:
{
memory: {
backend: "qmd",
search: {
experimental: { sessionMemory: true },
sources: ["memory", "sessions"],
},
qmd: {
sessions: { enabled: true },
},
},
}
Transcripts are exported as sanitized User/Assistant turns into a dedicated QMD collection under ~/.openclaw/agents/<id>/qmd/sessions/. Enabling only sources: ["sessions"] does not export transcripts into QMD; also activate rememberAcrossConversations or explicit QMD session export.
Session hits remain subject to filtering through
tools.sessions.visibility. By
default, tree visibility encompasses the current session, any sessions it
spawned, and sessions from the same agent group that are observed through ambient
group awareness. When session.dmScope: "main" is enabled, participants in a multi-user DM
environment share the primary session and can retrieve content from its watched
groups. For DM isolation, apply a per-peer
dmScope, or configure visibility to "self" to
disable ambient reading of watched sessions. Accessing other unrelated sessions
belonging to the same agent still demands
"agent" visibility.
Search scope
By default, QMD search results appear only in direct sessions, not in group or
channel chats. Modify memory.qmd.scope to alter this behavior:
{
memory: {
qmd: {
scope: {
default: "deny",
rules: [{ action: "allow", match: { chatType: "direct" } }],
},
},
},
}
The rule shown above is the actual default. When a search is denied by scope, OpenClaw logs a warning that includes the derived channel and chat type, making empty results simpler to troubleshoot.
Citations
When memory.citations is set to auto or
on, a Source: <path>#L<line> (or
#L<start>-L<end>) footer gets appended to search snippets. In
auto mode, the footer is added exclusively for direct-chat
sessions. Set memory.citations = "off" to suppress the footer while still
forwarding the path internally to the agent.
When to use
Choose QMD when you require:
- Reranking to achieve better result quality.
- Searching project documentation or notes located outside the workspace.
- Retrieving past session conversations.
- Fully local search without requiring any API keys.
For simpler scenarios, the builtin engine functions well and needs no extra dependencies.
Troubleshooting
QMD not found? Confirm that the binary is located on the gateway's
PATH. If OpenClaw operates as a service, create a symlink:
sudo ln -s ~/.bun/bin/qmd /usr/local/bin/qmd.
If qmd --version functions in your shell but OpenClaw still reports
spawn qmd ENOENT, the gateway process likely has a different
PATH than your interactive shell. Explicitly pin the binary:
{
memory: {
backend: "qmd",
qmd: {
command: "/absolute/path/to/qmd",
},
},
}
Run command -v qmd in the environment where QMD is installed, then verify
again with openclaw memory status --deep.
First search extremely slow? QMD downloads GGUF models on its first use.
Pre-warm it with qmd query "test" using the same XDG directories that
OpenClaw uses.
Many QMD subprocesses during search? Update QMD if possible. OpenClaw
spawns only one process for multi-collection searches from the same source when
the installed QMD declares support for multiple -c filters;
otherwise it falls back to the older per-collection approach for correctness.
BM25-only QMD still attempting to build llama.cpp? Set
memory.qmd.searchMode = "search". OpenClaw treats that mode as purely
lexical, skips QMD vector status probes and embedding maintenance, and delegates
semantic readiness checks to vsearch or query configurations.
Search timing out? Raise memory.qmd.limits.timeoutMs (default: 4000ms).
Set it higher, for instance 120000, on slower hardware. This
timeout applies to QMD's own search commands during agent
memory_search calls; setup, sync, builtin fallback, and supplemental
corpus operations each have their own shorter deadlines.
Empty results in group or channel chats? This is expected given the
default memory.qmd.scope, which permits only direct sessions. Add an
allow rule for group or
channel chat types if you want QMD results there.
Root memory search suddenly too broad? Restart the gateway or wait
for the next startup reconciliation. OpenClaw recreates stale managed
collections back to canonical MEMORY.md and
memory/ patterns when it detects a same-name conflict.
Workspace-visible temp repos causing ENAMETOOLONG or broken indexing?
QMD traversal follows the underlying QMD scanner rather than OpenClaw's
builtin symlink rules. Keep temporary monorepo checkouts under hidden
directories like .tmp/ or outside indexed QMD roots until QMD exposes
cycle-safe traversal or explicit exclusion controls.
Configuration
For the complete configuration surface (memory.qmd.*), search modes, update
intervals, scope rules, and all other settings, refer to the
Memory configuration reference.