#1611·adk-go

session/inmemory: Create persists temp: keys from the initial state into the session record

Author: coderdailyoneCreated Sep 16, 2026Updated Sep 18, 2026
Labelsbug

Describe the bug

inMemoryService.Create splits the caller's initial CreateRequest.State with sessionutils.ExtractStateDeltas — which already separates app:, user:, temp: and session-scoped keys — but then discards the session-scoped delta (_) and merges the raw state map into the stored session:

go
appDelta, userDelta, _ := sessionutils.ExtractStateDeltas(req.State)
appState := s.updateAppState(appDelta, req.AppName)
userState := s.updateUserState(userDelta, req.AppName, req.UserID)
val.state = sessionutils.MergeStates(appState, userState, state) // raw state

A temp: key in the initial state therefore lands in the canonical session record and is returned by every later Get, even though temp: keys are documented as invocation-scoped ("Discarded after the invocation completes", session/session.go KeyPrefixTemp) and AppendEvent deliberately strips them from the canonical event record (#1356) and from what it persists (#1514).

This also makes the same CreateRequest behave differently across backends: session/database stores only sessionState (the extracted session-scoped delta), and adk-python's _create_session_impl stores state_deltas['session'] for the same reason. temp: is the only observable difference, since app:/user: keys re-enter the record through the same merge either way.

Where

session/inmemory.go, (*inMemoryService).Create — the MergeStates call at ~line 84 and the discarded third return of ExtractStateDeltas at ~line 76.

Impact

Any caller that seeds a session with temp: keys in CreateRequest.State (e.g. restoring a saved state snapshot that contains invocation-scoped keys) gets those keys back on every read for the life of the session on the in-memory service — in State().Get, State().All, and anything that renders state into prompts (instruction templating iterates session state). Switching to session/database silently drops them instead, so behavior differs by backend.

How to reproduce

go
svc := session.InMemoryService()
created, _ := svc.Create(ctx, &session.CreateRequest{
    AppName: "app", UserID: "u",
    State:   map[string]any{"temp:scratch": "x", "keep": "y"},
})
got, _ := svc.Get(ctx, &session.GetRequest{
    AppName: "app", UserID: "u", SessionID: created.Session.ID(),
})
v, err := got.Session.State().Get("temp:scratch")
// got: v == "x", err == nil   — the temp key persisted
// want: err == session.ErrStateKeyNotExist

The regression test TestInMemoryService_Create_StripsTempKeysFromInitialState in session/inmemory_test.go fails on the unfixed code with temp key "temp:scratch" leaked into the stored session state.

Suggested fix

Use the session-scoped delta that Create already computes:

go
appDelta, userDelta, sessionDelta := sessionutils.ExtractStateDeltas(req.State)
...
val.state = sessionutils.MergeStates(appState, userState, sessionDelta)

MergeStates re-adds app:/user: keys with their prefixes, so the merged state is identical except that temp: keys no longer persist — matching session/database and adk-python.