Skip to content

fix(sessions): stop returned sessions aliasing scoped state - #7105

Closed
VedanthB wants to merge 1 commit into
google:mainfrom
VedanthB:fix-inmemory-scoped-state-copy
Closed

VedanthB wants to merge 1 commit into
google:mainfrom
VedanthB:fix-inmemory-scoped-state-copy

Conversation

@VedanthB

Copy link
Copy Markdown
Contributor

Please ensure you have read the contribution guide before creating a pull request.

Link to Issue or Description of Change

No existing issue; described below following the bug-report structure.

Describe the Bug:

InMemorySessionService copies its stored Session before returning it, but
_merge_state() then inserts app- and user-scoped nested values into the copy
by reference.

With the default deep-copy configuration, mutating a nested app: or user:
value on a session returned by create_session(), get_session(), or
list_sessions() therefore mutates service storage. A later session observes
the change even though no append_event() or state delta occurred.

Steps to Reproduce:

On current main, run:

import asyncio

from google.adk.sessions import InMemorySessionService


async def main() -> None:
  service = InMemorySessionService()
  returned = await service.create_session(
      app_name='app',
      user_id='user',
      session_id='first',
      state={
          'app:config': {'theme': 'light'},
          'user:profile': {'name': 'Alice'},
      },
  )

  returned.state['app:config']['theme'] = 'dark'
  returned.state['user:profile']['name'] = 'Mallory'

  later = await service.create_session(
      app_name='app', user_id='user', session_id='second'
  )
  print(later.state['app:config'])
  print(later.state['user:profile'])


asyncio.run(main())

Save the snippet as /tmp/repro.py and run python /tmp/repro.py.

Expected Behavior:

Mutating a returned deep-copied session does not mutate stored scoped state. A
later session prints:

{'theme': 'light'}
{'name': 'Alice'}

Observed Behavior:

Current main prints:

{'theme': 'dark'}
{'name': 'Mallory'}

Environment Details:

  • ADK: current main at 37aa7308 (2.9.0)
  • OS: macOS 26.6.1
  • Python: 3.11.15

Model Information:

  • LiteLLM: No
  • Model: N/A; no model is used

Frequency: Always (100%)

Solution:

Copy app and user state using the same configured depth as the session itself
before merging it into a returned session. Default mode deep-copies scoped
state; IN_MEMORY_SESSION_SERVICE_LIGHT_COPY retains its intentional shallow
copy behavior.

Testing Plan

Unit Tests:

  • I have added or updated unit tests for my change.
  • All unit tests pass locally. (Two environment-specific failures in the
    full matrix reproduce identically on unmodified main; details below.)

Added parameterized regression coverage for create_session(),
get_session(), and list_sessions(), with nested app- and user-scoped values
under both copy modes.

  • Unmodified main plus the regression test: 3 failed, 3 passed — exactly
    the three default-copy cases failed; all light-copy cases passed.
  • Patched regression test: 6 passed.
  • Focused copy-depth tests: 8 passed.
  • Complete session-service module: 271 passed, 3 xfailed.
  • Complete sessions directory: 484 passed, 3 xfailed.
  • Five-version tox matrix: Python 3.10, 3.11, and 3.13 passed in full
    (14,627, 14,634, and 14,627 passed). Python 3.12 and 3.14 each
    completed 14,625 passing tests with two unrelated import-loading failures:
    this machine's sitecustomize module appeared outside the allowlist. Both
    failures reproduce identically on unmodified main under the same tox
    environments.
  • Changed-file pre-commit hooks: all passed.
  • CI-style mypy comparison: 740 baseline errors, 740 patched errors, zero
    new errors.
  • uv build: source distribution and wheel built successfully.

Manual End-to-End (E2E) Tests:

The keyless public-API reproduction creates a session with nested app: and
user: values, mutates the returned object, and then creates a second session.
On main, the second session exposes the mutations. With this change, default
mode retains the original values, while light-copy mode continues to share
nested objects by design. No model, credentials, or network access is required.

Checklist

  • I have read the CONTRIBUTING.md document.
  • I have performed a self-review of my own code.
  • I have commented my code where the copy-depth contract is not obvious.
  • I have added tests that prove the fix is effective.
  • New and existing unit tests pass locally with my changes. (Two
    environment-specific failures reproduce identically on unmodified main.)
  • I have manually tested the public behavior end to end.
  • Any dependent changes have been merged and published in downstream
    modules. (N/A; this change has no dependencies.)

Additional context

This is an internal copy-isolation fix. It does not change public API
signatures or surface and does not require an adk-docs update.

AI assistance disclosure

OpenAI Codex assisted with implementation, test preparation, and review.
Patched results were run against commit 2234ac92; baseline comparisons were
run against unmodified main at 37aa7308 under matching environments.

App- and user-scoped values were merged into returned in-memory sessions after the session copy, preserving nested references to stored state.

Copy scoped dictionaries at the same configured depth so default reads cannot mutate storage while light-copy mode retains intentional aliasing.
copybara-service Bot pushed a commit that referenced this pull request Sep 15, 2026
@adk-bot

adk-bot commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

Thank you @VedanthB for your contribution! 🎉

Your changes have been successfully imported and merged via Copybara in commit 557643b.

Closing this PR as the changes are now in the main branch.

@adk-bot adk-bot added the merged [Status] This PR is merged label Sep 15, 2026
@adk-bot adk-bot closed this Sep 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merged [Status] This PR is merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants