Per-User Data Isolation

Scope user-owned AgentOS data and resources to the caller's user ID.

Authorization controls which operations a caller can perform. Per-user data isolation controls access to user-owned rows in built-in AgentOS routes. Opt in with user_isolation=True:

from agno.os import AgentOS
from agno.os.config import AuthorizationConfig

agent_os = AgentOS(
    id="my-agent-os",
    agents=[agent],
    authorization=True,
    authorization_config=AuthorizationConfig(
        verification_keys=["your-jwt-verification-key"],
        algorithm="RS256",
        user_isolation=True,
    ),
)

When enabled, AgentOS uses the JWT sub claim as the user_id for every non-admin caller:

OperationBehavior with user_isolation=True
User-owned readsScoped to the caller's user_id. Other users' rows are not returned.
User-owned writesuser_id is coerced to the caller's sub. A caller cannot persist rows attributed to another user.
Cancel / resume / continue routesRequire session_id and verify the caller owns the run.
WebSocket reconnectRequires session_id (and workflow_id) for non-admins.

A caller holding admin_scope (default agent_os:admin) bypasses isolation and sees all data. Set a custom override with admin_scope="ops:admin".

Some domains expose shared rows with no user_id, including shared knowledge and workspace service-account metadata. A scoped non-admin may read these rows but cannot mutate them. This does not grant access to another user's private rows.

Scoped operations require a usable JWT sub; otherwise they return 403. Custom routes must apply Agno's user-scope helpers explicitly. Enabling isolation does not automatically filter arbitrary database queries in your own handlers.

Isolation is off by default. JWT and scope checks still apply when user_isolation=False, but routes operate on the unscoped database and add no per-user ownership gates on top of authorization. Per-user isolation requires a database that records user_id (PostgreSQL recommended for production).

Service Accounts

Service account principals (sa:<name>) are always scoped to themselves, regardless of the user_isolation flag. The scoping decision runs in this order:

  1. A caller holding the admin scope is never scoped. This is checked first, so an admin service account reads across users.
  2. A service account is scoped to its own sa:<name> principal, even when user_isolation=False.
  3. JWT callers are scoped to their sub only when user_isolation=True.

A machine token stamps its sessions and memories with its own principal, so an unscoped default would read every user's history. If you need a token that reads across users, mint one with the admin scope.

Service-account ownership is separate from execution identity. The account's creator owns its metadata; requests made with its token execute as sa:<name>. Scoped callers can see shared workspace accounts but cannot revoke those unowned accounts.

Next Steps

TaskGuide
Issue tokens with the sub claimTokens
Mint machine tokensService Accounts
See the user isolation cookbook exampleuser_isolation.py