Skip to content

Memory has no removal path: delete_session() leaves what add_session_to_memory() copied, and BaseMemoryService exposes no way to remove it #7109

Description

@vasilisnasopoulos

🔴 Required Information

Describe the Bug:

BaseSessionService has delete_session, implemented by all five session backends. BaseMemoryService has three ingestion methods and one search method, and no removal method at all:

BaseMemoryService public API: ['add_events_to_memory', 'add_memory',
                               'add_session_to_memory', 'search_memory']

Since add_session_to_memory() copies the session's content into the memory service, deleting the session leaves that copy in place with no API to remove it. A caller who deletes a session reasonably expects the conversation to be gone; it is still returned verbatim by search_memory().

The base class docstring describes the service as providing "functionality to ingest conversation history into memory so that it can be used for user queries" — it does not say the ingestion is one-way.

Steps to Reproduce:

  1. pip install google-adk==2.9.0
  2. Save the script under Minimal Reproduction Code as repro.py
  3. python repro.py

Expected Behavior:

Either a way to remove what was ingested (for a session, a user, or a memory entry), or documentation stating that ingestion is one-way and that memory has to be cleaned up through the backend directly.

Observed Behavior:

before delete — in memory : True
after delete  — session   : gone
after delete  — in memory : STILL THERE
   retrieved verbatim    : my-passport-number-is-AB1234567

BaseMemoryService public API: ['add_events_to_memory', 'add_memory', 'add_session_to_memory', 'search_memory']
any removal method?        : False

The middle line is the negative control: the session really is deleted, so the surviving copy is the memory service's, not a stale session.

Environment Details:

  • ADK Library Version: google-adk 2.9.0
  • Desktop OS: macOS 26.6.2 (arm64)
  • Python Version: 3.12.13

Model Information:

  • Are you using LiteLLM: No
  • Which model is being used: N/A — no model is involved

🟡 Optional Information

Additional Context — scope, stated restrictively:

  • This is a gap in the abstraction, not a runtime failure. Nothing raises, and InMemoryMemoryService behaves exactly as written.
  • I have not checked whether the hosted backends (Vertex AI RAG, Memory Bank) offer deletion outside ADK through their own APIs or consoles. They may well do. The asymmetry I am reporting is in ADK's own surface: one store is deletable through the abstraction and the other is not, while one feeds the other.
  • I am deliberately not proposing an API shape. Whether removal is keyed by session, by user, or by memory entry is a design decision, and search_memory results do not currently carry an identifier that a delete call could take.
  • I am not raising any compliance or regulatory claim; that is yours to judge. I am only showing that the two lifecycles are asymmetric and that the docstring does not say so.

Minimal Reproduction Code:

"""delete_session() removes the session; what add_session_to_memory() copied stays,
and BaseMemoryService exposes no way to remove it."""
import asyncio, inspect
from google.genai import types
from google.adk.events.event import Event
from google.adk.sessions.in_memory_session_service import InMemorySessionService
from google.adk.memory.in_memory_memory_service import InMemoryMemoryService
from google.adk.memory.base_memory_service import BaseMemoryService

SECRET = 'my-passport-number-is-AB1234567'

async def main():
    sessions, memory = InMemorySessionService(), InMemoryMemoryService()
    s = await sessions.create_session(app_name='app', user_id='u1')
    await sessions.append_event(s, Event(author='user', invocation_id='i1',
        content=types.Content(role='user', parts=[types.Part(text=SECRET)])))
    s = await sessions.get_session(app_name='app', user_id='u1', session_id=s.id)
    await memory.add_session_to_memory(s)

    hit = await memory.search_memory(app_name='app', user_id='u1', query='passport')
    print('before delete — in memory :', bool(hit.memories))

    await sessions.delete_session(app_name='app', user_id='u1', session_id=s.id)

    gone = await sessions.get_session(app_name='app', user_id='u1', session_id=s.id)
    print('after delete  — session   :', 'gone' if gone is None else 'STILL THERE')  # negative control

    hit = await memory.search_memory(app_name='app', user_id='u1', query='passport')
    print('after delete  — in memory :', 'STILL THERE' if hit.memories else 'gone')
    if hit.memories:
        print('   retrieved verbatim    :', hit.memories[0].content.parts[0].text)

    api = [n for n, _ in inspect.getmembers(BaseMemoryService, inspect.isfunction)
           if not n.startswith('_')]
    print('\nBaseMemoryService public API:', api)
    print('any removal method?        :',
          any(k in n for n in api for k in ('delete','remove','purge','forget')))

asyncio.run(main())

How often has this issue occurred?:

  • Always (100%) — deterministic; no model, no network, no timing involved.

Activity

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

Metadata

Metadata

Labels

core[Component] This issue is related to the core interface and implementation

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions