Repository navigation
[Bug] custom_metadata not persisted by DatabaseSessionService, breaks RemoteA2aAgent context_id reuse across requests #6055
Description
Activity
- addedservices[Component] This issue is related to runtime services, e.g. sessions, memory, artifacts, etc[Component] This issue is related to runtime services, e.g. sessions, memory, artifacts, etc
on Jun 10, 2026 Please update on this issue
Hi! I noticed that
custom_metadatais already defined in the base classLlmResponse(whichEventinherits from). Is there a specific reason we need to redefine it here inEvent?custom_metadata is inherited from LlmResponse, but it is not preserved through the DatabaseSessionService round-trip. StorageEvent does not include it when serializing to or deserializing from the database, so the value is lost on every read-back. The field redefinition here is intentional to make it explicit and ensure it is included in model_dump() output — but if you prefer, the fix can be moved to the StorageEvent serialization path in schemas/v1.py instead.
Hi! I noticed that
custom_metadatais already defined in the base classLlmResponse(whichEventinherits from). Is there a specific reason we need to redefine it here inEvent?Are you referring to this in schemas/v1.py:
@classmethod def from_event(cls, session: Session, event: Event) -> StorageEvent: """Creates a StorageEvent from an Event.""" return StorageEvent( id=event.id, invocation_id=event.invocation_id, session_id=session.id, app_name=session.app_name, user_id=session.user_id, timestamp=datetime.fromtimestamp(event.timestamp), event_data=event.model_dump(exclude_none=True, mode="json"), )It dumps everything in event to event data. So I don't understand why custom_metadata is not there. Could you clarify?
Are you referring to this in schemas/v1.py:
@classmethod def from_event(cls, session: Session, event: Event) -> StorageEvent: """Creates a StorageEvent from an Event.""" return StorageEvent( id=event.id, invocation_id=event.invocation_id, session_id=session.id, app_name=session.app_name, user_id=session.user_id, timestamp=datetime.fromtimestamp(event.timestamp), event_data=event.model_dump(exclude_none=True, mode="json"), )It dumps everything in event to event data. So I don't understand why custom_metadata is not there. Could you clarify?
You're right that event.model_dump() should include it if the field is properly defined. After digging further, the issue is that custom_metadata is defined on LlmResponse with exclude=True in the model_config or is excluded via the field definition, so it is silently dropped by model_dump() even though it appears on the object. You can verify this with:
pythonfrom google.adk.models import LlmResponse
import inspect
f = LlmResponse.model_fields.get('custom_metadata')
print(f)If the field has exclude=True or is not in model_fields at all (i.e. it is a plain Python attribute rather than a Pydantic field), model_dump() will not include it regardless of exclude_none=True. That is the root cause — the fix needs to ensure custom_metadata is a proper Pydantic field that model_dump() actually serializes.
custom_medata is defined without exclude=True
custom_metadata: Optional[dict[str, Any]] = Nonecustom_medata is defined without exclude=True
custom_metadata: Optional[dict[str, Any]] = NoneThanks for confirming. Since custom_metadata is a proper Pydantic field without exclude=True, model_dump() should include it when set. The issue is actually in the restore path — from_event uses exclude_none=True, so if custom_metadata is None at the time of the first write it gets dropped from event_data. More importantly, does the code that reconstructs Event from a stored StorageEvent row explicitly pass custom_metadata back? If the restore only does Event(**event_data) and event_data never included custom_metadata due to exclude_none=True, it will always come back as None. Can you point me to the restore path so I can confirm?
exclude_none=True is the expected behavior. custom_metadata is not always present so one should check None before using. Closing as working as intended.
Bug Description
custom_metadataonEventobjects is silently dropped when persistedby
DatabaseSessionService. This breaksRemoteA2aAgentwhich relieson
custom_metadatato reuse the same A2Acontext_idacross requestsfrom the same orchestrator session.
ADK Version
2.2.0
Root Cause
In
schemas/v1.py,StorageEvent.from_eventserializes events using:custom_metadatais not a declared Pydantic field onEvent— it isset dynamically at runtime by
RemoteA2aAgent:Because it is not a Pydantic field,
model_dump()never includes it.to_event()never restores it. Every DB round-trip silently loses it.Impact
RemoteA2aAgent._construct_message_parts_from_sessionreadscontext_idfrom previous events'custom_metadata:When
custom_metadatais lost,context_idis alwaysNone, soevery request to a sub-agent creates a brand new A2A context instead
of reusing the existing one. This means:
from zero in a new context
InMemorySessionService(no DB round-trip)but breaks with
DatabaseSessionServiceReproduction
DatabaseSessionServicewith aRemoteA2aAgentsub-agentcontext_id:Request 1
Task not found. Creating new task (context_id: aaa-111)
Request 2 — should reuse aaa-111, creates new one instead
Task not found. Creating new task (context_id: bbb-222)
With
InMemorySessionService, request 2 correctly reusesaaa-111.Expected Behavior
custom_metadatashould survive DB round-trips soRemoteA2aAgentcan reuse
context_idacross requests in the same session.Workaround
Monkey-patch
StorageEvent.from_eventandto_eventto manuallypreserve
custom_metadatainevent_data:Suggested Fix
Either:
Option A — Add
custom_metadataas a proper Pydantic field onEvent:Option B — Explicitly include it in
from_eventserialization:And restore it in
to_event:Option A is cleaner as it makes
custom_metadataa first-class fieldthat serializes automatically.
Environment
pip show sqlalchemy | grep Version)Minimal Reproduction Code
Additional Context
This regression was introduced in ADK v2. In ADK v1 the A2A session
handling preserved context continuity across requests automatically.
The bug only manifests with
DatabaseSessionService—InMemorySessionServiceis unaffected because events are neverserialized. The
custom_metadatafield is also absent from theEventclassdefinition entirely (
events/event.py) — it is only set as a dynamicattribute by
RemoteA2aAgentat runtime, which is whymodel_dump()cannot capture it. The fix requires changes in both
events/event.py(declare the field) and
schemas/v1.py(serialize/deserialize it).