Skip to content

[AI-assisted] Fix memory_grep default session recall - #303

Closed
IWhatsskill wants to merge 2 commits into
xDarkicex:mainfrom
IWhatsskill:fix-memory-grep-session-collections-20260602
Closed

[AI-assisted] Fix memory_grep default session recall#303
IWhatsskill wants to merge 2 commits into
xDarkicex:mainfrom
IWhatsskill:fix-memory-grep-session-collections-20260602

Conversation

@IWhatsskill

@IWhatsskill IWhatsskill commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Problem: memory_grep only searched the experimental session_summary:<id> and session_raw:<id> collections, so it could miss active-session recall stored in LibraVDB's default session:<id> collection.
  • Solution: include the default session:<id> collection in memory_grep collection selection while preserving the summary/raw split when those collections exist.
  • What changed: memory_grep now searches a scoped collection set with searchTextCollections when needed, classifies summary vs turn hits from collection/metadata, and deduplicates results.
  • What did NOT change (scope boundary): no daemon, manifest, activation, lifecycle, dependency, lockfile, storage schema, auth, migration, memory_search, or memory_expand behavior changed.

Motivation

memory_grep is the exact-match fallback for active-session recall. After the recall tools were exposed, it still did not search the same default active-session collection used by normal session recall. That made the tool look available but unreliable for exact keys or text that lived under session:<id>.

Change Type

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope

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

Linked Issue/PR

Real behavior proof

  • Behavior or issue addressed: memory_grep now finds exact active-session hits stored under the default session:<id> collection.
  • Real environment tested: Linux throwaway test-server checkout of unmodified remote main (944bbbd) plus this patch, using Node v24.15.0 and pnpm@9.15.9.
  • Exact steps or command run after this patch:
corepack pnpm@9 install --frozen-lockfile --store-dir "$RUN/.pnpm-store"
corepack pnpm@9 exec tsc -p tsconfig.tests.json
node memory-grep-runtime-proof.mjs patched
node --test .ts-build/test/unit/memory-recall.test.js
node --test .ts-build/test/unit/memory-tools.test.js .ts-build/test/unit/memory-runtime.test.js .ts-build/test/unit/before-turn.test.js
node --test .ts-build/test/unit/*.test.js
node --test .ts-build/test/integration/host-flow.test.js
corepack pnpm@9 exec tsc --noEmit
corepack pnpm@9 run build
corepack pnpm@9 run plugin:ci
git diff --check
  • Evidence after fix:
RUNTIME_PROOF_PATCHED_FINDS_DEFAULT_SESSION=1
calls=[{"method":"searchTextCollections","params":{"collections":["session_raw:active-session","session:active-session"],"text":"needle","k":150,"excludeByCollection":{}}}]
details.totalMatches=1
details.turns[0].snippet="needle inside default session collection"
  • Observed result after fix: the default-session hit is returned as a turn result with role user, score 0.88, and the expected snippet.
  • What was not tested: production daemon data with a live OpenClaw packaged host session, external hosted providers, GitHub Actions completion, and maintainer-hosted production runtime.
  • Before evidence:
BASE_HEAD=944bbbd9b3b5899f8e3b07c25cf265a4ccc6b244
RUNTIME_PROOF_BASE_MISSES_DEFAULT_SESSION=1
calls=[{"method":"searchText","params":{"collection":"session_raw:active-session","text":"needle","k":150}}]
details.totalMatches=0

Root Cause

  • Root cause: memory_grep hard-coded session_summary:<id> for summaries and session_raw:<id> for messages, but the default session recall path can store/search hits under session:<id>.
  • Missing detection / guardrail: existing memory_grep coverage only asserted the experimental summary collection, not the default session collection used by ordinary active-session recall.
  • Contributing context (if known): the manifest/tool wiring fix made memory_grep callable, which exposed that its collection routing was narrower than memory_search session recall.

Regression Test Plan

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: test/unit/memory-recall.test.ts
  • Scenario the test should lock in: memory_grep with scope=messages searches both session_raw:<id> and default session:<id>, and returns the default-session hit.
  • Why this is the smallest reliable guardrail: the bug is in plugin-side collection selection before daemon search, so a focused fake-client unit test can assert the outgoing collection set and returned result shape.
  • Existing test that already covers this (if any): none before this patch.
  • If no new test is added, why not: N/A; focused regression coverage was added.

User-visible / Behavior Changes

memory_grep can now find exact active-session text stored in the default session recall collection. Existing summary/raw collection behavior remains available.

Diagram

Before:
memory_grep(scope=messages) -> session_raw:<id> only -> default session hit missed

After:
memory_grep(scope=messages) -> session_raw:<id> + session:<id> -> default session hit returned

Security Impact

  • 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; the added collection is the same active-session namespace already used by LibraVDB session recall.
  • If any Yes, explain risk + mitigation: N/A

Repro + Verification

Environment

  • OS: Linux throwaway test-server checkout
  • Runtime/container: Node v24.15.0, pnpm@9.15.9
  • Model/provider: N/A
  • Integration/channel (if any): LibraVDB recall tool path, fake daemon client for deterministic collection routing
  • Relevant config (redacted): no credentials, tokens, private runtime config, or external provider account used

Steps

  1. Clone unmodified remote main at 944bbbd.
  2. Compile test artifacts with corepack pnpm@9 exec tsc -p tsconfig.tests.json.
  3. Run a deterministic runtime proof where the only hit is in session:active-session.
  4. Confirm unpatched memory_grep searches only session_raw:active-session and returns zero matches.
  5. Apply this patch.
  6. Confirm patched memory_grep searches session_raw:active-session plus session:active-session and returns the hit.
  7. Run targeted, adjacent, TypeScript, build, broad baseline comparison, and plugin-inspector baseline comparison gates.

Expected

  • Unpatched main misses the default-session hit.
  • Patched branch returns the default-session hit.
  • Targeted/adjacent memory tests pass.
  • TypeScript and build pass.
  • Any broad-suite or inspector failures are either green or proven baseline-identical.

Actual

  • Unpatched main missed the default-session hit.
  • Patched branch returned the hit.
  • Targeted and adjacent memory tests passed.
  • TypeScript and build passed.
  • Broad unit, host-flow, and plugin:ci remained red on both base and patched with baseline-identical exits/output class.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)
Targeted memory-recall after patch: tests 7, pass 7, fail 0
Adjacent memory/runtime/before-turn after patch: tests 34, pass 34, fail 0
tsc -p tsconfig.tests.json: pass
tsc --noEmit: pass
pnpm run build: pass
git diff --check: pass

Broad unit comparison:
base exit: 1
patched exit: 1
representative baseline failure class: slot-conflict expectations around recall tools

Host-flow comparison:
base exit: 1
patched exit: 1
representative baseline failure class: existing assembly/compaction expectation drift

Plugin inspector comparison:
base exit: 1
patched exit: 1
baseline output: plugin-inspector runtime capture failed for 1 entrypoints

Human Verification

  • Verified scenarios: default session:<id> message hit, existing summary collection search, scoped messages routing, targeted recall tools, adjacent memory/runtime/before-turn units, TypeScript, production build, whitespace diff check, and baseline comparisons for known red broad gates.
  • Edge cases checked: multi-collection search, deduped collection list, summary vs turn classification from collection and metadata, default active session ID fallback, and no dependency/lockfile changes.
  • What you did not verify: live packaged OpenClaw host session, external hosted providers, GitHub Actions completion, and maintainer-hosted production daemon behavior.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

No bot or reviewer conversations exist yet for this draft PR.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: searching one additional active-session collection could return duplicate hits when a daemon indexes both raw and default session collections.
    • Mitigation: the patch deduplicates by collection and result ID while preserving separate summary and turn result buckets.
  • Risk: default-session results may not include rich summary metadata.
    • Mitigation: default-session hits are classified as turn results unless summary metadata/IDs indicate a summary.
  • Risk: broad repository gates are already red and could be mistaken for this patch.
    • Mitigation: base-vs-patched comparison showed the same exit state/output class for broad unit, host-flow, and plugin:ci; targeted/adjacent/TypeScript/build gates passed.

Summary by CodeRabbit

  • New Features
    • Expanded memory search to query across multiple storage collections with improved result deduplication and organization

@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@IWhatsskill, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 13 minutes and 8 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 59f030dc-de46-4f58-b4d7-3c71aa10a04e

📥 Commits

Reviewing files that changed from the base of the PR and between 71b8a19 and ec85f53.

📒 Files selected for processing (2)
  • src/tools/memory-recall.ts
  • test/unit/memory-recall.test.ts
📝 Walkthrough

Walkthrough

The PR refactors memory_grep to unify multi-collection search instead of separate per-scope calls. It introduces type definitions and helpers for collection management, metadata parsing, and result classification, then applies these to the search implementation while maintaining existing filtering and truncation behavior. Test updates cover the new searchTextCollections behavior.

Changes

Multi-collection grep search

Layer / File(s) Summary
Search helpers and types
src/tools/memory-recall.ts
Introduces GrepSearchResult type with optional metadata and helper utilities for building deduplicated collection lists, selecting search execution (single or multi-collection), parsing metadata JSON, and classifying results as summaries or turns based on collection, id, and metadata fields.
Grep search implementation
src/tools/memory-recall.ts
Replaces separate per-scope client.searchText calls with unified multi-collection search via searchTextCollections, deduplicates results using ${collection}:${id} key, routes matches into summaries or turns using new classifiers, and retains safeMatch, snippet truncation, limit, and MAX_GREP_CHARS behavior.
Test coverage for multi-collection search
test/unit/memory-recall.test.ts
Updates FakeRecallClient with searchTextCollections implementation returning collection-specific metadata; adds DefaultSessionRecallClient for default-session messages search; revises memory_grep tests to assert collections parameter array and validates turn classification and snippet extraction from default session results.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

release:patch

Suggested reviewers

  • fuller-stack-dev

Poem

🐰 A grep that hops from pile to pile,
Collections unified in style!
One search to rule them all so bright,
Summaries and turns in single light!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly addresses the main change: fixing memory_grep to properly search the default session collection for recall operations.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@IWhatsskill
IWhatsskill marked this pull request as ready for review June 2, 2026 09:08

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
test/unit/memory-recall.test.ts (1)

126-147: ⚡ Quick win

Add a duplicate-hit regression for the new multi-collection path.

This test proves session:active-session is searched and classified as a turn, but it never exercises the new dedup contract from the PR. A case where the same id appears in both session_raw:active-session and session:active-session would lock in the intended collection:id dedup behavior and catch accidental over-deduping between raw/default-session hits.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/unit/memory-recall.test.ts` around lines 126 - 147, The test doesn't
exercise the new deduplication when the same id appears in both collections;
update the unit test for DefaultSessionRecallClient/createMemoryGrepTool to mock
a duplicate hit with the same id present in both "session_raw:active-session"
and "session:active-session" (via the client’s returned hits used by
tool.execute) and assert the dedup contract: client.calls still requested both
collections, details.totalMatches remains 1, and the single returned turn
corresponds to that id (ensure the returned turn's collection:id or turnId
matches the deduped identifier and the snippet/role/score remain as expected).
src/tools/memory-recall.ts (1)

520-533: 💤 Low value

Minor: collapse the declare-then-reassign pairs.

evictionCue (520-521) and role (532-533) are declared and then immediately overwritten on the next line; assign once.

Proposed change
-            let evictionCue: string | undefined;
-            evictionCue = typeof meta.eviction_cue === "string" ? meta.eviction_cue : undefined;
+            const evictionCue = typeof meta.eviction_cue === "string" ? meta.eviction_cue : undefined;
-            let role = "unknown";
-            role = typeof meta.role === "string" ? meta.role : "unknown";
+            const role = typeof meta.role === "string" ? meta.role : "unknown";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tools/memory-recall.ts` around lines 520 - 533, Collapse the
declare-then-reassign pairs by initializing variables in one statement: replace
the separate declaration and subsequent conditional assignment for evictionCue
with a single initialization using the existing ternary (e.g., let evictionCue =
typeof meta.eviction_cue === "string" ? meta.eviction_cue : undefined;) and do
the same for role (e.g., let role = typeof meta.role === "string" ? meta.role :
"unknown;") in the same blocks where summaries.push and snippet/role are set so
you don't declare then immediately overwrite these variables.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/tools/memory-recall.ts`:
- Around line 255-262: In isSummaryResult, tighten the summary-ID check by
matching the explicit "sum_" prefix instead of the loose startsWith("sum")
check; update the condition that currently uses result.id.startsWith("sum") to
require result.id.startsWith("sum_") (or an equivalent anchored regex) so only
IDs in the sum_xxx format are classified as summaries.

---

Nitpick comments:
In `@src/tools/memory-recall.ts`:
- Around line 520-533: Collapse the declare-then-reassign pairs by initializing
variables in one statement: replace the separate declaration and subsequent
conditional assignment for evictionCue with a single initialization using the
existing ternary (e.g., let evictionCue = typeof meta.eviction_cue === "string"
? meta.eviction_cue : undefined;) and do the same for role (e.g., let role =
typeof meta.role === "string" ? meta.role : "unknown;") in the same blocks where
summaries.push and snippet/role are set so you don't declare then immediately
overwrite these variables.

In `@test/unit/memory-recall.test.ts`:
- Around line 126-147: The test doesn't exercise the new deduplication when the
same id appears in both collections; update the unit test for
DefaultSessionRecallClient/createMemoryGrepTool to mock a duplicate hit with the
same id present in both "session_raw:active-session" and
"session:active-session" (via the client’s returned hits used by tool.execute)
and assert the dedup contract: client.calls still requested both collections,
details.totalMatches remains 1, and the single returned turn corresponds to that
id (ensure the returned turn's collection:id or turnId matches the deduped
identifier and the snippet/role/score remain as expected).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 18d30e9f-6496-41c1-8f98-9baf7df8b54a

📥 Commits

Reviewing files that changed from the base of the PR and between 944bbbd and 71b8a19.

📒 Files selected for processing (2)
  • src/tools/memory-recall.ts
  • test/unit/memory-recall.test.ts

Comment thread src/tools/memory-recall.ts
@compoodment

Copy link
Copy Markdown
Collaborator

Vale Review

Verdict: request changes. The bug target is real: memory_grep should search the default session:<id> collection. But the patch changes the search model from independent summary/message searches to one mixed top-k stream, and that creates correctness regressions around scope, ranking, and deduplication.

Findings

  1. Blocker: scope="summaries" now searches session:<id> and can lose real summary hits.

buildGrepCollections() always appends session:${sessionId} when sessionId is non-empty (src/tools/memory-recall.ts:197-210), even when the caller asked for scope="summaries".

Then memory_grep runs one shared searchTextCollections call (src/tools/memory-recall.ts:492-498) and later discards non-summary hits (src/tools/memory-recall.ts:516-526). That means default-session turn hits can consume the shared top-k window and then be thrown away, causing summary hits to disappear.

This also weakens scope="both": old code searched summaries and messages independently, so each bucket got its own top-k. New code uses one mixed ranking stream, so a dominant collection can starve the other bucket.

Fix direction: preserve bucket-specific search budgets. Search summaries and messages/default-session separately, or otherwise guarantee per-bucket/per-collection quotas before combining results.

  1. Blocker: dedup happens before exact-match filtering, so a non-matching duplicate can suppress a matching one.

In src/tools/memory-recall.ts:508-512, the result ID is added to seen before safeMatch() is checked. If the first duplicate candidate for an ID does not actually contain the requested text/regex, but a later duplicate does, the later valid match is skipped.

That is especially dangerous because searchText / searchTextCollections are vector-ish search APIs followed by local exact/regex filtering. The local filter is the real memory_grep contract; dedup must not happen before it.

Fix direction: classify and safeMatch first, then dedup the kept candidates.

  1. Major: duplicate selection currently preserves arbitrary result order, not the best match.

The new duplicate test actually exposes the problem: DefaultSessionRecallClient(true) returns a raw hit with score 0.71 before a default-session hit with score 0.88, and the expected output locks in the lower-score first result (test/unit/memory-recall.test.ts:168-190).

If duplicates are genuinely the same memory projected into two collections, keep the better candidate deterministically: highest score, or an explicit collection preference if raw/default-session should win. “First item returned by daemon” is not a stable semantic rule.

  1. Major: classification relies on metadata_json.collection, but the generated SearchResult contract does not provide collection provenance.

The generated contract for SearchResult only has id, score, text, metadataJson, and version; there is no top-level collection field. This patch recovers provenance with:

const collection = resultCollection(meta); (src/tools/memory-recall.ts:508-510)

The tests inject collection into fake metadataJson (test/unit/memory-recall.test.ts:47-60 and 70-95), so the tests prove the fake client, not the daemon contract. If the daemon does not always echo collection inside metadata JSON for multi-collection results, summary/turn classification becomes heuristic-only.

Fix direction: either document and test the daemon invariant that metadata_json.collection is always present for these collections, or avoid relying on mixed-search provenance by issuing separate collection searches where the caller already knows the collection being searched.

  1. Test gaps needed before approval.

Add tests for:

  • scope="summaries" does not query/default-starve on session:<id> turn hits
  • scope="both" returns both summaries and turns when one collection dominates global ranking
  • duplicate ID where first candidate fails safeMatch but second passes
  • duplicate ID chooses highest score or the declared collection preference, not first returned
  • classification behavior when metadataJson lacks collection

Positive notes

The regression target is valid, and adding default session:<id> coverage for message recall is the right direction. The current patch is also scoped to tool execution and tests; no daemon/schema/auth/manifest churn, which is good.

CI / verification state

GitHub reports this PR as mergeable, but I found no GitHub Actions runs for head ec85f53; only CodeRabbit status is present, and CodeRabbit itself hit review-rate limits earlier. The PR body’s local proof is useful, but the edge cases above are not covered by the added tests.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants