Skip to content

DatabaseSessionService.to_event() fails to deserialize nested Pydantic models (EventCompaction) when using EventsCompactionConfig #4047

@fabio-aetherfoundry

Description

@fabio-aetherfoundry

ADK Version:
Tested on: v1.19.0
Still present in: v1.21.0 (latest)
Python Version:
3.12
Database:
PostgreSQL (via asyncpg driver)
Description
When using EventsCompactionConfig with DatabaseSessionService, compaction events are created and stored correctly. However, when those events are later loaded from the database, the application crashes with:
AttributeError: 'dict' object has no attribute 'start_timestamp'
This occurs in google/adk/flows/llm_flows/contents.py at line 298 when trying to access compaction.start_timestamp.
Root Cause
The bug is in DatabaseSessionService's StorageEvent.to_event() method at line 364 of database_session_service.py:
def to_event(self) -> Event: return Event( ... # BUG: model_copy() doesn't validate/convert nested dicts to Pydantic models actions=EventActions().model_copy(update=self.actions.model_dump()), ... )
What happens:
self.actions is a pickled EventActions object with compaction as an EventCompaction instance
model_dump() converts the entire object tree to dicts, including EventCompaction → dict
model_copy(update=...) does a shallow update but doesn't validate nested models
The result has actions.compaction as a dict instead of an EventCompaction object
Pydantic v2 behavior:
model_copy(update=dict) - shallow copy without validation of nested structures
model_validate(dict) - full validation with proper nested model instantiation
Steps to Reproduce
import asynciofrom google.adk.apps.app import App, EventsCompactionConfigfrom google.adk.sessions import DatabaseSessionServicefrom google.adk.agents import LlmAgentfrom google.adk.runners import Runnerfrom google.genai import types# Setupdb_url = "postgresql+asyncpg://user:pass@localhost:5432/mydb"session_service = DatabaseSessionService(db_url=db_url)# Create agent with compactionagent = LlmAgent(name='test', model='gemini-2.0-flash', instruction='Test agent')compaction_config = EventsCompactionConfig(compaction_interval=3, overlap_size=1)app = App(name='test_app', root_agent=agent, events_compaction_config=compaction_config)runner = Runner(app=app, session_service=session_service)async def reproduce(): # Create session session = await session_service.create_session( app_name='test_app', user_id='test_user', session_id='test_session' ) # Run agent multiple times to trigger compaction for i in range(5): # More than compaction_interval message = types.Content(role="user", parts=[types.Part(text=f"Message {i}")]) async for event in runner.run_async( user_id='test_user', session_id='test_session', new_message=message ): pass print("Compaction should have occurred. Now reload session...") # This is where the bug manifests - loading a session with compaction events # The next agent run will fail when processing the loaded compaction events message = types.Content(role="user", parts=[types.Part(text="Final message")]) async for event in runner.run_async( user_id='test_user', session_id='test_session', new_message=message ): pass # CRASHES HEREasyncio.run(reproduce())
Expected Behavior
Sessions with compaction events should load correctly, with event.actions.compaction being a proper EventCompaction instance.
Actual Behavior
Traceback (most recent call last): File ".../google/adk/flows/llm_flows/contents.py", line 298, in _apply_compaction if ( compaction.start_timestamp is not None ^^^^^^^^^^^^^^^^^^^^^^^^^AttributeError: 'dict' object has no attribute 'start_timestamp'
Proposed Fix
Change line 364 in database_session_service.py from:
actions=EventActions().model_copy(update=self.actions.model_dump()),
To:
actions=EventActions.model_validate(self.actions.model_dump()) if self.actions else EventActions(),
This ensures all nested Pydantic models (including EventCompaction) are properly instantiated during deserialization.
Workaround
Currently, the only workaround is to disable compaction entirely:

Don't use EventsCompactionConfig with DatabaseSessionServicerunner = Runner(app_name='...', agent=agent, session_service=session_service)

Additional Context
The _apply_compaction function in contents.py correctly expects EventCompaction objects:

contents.py lines 295-316if event.actions and event.actions.compaction: compaction = event.actions.compaction if ( compaction.start_timestamp is not None # <- Expects EventCompaction, gets dict and compaction.end_timestamp is not None ): ...

The EventCompaction model is defined in event_actions.py:
class EventCompaction(BaseModel): start_timestamp: float end_timestamp: float compacted_content: Content
Impact
This bug makes EventsCompactionConfig unusable with DatabaseSessionService, which is likely the most common production setup. Users who enable compaction will have their sessions fail to load after compaction events are stored.

Metadata

Metadata

Assignees

Labels

services[Component] This issue is related to runtime services, e.g. sessions, memory, artifacts, etc

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions