Skip to content

fix(pipecat): stop re-storing injected memory blocks as new memories#1279

Open
abhay-codes07 wants to merge 1 commit into
supermemoryai:mainfrom
abhay-codes07:fix/pipecat-memory-restorage
Open

fix(pipecat): stop re-storing injected memory blocks as new memories#1279
abhay-codes07 wants to merge 1 commit into
supermemoryai:mainfrom
abhay-codes07:fix/pipecat-memory-restorage

Conversation

@abhay-codes07

Copy link
Copy Markdown
Contributor

What

The pipecat service was writing its own injected memories back to Supermemory as brand-new memories.

LLMContext.get_messages() returns the context's live message list — the enhancement code depends on that, mutating the returned list in place to inject the <user_memories> block. But process_frame captured that same reference before enhancement:

context_messages = context.get_messages()      # live reference
...
self._enhance_context_with_memories(...)       # appends the memory block
...
storable_messages = [msg for msg in context_messages if msg["role"] in ("user", "assistant")]

By the time storable_messages was sliced, the list already contained this turn's injected user-message, so _store_messages persisted the recalled memories as a new memory. The next turn recalls the stored block and re-stores it again — a self-referential loop that compounds every turn. (It was previously masked by the dedup crash fixed in #1268; with profile facts present and search results empty, the loop is live.)

Fix

Two defenses, both needed:

  • snapshotlist(context.get_messages()) before enhancement, so the current turn's injection never enters the storage slice
  • filter — a _is_memory_injection helper excludes any message carrying the memory tag from storable_messages, which covers the previous turn's injected block still sitting in the context (it's only replaced during the next enhancement, after the snapshot is taken)

The helper is tolerant of non-string content (multimodal parts, None).

Testing

  • New tests/test_no_memory_restorage.py drives process_frame end-to-end with a live-list fake context (same semantics the enhancement code relies on) and a mocked Supermemory client: the memory block lands in the context but never in the storage payload, for both the same-turn and next-turn shapes; plus non-string-content tolerance for the helper
  • python -m pytest tests in the package — 4 passed
  • No conflict with fix(python-sdks): stop mishandling SDK model objects in memory dedup and formatting #1268 (that PR touches utils.py; this touches service.py)

cc @MaheshtheDev

LLMContext.get_messages() returns the context's live message list — the
enhancement code depends on that, mutating it in place to inject the
<user_memories> block. But the frame handler snapshotted that same
reference *before* enhancement, so by the time it sliced out
storable_messages the list already contained this turn's injected
user-message, and _store_messages wrote the recalled memories back to
Supermemory as a brand-new memory. Next turn recalls the stored block
and re-stores it again, compounding every turn. (Previously masked by
the dedup crash fixed in supermemoryai#1268 — with search results empty and profile
facts present, the loop is live.)

Two defenses:
- copy the message list before enhancement mutates it, so the current
  turn's injection never enters the storage slice
- filter any message carrying the memory tag out of storable_messages,
  which also covers the previous turn's injected block still sitting in
  the context (it is only replaced during the next enhancement, after
  the snapshot is taken)

Regression tests drive process_frame with a live-list fake context and
a mocked client: the injected block lands in the context but never in
the storage payload, across both the same-turn and next-turn shapes.
Copilot AI review requested due to automatic review settings July 13, 2026 05:38

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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