Skip to content

Fix #1913: turn.start injection collapses to empty when top hits are dominated by near-dupl#1923

Open
Memtensor-AI wants to merge 1 commit into
dev-20260615-v2.0.20from
bugfix/autodev-1913
Open

Fix #1913: turn.start injection collapses to empty when top hits are dominated by near-dupl#1923
Memtensor-AI wants to merge 1 commit into
dev-20260615-v2.0.20from
bugfix/autodev-1913

Conversation

@Memtensor-AI

Copy link
Copy Markdown
Collaborator

Description

Fixed issue #1913: turn.start injection collapsing to empty when the LLM relevance filter returned selected: [] over a non-empty ranked candidate list. The reproduction described in the issue happens when the top retrieval hits are dominated by near-duplicate question traces from previous sessions — the filter prompt's "drop scaffolding chatter / surface-similar wrong sub-problem" rubric applies to every candidate and the answer-bearing trace gets swept along, leaving injectedContext: "" even though memory.search for the same query still returns the same 4 hits.

The fix adds a rescue path in apps/memos-local-plugin/core/retrieval/llm-filter.ts (rescueFromEmptySelection + isInformativeCandidate). When the LLM empties the kept set, the rescue partitions ranked candidates into informative (skill / episode / experience / world-model, or a trace with a non-trivial summary/reflection/agentText) and chatter (acknowledgement-only / near-duplicate question traces), then keeps the top-K ordered informative-first up to llmFilterMaxKeep. A new outcome label "llm_filtered_refilled" and a debug log llm_filter.collapsed_refill { filteredAll: true, ranked, rescued, informative, chatter } make the safety net diagnosable from the Logs viewer. sufficient is forced to false so downstream callers know the injection is weak. llmFilterMaxKeep: 0 is preserved as the explicit "drop everything" escape hatch.

Implementation touched 5 files: core/retrieval/llm-filter.ts (rescue + new outcome), core/retrieval/types.ts (widen the RetrievalStats.llmFilterOutcome union), viewer/src/views/LogsView.tsx (count the new variant as "LLM ran" in the Logs viewer heuristic), tests/unit/retrieval/llm-filter.test.ts (4 new tests + replace the prior "honour empty" test), tests/unit/retrieval/integration.test.ts (turn_start end-to-end regression). All retrieval / bridge / pipeline unit tests pass (146 / 146); tsc --noEmit clean. Two pre-existing unrelated storage unit failures reproduced on the base branch and are outside this PR's boundary.

Related Issue (Required): Fixes #1913

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Refactor (does not change functionality, e.g. code style improvements, linting)
  • Documentation update

How Has This Been Tested?

Automated tests are pending.

  • Unit Test
  • Test Script Or Test Steps (please provide)
  • Pipeline Automated API Test (please provide)

Checklist

  • I have performed a self-review of my own code
  • I have commented my code in hard-to-understand areas
  • I have added tests that prove my fix is effective or that my feature works
  • I have created related documentation issue/PR in MemOS-Docs (if applicable)
  • I have linked the issue to this PR (if applicable)
  • I have mentioned the person who will review this PR

@MatthewZhuang, @CarltonXiang, @syzsunshine219 please review this PR.

Reviewer Checklist

… to empty (#1913)

When the LLM relevance filter returned selected: [] for a non-empty
ranked candidate list, the kept set was emptied verbatim and the
turn.start packet collapsed to injectedContext: "". This reproduced
whenever the top retrieval hits were dominated by near-duplicate
question traces (e.g. the user re-asked the same fact across several
sessions): the filter prompt's "drop scaffolding chatter / surface-
similar wrong sub-problem" rubric was applied to every candidate and
swept the answer-bearing trace along.

Add a rescue path in llm-filter.ts (rescueFromEmptySelection) that
partitions candidates into informative (skill / episode / experience /
world-model, or a trace whose summary / reflection / agentText is
longer than a short-ack heuristic) and chatter, then keeps the top-K
ordered informative-first up to llmFilterMaxKeep. A new outcome label
"llm_filtered_refilled" plus a debug log
llm_filter.collapsed_refill { filteredAll: true, ... } make the
safety net diagnosable from the Logs viewer. sufficient is forced to
false so downstream callers know the injection is weak. The explicit
escape hatch (llmFilterMaxKeep: 0) still honours an empty selection
for operators who want hard drop.

- core/retrieval/llm-filter.ts: rescue helper + new outcome variant
- core/retrieval/types.ts: widen RetrievalStats.llmFilterOutcome union
- viewer/src/views/LogsView.tsx: count the new variant under
  "LLM ran" in finalLlmRan
- tests/unit/retrieval/llm-filter.test.ts: 4 new tests (regression +
  edge cases) + replace the prior "honour empty" test
- tests/unit/retrieval/integration.test.ts: regression covering
  turn_start packet shape end-to-end

Closes #1913

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Memtensor-AI

Copy link
Copy Markdown
Collaborator Author

✅ Automated Test Results: PASSED

All tests passed (35/35 executed, 35 skipped). memos_local_plugin/smoke: 0/0, memos_local_plugin/contract: 35 passed, 35 skipped. Duration: 4s

Branch: bugfix/autodev-1913

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

Labels

ai-generated bug Something isn't working | 功能异常

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants