# How Session Isolation Works in Agno's AgentOS for Multi-User Environments

> Discover how Agno's AgentOS ensures session isolation for multi-user environments. Learn how unique RunContexts with session IDs and state dictionaries separate user namespaces.

- Repository: [Agno/agno](https://github.com/agno-agi/agno)
- Tags: internals
- Published: 2026-02-23

---

**Agno's AgentOS guarantees session isolation by binding every execution to a unique `RunContext` containing a `session_id` and `session_state` dictionary, ensuring that concurrent users operate in separate namespaces while allowing steps within the same run to share mutable state.**

Session isolation is a critical security and data integrity feature for any multi-user agent framework. In the `agno-agi/agno` repository, the AgentOS architecture implements robust isolation mechanisms that prevent cross-talk between concurrent users while maintaining state continuity within individual workflows. This implementation centers on the `RunContext` class and persistent storage abstractions that together create isolated execution environments.

## The RunContext Foundation for Session Isolation

At the core of Agno's isolation strategy is the `RunContext` class defined in [`libs/agno/agno/run/base.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/run/base.py). Every workflow execution receives a fresh `RunContext` instance that carries two critical identifiers: a unique `session_id` string and a mutable `session_state` dictionary. When an HTTP request or external trigger initiates a workflow, the OS instantiates this context with the caller's specific session identifier.

All steps, tools, and sub-workflows receive the same `RunContext` instance during execution. This design ensures that operations read from and write to the same `session_state` **only for that specific session**, effectively creating an isolated namespace per user request.

```python
from agno.run.base import RunContext

# A fresh request from user "alice" gets its own session ID

run_ctx = RunContext(
    run_id="run-001",
    session_id="alice-session",   # unique per user / request

    user_id="alice",              # optional user identifier

    session_state={},             # start with empty state

)

# Pass the context into a workflow

workflow = MyWorkflow()
output = workflow.run(run_ctx)

```

## Intra-Run State Sharing vs. Inter-Run Isolation

The system distinguishes between two isolation boundaries: sharing state within a single execution versus complete separation across different sessions.

### Parallel Step Execution Within a Session

When a workflow executes parallel steps within the same `RunContext`, those steps share the *same* `session_state` object through a shallow copy mechanism. This allows sibling steps to see each other's updates in real-time during the execution, facilitating coordination between concurrent operations inside a single user session.

According to the test suite in [`libs/agno/tests/unit/workflow/test_parallel_run_context_isolation.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/tests/unit/workflow/test_parallel_run_context_isolation.py), parallel steps receive identical object references for `session_state`, confirming that mutations are immediately visible across all branches:

```python

# In test_parallel_run_context_isolation.py

shared_state = {"shared": True}
run_context = RunContext(run_id="test", session_id="test", session_state=shared_state)

# Two parallel steps receive the *same* `run_context`

a_state_id = id(run_context.session_state)   # → same object

b_state_id = id(run_context.session_state)   # → same object

assert a_state_id == b_state_id

```

Steps can mutate this shared state during execution, as shown in this counter implementation:

```python
from agno.step import Step

class CounterStep(Step):
    async def aexecute(self, run_context: RunContext) -> dict:
        # Increment a counter stored in the session

        state = run_context.session_state or {}
        state["counter"] = state.get("counter", 0) + 1
        run_context.session_state = state
        return {"counter": state["counter"]}

```

### Cross-Session Boundaries

While parallel steps share state within a run, the `session_id` creates an impermeable boundary between different user sessions. The OS never shares `session_state` between contexts carrying different `session_id` values. Each unique identifier operates in its own isolated namespace, preventing data leakage even when multiple users execute workflows concurrently on the same infrastructure.

## Persistent Session Storage with TeamSession

For workflows requiring continuity across multiple invocations, Agno provides persistent storage through the `TeamSession` class in [`libs/agno/agno/session/team.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/session/team.py). This object maintains a `session_data` mapping that survives individual request lifecycles.

When a run completes, the AgentOS writes the mutated `session_state` back to `TeamSession.session_data` and persists it to the configured storage backend (such as a vector database). On subsequent invocations with the same `session_id`, the runtime loads the stored state into a fresh `RunContext`, restoring the user's previous context while leaving other sessions untouched:

```python

# Team runtime loads stored state (pseudo-code)

session = TeamSession(session_id="bob-session")
if session.session_data.get("session_state"):
    run_context.session_state = session.session_data["session_state"]

# After the workflow finishes, the runtime writes back any changes

session.session_data["session_state"] = run_context.session_state
session.save()   # persists to the configured DB

```

The test suite in [`libs/agno/tests/unit/team/test_team_run_regressions.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/tests/unit/team/test_team_run_regressions.py) validates that paused and resumed team runs correctly restore `session_state` across distinct execution boundaries.

## Configuring Session State Behavior

While session state injection is enabled by default for multi-turn conversations and stateful workflows, callers can explicitly opt out via the `add_session_state_to_context` flag on `RunOptions`. Disabling this flag creates truly stateless executions where the `session_state` dictionary remains empty or unmanaged, suitable for simple one-shot inference tasks that require no memory between calls.

## Summary

- **Per-request isolation**: Every execution receives a unique `RunContext` with a distinct `session_id`, ensuring user data remains separated at the framework level.
- **Shared intra-run state**: Parallel steps within the same `RunContext` operate on identical `session_state` object references, enabling coordination without external storage.
- **Persistent continuity**: The `TeamSession` class in [`libs/agno/agno/session/team.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/session/team.py) saves and restores `session_state` across multiple runs for the same `session_id`.
- **Explicit opt-out**: Developers can disable automatic state management using the `add_session_state_to_context` flag for fully stateless workflows.

## Frequently Asked Questions

### How does Agno prevent session state from leaking between different users?

Agno enforces isolation by requiring every workflow execution to specify a `session_id` within the `RunContext`. The framework only retrieves and persists `session_state` data associated with that specific identifier. Since each user receives a unique `session_id` (typically generated per-request or per-user), their `session_state` dictionaries exist in separate namespaces within the `TeamSession` storage layer, making cross-session data leakage impossible under normal operation.

### Can parallel steps within the same workflow modify shared session state?

Yes. Parallel steps executed within a single `RunContext` share the same `session_state` object reference (via shallow copy). As confirmed in [`test_parallel_run_context_isolation.py`](https://github.com/agno-agi/agno/blob/main/test_parallel_run_context_isolation.py), modifications made by one parallel branch are immediately visible to sibling branches because they reference the identical Python dictionary object. This allows concurrent steps to coordinate through shared counters, flags, or accumulated results.

### Where is session state persisted between workflow runs?

Session state persists in the `session_data` attribute of the `TeamSession` class located in [`libs/agno/agno/session/team.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/session/team.py). When a workflow run terminates, the AgentOS serializes the final `session_state` dictionary into this attribute and commits it to the configured storage backend. On the next invocation with the matching `session_id`, the framework deserializes this data back into the new `RunContext`, restoring the conversation or workflow history.

### Is it possible to run workflows without session state entirely?

Yes. Developers can disable automatic session state injection by setting `add_session_state_to_context` to `False` in the `RunOptions` configuration. When this flag is disabled, the `RunContext` either receives an empty `session_state` dictionary or bypasses state management logic entirely, creating a stateless execution environment suitable for one-shot tasks that require no memory of previous interactions.