# 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`.