Skip to content

InMemorySessionService retains appended Event references, allowing later mutations to change saved history and state #7701

Open

Description

@BichengWang

🔴 Required Information

Describe the Bug:
With the default deep-copy configuration, InMemorySessionService.append_event() retains the caller's mutable Event and nested state-delta values. Mutating that event after the append returns changes the stored conversation history and session state without another append. A subsequent get_session() returns those changed values.

Steps to Reproduce:

  1. Check out main at 48d18a6aea65ed399bf036f29e383219ec167b3b (ADK 2.12.0) with runtime dependencies installed.
  2. Save the code below as repro.py and run PYTHONPATH=src python repro.py.
  3. Compare the in-memory result with the SQLite control. No model, credentials, or network are used.

Expected Behavior:
In the default deep-copy mode, a completed append should preserve the saved event and state. Changing the caller's retained event should leave the saved answer as original answer and the saved cart as {'items': ['coffee']}.

Observed Behavior:
The in-memory service reloads changed after append and {'items': ['coffee', 'tea']}. The SQLite control reloads the original answer and cart.

Environment Details:

  • ADK Library Version: source 2.12.0 at the commit above.
  • Desktop OS: macOS 27.0.1, arm64.
  • Python Version: 3.13.16; dependencies installed from cache offline.

Model Information:

  • LiteLLM: No.
  • Model: N/A; session-service calls only.

🟡 Optional Information

Regression: Unknown; no earlier release was tested.

Logs:

InMemorySessionService: text='changed after append', cart={'items': ['coffee', 'tea']}
SqliteSessionService: text='original answer', cart={'items': ['coffee']}

Screenshots / Video: N/A.

Additional Context:
This is the append boundary, separate from #7676's initial create_session(state=...) snapshot and #7105's copies of scoped state returned to callers. It also reproduces on #7676's head 110262ad33f2c82b6eaa47faca9d6931e0566782. #7454 concerns duplicate delivery to stale callers; this reproduction appends one event once. No fix is included.

Minimal Reproduction Code:

import asyncio

from google.adk.events import Event
from google.adk.events.event_actions import EventActions
from google.adk.features import FeatureName, override_feature_enabled
from google.adk.sessions import InMemorySessionService
from google.adk.sessions.sqlite_session_service import SqliteSessionService
from google.genai import types


async def check(service):
    try:
        session = await service.create_session(app_name="app", user_id="user")
        event = Event(
            author="assistant",
            content=types.Content(
                role="model", parts=[types.Part(text="original answer")]
            ),
            actions=EventActions(state_delta={"cart": {"items": ["coffee"]}}),
        )
        await service.append_event(session, event)
        event.content.parts[0].text = "changed after append"
        event.actions.state_delta["cart"]["items"].append("tea")
        stored = await service.get_session(
            app_name="app", user_id="user", session_id=session.id
        )
        text = stored.events[0].content.parts[0].text
        print(f"{type(service).__name__}: text={text!r}, cart={stored.state['cart']}")
    finally:
        if isinstance(service, SqliteSessionService):
            await service.close()


async def main():
    override_feature_enabled(FeatureName.IN_MEMORY_SESSION_SERVICE_LIGHT_COPY, False)
    await check(InMemorySessionService())
    await check(SqliteSessionService(":memory:"))


asyncio.run(main())

How often has this issue occurred?: Always with the reproduction above.

Activity

  1. widechaos commented on Oct 23, 2026

    @widechaos

    May I take this on if nobody is already working on it? I reproduced the append-boundary mutation and have a local candidate that snapshots the event after temporary state is trimmed, then uses that snapshot for internal history and persistent scoped state. The caller’s event/return value and light-copy reference sharing stay unchanged.

    An independent source-revert check gives four failures on main and 22 passes with the candidate, covering nested session/user/app state, SQLite controls, partial events, temporary non-copyable values, and the canonical-session append path. The related session/Runner suite passes 1041 tests (one skipped, three xfailed).

    One compatibility point needs confirmation: persistent payloads that cannot be deep-copied would fail in default mode at append rather than later at get/list. Light-copy still accepts them. Is that boundary consistent with the intended default snapshot contract? I have not submitted a PR; full supported-Python tox validation remains outstanding.

  2. added
    services[Component] This issue is related to runtime services, e.g. sessions, memory, artifacts, etc
    on Oct 26, 2026
  3. sanketpatil06 commented on Oct 28, 2026

    @sanketpatil06

    Thanks @BichengWang for the detailed report and the SQLite comparison. I've reproduced this on main. In deep-copy mode, append_event() stores the caller's own Event, and it merges state_delta values into state by reference. Any change made after the append ends up in storage.

    @widechaos, failing at append for persistent values that can't be deep-copied seems reasonable to me. Today those values already fail later at get_session(), so this just moves the error to where the value comes in. You're welcome to send a PR with your approach.

    Workaround for now: await session_service.append_event(session, event.model_copy(deep=True))

  4. willie3838 commented on Oct 29, 2026

    @willie3838

    Hi @widechaos, actually an internal team at Google will be diving into this. Thanks for doing the initial investigation into the approach!

  5. self-assigned this
    on Oct 29, 2026
  6. added
    wip[Status] This issue is being worked on. Either there is a pending PR or is planned to be fixed
    on Oct 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

services[Component] This issue is related to runtime services, e.g. sessions, memory, artifacts, etcwip[Status] This issue is being worked on. Either there is a pending PR or is planned to be fixed

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions