Skip to content

fix(memory): isolate ingested events and returned search content - #7566

Open
hackermengzhi wants to merge 1 commit into
google:mainfrom
hackermengzhi:fix/memory-event-copy-isolation
Open

hackermengzhi wants to merge 1 commit into
google:mainfrom
hackermengzhi:fix/memory-event-copy-isolation

Conversation

@hackermengzhi

Copy link
Copy Markdown

Summary

Fixes #7565.

Detach ingested events and returned search content in InMemoryMemoryService. Editing an event after ingestion or nested content in a search result no longer rewrites stored memory or the source session. Keyword ranking, duplicate handling and deletion logic stay unchanged.

Testing plan

  • Four public-API mutation regressions fail before the change, covering both ingestion methods and nested function-call arguments.
  • Complete tests/unittests/memory/test_in_memory_memory_service.py: 53 passed.
  • Targeted isort, pyink, Ruff and mypy: passed.
  • Offline end-to-end service cycle: ingest a real Event, search, mutate returned text and nested arguments, then search again and verify original content. No cloud services or LLM calls.

Verified on macOS arm64, Python 3.12.13. Repository-wide CI and live-provider integration are not claimed.

@google-cla

google-cla Bot commented Oct 16, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the in-memory memory service to deep-copy events and content when storing or searching memories, ensuring that ingested events and search results are detached from the caller. Unit tests are also added to verify this behavior. The review feedback correctly points out that using Pydantic's model_copy(deep=True) on Event and Session instances bypasses their custom __deepcopy__ implementations. It is recommended to use copy.deepcopy() instead to ensure the custom deep-copy logic is properly executed.

self._session_events[user_key] = self._session_events.get(user_key, {})
self._session_events[user_key][session.id] = [
event
event.model_copy(deep=True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The Event class explicitly overrides __deepcopy__ with a custom implementation (_model_copy.deep_copy_model) to handle its copying logic correctly. Using event.model_copy(deep=True) bypasses this custom __deepcopy__ because Pydantic's model_copy deep-copies the underlying dictionary directly rather than calling copy.deepcopy on the model instance.\n\nTo ensure the custom deep copy logic is executed, use copy.deepcopy(event) instead.\n\nNote: You will need to import copy at the top of the file.

Suggested change
event.model_copy(deep=True)
copy.deepcopy(event)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified against the actual Pydantic implementation: BaseModel.model_copy(deep=True) without update calls self.__deepcopy__(), so these calls do invoke Event/Session's custom copier. ADK's utils/_model_copy.py::deep_copy_model also explicitly documents support for both copy.deepcopy and model_copy(deep=True).

I ran an additional probe using that production ADK helper as a custom model copier: model_copy(deep=True) invoked the override once and editing nested copied content left the source unchanged. The existing four public-service regressions and full memory-service tests already pass (53 tests). Keeping the current calls avoids a behavior-neutral rewrite; the bypass described in these comments does not occur for these calls.

for event in events_to_add:
if event.id not in existing_ids and event.id not in deleted_ids:
existing_events.append(event)
existing_events.append(event.model_copy(deep=True))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similarly to add_session_to_memory, using event.model_copy(deep=True) bypasses the custom __deepcopy__ implementation defined on the Event class. Use copy.deepcopy(event) to ensure the custom copy logic is respected.

Suggested change
existing_events.append(event.model_copy(deep=True))
existing_events.append(copy.deepcopy(event))

) -> None:
"""Changing an ingested event must not rewrite stored memory."""
service = InMemoryMemoryService()
session = MOCK_SESSION_1.model_copy(deep=True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The Session class overrides __deepcopy__ with _model_copy.deep_copy_model. Using model_copy(deep=True) bypasses this custom copy logic. It is safer to use copy.deepcopy(MOCK_SESSION_1) instead.\n\nNote: You will need to import copy in this test file.

Suggested change
session = MOCK_SESSION_1.model_copy(deep=True)
session = copy.deepcopy(MOCK_SESSION_1)

) -> None:
"""Editing returned content must not affect memory or the source session."""
service = InMemoryMemoryService()
session = MOCK_SESSION_1.model_copy(deep=True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similarly, use copy.deepcopy(MOCK_SESSION_1) to respect the custom __deepcopy__ implementation of the Session class.

Suggested change
session = MOCK_SESSION_1.model_copy(deep=True)
session = copy.deepcopy(MOCK_SESSION_1)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

InMemoryMemoryService exposes stored memory through mutable event and search-result references

2 participants