# State: The Session's Scratchpad
Supported in ADKPython v0.1.0Go v0.1.0Java v0.1.0
Within each `Session` (our conversation thread), the **`state`** attribute acts like the agent's dedicated scratchpad for that specific interaction. While `session.events` holds the full history, `session.state` is where the agent stores and updates dynamic details needed *during* the conversation. ## What is `session.state`? Conceptually, `session.state` is a collection (dictionary or Map) holding key-value pairs. It's designed for information the agent needs to recall or track to make the current conversation effective: * **Personalize Interaction:** Remember user preferences mentioned earlier (e.g., `'user_preference_theme': 'dark'`). * **Track Task Progress:** Keep tabs on steps in a multi-turn process (e.g., `'booking_step': 'confirm_payment'`). * **Accumulate Information:** Build lists or summaries (e.g., `'shopping_cart_items': ['book', 'pen']`). * **Make Informed Decisions:** Store flags or values influencing the next response (e.g., `'user_is_authenticated': True`). ### Key Characteristics of `State` 1. **Structure: Serializable Key-Value Pairs** * Data is stored as `key: value`. * **Keys:** Always strings (`str`). Use clear names (e.g., `'departure_city'`, `'user:language_preference'`). * **Values:** Must be **serializable**. This means they can be easily saved and loaded by the `SessionService`. Stick to basic types in the specific languages (Python/Go/Java) like strings, numbers, booleans, and simple lists or dictionaries containing *only* these basic types. (See API documentation for precise details). * **⚠️ Avoid Complex Objects:** **Do not store non-serializable objects** (custom class instances, functions, connections, etc.) directly in the state. Store simple identifiers if needed, and retrieve the complex object elsewhere. 2. **Mutability: It Changes** * The contents of the `state` are expected to change as the conversation evolves. 3. **Persistence: Depends on `SessionService`** * Whether state survives application restarts depends on your chosen service: * `InMemorySessionService`: **Not Persistent.** State is lost on restart. * `DatabaseSessionService` / `VertexAiSessionService`: **Persistent.** State is saved reliably. !!! Note The specific parameters or method names for the primitives may vary slightly by SDK language (e.g., `session.state['current_intent'] = 'book_flight'` in Python,`context.State().Set("current_intent", "book_flight")` in Go, `session.state().put("current_intent", "book_flight)` in Java). Refer to the language-specific API documentation for details. ### Organizing State with Prefixes: Scope Matters Prefixes on state keys define their scope and persistence behavior, especially with persistent services: * **No Prefix (Session State):** * **Scope:** Specific to the *current* session (`id`). * **Persistence:** Only persists if the `SessionService` is persistent (`Database`, `VertexAI`). * **Use Cases:** Tracking progress within the current task (e.g., `'current_booking_step'`), temporary flags for this interaction (e.g., `'needs_clarification'`). * **Example:** `session.state['current_intent'] = 'book_flight'` * **`user:` Prefix (User State):** * **Scope:** Tied to the `user_id`, shared across *all* sessions for that user (within the same `app_name`). * **Persistence:** Persistent with `Database` or `VertexAI`. (Stored by `InMemory` but lost on restart). * **Use Cases:** User preferences (e.g., `'user:theme'`), profile details (e.g., `'user:name'`). * **Example:** `session.state['user:preferred_language'] = 'fr'` * **`app:` Prefix (App State):** * **Scope:** Tied to the `app_name`, shared across *all* users and sessions for that application. * **Persistence:** Persistent with `Database` or `VertexAI`. (Stored by `InMemory` but lost on restart). * **Use Cases:** Global settings (e.g., `'app:api_endpoint'`), shared templates. * **Example:** `session.state['app:global_discount_code'] = 'SAVE10'` * **`temp:` Prefix (Temporary Invocation State):** * **Scope:** Specific to the current **invocation** (the entire process from an agent receiving user input to generating the final output for that input). * **Persistence:** **Not Persistent.** Discarded after the invocation completes and does not carry over to the next one. * **Use Cases:** Storing intermediate calculations, flags, or data passed between tool calls within a single invocation. * **When Not to Use:** For information that must persist across different invocations, such as user preferences, conversation history summaries, or accumulated data. * **Example:** `session.state['temp:raw_api_response'] = {...}` !!! note "Sub-Agents and Invocation Context" When a parent agent calls a sub-agent (e.g., using `SequentialAgent` or `ParallelAgent`), it passes its `InvocationContext` to the sub-agent. This means the entire chain of agent calls shares the same invocation ID and, therefore, the same `temp:` state. **How the Agent Sees It:** Your agent code interacts with the *combined* state through the single `session.state` collection (dict/ Map). The `SessionService` handles fetching/merging state from the correct underlying storage based on prefixes. ### Accessing Session State in Agent Instructions When working with `LlmAgent` instances, you can directly inject session state values into the agent's instruction string using a simple templating syntax. This allows you to create dynamic and context-aware instructions without relying solely on natural language directives. #### Using `{key}` Templating To inject a value from the session state, enclose the key of the desired state variable within curly braces: `{key}`. The framework will automatically replace this placeholder with the corresponding value from `session.state` before passing the instruction to the LLM. **Example:** === "Python" ```python from google.adk.agents import LlmAgent story_generator = LlmAgent( name="StoryGenerator", model="gemini-2.0-flash", instruction="""Write a short story about a cat, focusing on the theme: {topic}.""" ) # Assuming session.state['topic'] is set to "friendship", the LLM # will receive the following instruction: # "Write a short story about a cat, focusing on the theme: friendship." ``` === "Go" ```go --8<-- "examples/go/snippets/sessions/instruction_template/instruction_template_example.go:key_template" ``` #### Important Considerations * Key Existence: Ensure that the key you reference in the instruction string exists in the session.state. If the key is missing, the agent will throw an error. To use a key that may or may not be present, you can include a question mark (?) after the key (e.g. {topic?}). * Data Types: The value associated with the key should be a string or a type that can be easily converted to a string. * Escaping: If you need to use literal curly braces in your instruction (e.g., for JSON formatting), you'll need to escape them. #### Bypassing State Injection with `InstructionProvider` In some cases, you might want to use `{{` and `}}` literally in your instructions without triggering the state injection mechanism. For example, you might be writing instructions for an agent that helps with a templating language that uses the same syntax. To achieve this, you can provide a function to the `instruction` parameter instead of a string. This function is called an `InstructionProvider`. When you use an `InstructionProvider`, the ADK will not attempt to inject state, and your instruction string will be passed to the model as-is. The `InstructionProvider` function receives a `ReadonlyContext` object, which you can use to access session state or other contextual information if you need to build the instruction dynamically. === "Python" ```python from google.adk.agents import LlmAgent from google.adk.agents.readonly_context import ReadonlyContext # This is an InstructionProvider def my_instruction_provider(context: ReadonlyContext) -> str: # You can optionally use the context to build the instruction # For this example, we'll return a static string with literal braces. return "This is an instruction with {{literal_braces}} that will not be replaced." agent = LlmAgent( model="gemini-2.0-flash", name="template_helper_agent", instruction=my_instruction_provider ) ``` === "Go" ```go --8<-- "examples/go/snippets/sessions/instruction_provider/instruction_provider_example.go:bypass_state_injection" ``` If you want to both use an `InstructionProvider` *and* inject state into your instructions, you can use the `inject_session_state` utility function. === "Python" ```python from google.adk.agents import LlmAgent from google.adk.agents.readonly_context import ReadonlyContext from google.adk.utils import instructions_utils async def my_dynamic_instruction_provider(context: ReadonlyContext) -> str: template = "This is a {adjective} instruction with {{literal_braces}}." # This will inject the 'adjective' state variable but leave the literal braces. return await instructions_utils.inject_session_state(template, context) agent = LlmAgent( model="gemini-2.0-flash", name="dynamic_template_helper_agent", instruction=my_dynamic_instruction_provider ) ``` === "Go" ```go --8<-- "examples/go/snippets/sessions/instruction_provider/instruction_provider_example.go:manual_state_injection" ``` **Benefits of Direct Injection** * Clarity: Makes it explicit which parts of the instruction are dynamic and based on session state. * Reliability: Avoids relying on the LLM to correctly interpret natural language instructions to access state. * Maintainability: Simplifies instruction strings and reduces the risk of errors when updating state variable names. **Relation to Other State Access Methods** This direct injection method is specific to LlmAgent instructions. Refer to the following section for more information on other state access methods. ### How State is Updated: Recommended Methods !!! note "The Right Way to Modify State" When you need to change the session state, the correct and safest method is to **directly modify the `state` object on the `Context`** provided to your function (e.g., `callback_context.state['my_key'] = 'new_value'`). This is considered "direct state manipulation" in the right way, as the framework automatically tracks these changes. This is critically different from directly modifying the `state` on a `Session` object you retrieve from the `SessionService` (e.g., `my_session.state['my_key'] = 'new_value'`). **You should avoid this**, as it bypasses the ADK's event tracking and can lead to lost data. The "Warning" section at the end of this page has more details on this important distinction. State should **always** be updated as part of adding an `Event` to the session history using `session_service.append_event()`. This ensures changes are tracked, persistence works correctly, and updates are thread-safe. **1\. The Easy Way: `output_key` (for Agent Text Responses)** This is the simplest method for saving an agent's final text response directly into the state. When defining your `LlmAgent`, specify the `output_key`: === "Python" ```py from google.adk.agents import LlmAgent from google.adk.sessions import InMemorySessionService, Session from google.adk.runners import Runner from google.genai.types import Content, Part # Define agent with output_key greeting_agent = LlmAgent( name="Greeter", model="gemini-2.0-flash", # Use a valid model instruction="Generate a short, friendly greeting.", output_key="last_greeting" # Save response to state['last_greeting'] ) # --- Setup Runner and Session --- app_name, user_id, session_id = "state_app", "user1", "session1" session_service = InMemorySessionService() runner = Runner( agent=greeting_agent, app_name=app_name, session_service=session_service ) session = await session_service.create_session(app_name=app_name, user_id=user_id, session_id=session_id) print(f"Initial state: {session.state}") # --- Run the Agent --- # Runner handles calling append_event, which uses the output_key # to automatically create the state_delta. user_message = Content(parts=[Part(text="Hello")]) for event in runner.run(user_id=user_id, session_id=session_id, new_message=user_message): if event.is_final_response(): print(f"Agent responded.") # Response text is also in event.content # --- Check Updated State --- updated_session = await session_service.get_session(app_name=APP_NAME, user_id=USER_ID, session_id=session_id) print(f"State after agent run: {updated_session.state}") # Expected output might include: {'last_greeting': 'Hello there! How can I help you today?'} ``` === "Java" ```java --8<-- "examples/java/snippets/src/main/java/state/GreetingAgentExample.java:full_code" ``` === "Go" ```go --8<-- "examples/go/snippets/sessions/state_example/state_example.go:greeting" ``` Behind the scenes, the `Runner` uses the `output_key` to create the necessary `EventActions` with a `state_delta` and calls `append_event`. **2\. The Standard Way: `EventActions.state_delta` (for Complex Updates)** For more complex scenarios (updating multiple keys, non-string values, specific scopes like `user:` or `app:`, or updates not tied directly to the agent's final text), you manually construct the `state_delta` within `EventActions`. === "Python" ```py from google.adk.sessions import InMemorySessionService, Session from google.adk.events import Event, EventActions from google.genai.types import Part, Content import time # --- Setup --- session_service = InMemorySessionService() app_name, user_id, session_id = "state_app_manual", "user2", "session2" session = await session_service.create_session( app_name=app_name, user_id=user_id, session_id=session_id, state={"user:login_count": 0, "task_status": "idle"} ) print(f"Initial state: {session.state}") # --- Define State Changes --- current_time = time.time() state_changes = { "task_status": "active", # Update session state "user:login_count": session.state.get("user:login_count", 0) + 1, # Update user state "user:last_login_ts": current_time, # Add user state "temp:validation_needed": True # Add temporary state (will be discarded) } # --- Create Event with Actions --- actions_with_update = EventActions(state_delta=state_changes) # This event might represent an internal system action, not just an agent response system_event = Event( invocation_id="inv_login_update", author="system", # Or 'agent', 'tool' etc. actions=actions_with_update, timestamp=current_time # content might be None or represent the action taken ) # --- Append the Event (This updates the state) --- await session_service.append_event(session, system_event) print("`append_event` called with explicit state delta.") # --- Check Updated State --- updated_session = await session_service.get_session(app_name=app_name, user_id=user_id, session_id=session_id) print(f"State after event: {updated_session.state}") # Expected: {'user:login_count': 1, 'task_status': 'active', 'user:last_login_ts': } # Note: 'temp:validation_needed' is NOT present. ``` === "Go" ```go --8<-- "examples/go/snippets/sessions/state_example/state_example.go:manual" ``` === "Java" ```java --8<-- "examples/java/snippets/src/main/java/state/ManualStateUpdateExample.java:full_code" ``` **3. Via `CallbackContext` or `ToolContext` (Recommended for Callbacks and Tools)** Modifying state within agent callbacks (e.g., `on_before_agent_call`, `on_after_agent_call`) or tool functions is best done using the `state` attribute of the `CallbackContext` or `ToolContext` provided to your function. * `callback_context.state['my_key'] = my_value` * `tool_context.state['my_key'] = my_value` These context objects are specifically designed to manage state changes within their respective execution scopes. When you modify `context.state`, the ADK framework ensures that these changes are automatically captured and correctly routed into the `EventActions.state_delta` for the event being generated by the callback or tool. This delta is then processed by the `SessionService` when the event is appended, ensuring proper persistence and tracking. This method abstracts away the manual creation of `EventActions` and `state_delta` for most common state update scenarios within callbacks and tools, making your code cleaner and less error-prone. For more comprehensive details on context objects, refer to the [Context documentation](../context/index.md). === "Python" ```python # In an agent callback or tool function from google.adk.agents import CallbackContext # or ToolContext def my_callback_or_tool_function(context: CallbackContext, # Or ToolContext # ... other parameters ... ): # Update existing state count = context.state.get("user_action_count", 0) context.state["user_action_count"] = count + 1 # Add new state context.state["temp:last_operation_status"] = "success" # State changes are automatically part of the event's state_delta # ... rest of callback/tool logic ... ``` === "Go" ```go --8<-- "examples/go/snippets/sessions/state_example/state_example.go:context" ``` === "Java" ```java // In an agent callback or tool method import com.google.adk.agents.CallbackContext; // or ToolContext // ... other imports ... public class MyAgentCallbacks { public void onAfterAgent(CallbackContext callbackContext) { // Update existing state Integer count = (Integer) callbackContext.state().getOrDefault("user_action_count", 0); callbackContext.state().put("user_action_count", count + 1); // Add new state callbackContext.state().put("temp:last_operation_status", "success"); // State changes are automatically part of the event's state_delta // ... rest of callback logic ... } } ``` **What `append_event` Does:** * Adds the `Event` to `session.events`. * Reads the `state_delta` from the event's `actions`. * Applies these changes to the state managed by the `SessionService`, correctly handling prefixes and persistence based on the service type. * Updates the session's `last_update_time`. * Ensures thread-safety for concurrent updates. ### ⚠️ A Warning About Direct State Modification Avoid directly modifying the `session.state` collection (dictionary/Map) on a `Session` object that was obtained directly from the `SessionService` (e.g., via `session_service.get_session()` or `session_service.create_session()`) *outside* of the managed lifecycle of an agent invocation (i.e., not through a `CallbackContext` or `ToolContext`). For example, code like `retrieved_session = await session_service.get_session(...); retrieved_session.state['key'] = value` is problematic. State modifications *within* callbacks or tools using `CallbackContext.state` or `ToolContext.state` are the correct way to ensure changes are tracked, as these context objects handle the necessary integration with the event system. **Why direct modification (outside of contexts) is strongly discouraged:** 1. **Bypasses Event History:** The change isn't recorded as an `Event`, losing auditability. 2. **Breaks Persistence:** Changes made this way **will likely NOT be saved** by `DatabaseSessionService` or `VertexAiSessionService`. They rely on `append_event` to trigger saving. 3. **Not Thread-Safe:** Can lead to race conditions and lost updates. 4. **Ignores Timestamps/Logic:** Doesn't update `last_update_time` or trigger related event logic. **Recommendation:** Stick to updating state via `output_key`, `EventActions.state_delta` (when manually creating events), or by modifying the `state` property of `CallbackContext` or `ToolContext` objects when within their respective scopes. These methods ensure reliable, trackable, and persistent state management. Use direct access to `session.state` (from a `SessionService`-retrieved session) only for *reading* state. ### Best Practices for State Design Recap * **Minimalism:** Store only essential, dynamic data. * **Serialization:** Use basic, serializable types. * **Descriptive Keys & Prefixes:** Use clear names and appropriate prefixes (`user:`, `app:`, `temp:`, or none). * **Shallow Structures:** Avoid deep nesting where possible. * **Standard Update Flow:** Rely on `append_event`.