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/qmd or bun install -g @tobilu/qmd
  • A SQLite build that supports extensions (brew install sqlite on 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 --fix removes 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.md along with the memory/ tree. Lowercase memory.md is 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, and build. 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 supports vsearch and query). search operates as BM25-only, so OpenClaw skips semantic vector readiness checks and embedding maintenance in this mode. If a mode fails, OpenClaw retries with qmd query.
  • When searchMode is set to query, configure 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.
  • 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 status and 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 query execution.

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.