diff --git a/agent/memory_manager.py b/agent/memory_manager.py index fb1c4d639a..a76cee4b5f 100644 --- a/agent/memory_manager.py +++ b/agent/memory_manager.py @@ -175,24 +175,12 @@ class StreamingContextScrubber: def build_memory_context_block(raw_context: str) -> str: - """Wrap prefetched memory in a fenced block with system note. - - The fence prevents the model from treating recalled context as user - discourse. Injected at API-call time only — never persisted. - - A provider returning text that already contains the wrapper is a - contract violation (would produce nested fences). We strip defensively - and warn so the buggy provider surfaces in logs instead of silently - double-fencing. - """ + """Wrap prefetched memory in a fenced block with system note.""" if not raw_context or not raw_context.strip(): return "" clean = sanitize_context(raw_context) if clean != raw_context: - logger.warning( - "memory provider returned text containing wrapper; " - "stripped before re-fencing (provider contract violation)" - ) + logger.warning("memory provider returned pre-wrapped context; stripped") return ( "\n" "[System note: The following is recalled memory context, " diff --git a/gateway/run.py b/gateway/run.py index cbb4ae00d2..ac8f763b7f 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -8502,10 +8502,6 @@ class GatewayRunner: result = json.loads(result_json) if result.get("success"): description = result.get("analysis", "") - # Vision auxiliary LLM can echo the injected system-prompt - # memory-context wrapper back into its output (#5719). - # sanitize_context strips the fenced wrapper; plugin-specific - # header cleanup belongs at the provider boundary, not here. description = sanitize_context(description) enriched_parts.append( f"[The user sent an image~ Here's what I can see:\n{description}]\n" diff --git a/run_agent.py b/run_agent.py index 37f162761b..aff0ba1298 100644 --- a/run_agent.py +++ b/run_agent.py @@ -6102,10 +6102,7 @@ class AIAgent: else: # Defensive: legacy callers without the scrubber attribute. text = sanitize_context(text) - # Strip leading newlines only on the very first delta of the stream, - # and only when we didn't just prepend a paragraph break ourselves. - # Mid-stream "\n" is legitimate markdown (lists, code, paragraphs) - # and must survive — chunk boundaries are arbitrary. + # Only strip leading newlines on the first delta — mid-stream "\n" is legitimate markdown. if not prepended_break and not getattr( self, "_current_streamed_assistant_text", "" ): diff --git a/tests/agent/test_streaming_context_scrubber.py b/tests/agent/test_streaming_context_scrubber.py index 13888dfe7b..99f33e7ce9 100644 --- a/tests/agent/test_streaming_context_scrubber.py +++ b/tests/agent/test_streaming_context_scrubber.py @@ -196,7 +196,7 @@ class TestBuildMemoryContextBlockWarnsOnViolation: with caplog.at_level(logging.WARNING, logger="agent.memory_manager"): out = build_memory_context_block(prewrapped) - assert any("contract violation" in rec.message for rec in caplog.records) + assert any("pre-wrapped" in rec.message for rec in caplog.records) assert out.count("") == 1 assert out.count("") == 1 @@ -207,5 +207,5 @@ class TestBuildMemoryContextBlockWarnsOnViolation: with caplog.at_level(logging.WARNING, logger="agent.memory_manager"): out = build_memory_context_block("plain fact about user") - assert not any("contract violation" in rec.message for rec in caplog.records) + assert not any("pre-wrapped" in rec.message for rec in caplog.records) assert "plain fact about user" in out