How Agno's Agent Class Manages Session State and User Context Across Runs
Agno's Agent class manages session state and user context across runs by separating the runnable agent logic from persistent session data, providing explicit attributes like session_id and session_state, and exposing a public API that allows both developers and LLMs to read and mutate state stored in the database.
The agno-agi/agno repository provides a robust framework for building AI agents that maintain context across multiple interactions. Understanding how the Agno Agent session state system works is crucial for developing applications that require memory persistence and user-specific context management. The architecture cleanly separates the Agent's execution logic from the Session's data layer, enabling both programmatic and agentic state manipulation.
Core Session State Concepts
The session state system is built around several key attributes defined in libs/agno/agno/agent/agent.py that control how data persists and flows through the agent.
Session Identification with session_id
Every agent interaction occurs within a session identified by a UUID. In libs/agno/agno/agent/agent.py (lines 73-84), the session_id attribute stores this identifier. If omitted during agent initialization, the system auto-generates a unique session ID, ensuring that stateless runs remain isolated while stateful runs can retrieve their historical context from the database.
User Context Storage via session_state
The session_state attribute (lines 83-86) holds an arbitrary JSON-serializable dictionary that users can supply when calling run(). This dictionary merges with any persisted state retrieved from the database, allowing applications to inject user preferences, counters, or metadata into the agent's context. The merging behavior respects the overwrite_db_session_state flag (lines 89-91), which when set to True, replaces the database state entirely rather than performing a shallow merge.
Context Injection with add_session_state_to_context
When the add_session_state_to_context attribute is set to True (lines 84-86), the current session_state dictionary is automatically serialized and injected into the system message or context. This makes the session data visible to the LLM during inference, enabling the model to reference previous user preferences or application state without requiring explicit retrieval logic in the prompt.
Agentic State Management via enable_agentic_state
The enable_agentic_state attribute (lines 87-89) grants the LLM itself the ability to modify session state. When enabled, the agent exposes a tool named _session.update_session_state that the model can invoke during a run. This tool delegates to Agent.update_session_state, writing changes directly to the database and updating the cached session object, effectively allowing the agent to "remember" facts or update counters autonomously.
The Session State API
The Agent class exposes a public API for state management that wraps low-level utilities defined in libs/agno/agno/agent/_session.py. These methods handle both synchronous and asynchronous execution patterns.
Reading State with get_session_state
To retrieve the current persisted state, the agent provides:
def get_session_state(self, session_id: Optional[str] = None) -> Dict[str, Any]:
session_id = session_id or self.session_id
if session_id is None:
raise Exception("Session ID is not set")
return get_session_state_util(self, session_id=session_id)
Source: libs/agno/agno/agent/_session.py (lines 73-86)
The async variant aget_session_state provides non-blocking access for asynchronous applications.
Updating State with update_session_state
Programmatic updates merge new data into the existing state:
def update_session_state(
self,
session_state_updates: Dict[str, Any],
session_id: Optional[str] = None
) -> str:
# Merges updates into DB state (or overwrites if flag set)
# Returns new version ID
...
This method handles the logic for merging versus overwriting based on the overwrite_db_session_state configuration. When called by the LLM via the agentic state tool, it updates the database and refreshes the _cached_session object if session caching is enabled.
Persisting Sessions with save_session
To persist the full session object including runs and metadata:
def save_session(self, session: AgentSession) -> None:
# Persists to database via agno.session models
...
The get_session and aget_session methods retrieve full AgentSession objects, applying the in-memory cache (_cached_session) when cache_session=True is set during agent initialization (lines 134-136 in agent.py).
How State Flows Across Runs
The session state lifecycle follows a predictable path through each agent invocation:
-
Run Initialization – When
Agent.run()is called, it accepts an optionalsession_statedictionary from the user. -
Session Resolution – The
_session.get_sessionutility fetches an existingAgentSessionfrom the database or creates a new one using thesession_id. Ifcache_session=True, the result is stored inself._cached_sessionto speed up subsequent lookups. -
State Merging – The provided
session_statemerges with the persisted state from the database. The merge strategy depends onoverwrite_db_session_state: whenFalse(default), it performs a shallow merge; whenTrue, the supplied state replaces the database entry entirely. -
Context Injection – If
add_session_state_to_context=True, the mergedsession_stateis serialized and injected into the system message via_messages.get_system_message, making the data available to the LLM. -
Agentic Updates – During inference, if
enable_agentic_state=True, the model may invoke the_session.update_session_statetool to modify state mid-run. This writes immediately to the database and updates the cache. -
Persistence – Upon completion,
Agent.save_sessionpersists the updatedAgentSession(including any new runs and the finalsession_state) to the database, ensuring the next run retrieves the current context.
Practical Implementation Example
The following example demonstrates a counter that persists across multiple agent invocations using the session state system:
from agno import Agent, Model
# Initialize agent with session state enabled
agent = Agent(
model=Model("gpt-4o-mini"),
session_id="demo-123",
add_session_state_to_context=True,
enable_agentic_state=True
)
# First call initializes the counter
agent.run({"counter": 0})
# The system prompt now contains: {"counter": 0}
# Model can update state via tool call:
# _session.update_session_state({"counter": 5})
# Second call automatically merges with persisted state
agent.run({"extra": "info"})
# System prompt now contains: {"counter": 5, "extra": "info"}
Sources: Agent constructor arguments (libs/agno/agno/agent/agent.py lines 70-90); session-state helpers (libs/agno/agno/agent/_session.py lines 73-102).
Summary
- Session Isolation: Every
Agentinstance uses asession_id(UUID) to isolate context between conversations, auto-generating one if not provided. - Explicit State Management: The
session_statedictionary provides a JSON-serializable container for user context that merges with database-persisted state on each run. - LLM Visibility: Setting
add_session_state_to_context=Trueinjects the state dictionary into the system message, allowing the model to reference user preferences and application data. - Agentic Mutation: With
enable_agentic_state=True, the LLM can modify its own session state mid-run via the_session.update_session_statetool, enabling autonomous memory updates. - Persistence Layer: The
_session.pyutilities handle database operations, while optional in-memory caching (cache_session=True) improves performance for repeated lookups.
Frequently Asked Questions
How does Agno handle session state when the same session_id is used across different Agent instances?
When multiple Agent instances share the same session_id, they access the same underlying database record. Each instance retrieves the current session_state from the database at the start of run(), merges it with any locally provided state, and persists changes at the end. If cache_session=True, each instance maintains its own in-memory cache (_cached_session), so simultaneous modifications from different processes could lead to stale reads unless the database is the source of truth.
Can the LLM modify session state without explicit user intervention?
Yes, when enable_agentic_state=True, the agent exposes a tool named _session.update_session_state that the LLM can invoke during inference. This tool delegates to Agent.update_session_state, which writes changes directly to the database and updates the cached session object. This allows the model to autonomously "remember" facts, update counters, or modify user preferences based on conversation flow without requiring the user to manually pass state in subsequent calls.
What is the difference between session_state and the Agent's memory or storage systems?
The session_state is a specific JSON-serializable dictionary designed for structured user context and application data that persists across runs within a single session. It is distinct from the agent's memory (which typically handles conversation history and RAG contexts) and storage (which refers to the database backend where sessions are persisted). While memory systems handle what the model "remembers" conversationally, session_state provides a programmatic key-value store that developers can explicitly control and inject into the model's context via add_session_state_to_context.
How does state merging work when providing session_state to run()?
When calling agent.run(session_state={"key": "value"}), the provided dictionary merges with the existing state retrieved from the database. By default (overwrite_db_session_state=False), this performs a shallow merge where top-level keys in the provided dictionary overwrite those in the stored state, but nested structures may require careful handling. If overwrite_db_session_state=True, the provided session_state completely replaces the database entry, effectively resetting the session context to the provided values. This merge occurs during the session resolution phase at the start of each run, ensuring the agent operates with the most current combined state.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →