Skip to content

fix(memory): use per-keyword FTS search in hybrid mode #39484#39555

Closed
yangzi33 wants to merge 3 commits into
openclaw:mainfrom
yangzi33:yangzi33/memory_search-0hit-fix
Closed

fix(memory): use per-keyword FTS search in hybrid mode #39484#39555
yangzi33 wants to merge 3 commits into
openclaw:mainfrom
yangzi33:yangzi33/memory_search-0hit-fix

Conversation

@yangzi33

@yangzi33 yangzi33 commented Mar 8, 2026

Copy link
Copy Markdown

Summary

  • Problem: memory_search returns 0 hits for multi-word queries (e.g. Rocky Point bagging, retail garden products) in hybrid mode, even when the terms appear 17+ times across indexed files.
  • Why it matters: Users relying on memory search for domain-specific multi-word terms get silently empty results with no indication of why, making the feature appear broken.
  • What changed: Hybrid mode now searches each extracted keyword individually and merges results by highest score (OR-style recall), matching the strategy already used by FTS-only mode.
  • What did NOT change (scope boundary): buildFtsQuery, searchKeyword, the FTS-only code path, vector search, scoring weights, and all other memory subsystems are untouched.
  • Note that test cases were written with assists of AI.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

User-visible / Behavior Changes

Multi-word memory_search queries in hybrid mode now return results when the query terms span separate chunks. Previously these returned 0 hits. Single-word queries and FTS-only mode behavior are unchanged.

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Environment

  • OS: Linux (WSL2 / native)
  • Runtime/container: Node 22+
  • Model/provider: any (bug is in FTS path, provider-independent)
  • Integration/channel (if any): N/A
  • Relevant config (redacted): memorySearch.query.hybrid.enabled: true (default)

Steps

  1. Index a document where query terms appear in separate sections (e.g. "Rocky Point" in one paragraph, "bagging" in another).
  2. Run memory_search with query Rocky Point bagging.
  3. Observe result count.

Expected

  • At least one result returned referencing the indexed document.

Actual

  • Before fix: 0 results returned despite 17+ term instances across indexed files.
  • After fix: results returned correctly.

Evidence

  • Failing test/log before + passing after

src/memory/manager.hybrid-keyword-multi.test.ts — 2 tests fail against the reverted manager.ts (0 results asserted > 0), both pass with the fix applied.

Human Verification (required)

  • Verified scenarios: Multi-word cross-chunk query (Rocky Point bagging); two-word queries (bagging operations, retail garden products); single-word queries unaffected; FTS-only mode unaffected.
  • Edge cases checked: Reverted manager.ts confirms tests correctly catch the regression; restored fix confirms green.
  • What you did not verify: Live index with real embedding provider; very large documents with hundreds of chunks; non-English keyword extraction paths.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: revert src/memory/manager.ts to parent commit (3eec79bd6).
  • Files/config to restore: src/memory/manager.ts only.
  • Known bad symptoms reviewers should watch for: hybrid keyword search returning unexpected results for single-word queries (should not occur — single-keyword path is identical before and after).

Risks and Mitigations

  • Risk: per-keyword FTS searches increase the number of SQLite MATCH queries from 1 to N per hybrid search call.
  • Mitigation: extractKeywords already strips stop words and short tokens, keeping N small for typical queries (2–5 terms). The candidates multiplier already caps each individual lookup at 200 rows. No change for single-word queries.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 47aad9c6ad

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/memory/manager.ts Outdated
@greptile-apps

greptile-apps Bot commented Mar 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a bug in hybrid memory search mode where multi-word queries (e.g. Rocky Point bagging) returned 0 results because the full query string was passed to FTS5 as an AND query, requiring all tokens to co-occur in the same chunk. The fix introduces searchKeywordsMulti, which mirrors the FTS-only mode strategy: extract individual keywords, search each in parallel, and merge by highest textScore per chunk id.

Key changes:

  • src/memory/manager.ts: New private searchKeywordsMulti method replaces the single searchKeyword(cleaned, candidates) call in hybrid mode. The merging logic (dedup by chunk id, sort by textScore, slice to limit) is correct and consistent with the FTS-only path.
  • src/memory/manager.hybrid-keyword-multi.test.ts: Well-constructed regression tests using 8-token chunks and flat embeddings to isolate the keyword path, directly reproducing the reported failure.

Implementation quality: The refactoring maintains consistency with the FTS-only search path: individual searchKeyword calls are protected with .catch() at line 408, while extractKeywords (a pure string tokenization function) is unprotected in both code paths. This is appropriate error handling — FTS errors are gracefully caught, while logic errors surface for investigation.

Confidence Score: 5/5

  • Safe to merge — the fix correctly addresses the reported bug with well-tested, consistent error handling.
  • The implementation is correct and follows established patterns in the codebase. The searchKeywordsMulti method properly mirrors the FTS-only search strategy by extracting keywords individually, searching each in parallel, and merging by highest textScore. Error handling is appropriate and consistent — FTS errors from individual searchKeyword calls are gracefully caught, while pure string operations behave the same as in FTS-only mode. Regression tests directly validate the fix for the reported multi-word query issue.
  • No files require special attention

Last reviewed commit: 47aad9c

@yangzi33
yangzi33 force-pushed the yangzi33/memory_search-0hit-fix branch from 9186827 to 47aad9c Compare March 8, 2026 07:06
@chatgpt-codex-connector

Copy link
Copy Markdown
Contributor

💡 Codex Review

https://github.com/openclaw/openclaw/blob/9186827477d361f6ccda5042c864b450d6a1290b/.secrets.baseline#L121-L124
P2 Badge Restore detect-secrets excludes in baseline metadata

This baseline dropped the filters.regex.should_exclude_file / filters.regex.should_exclude_line entries, so detect-secrets scan --baseline .secrets.baseline (the documented local workflow in SECURITY.md) no longer carries the repo’s intended excludes and will start flagging churn from files like pnpm-lock.yaml or known fixture lines whenever they change. CI’s pre-commit hook still passes explicit excludes from .pre-commit-config.yaml, so this mainly regresses local reproducibility and makes baseline regeneration noisy/inconsistent unless those filters are restored in the baseline.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@openclaw-barnacle

Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Apr 24, 2026
@clawsweeper

clawsweeper Bot commented Apr 26, 2026

Copy link
Copy Markdown
Contributor

Codex review: keeping this open for maintainer follow-up; there is still a little grit to resolve.

Keep this PR open. Current main still does not implement the intended provider-backed hybrid memory-search behavior: it only fans out per keyword in the no-provider FTS-only branch, while provider-backed hybrid search still performs one full-query FTS lookup. The live PR remains open, now has a bounded fan-out repair at head 8ad671b, and maintainer context keeps it as the canonical contributor path after #57711 was closed unmerged.

Best possible solution:

Keep this PR open for maintainer review and landing. The best path is to rebase or port the bounded searchKeywordsMulti implementation into extensions/memory-core/src/memory/manager.ts, keep provider-backed split multi-word regression coverage in extensions/memory-core/src/memory/index.test.ts, preserve the changelog credit, and run the changed gate before merge.

What I checked:

  • Current provider-backed hybrid path still uses one full-query FTS lookup: After the no-provider branch, current main computes keywordResults with a single searchKeyword(cleaned, candidates, undefined, sourceFilterList) call when hybrid/FTS are enabled. There is no searchKeywordsMulti helper or HYBRID_KEYWORD_FANOUT_LIMIT in the provider-backed path. (extensions/memory-core/src/memory/manager.ts:428, a820a307dfeb)
  • Per-keyword fan-out is limited to the FTS-only branch on current main: Current main extracts keywords and fans out only inside if (!this.provider), after full-query FTS returns no results. That does not cover the provider-backed hybrid case described by this PR. (extensions/memory-core/src/memory/manager.ts:369, a820a307dfeb)
  • Full-query FTS still AND-joins tokens: buildFtsQuery tokenizes the raw query, quotes each token, and joins them with AND, so a single multi-word MATCH query can still require all query tokens in the same FTS row/chunk. (extensions/memory-core/src/memory/hybrid.ts:33, a820a307dfeb)
  • Current main lacks this regression coverage: Searches for the PR's markers and examples found no current-main implementation or test coverage. The existing hybrid keyword tests in index.test.ts are skipped generic cases, not provider-backed split multi-word regression coverage. (extensions/memory-core/src/memory/index.test.ts:383, a820a307dfeb)
  • PR branch implements bounded provider-backed fan-out: The live PR diff adds HYBRID_KEYWORD_FANOUT_LIMIT = 8, adds searchKeywordsMulti/resolveHybridKeywordTerms, replaces the provider-backed single searchKeyword call with searchKeywordsMulti, and adds a provider-backed split multi-word test that asserts MATCH fan-out stays <= 8. (extensions/memory-core/src/memory/manager.ts:62, 8ad671b64b9c)
  • Related PR does not supersede this one: Live API data shows feat(memory): enable query expansion in hybrid search mode #57711 is closed and not merged, while this PR is still open. A maintainer comment says ProjectClownfish pushed a narrow repair to this branch so the original contributor path can stay canonical.

Likely related people:

  • steipete: Recent current-main history for extensions/memory-core/src/memory/manager.ts and index.test.ts is heavily maintained by steipete, including memory manager/status/search preflight refactors and recent memory CLI/status fixes. The local blame for the inspected current-main search region also routes through the current shallow checkout to Peter Steinberger. (role: current-main memory-core maintainer and recent refactor owner; confidence: high; commits: df65a75f92a3, 9dcd53c0b676, ca27d932b4ff; files: extensions/memory-core/src/memory/manager.ts, extensions/memory-core/src/memory/index.test.ts, extensions/memory-core/src/memory/hybrid.ts)
  • vincentkoc: Live PR comments say vincentkoc pushed the narrow ProjectClownfish repair to keep this contributor path canonical. Current-main history also shows vincentkoc touching memory-core manager/tests and related plugin cleanup, making him a strong routing candidate for review and landing. (role: recent memory-core maintainer and PR branch repair author; confidence: high; commits: 8ad671b64b9c, ca26489fe882, 24c39de9c143; files: extensions/memory-core/src/memory/manager.ts, extensions/memory-core/src/memory/index.test.ts, CHANGELOG.md)
  • irchelper: Merged PR feat: FTS fallback + query expansion for memory search #18304 introduced FTS fallback plus query expansion for memory search in the older src/memory layout, including manager.ts and query-expansion.ts. This PR is a follow-up to that feature because current main only applies the expansion strategy in the FTS-only path, not provider-backed hybrid search. (role: original merged query-expansion / FTS fallback contributor; confidence: medium; commits: de98782c3736; files: src/memory/manager.ts, src/memory/query-expansion.ts, src/memory/manager-search.ts)

Remaining risk / open question:

  • The PR intentionally broadens lexical recall in provider-backed hybrid search, so maintainers should review ranking and precision before landing.
  • Per-keyword FTS fan-out increases SQLite MATCH work; the current branch caps fan-out at 8 terms, but representative-index performance sanity is still useful.
  • The PR base is older than the checked-out main, so it likely needs a careful rebase around recent memory-core manager and index-test changes.
  • Security-sensitive risk appears low in the current diff because it avoids CI, dependencies, lockfiles, package scripts, generated/vendor files, permissions, and secret-handling surfaces.

Codex review notes: model gpt-5.5, reasoning high; reviewed against a820a307dfeb.

@openclaw-barnacle openclaw-barnacle Bot removed the stale Marked as stale due to inactivity label Apr 26, 2026
@vincentkoc

Copy link
Copy Markdown
Member

ProjectClownfish pushed a narrow repair to this branch so the original contributor path can stay canonical.

Source PR: #39555
Validation: pnpm check:changed
Contributor credit is preserved in the branch history and PR context.

@openclaw-clownfish

Copy link
Copy Markdown
Contributor

ProjectClownfish could not safely update this branch, so it opened a narrow replacement PR instead.

Replacement PR: #73976
Source PR: #39555
Contributor credit is preserved in the replacement PR body and changelog plan.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clawsweeper Tracked by ClawSweeper automation extensions: memory-core Extension: memory-core size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

memory_search returns 0 hits for specific terms despite indexed content

2 participants