docs/concepts/memory-qmd.md
QMD is a local-first search sidecar that runs alongside OpenClaw. It combines BM25, vector search, and reranking in a single binary, and can index content beyond your workspace memory files.
npm install -g @tobilu/qmd or bun install -g @tobilu/qmdbrew install sqlite on macOS).PATH.{
memory: {
backend: "qmd",
},
}
OpenClaw creates a self-contained QMD home under
~/.openclaw/agents/<agentId>/qmd/ and manages the sidecar lifecycle
automatically - collections, updates, and embedding runs are handled for you.
It prefers current QMD collection and MCP query shapes, but falls back to
alternate collection-pattern flags and older MCP tool names when needed.
Startup reconciliation also recreates stale managed collections back to their
canonical patterns when an older QMD collection with the same name is still
present.
memory.qmd.paths, then runs qmd update when the QMD manager
opens and periodically afterward (memory.qmd.update.interval, default
5m). Refreshes run through QMD subprocesses, not an in-process filesystem
crawl. Semantic search modes also run qmd embed
(memory.qmd.update.embedInterval, default 60m).MEMORY.md plus the memory/
tree. Lowercase memory.md is not indexed as a root memory file..git, .cache, node_modules, vendor, dist, and
build. Gateway startup does not initialize QMD by default
(memory.qmd.update.startup defaults to off), so cold boot avoids
importing the memory runtime or creating the long-lived watcher before
memory is first used.memory.qmd.update.startup to idle or immediate to initialize QMD
at gateway start anyway. memory.qmd.update.onBoot defaults to true and
runs the initial refresh at startup; set it to false to skip that
immediate refresh (the long-lived manager still opens when update or embed
intervals are configured, so QMD keeps owning its regular watcher/timers).searchMode (default: search; also supports
vsearch and query). search is BM25-only, so OpenClaw skips semantic
vector readiness probes and embedding maintenance in that mode. If a mode
fails, OpenClaw retries with qmd query.searchMode is query, set memory.qmd.rerank to false to use
QMD's hybrid query path without the reranker (requires QMD 2.1 or newer).
OpenClaw passes --no-rerank to the direct QMD CLI path and
rerank: false to QMD's MCP query tool.openclaw memory status and one-shot CLI probes still recheck QMD
directly.OpenClaw keeps the QMD search path compatible with both current and older QMD installs.
On startup, OpenClaw checks the installed QMD help text once per manager. If the binary advertises support for multiple collection filters, OpenClaw searches all same-source collections with one command:
qmd search "router notes" --json -n 10 -c memory-root-main -c memory-dir-main
This avoids starting one QMD subprocess per durable-memory collection.
Session transcript collections stay in their own source group, so mixed
memory + sessions searches still give the result diversifier input from
both sources.
Older QMD builds only accept one collection filter. When OpenClaw detects one of those builds, it keeps the compatibility path and searches each collection separately before merging and deduplicating results.
To inspect the installed contract manually, run:
qmd --help | grep -i collection
Current QMD help mentions targeting one or more collections. Older help usually describes a single collection.
QMD model environment variables pass through unchanged from the gateway process, so you can tune QMD globally without adding new OpenClaw config:
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 changing the embedding model, rerun embeddings so the index matches the new vector space.
Point QMD at additional directories to make them searchable:
{
memory: {
backend: "qmd",
qmd: {
paths: [{ name: "docs", path: "~/notes", pattern: "**/*.md" }],
},
},
}
Snippets from extra paths appear as qmd/<collection>/<relative-path> in
search results. memory_get understands this prefix and reads from the
correct collection root.
Enable session indexing to recall earlier conversations. QMD needs both the
general memorySearch session source and the QMD transcript exporter:
{
agents: {
defaults: {
memorySearch: {
experimental: { sessionMemory: true },
sources: ["memory", "sessions"],
},
},
},
memory: {
backend: "qmd",
qmd: {
sessions: { enabled: true },
},
},
}
Transcripts export as sanitized User/Assistant turns into a dedicated QMD
collection under ~/.openclaw/agents/<id>/qmd/sessions/. Setting only
memorySearch.experimental.sessionMemory does not export transcripts into
QMD.
Session hits are still filtered by
tools.sessions.visibility. The
default tree visibility does not expose unrelated same-agent sessions. If a
gateway-dispatched session should be recallable from a separate DM session,
set tools.sessions.visibility: "agent" intentionally.
By default, QMD search results are surfaced only in direct sessions (not
group or channel chats). Configure memory.qmd.scope to change this:
{
memory: {
qmd: {
scope: {
default: "deny",
rules: [{ action: "allow", match: { chatType: "direct" } }],
},
},
},
}
The snippet above is the actual default rule. When scope denies a search, OpenClaw logs a warning with the derived channel and chat type so empty results are easier to debug.
When memory.citations is auto or on, search snippets get a
Source: <path>#L<line> (or #L<start>-L<end>) footer appended. In auto
mode the footer is added only for direct-chat sessions. Set
memory.citations = "off" to omit the footer while still passing the path to
the agent internally.
Choose QMD when you need:
For simpler setups, the builtin engine works well with no extra dependencies.
QMD not found? Ensure the binary is on the gateway's PATH. If OpenClaw
runs as a service, create a symlink:
sudo ln -s ~/.bun/bin/qmd /usr/local/bin/qmd.
If qmd --version works in your shell but OpenClaw still reports
spawn qmd ENOENT, the gateway process likely has a different PATH than
your interactive shell. Pin the binary explicitly:
{
memory: {
backend: "qmd",
qmd: {
command: "/absolute/path/to/qmd",
},
},
}
Use command -v qmd in the environment where QMD is installed, then recheck
with openclaw memory status --deep.
First search very slow? QMD downloads GGUF models on first use. Pre-warm
with qmd query "test" using the same XDG dirs OpenClaw uses.
Many QMD subprocesses during search? Update QMD if possible. OpenClaw
uses one process for same-source multi-collection searches only when the
installed QMD advertises support for multiple -c filters; otherwise it
keeps the older per-collection fallback for correctness.
BM25-only QMD still trying to build llama.cpp? Set
memory.qmd.searchMode = "search". OpenClaw treats that mode as
lexical-only, skips QMD vector status probes and embedding maintenance, and
leaves semantic readiness checks to vsearch or query setups.
Search times out? Increase memory.qmd.limits.timeoutMs (default:
4000ms). Set it higher, for example 120000, for slower hardware.
Empty results in group or channel chats? This is expected with the
default memory.qmd.scope, which allows only direct sessions. Add an
allow rule for group or channel chat types if you want QMD results
there.
Root memory search suddenly got 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.
For the full config surface (memory.qmd.*), search modes, update intervals,
scope rules, and all other knobs, see the
Memory configuration reference.