Memory search: finding relevant notes with embeddings and hybrid retrieval

Learn how memory search uses embeddings, keywords, or both to find relevant notes from your memory files. This guide is for developers configuring memory search in OpenClaw.

Read this when

  • You want to understand how memory_search works
  • You want to choose an embedding provider
  • You want to tune search quality

memory_search pulls relevant notes from your memory files, even when the wording doesn't match the original text. It breaks memory into small pieces and searches them using embeddings, keywords, or both.

Quick start

OpenClaw defaults to OpenAI embeddings. To switch to a different provider, set it explicitly:

{
  memory: {
    search: {
      provider: "openai", // or "gemini", "voyage", "mistral", "bedrock", "local", "ollama", "lmstudio", "github-copilot", "openai-compatible"
    },
  },
}

provider can also point to a custom models.providers.<id> entry (for example ollama-5080), as long as that entry has api set to "ollama" or another provider ID with a memory embedding adapter.

For local embeddings without an API key, install the official llama.cpp provider plugin and set provider: "local":

openclaw plugins install @openclaw/llama-cpp-provider

Source checkouts still need native build approval: pnpm approve-builds, then pnpm rebuild node-llama-cpp.

Some OpenAI-compatible embedding endpoints need asymmetric input_type labels, like "query" for searches and "document"/"passage" for indexed chunks. Configure these with queryInputType and documentInputType; see Memory configuration reference.

Supported providers

ProviderIDNeeds API keyNotes
BedrockbedrockNoUses the AWS credential chain
DeepInfradeepinfraYesDefault model BAAI/bge-m3
GeminigeminiYesSupports image/audio indexing
GitHub Copilotgithub-copilotNoUses your Copilot subscription
LocallocalNoGGUF model, ~0.6 GB auto-download
LM StudiolmstudioNoLocal/self-hosted server
MistralmistralYes
OllamaollamaNoLocal/self-hosted server
OpenAIopenaiYesDefault
OpenAI-compatibleopenai-compatibleUsuallyGeneric /v1/embeddings endpoint
VoyagevoyageYes

How search works

OpenClaw runs two retrieval paths in parallel and merges the results:

flowchart LR
    Q["Query"] --> E["Embedding"]
    Q --> T["Tokenize"]
    E --> VS["Vector search"]
    T --> BM["BM25 search"]
    VS --> M["Weighted merge"]
    BM --> M
    M --> R["Top results"]
  • Vector search matches similar meaning ("gateway host" matches "the machine running OpenClaw").
  • BM25 keyword search matches exact terms (IDs, error strings, config keys).
  • Filename search indexes paths separately from note bodies. Exact full paths, basenames, and filename stems rank ahead of partial path matches, while snippets and body keyword scores still come from note content.

If only one path is available, the other runs alone.

FTS-only mode. Set provider: "none" to intentionally disable embeddings and search with keywords only. Leaving provider unset or set to "auto" also falls back to keyword-only ranking if no embedding auth is configured, without erroring, and so does provider: "local" (the GGUF/llama.cpp provider) when it fails.

Explicit provider unavailable. If you name any other provider explicitly (for example openai, ollama, gemini) and it becomes unavailable at request time (bad auth, network failure), memory_search reports memory as unavailable instead of silently degrading to FTS-only results. This keeps a broken configured provider visible. Set provider: "none" for deliberate FTS-only recall, or fix the provider/auth configuration to restore semantic ranking.

Improving search quality

Two optional features help with a large note history.

Temporal decay

Old notes gradually lose ranking weight so recent information surfaces first. With the default 30-day half-life, a note from last month scores at 50% of its original weight. MEMORY.md and other non-dated files under memory/ are evergreen and never decayed; only dated memory/YYYY-MM-DD.md files decay.

Tip

Enable this if your agent has months of daily notes and stale information keeps outranking recent context.

MMR (diversity)

Reduces redundant results. If five notes all mention the same router config, MMR ensures the top results cover different topics instead of repeating.

Tip

Enable this if memory_search keeps returning near-duplicate snippets from different daily notes.

Enable both

{
  memory: {
    search: {
      query: {
        hybrid: {
          mmr: { enabled: true },
          temporalDecay: { enabled: true },
        },
      },
    },
  },
}

Multimodal memory

With gemini-embedding-2-preview, you can index images and audio alongside Markdown. This only applies to files under memory.search.extraPaths; default memory roots (MEMORY.md, memory/*.md) stay Markdown-only. Search queries remain text, but they match against visual and audio content. See Memory configuration reference for setup.

For exact full-text recall from session transcripts, use sessions_search and then open a result with sessions_history. Session-memory search remains the semantic, experimental complement.

Optionally index session transcripts so memory_search can recall earlier conversations. This is opt-in: set experimental.sessionMemory: true and add "sessions" to sources (default sources is ["memory"]).

Session hits obey tools.sessions.visibility: the default "tree" exposes the current session, sessions it spawned, and same-agent group sessions watched through ambient group awareness. With session.dmScope: "main", a multi-user DM setup shares that main session, so users routed there can recall content from its watched groups. Use a per-peer dmScope for DM isolation, or set visibility to "self" to opt out of ambient watched-session reads. Other unrelated same-agent sessions still require "agent" visibility.

When using the QMD backend, also set memory.qmd.sessions.enabled: true so transcripts get exported into the QMD collection; experimental.sessionMemory and sources alone do not export transcripts into QMD. See configuration reference.

Troubleshooting

No results? Run openclaw memory status to check the index. If empty, run openclaw memory index --force.

Only keyword matches? Your embedding provider may not be configured. Check openclaw memory status --deep.

Local embeddings time out? ollama, lmstudio, and local use longer provider-owned batch deadlines. Check provider health and rerun openclaw memory index --force.

CJK text not found? Rebuild the FTS index with openclaw memory index --force.