Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.agno.com/llms.txt

Use this file to discover all available pages before exploring further.

Securing an agent is a game of layers. Each layer stops a different class of attack, from unauthenticated requests at the edge down to protected tool calls at the agent level.
LayerWhat it stopsMechanism
AuthenticationUnauthenticated requestsJWT validation at the middleware
AuthorizationAuthenticated but out-of-scope requestsRBAC scopes on every endpoint
Request isolationConcurrent requests reading each other’s dataFresh, isolated object per request
User isolationCross-user data leakage on shared endpointsOpt-in using AuthorizationConfig(user_isolation=True)
Human approvalProtected actions running unsupervisedrequires_confirmation, @approval (see Human Approval)
AgentOS gives you good defaults at every layer.
Network-layer controls (rate limiting, WAF, IP allowlists, mTLS) live at your reverse proxy or API gateway layer.

Authentication

A production AgentOS sits behind JWT-validating middleware. Every request carries a token signed by the control plane; your service verifies it with a public key.
from agno.os import AgentOS

agent_os = AgentOS(
    agents=[agent],
    db=db,
    authorization=True,  # reads JWT_VERIFICATION_KEY from env
)
When authorization=True, every endpoint requires a valid JWT except a small set of public routes: /, /health, /info, /docs, /openapi.json

Get a JWT_VERIFICATION_KEY

  1. Open os.agno.comAdd OSLive → paste your URL.
  2. Go to SettingsGenerate key pair.
  3. Copy the public key. Paste into your env (JWT_VERIFICATION_KEY).
  4. Deploy AgentOS with authorization=True and JWT_VERIFICATION_KEY set.
The control plane keeps the private key. Your service only ever sees the public key. JWTs are signed by the control plane and verified by your service.

Custom JWT configuration

If you’re issuing JWTs from your own auth provider, pass an AuthorizationConfig:
from agno.os import AgentOS
from agno.os.config import AuthorizationConfig

agent_os = AgentOS(
    agents=[agent],
    db=db,
    authorization=True,
    authorization_config=AuthorizationConfig(
        verification_keys=["public-key-1", "public-key-2"],   # rotation
        algorithm="RS256",
        audience="agno-os",
        verify_audience=True,
        admin_scope="agent_os:admin",
        user_isolation=True,                                  # see below
    ),
)
verification_keys is a list, so you can rotate keys without downtime. Old tokens validate against the old key, new tokens against the new. Drop the old key once tokens have expired. JWT claim names (scopes, sub) are configured on the JWT middleware itself, not on AuthorizationConfig. The defaults — scopes for the scopes claim, sub for the user id claim — match the tokens minted by the control plane.

Authorization (RBAC scopes)

Every JWT carries a scopes claim. Endpoints are gated on scopes.
ScopeGrants
agents:readList agents and read their config
agents:<id>:runRun a specific agent
agents:*:runRun any agent
teams:<id>:runRun a specific team
teams:*:runRun any team
workflows:<id>:runRun a specific workflow
workflows:*:runRun any workflow
agent_os:adminFull access including session, memory, and trace queries
A typical end-user gets agents:my-agent:run. An internal service gets agent_os:admin. The control plane mints both with appropriate scopes. See RBAC for the full setup.

Request isolation

Every request gets a fresh copy of the agent, team, or workflow it’s hitting. AgentOS calls deep_copy() on the registered component per request, so mutable state — session-scoped variables, tool execution context, run metadata — never bleeds between concurrent calls. Heavy resources (the DB connection, the model client, MCP tool handles) are shared by reference; only the mutable per-run state is isolated. You get cheap concurrency without the footgun of two requests racing on the same in-memory agent instance. This is on by default for every run endpoint. There’s nothing to configure.

User isolation

Per-user data isolation is opt-in. JWT and RBAC stay in force without it, but routes operate on the unscoped DB — a caller with agents:my-agent:run could read another user’s sessions if they know the IDs. For multi-tenant deployments, turn it on:
from agno.os.config import AuthorizationConfig

agent_os = AgentOS(
    agents=[agent],
    db=db,
    authorization=True,
    authorization_config=AuthorizationConfig(
        verification_keys=[public_key],
        user_isolation=True,
    ),
)
With user_isolation=True, every non-admin caller gets:
GuaranteeHow
No cross-user readsThe JWT sub is threaded as user_id on every user-scoped read (sessions, memory, traces). Callers only see their own rows.
No cross-user writesuser_id is coerced on every write — a non-admin can’t persist a session, memory, or trace attributed to another user.
Run ownershipCancel, resume, and continue routes require session_id and verify the run belongs to the caller’s session.
WebSocket reconnectReconnecting to a workflow run requires workflow_id and an ownership check.
Admin callers (whoever holds the configured admin_scope, defaulting to agent_os:admin) bypass all of the above and see the full unscoped view — service accounts, internal tooling, the control plane. Adding a new router endpoint that touches user-owned data? It must call get_scoped_user_id(request) (or one of its siblings in agno.os.middleware.user_scope) and thread the resulting user_id into every DB read/write. Missing that call silently bypasses isolation with no runtime error.

Human approval

The last layer. Some actions should never run unsupervised — refunds, deletes, prod deploys, anything irreversible. AgentOS pauses the run, surfaces the pending approval, resumes when a human signs off. Two patterns: requires_confirmation for the user asking the agent, @approval for a designated admin with policy authority. Full details on the Human Approval page.

Defaults

ConcernDefault behavior
Authenticationauthorization=False. Opt in with authorization=True and JWT_VERIFICATION_KEY for production.
Request isolationOn — every run endpoint deep-copies the component.
User isolationOff. Opt in via AuthorizationConfig(user_isolation=True) for multi-tenant.
Trace tamperingTraces are append-only; no API to mutate past spans.
Schedule abuseScheduler write endpoints require the schedules:write scope.