Files
hermes-agent/tests/agent/test_memory_context_dedupe.py
kshitijk4poor 28bd8cc08c fix(memory): never dedupe indented recall lines
Only column-0 bullets participate in `_drop_repeated_recall_lines`.
An indented line is a continuation of the bullet above it (nested
child, provenance, wrapped prose) and is kept verbatim, so identical
children under two different parents both survive. The docstring
already promised this; the code now matches.

PROOF: with the old code, `- Project A\n  - status: active\n- Project B\n
  - status: active\n` lost B's child (probe s4_eff_nested.py) and the
new assertion in test_a_bullet_with_continuation_lines_is_never_touched
fails (AssertionError at :49); after the change the probe returns the
input unchanged and tests/agent/test_memory_context_dedupe.py passes.
2026-09-22 15:55:09 +05:30

50 lines
2.4 KiB
Python

"""A recalled bullet is stated once per ``<memory-context>`` block.
Providers merge several stores, and the manager merges several providers, so one prefetch routinely
surfaces the same fact more than once. The composed block is stamped into the user row's
``api_content`` sidecar and replayed verbatim on every later request, so a byte-identical repeat is
paid once per turn for the life of the row — while telling the model nothing the block has not
already said.
Structure is not touched: headings, prose, blank lines and ``---`` rules survive as written, so a
provider's sections still read the way it wrote them.
"""
from __future__ import annotations
from agent.memory_manager import build_memory_context_block
def _body(block: str) -> str:
"""The provider's own content, without the wrapper and the system note that precedes it."""
return block.split("]\n\n", 1)[1].rsplit("\n</memory-context>", 1)[0]
def test_a_repeated_bullet_is_kept_once_in_first_position():
"""Within a section a repeat is dropped; a section is delimited by a heading of ANY style.
RetainDB writes prose headings (``Profile:`` / ``Relevant memories:``); other providers write
markdown ones. In both shapes a placeholder bullet repeated under the second heading belongs to
that heading and must stay, or the heading is left claiming nothing."""
raw = "- alpha\n- beta\n- alpha\n- gamma\n"
body = _body(build_memory_context_block(raw))
# First-occurrence order, not a re-sort, and nothing else moved.
assert body.splitlines() == ["- alpha", "- beta", "- gamma"]
prose = "Profile:\n- None\nRelevant memories:\n- None\n"
markdown = "## A\n- (none recorded)\n\n## B\n- (none recorded)\n"
assert _body(build_memory_context_block(prose)) == prose
assert _body(build_memory_context_block(markdown)) == markdown
def test_a_bullet_with_continuation_lines_is_never_touched():
"""Two entries can share a headline and differ underneath it. Dropping one would re-parent its
provenance under the other and invent a record neither provider reported."""
raw = ("- prefers draft PRs\n (logged 12 Jan, source: supermemory)\n"
"- prefers draft PRs\n (logged 3 Feb, source: builtin)\n")
nested = "- Project A\n - status: active\n- Project B\n - status: active\n"
assert _body(build_memory_context_block(raw)) == raw
assert _body(build_memory_context_block(nested)) == nested