Repository navigation
Description
🔴 Required Information
Describe the Bug:
VertexAiMemoryBankService sends the model's thought parts (Part.thought=True) to Memory Bank as conversation content. Both add_session_to_memory and add_events_to_memory pass event.content through unchanged, on the ingest_events path and on the memories.generate path. Memory Bank then extracts memories from the model's private reasoning as if it were part of the conversation, so a guess the model made about the user can be stored as a fact about them.
An event whose only text is a thought is not filtered out either, because _is_empty_content counts any part.text as content.
The other memory services already exclude thoughts: InMemoryMemoryService and VertexAiRagMemoryService (#7473), and SqliteMemoryService (#7691). #7830 reports the same problem in FirestoreMemoryService.
Steps to Reproduce:
- Build a session with two model events: one with parts
[Part(text="the user sounds anxious, keep the reply short", thought=True), Part(text="Here is the answer.")], and one whose only part is a thought. - Call
add_session_to_memory(session)with theagentplatform.Clientmocked the way the existing unit tests do. - Inspect the events passed to
memory_banks.ingest_events.
Expected Behavior:
The first event is sent with only Here is the answer., and the thought-only event is not sent.
Observed Behavior:
Both events are sent with their thought parts:
901 [('the user sounds anxious, keep the reply short', True), ('Here is the answer.', None)]
902 [('the user sounds anxious, keep the reply short', True)]
add_events_to_memory with a generate-only key such as revision_ttl sends the same content to memories.generate.
Environment Details:
- ADK Library Version (pip show google-adk): 2.13.0 (also on
mainat 4f8e4ae) - Desktop OS: macOS
- Python Version (python -V): 3.14.7
Model Information:
- Are you using LiteLLM: No
- Which model is being used: any thinking model (e.g. Gemini with
include_thoughts=True)
🟡 Optional Information
Additional Context:
In src/google/adk/memory/vertex_ai_memory_bank_service.py:
_add_events_to_memory_from_eventsand_add_events_to_memory_via_ingestbuild each direct-contents event withcontent=event.content._should_filter_out_eventrelies on_is_empty_content, which does not look atpart.thought.
I verified this with the mocked client; I don't have a Memory Bank instance.
Minimal Reproduction Code:
import asyncio
from unittest import mock
from google.adk.events.event import Event
from google.adk.memory import VertexAiMemoryBankService
from google.adk.sessions.session import Session
from google.genai import types
THOUGHT = "the user sounds anxious, keep the reply short"
async def main():
with mock.patch("agentplatform.Client") as client:
aio = mock.MagicMock()
aio.memory_banks.ingest_events = mock.AsyncMock()
client.return_value = mock.MagicMock(aio=aio)
service = VertexAiMemoryBankService(
project="p", location="us-central1", agent_engine_id="123"
)
session = Session(
app_name="app",
user_id="u",
id="s",
last_update_time=0,
events=[
Event(
id="901",
author="agent",
content=types.Content(
role="model",
parts=[
types.Part(text=THOUGHT, thought=True),
types.Part(text="Here is the answer."),
],
),
),
Event(
id="902",
author="agent",
content=types.Content(
role="model",
parts=[types.Part(text=THOUGHT, thought=True)],
),
),
],
)
await service.add_session_to_memory(session)
await asyncio.sleep(0)
source = aio.memory_banks.ingest_events.call_args.kwargs[
"direct_contents_source"
]
for event in source.events:
print(event.event_id, [(p.text, p.thought) for p in event.content.parts])
asyncio.run(main())How often has this issue occurred?:
- Always (100%), with thinking models
Activity
fix(memory): keep thought parts out of Memory Bank events
surajksharma07 commentedon Oct 30, 2026
Thanks @hyeonsang010716, really clear report. Reproduced this on 2.13.0 with a mocked client and it hits both the ingest_events and memories.generate paths.
As a workaround for now, subclass VertexAiMemoryBankService and drop the thought parts from each event before passing it to super()._add_events_to_memory_from_events. That kept them out on both paths and thought-only events got skipped too. Could you check if that works on your side?
Before #7851 goes for review please give it a good round of testing and make sure the PR covers both paths, drops thought-only events and doesn’t modify the original session events.
hyeonsang010716 commentedon Oct 30, 2026
Thanks for reproducing it. I tried the workaround on
mainwith the mocked client, and it works on both paths: onlyHere is the answer.is sent, the thought-only event is skipped, and the session's events are unchanged. Since it overrides a private method, it may need updating if that method changes.I added tests to #7851 so each of those is checked on both paths: thought parts dropped, thought-only events not sent, and the caller's events left unchanged. All new tests fail on
mainand pass with the change. Changing the content in place instead of copying it fails the "unchanged" checks on both paths.