Skip to content

Reduce QMD memory search wrapper overhead in CLI and tool paths #96561

Description

@bek91

QMD-backed memory search adds repeated wrapper overhead in CLI and tool paths

What Problem This Solves

QMD-backed memory search can take several seconds longer through OpenClaw than through direct QMD calls.

Two measured paths show the gap:

  • openclaw memory search CLI: traced child-process time shows repeated setup/probe/search subprocess work per invocation.
  • Standard memory_search tool: wall time is much higher than the tool result's own debug.searchMs, which points at fixed work before or outside the measured search body.

This makes interactive memory recall feel slow in CLI/debug workflows and in agent tool use. The issue is not that memory + sessions is a bad configuration, and it does not appear to be caused by QMD embeddings, ABI, or a large timeout. The problem appears to be OpenClaw wrapper architecture paying repeated cold-path or pre-search costs.

Observed Behavior

CLI Trace

A traced openclaw memory search invocation with QMD showed this shape:

collection list --json                         180ms
collection show memory-root-main               155ms
collection show memory-dir-main                157ms
collection show workspace-main                 171ms
collection show sessions-main                  153ms
qmd --help                                     147ms
vsearch memory collections                    1019ms
vsearch sessions-main                          988ms

openclaw memory search total:                  5.59s
time inside QMD children:                     ~2.97s
wrapper/OpenClaw overhead:                    ~2.6s

Relevant config in that environment:

memory.qmd.searchMode = "vsearch"
agents.defaults.memorySearch.sources = ["memory", "sessions"]
memory.qmd.limits.timeoutMs = 30000

timeoutMs = 30000 does not appear to cause the latency; it only raises the ceiling so searches do not hit a shorter timeout. The relevant behavior is that memory + sessions currently multiplies subprocess work.

Tool Timing

The standard OpenClaw memory_search tool path was also timed separately and showed similar overhead above direct QMD:

openclaw__memory_search query="qmd latency openclaw memory embedding" maxResults=5: 11.27s
repeat, all sources, maxResults=5:                                             8.43s
repeat, all sources, maxResults=1:                                             8.63s
memory corpus only:                                                            7.18s
sessions corpus only:                                                          6.95s

direct QMD single/vector-ish path:                                             ~1.0-1.5s
openclaw memory search CLI:                                                    ~5.6-9.9s
standard memory_search tool:                                                   ~7-11s

maxResults=1 did not materially improve tool-call timing, and corpus-specific searches were still around 7s. That points away from result volume and toward fixed wrapper/search-path overhead.

A later trace compared wall time against the tool result's own debug.searchMs field. From source, debug.searchMs starts after manager acquisition and covers the measured search body plus tool-side filtering/decorating, so the gap points at work before or outside that measured search body:

all sources, maxResults=5
wall: 7183ms
debug.searchMs: 2383ms
outside debug timer: 4800ms

all sources, maxResults=5 repeat
wall: 7774ms
debug.searchMs: 2818ms
outside debug timer: 4956ms

all sources, maxResults=1
wall: 7209ms
debug.searchMs: 2420ms
outside debug timer: 4789ms

memory corpus only
wall: 6263ms
debug.searchMs: 1464ms
outside debug timer: 4799ms

sessions corpus only
wall: 6149ms
debug.searchMs: 1324ms
outside debug timer: 4825ms

The stable ~4.8-5.0s outside debug.searchMs suggests fixed pre-search overhead in the standard tool path.

Current Flow

CLI Path

Source trace:

  • openclaw memory search is registered in extensions/memory-core/src/cli.ts.
  • runMemorySearch loads config/secrets, resolves the agent, and calls withMemoryManagerForAgent with purpose: "cli" in extensions/memory-core/src/cli.runtime.ts.
  • getMemorySearchManager treats purpose: "cli" as transient in extensions/memory-core/src/memory/search-manager.ts.
  • QMD manager creation probes the QMD binary and initializes a fresh QmdMemoryManager.
  • QMD manager initialization ensures collections, including qmd collection list --json and possible qmd collection show <name> calls in extensions/memory-core/src/memory/qmd-manager.ts.
  • Search may then probe QMD multi-collection filter support with qmd --help.

Tool Path

Source trace:

  • memory_search is registered by extensions/memory-core/index.ts and executed by extensions/memory-core/src/tools.ts.
  • Normal Gateway/tool calls should request a cached full manager because oneShotCliRun is false. Local one-shot CLI agent runs can set oneShotCliRun, which forces purpose: "cli" and closes the manager after the call.
  • getMemoryManagerContextWithPurpose / getMemorySearchManager runs before searchStartedAt, so repeated manager acquisition/init/cache-miss cost appears outside debug.searchMs.
  • The tool then calls activeMemory.manager.search(...), records debug.searchMs, filters session visibility, decorates citations, clamps results, and may queue recall tracking.
  • If the first result set is empty, the tool forces manager.sync({ reason: "search", force: true }) and repeats the search.
  • Gateway /tools.invoke resolves tools, policy, and before-tool-call hooks before plugin execute; that work also sits outside the tool's debug.searchMs.

Shared QMD Search Behavior

  • With memory plus sessions, OpenClaw can group collections by source and run separate sequential QMD searches, then normalize, dedupe, diversify, and clamp results in JS.
  • Even when QMD supports multiple -c filters, OpenClaw may still group collections by source and run separate searches to preserve source behavior/diversity.
  • QMD multi-collection support probing is cached on the manager instance, so cache misses or transient managers can repeat qmd --help.

Why This Matters

The CLI path pays setup/probe/collection validation cost every search because the manager is transient. Capability and collection probes are cached only on the manager instance, so the CLI path loses those caches after each command exits.

The standard tool path is not identical to CLI, but measured wall time exceeds debug.searchMs by ~4.8-5.0s on repeated runs. That means the fix needs instrumentation and optimization before the measured search body, not only inside QMD search execution.

Memory + sessions is a useful configuration. The performance issue is that this configuration currently multiplies QMD subprocess work and may also expose fixed manager/tool wrapper overhead.

Proposed Fixes

Preferred fixes, in order:

  1. Instrument the current path before changing behavior: report wall time, debug.searchMs, manager lookup/init time, QMD manager cache hit/miss/identity key status, collection validation time, Gateway tool resolution/hook time, and individual QMD child timings.
  2. Cache or skip repeated collection validation and QMD support probes when the managed collection config has not changed. A safe cache key/invalidation shape could include agent id, QMD command path plus QMD version, managed collection config hash, QMD index/config path, search mode, and source set.
  3. Search all relevant QMD collections in one call, request enough candidates, then preserve source filtering/diversity in JS. For example, run one qmd vsearch <query> --json -n <maxResults * sourceCount * candidateMultiplier> -c <all collections...> call, then dedupe/diversify by source in OpenClaw.
  4. If separate source-group searches must stay, run them concurrently with bounded concurrency and shared cancellation when it improves wall time and does not create SQLite/model/CPU/Metal contention.
  5. Ensure the standard memory_search tool path reuses a warm full QMD manager when possible, and identify any identity churn or cache miss that makes repeated tool calls pay manager setup.
  6. Longer-term, route CLI memory search through a warm gateway-resident QMD manager or daemon path so CLI searches do not pay manager cold-start costs.
  7. Consider mcporter daemon usage later, after removing repeated wrapper work.

Acceptance Criteria

  • A repeated openclaw memory search with unchanged QMD config does not run collection list, per-collection collection show, and qmd --help on every invocation.
  • Standard memory_search tool verification reports wall time, returned debug.searchMs, manager lookup/init timing, Gateway/plugin wrapper timing, and the gap between those measurements.
  • Repeated standard memory_search tool calls show a warm-manager path, or the issue explains why a manager cache miss/identity change is required.
  • Searching memory plus sessions avoids sequential full-cost QMD searches when QMD can answer across all collections in one call, or runs grouped searches concurrently when splitting is required and safe.
  • Source filtering/diversity semantics remain covered by tests.
  • Latency evidence before/after includes both openclaw memory search CLI timing and standard memory_search tool-call timing, or explicitly explains why one path is unaffected.

Notes

Related but not exact duplicates:

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:needs-product-decisionClawSweeper marked this issue as needing a product or behavior decision.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.maintainerMaintainer-authored PR

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions