From 590d5b9cb3da254178f327f143aa2e20e32bd017 Mon Sep 17 00:00:00 2001 From: tonmoy-y Date: Tue, 27 Oct 2026 01:40:17 +0600 Subject: [PATCH] fix(memory): keep sessionless memories separate from a session named __unknown_session_id__ InMemoryMemoryService stored events added without a session ID under the string '__unknown_session_id__'. A session with that literal ID shared the bucket, so add_session_to_memory replaced the sessionless memories. Key the sessionless bucket by None, which no session ID can alias. --- .../adk/memory/in_memory_memory_service.py | 10 ++++--- .../memory/test_in_memory_memory_service.py | 28 +++++++++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/google/adk/memory/in_memory_memory_service.py b/src/google/adk/memory/in_memory_memory_service.py index f283293a93d..57855b97149 100644 --- a/src/google/adk/memory/in_memory_memory_service.py +++ b/src/google/adk/memory/in_memory_memory_service.py @@ -32,7 +32,6 @@ from ..events.event import Event from ..sessions.session import Session -_UNKNOWN_SESSION_ID = '__unknown_session_id__' _MAX_SEARCH_RESULTS = 10 @@ -86,9 +85,12 @@ class InMemoryMemoryService(BaseMemoryService): def __init__(self) -> None: self._lock = threading.Lock() - self._session_events: dict[tuple[str, str], dict[str, list[Event]]] = {} + self._session_events: dict[ + tuple[str, str], dict[str | None, list[Event]] + ] = {} """Keys are (app_name, user_id). Values are dicts of session_id to - session event lists. + session event lists. Events added without a session ID are kept under the + None key, which no caller-provided session ID can alias. """ self._deleted_memory_ids: dict[tuple[str, str], set[str]] = {} """Keys are (app_name, user_id). Values are sets of deleted memory_ids @@ -122,7 +124,7 @@ async def add_events_to_memory( ) -> None: _ = custom_metadata user_key = _user_key(app_name, user_id) - scoped_session_id = session_id or _UNKNOWN_SESSION_ID + scoped_session_id = session_id or None events_to_add = [ event for event in events if event.content and event.content.parts ] diff --git a/tests/unittests/memory/test_in_memory_memory_service.py b/tests/unittests/memory/test_in_memory_memory_service.py index 2af93166258..038b8e3b247 100644 --- a/tests/unittests/memory/test_in_memory_memory_service.py +++ b/tests/unittests/memory/test_in_memory_memory_service.py @@ -160,6 +160,34 @@ async def test_add_events_to_memory_without_session_id_uses_default_bucket(): assert unknown_session_events[0].id == 'event-1a' +@pytest.mark.asyncio +@pytest.mark.parametrize( + 'session_id', ['ordinary-session', '__unknown_session_id__'] +) +async def test_add_session_to_memory_keeps_sessionless_memories(session_id): + """A session must not replace events added without a session ID.""" + memory_service = InMemoryMemoryService() + await memory_service.add_events_to_memory( + app_name=MOCK_APP_NAME, + user_id=MOCK_USER_ID, + events=[MOCK_SESSION_1.events[0]], + ) + + await memory_service.add_session_to_memory( + Session( + app_name=MOCK_APP_NAME, + user_id=MOCK_USER_ID, + id=session_id, + last_update_time=1000, + ) + ) + + result = await memory_service.search_memory( + app_name=MOCK_APP_NAME, user_id=MOCK_USER_ID, query='toolkit' + ) + assert [memory.id for memory in result.memories] == ['event-1a'] + + @pytest.mark.asyncio async def test_add_events_to_memory_alias_is_supported(): """Tests that add_events_to_memory remains a compatibility alias."""