In Google ADK session state, what do the app:, user: and temp: prefixes do?
answer
- prefix encodes lifetime, not type
- one dict, four reach distances
- across users versus across sessions
- one of the four is never written down
- scratch values that must not enter history
basics
~20 sThey set scope. An unprefixed key belongs to one session; app: is shared by every user of that app_name; user: follows one user_id across all their sessions; temp: exists only for the current invocation and is never persisted.
solid answer
~40 sSession `state` in ADK is a flat dict, and the key's prefix tells the `SessionService` how far the value travels. No prefix means session-scoped: the value belongs to this conversation only. `app:` makes the value shared across every session of the same `app_name` — good for a global config value or a feature flag. `user:` scopes it to one `user_id` across all of that user's sessions, which is the right home for preferences that must survive a new conversation. `temp:` is the odd one out: it is readable during the current invocation but is deliberately discarded rather than written down, so it is where you park a bulky intermediate value you do not want in permanent history. A persistent service such as `DatabaseSessionService` honours the first three; nothing makes `temp:` durable.
code
python · 15 linesfrom google.adk.sessions import InMemorySessionService
async def seed(session_service: InMemorySessionService) -> None:
initial_state = {
"app:default_tone": "concise", # every user of this app
"user:preferred_language": "fr", # every session of this user
"order_id": "A-1001", # this conversation only
"temp:raw_payload": {"status": 200}, # this invocation only
}
await session_service.create_session(
app_name="shop",
user_id="u1",
state=initial_state,
)go deeper
Memorise the four scopes and say which one crosses sessions and which one is thrown away. Know that the prefix is part of the key you read and write.
Explain that the session service routes keys by prefix at commit time, and give a concrete example of a value belonging in each scope — especially why a bulky intermediate belongs under temp:.
Show judgment about what should not be in state at all, the multi-tenant hazard of shared app: writes, and how state size affects every subsequent turn's load.
Own the data-governance question: retention, deletion and PII in session state versus your own store, and the tenancy model that app_name scoping commits you to.
## State is one dict with four scopes encoded in the keys ADK deliberately avoids giving you four separate stores. There is one `state` dictionary on the `Session`, and the scope of each entry is carried by a prefix on its key. The session service reads the prefix when the value is committed and files it accordingly. - **`"order_id"`** — no prefix, session scope. Visible only inside this conversation thread. - **`"app:default_tone"`** — application scope. Shared by every session under the same `app_name`, for every user. - **`"user:preferred_language"`** — user scope. Shared by every session belonging to the same `user_id` in that app. - **`"temp:raw_payload"`** — invocation scope. Usable within the current turn and then dropped; it never becomes part of the persisted session. When an agent or tool reads `state["user:preferred_language"]`, the service has already merged the scoped values into the dict it handed over, so reading is uniform — you just use the full prefixed key. ## Why the scopes exist The first three exist because conversational systems have three natural lifetimes and only one of them is the thread. A user's language preference should not be re-asked in every new conversation; a global model or tone setting should not be copied into ten thousand session rows. Encoding scope in the key means an agent's prompt template or a tool can read all three with one lookup mechanism, and a persistent session service can store them in separate tables without any of that leaking into agent code. `temp:` exists for a different reason: **not everything a turn computes deserves to be written down**. A tool that fetches a 200 KB API response and hands a summary to the model has no business persisting the raw payload into permanent session state, where it would be reloaded on every subsequent turn and inflate the store. Marking it `temp:` gives you a pass-value between steps of the same invocation with an explicit guarantee that it evaporates. ## The mistakes people make **Treating `temp:` as short-lived-but-saved.** It is not a TTL. It is not persisted at all — after the invocation there is nothing to expire. If you need a value on the next turn, drop the prefix. **Treating `app:` as a config system.** It is shared mutable state across every user of the app. A tool that writes `app:` on behalf of one user has changed what every other user sees. Use it for values a human operator sets, not for anything derived from a single conversation. **Using `user:` as a user database.** It follows the user across sessions, which makes it tempting as a profile store. It is still conversation scratch space living in the session backend, with the session backend's retention and deletion story. Anything you would want to query, index, join or audit belongs in your own database, with state holding at most a key into it. **Forgetting scope in multi-tenant deployments.** `app:` is keyed by `app_name`, so two logical tenants sharing an `app_name` share application state. If tenants must be isolated, they need distinct app names or their own deployments — not a convention in the key suffix. ## Reading and writing You can seed all four kinds at session creation by passing a `state` dict to `create_session`. During a turn, changes should be made through the mechanism that produces a state delta on the emitted event rather than by mutating the retrieved `Session` object in place, because only the delta path is committed by the session service. The prefix rules apply to that delta: keys under `temp:` are dropped when the event is appended, while `app:` and `user:` keys are routed to their shared scope instead of the session row. ## A rule of thumb Ask how long the value must be true. Until the end of this tool chain — `temp:`. Until this conversation ends — no prefix. Until this person changes their mind — `user:`. Until an operator changes the app's configuration — `app:`. If the answer is "forever, and other systems need it too", it does not belong in session state at all.
- Where would you put a value that must be read by the next tool in the same turn but never appear in history?Under a `temp:` key. It is readable for the remainder of the current invocation and is discarded rather than persisted when the event is committed, so a bulky intermediate — a raw API payload, an unredacted document — never enters the durable session. If the next turn needs it, `temp:` is the wrong choice and you should use an unprefixed session key.
- Two tenants run under the same app_name. What breaks when a tool writes an app: key?Application-scoped state is keyed by `app_name`, so the write is visible to every session of every user under that name — the second tenant included. There is no sub-scoping convention that makes it safe. Isolation has to come from separate `app_name` values or separate deployments, and tenant-derived values should generally live in your own store rather than in `app:` state.
- Does a user: state key give the agent memory of what happened in a previous conversation?Only for the specific facts you chose to write there. It is a key/value carry-over, not recall: the previous conversation's events are not visible. Searching what was said in earlier sessions is the `MemoryService`'s job. In practice you use `user:` state for a handful of stable facts and memory for open-ended recall.
saying these in an interview costs you the question
- Calling temp: a short expiry rather than never persisted
- Assuming app: state is per-user configuration
- Using user: state as the system of record for profiles
- Thinking each scope is a separate dict on the session
- Expecting prefixes to work without a session service that persists