馃敶 Required Information
Describe the Bug:
FirestoreSessionService does not implement ADK's scoped state. ADK's State class defines app:, user: and temp: prefixes (State.APP_PREFIX, USER_PREFIX, TEMP_PREFIX), and InMemorySessionService stores app:/user: keys in shared app/user stores and merges them back into every session. FirestoreSessionService behaves differently:
appendEvent only routes keys starting with _app_ / _user_ to the app-state / user-state collections. Keys written through the normal ADK API (app:theme, user:name) fall through to the per-session state field, so they are only visible in that one session.
- The
app-state and user-state collections are write-only. getSession, createSession and listSessions never read them, so even a value stored under _app_ / _user_ never reaches another session.
temp: keys are persisted in the session document. BaseSessionService.appendEvent skips them.
- Removal checks
value == null, but ADK marks removals with the State.REMOVED sentinel, so a removed key is kept and the sentinel object is written to the session's state map instead of the key being deleted.
Code references (main @ 189d463):
contrib/firestore-session-service/.../FirestoreSessionService.java appendEvent (prefix split around L657-L689, removal check L666)
getSession builds the session only from the session document's state field (around L262-L272)
- Compare
core/.../InMemorySessionService.java appendEvent (L316-L338) and mergeWithGlobalState (L398)
Steps to Reproduce:
- Configure a runner with
FirestoreSessionService.
- In session A, a tool or callback sets
context.state().put("user:name", "Jae").
- Create session B for the same app and user, and read
user:name.
The same can be shown with the module's existing mocked tests (no Firestore needed): appending an event with state delta {"app:theme": "dark", "user:name": "Jae"} never calls set on the app-state / user-state documents, and the session document is updated with state = {app:theme=dark, user:name=Jae}. Appending {"temp:scratch": "x", "gone": State.REMOVED} writes both temp:scratch and the REMOVED sentinel into the session's state.
Expected Behavior:
Same semantics as InMemorySessionService (and the Python ADK database session services): app: keys shared by all sessions of the app, user: keys shared by all sessions of the app and user, merged back into Session.state() on getSession / createSession / listSessions, temp: keys not persisted, and State.REMOVED deleting the key.
Observed Behavior:
In session B, user:name is missing. App/user state behaves like session state. temp: values persist, and removed keys are not deleted.
Environment Details:
- ADK Library Version: main @ 189d463 (1.11.1-SNAPSHOT),
google-adk-firestore-session-service
- OS: Linux
- TS Version: N/A
Model Information:
- N/A (session service bug, model independent)
馃煛 Optional Information
Regression: No. The _app_ / _user_ handling has been there since the Firestore service was added.
Additional Context:
A fix would (a) split on State.APP_PREFIX / State.USER_PREFIX, (b) merge the app-state / user-state documents back into the session state with the prefixes on read, (c) skip State.TEMP_PREFIX, and (d) treat State.REMOVED as a delete (FieldValue.delete() in the shared stores). Existing data stored under _app_ / _user_ keys (if anyone relied on that convention) may need a note in the changelog. I'm happy to send a PR if this direction looks right.
馃敶 Required Information
Describe the Bug:
FirestoreSessionServicedoes not implement ADK's scoped state. ADK'sStateclass definesapp:,user:andtemp:prefixes (State.APP_PREFIX,USER_PREFIX,TEMP_PREFIX), andInMemorySessionServicestoresapp:/user:keys in shared app/user stores and merges them back into every session.FirestoreSessionServicebehaves differently:appendEventonly routes keys starting with_app_/_user_to theapp-state/user-statecollections. Keys written through the normal ADK API (app:theme,user:name) fall through to the per-sessionstatefield, so they are only visible in that one session.app-stateanduser-statecollections are write-only.getSession,createSessionandlistSessionsnever read them, so even a value stored under_app_/_user_never reaches another session.temp:keys are persisted in the session document.BaseSessionService.appendEventskips them.value == null, but ADK marks removals with theState.REMOVEDsentinel, so a removed key is kept and the sentinel object is written to the session'sstatemap instead of the key being deleted.Code references (main @ 189d463):
contrib/firestore-session-service/.../FirestoreSessionService.javaappendEvent(prefix split around L657-L689, removal check L666)getSessionbuilds the session only from the session document'sstatefield (around L262-L272)core/.../InMemorySessionService.javaappendEvent(L316-L338) andmergeWithGlobalState(L398)Steps to Reproduce:
FirestoreSessionService.context.state().put("user:name", "Jae").user:name.The same can be shown with the module's existing mocked tests (no Firestore needed): appending an event with state delta
{"app:theme": "dark", "user:name": "Jae"}never callsseton theapp-state/user-statedocuments, and the session document is updated withstate = {app:theme=dark, user:name=Jae}. Appending{"temp:scratch": "x", "gone": State.REMOVED}writes bothtemp:scratchand theREMOVEDsentinel into the session'sstate.Expected Behavior:
Same semantics as
InMemorySessionService(and the Python ADK database session services):app:keys shared by all sessions of the app,user:keys shared by all sessions of the app and user, merged back intoSession.state()ongetSession/createSession/listSessions,temp:keys not persisted, andState.REMOVEDdeleting the key.Observed Behavior:
In session B,
user:nameis missing. App/user state behaves like session state.temp:values persist, and removed keys are not deleted.Environment Details:
google-adk-firestore-session-serviceModel Information:
馃煛 Optional Information
Regression: No. The
_app_/_user_handling has been there since the Firestore service was added.Additional Context:
A fix would (a) split on
State.APP_PREFIX/State.USER_PREFIX, (b) merge theapp-state/user-statedocuments back into the session state with the prefixes on read, (c) skipState.TEMP_PREFIX, and (d) treatState.REMOVEDas a delete (FieldValue.delete()in the shared stores). Existing data stored under_app_/_user_keys (if anyone relied on that convention) may need a note in the changelog. I'm happy to send a PR if this direction looks right.