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 #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.
#### RE-RAISING Pipecat live speech PR
### Added native speech-to-speech model support
### Summary:
- Speech-to-speech support - Auto-detect audio frames and inject memories to system prompt for native audio models (Gemini Live, etc.)
- Fix memory bloating - Replace memories each turn using XML tags instead of accumulating
- Add temporal context - Show recency on search results ([2d ago], [15 Jan])
- New inject_mode param - auto (default), system, or user
### Docs update
- Update the docs for native speech-2-speech models