Skip to content

Commit 018c32a

Browse files
committed
fix(scheduler): keep unrelated filtering for a single memory
`filter_unrelated_and_redundant_memories` returned early on `len(memories) <= 1`, skipping both of the filtering steps it is responsible for. That guard is correct for `filter_redundant_memories` — one memory cannot be redundant with itself — but this method also removes memories that are unrelated to the query history, and that check is per-memory: it applies just as well to a list of one. `filter_unrelated_memories`, which does only that step, has no such guard. So the two paths disagreed on the same input: given one off-topic memory, the unrelated-only filter dropped it while the combined filter kept it and never consulted the LLM at all. The log lines record the copy: the redundancy filter says "no redundancy to filter" (accurate), while the combined one says "no filtering needed" (not accurate — unrelated filtering was still needed). The guard was carried over without widening its condition. The prompt confirms the intent. MEMORY_COMBINED_FILTERING_PROMPT asks for two steps, the first being "Unrelated Memory Removal ... Has no semantic connection to any query in the history", which needs no second memory to be meaningful. This runs on the working-memory replacement path (`optimized_scheduler.replace_working_memory`), so a single unrelated memory survived into working memory. Drop the guard. The `not memories` and `not query_history` early returns are untouched, and the LLM-failure path still conservatively keeps everything.
1 parent 0f74774 commit 018c32a

2 files changed

Lines changed: 50 additions & 4 deletions

File tree

‎src/memos/mem_scheduler/memory_manage_modules/memory_filter.py‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -234,10 +234,6 @@ def filter_unrelated_and_redundant_memories(
234234
logger.info("No query history provided - keeping all memories")
235235
return memories, True
236236

237-
if len(memories) <= 1:
238-
logger.info("Only one memory - no filtering needed")
239-
return memories, True
240-
241237
logger.info(
242238
f"Starting combined unrelated and redundant filtering for {len(memories)} memories against {len(query_history)} queries"
243239
)

‎tests/mem_scheduler/test_retriever.py‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,3 +360,53 @@ def test_filter_unrelated_memories_conservative_filtering(self):
360360
# Should return all memories
361361
self.assertEqual(result, memories)
362362
self.assertTrue(success_flag)
363+
364+
def test_combined_filtering_still_filters_a_single_unrelated_memory(self):
365+
"""A lone memory must still go through unrelated filtering.
366+
367+
The combined filter used to return early on `len(memories) <= 1`, a
368+
guard that only makes sense for redundancy (one memory cannot be
369+
redundant with itself). Unrelated filtering is per-memory, so skipping
370+
it left an off-topic memory sitting in working memory.
371+
"""
372+
query_history = ["What is my deployment pipeline?"]
373+
memories = [TextualMemoryItem(memory="The user's cat is named Whiskers")]
374+
375+
self.llm.generate.return_value = json.dumps(
376+
{
377+
"kept_memories": [],
378+
"unrelated_removed_count": 1,
379+
"redundant_removed_count": 0,
380+
"reasoning": "No semantic connection to the deployment query",
381+
}
382+
)
383+
384+
result, success_flag = self.retriever.filter_unrelated_and_redundant_memories(
385+
query_history=query_history, memories=memories
386+
)
387+
388+
self.assertEqual(result, [])
389+
self.assertTrue(success_flag)
390+
# The LLM must actually be consulted, not short-circuited.
391+
self.assertTrue(self.llm.generate.called)
392+
393+
def test_combined_filtering_keeps_a_single_relevant_memory(self):
394+
"""The counterpart: one on-topic memory must survive the filter."""
395+
query_history = ["What is my deployment pipeline?"]
396+
memories = [TextualMemoryItem(memory="Deployment uses a blue-green pipeline")]
397+
398+
self.llm.generate.return_value = json.dumps(
399+
{
400+
"kept_memories": [0],
401+
"unrelated_removed_count": 0,
402+
"redundant_removed_count": 0,
403+
"reasoning": "Directly answers the deployment query",
404+
}
405+
)
406+
407+
result, success_flag = self.retriever.filter_unrelated_and_redundant_memories(
408+
query_history=query_history, memories=memories
409+
)
410+
411+
self.assertEqual(result, memories)
412+
self.assertTrue(success_flag)

0 commit comments

Comments
 (0)