diff --git a/headroom/proxy/memory_query_policy.py b/headroom/proxy/memory_query_policy.py index 8d765e247..1480a5f15 100644 --- a/headroom/proxy/memory_query_policy.py +++ b/headroom/proxy/memory_query_policy.py @@ -63,11 +63,18 @@ def extract_memory_query_sources( # Anthropic user turns carry the actual prompt as text blocks # ({"type":"text","text":...}), not a plain string. Capture it # so the memory retrieval query keys on the user's question and - # not just any tool_result blocks in the same turn. + # not just any tool_result blocks in the same turn. Skip Claude + # Code's harness blocks: they are appended to + # the same turn and, concatenated into the embedding input, + # dilute the user's question below the similarity floor so + # nothing is retrieved (#2195). Filtering them keeps the query + # on the substantive question. user_text = "\n".join( - b.get("text", "") + text for b in content if isinstance(b, dict) and b.get("type") == "text" + for text in (str(b.get("text", "")).strip(),) + if text and not text.startswith(" None: assert user_text == "help me refactor auth" +def test_extract_sources_skips_system_reminder_blocks() -> None: + """Claude Code appends harness blocks to the user turn. + Concatenated into the embedding input they dilute the real question below the + similarity floor so nothing is retrieved (#2195); they must be filtered out.""" + messages = [ + { + "role": "user", + "content": [ + {"type": "text", "text": "how do I add caching to the auth handler?"}, + { + "type": "text", + "text": "\nThe user opened file x.\n", + }, + ], + }, + ] + + user_text, _tool_outputs, _assistant_turns = extract_memory_query_sources(messages) + + assert user_text == "how do I add caching to the auth handler?" + assert "system-reminder" not in user_text + + +def test_extract_sources_reminder_only_turn_yields_no_user_text() -> None: + messages = [ + { + "role": "user", + "content": [{"type": "text", "text": "x"}], + }, + ] + + user_text, _tool_outputs, _assistant_turns = extract_memory_query_sources(messages) + + assert user_text == "" + + def test_extract_sources_captures_user_text_alongside_tool_result() -> None: """A user turn mixing a tool_result and a text block yields both: the text as the user query and the tool output as context."""