google / google/adk-python

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

Offen
#7,109 8 Kommentare 0 Reaktionen 1 zugewiesene Person Beansprucht von @surajksharma07 Auf GitHub ansehen
core
Vorherrschende Sprache
Python
Sterne
21.5k
Forks
4k
Ø Merge
1 T. 14 Std.
Gemergte PRs (30 T.)
37

Beschreibung

## 🔴 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:**

```python
"""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.

Beitragsleitfaden

Beitragsleitfaden öffnen

Bewertung

Dieses Issue wurde noch nicht bewertet.

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.