MemoryManager forwarded turns, transcripts and tool output to external
memory providers without the egress scrub, so a key echoed into tool
output was archived verbatim in the provider's store.
Every fan-out now goes through redact_for_egress (the chat/cron delivery
scrub; holds with security.redact_secrets off, fails closed): sync_all
(turn strings + messages), prefetch_all / queue_prefetch_all,
on_turn_start, on_session_end, on_pre_compress (both transcripts),
on_memory_write (content + metadata), on_delegation and provider
handle_tool_call args. Nested dicts/lists (tool-call arguments,
multimodal parts) are walked; data: URLs pass; the live transcript is
never mutated. A value masked anywhere in one hand-off is masked
everywhere in it, so a key the model repeats bare in prose is caught.
redact.py: the JSON-field rule now also matches inside JSON-encoded
strings (tool results wrap config dumps in escaped JSON), which is how
the reported opaque AQ.-prefixed key slipped past.
Co-authored-by: Teknium <teknium1@gmail.com>