diff --git a/agent-os/custom-fastapi/overview.mdx b/agent-os/custom-fastapi/overview.mdx
index bdcbb35be..73027d9ea 100644
--- a/agent-os/custom-fastapi/overview.mdx
+++ b/agent-os/custom-fastapi/overview.mdx
@@ -171,6 +171,30 @@ if __name__ == "__main__":
agent_os.serve(app="custom_fastapi_app:app", reload=True)
```
+Run it end to end:
+
+
+
+
+
+ ```bash
+ export ANTHROPIC_API_KEY=your_anthropic_api_key
+ ```
+
+
+
+ ```bash
+ uv pip install -U agno anthropic fastapi uvicorn sqlalchemy
+ ```
+
+
+
+ ```bash
+ python custom_fastapi_app.py
+ ```
+
+
+
## Middleware and Dependencies
@@ -234,5 +258,4 @@ for route in routes:
## Developer Resources
- [AgentOS Reference](/reference/agent-os/agent-os)
-- [Full Example](/agent-os/usage/custom-fastapi)
- [FastAPI Documentation](https://fastapi.tiangolo.com/)
diff --git a/agent-os/middleware/jwt.mdx b/agent-os/middleware/jwt.mdx
index f262fd501..d563ca4cc 100644
--- a/agent-os/middleware/jwt.mdx
+++ b/agent-os/middleware/jwt.mdx
@@ -225,10 +225,30 @@ app.add_middleware(
For detailed RBAC documentation including all available scopes and default mappings, see [RBAC Documentation](/agent-os/security/rbac).
+## User Isolation
+
+RBAC controls which endpoints a caller can hit. User isolation controls which rows they can see and mutate. The two are independent toggles.
+
+```python jwt_with_user_isolation.py
+app.add_middleware(
+ JWTMiddleware,
+ verification_keys=["your-jwt-key"],
+ authorization=True,
+ user_isolation=True,
+)
+```
+
+When `user_isolation=True`, non-admin callers are scoped to their own `user_id` (from the JWT `sub` claim) for sessions, memory, and traces. Callers holding `admin_scope` bypass isolation. See [Per-User Data Isolation](/agent-os/security/rbac#per-user-data-isolation) for the full behavior.
## Excluded Routes
-Skip middleware for specific routes:
+These routes skip JWT and RBAC checks by default:
+
+```python
+["/", "/health", "/info", "/docs", "/redoc", "/openapi.json", "/docs/oauth2-redirect"]
+```
+
+Override them with `excluded_route_paths`:
```python jwt_excluded_routes.py
app.add_middleware(
@@ -236,12 +256,17 @@ app.add_middleware(
verification_keys=["your-key"],
excluded_route_paths=[
"/health",
- "/auth/login",
+ "/auth/login",
"/auth/register",
"/public/*", # Wildcards supported
]
)
```
+
+
+`excluded_route_paths` replaces the defaults, it is not additive. Re-include any default routes you want to keep.
+
+
## Configuration Options
See the [JWTMiddleware Reference](/reference/agent-os/jwt-middleware) for the complete list of configuration options.
@@ -282,6 +307,7 @@ See the [JWTMiddleware Reference](/reference/agent-os/jwt-middleware) for the co
| `authorization` | Enable RBAC scope checking | `False` |
| `verify_audience` | Verify `aud` claim matches AgentOS ID | `False` |
| `audience` | Expected audience claim to validate against the token's audience claim | `AgentOS ID` |
+| `user_isolation` | Opt in to per-user data isolation. When `True`, AgentOS uses the JWT `sub` claim as the `user_id` for every non-admin caller: reads are scoped to it and writes are coerced to it, so callers can't see or persist other users' rows. Callers holding `admin_scope` bypass it. | `False` |
| `scope_mappings` | Custom route-to-scope mappings (additive to defaults) | `None` |
| `admin_scope` | Scope that grants full admin access | `"agent_os:admin"` |
| `excluded_route_paths` | Routes to skip JWT/RBAC checks | See below |
diff --git a/agent-os/middleware/overview.mdx b/agent-os/middleware/overview.mdx
index de71ab0b8..0302e1eeb 100644
--- a/agent-os/middleware/overview.mdx
+++ b/agent-os/middleware/overview.mdx
@@ -9,12 +9,12 @@ keywords: [middleware, fastapi middleware, agentos middleware, jwt middleware, c
v2.1.0
-AgentOS is built on FastAPI, allowing you to add any [FastAPI/Starlette compatible middleware](https://fastapi.tiangolo.com/tutorial/middleware/) for authentication, logging, monitoring, and security. Agno provides built-in JWT middleware for authentication, and you can create custom middleware for rate limiting, request logging, and security headers.
+AgentOS is built on FastAPI, so you can add any [FastAPI/Starlette-compatible middleware](https://fastapi.tiangolo.com/tutorial/middleware/) for authentication, logging, monitoring, and security.
-Additionally, Agno provides some built-in middleware for common use cases, including authentication.
+Agno ships a built-in JWT middleware for authentication. You can write your own custom middleware for rate limiting, request logging, and security headers.
See the following guides:
-
+
Built-in JWT authentication with automatic parameter injection and claims extraction.
-
- Use the built-in JWT middleware with Role-based access control and fine-grained permission scopes.
-
diff --git a/agent-os/security/rbac.mdx b/agent-os/security/rbac.mdx
index 792236320..1a4fff019 100644
--- a/agent-os/security/rbac.mdx
+++ b/agent-os/security/rbac.mdx
@@ -42,7 +42,6 @@ app = agent_os.get_app()
```
-
## Generate a Verification Key
`authorization=True` only tells AgentOS to enforce JWT authorization. To verify tokens, AgentOS also needs a public key. Generate one from the control plane and wire it in.
diff --git a/agent-os/usage/custom-fastapi.mdx b/agent-os/usage/custom-fastapi.mdx
deleted file mode 100644
index 80bc6f0fd..000000000
--- a/agent-os/usage/custom-fastapi.mdx
+++ /dev/null
@@ -1,118 +0,0 @@
----
-title: "AgentOS Custom FastAPI"
-description: "AgentOS where you provide your own FastAPI app"
----
-
-Here is a full example of an AgentOS where you provide your own FastAPI app.
-
-## Code
-
-```python custom_fastapi.py
-from agno.agent import Agent
-from agno.db.postgres import PostgresDb
-from agno.models.anthropic import Claude
-from agno.os import AgentOS
-from agno.tools.hackernews import HackerNewsTools
-from fastapi import FastAPI
-
-# Setup the database
-db = PostgresDb(db_url="postgresql+psycopg://ai:ai@localhost:5532/ai")
-
-web_research_agent = Agent(
- id="web-research-agent",
- name="Web Research Agent",
- model=Claude(id="claude-sonnet-4-0"),
- db=db,
- tools=[HackerNewsTools()],
- add_history_to_context=True,
- num_history_runs=3,
- add_datetime_to_context=True,
- markdown=True,
-)
-
-# Custom FastAPI app
-app: FastAPI = FastAPI(
- title="Custom FastAPI App",
- version="1.0.0",
-)
-
-
-# Add your own routes
-@app.post("/customers")
-async def get_customers():
- return [
- {
- "id": 1,
- "name": "John Doe",
- "email": "john.doe@example.com",
- },
- {
- "id": 2,
- "name": "Jane Doe",
- "email": "jane.doe@example.com",
- },
- ]
-
-
-# Setup our AgentOS app by passing your FastAPI app
-# Use route_prefix to avoid conflicts with your custom routes
-agent_os = AgentOS(
- description="Example app with custom routers",
- agents=[web_research_agent],
- base_app=app,
-)
-
-# Alternatively, add all routes from AgentOS app to the current app
-# for route in agent_os.get_routes():
-# app.router.routes.append(route)
-
-app = agent_os.get_app()
-
-
-if __name__ == "__main__":
- """Run your AgentOS.
-
- With this setup:
- - API docs: http://localhost:7777/docs
-
- """
- agent_os.serve(app="custom_fastapi:app", reload=True)
-
-```
-
-## Usage
-
-
-
-
-
- ```bash
- export ANTHROPIC_API_KEY=your_anthropic_api_key
- ```
-
-
-
- ```bash
- uv pip install -U agno anthropic fastapi uvicorn sqlalchemy pgvector psycopg
- ```
-
-
-
- ```bash
- # Using Docker
- docker run -d \
- --name agno-postgres \
- -e POSTGRES_DB=ai \
- -e POSTGRES_USER=ai \
- -e POSTGRES_PASSWORD=ai \
- -p 5532:5432 \
- pgvector/pgvector:pg17
- ```
-
-
-
- ```bash
- python custom_fastapi.py
- ```
-
-
\ No newline at end of file
diff --git a/videos/roles.mp4 b/images/roles.mp4
similarity index 100%
rename from videos/roles.mp4
rename to images/roles.mp4
diff --git a/reference/agent-os/authorization-config.mdx b/reference/agent-os/authorization-config.mdx
index fd51f7449..83e944c73 100644
--- a/reference/agent-os/authorization-config.mdx
+++ b/reference/agent-os/authorization-config.mdx
@@ -19,6 +19,7 @@ from agno.os.config import AuthorizationConfig
| `jwks_file` | `Optional[str]` | `None` | Path to a static JWKS (JSON Web Key Set) file containing public keys. Keys are matched by `kid` (key ID) from the JWT header. Alternative to `verification_keys` for RSA key management. |
| `algorithm` | `Optional[str]` | `RS256` | JWT algorithm for token verification. Common options: `RS256` (asymmetric), `HS256` (symmetric). |
| `verify_audience` | `Optional[bool]` | `False` | Whether to verify the audience claim of the JWT token. This should not be enabled for AgentOS Control Plane traffic. |
+| `user_isolation` | `bool` | `False` | Opt in to per-user data isolation. When `True`, AgentOS uses the JWT `sub` claim as the `user_id` for every non-admin caller: reads are scoped to it and writes are coerced to it, so callers can't see or persist other users' rows. Callers holding `admin_scope` bypass it. Requires a database that records `user_id`. |
## Usage
diff --git a/reference/agent-os/jwt-middleware.mdx b/reference/agent-os/jwt-middleware.mdx
index fc18e428e..8297bf115 100644
--- a/reference/agent-os/jwt-middleware.mdx
+++ b/reference/agent-os/jwt-middleware.mdx
@@ -21,7 +21,7 @@ from agno.os.middleware.jwt import TokenSource
| `secret_key` | `Optional[str]` | `None` | **(Deprecated)** Use `verification_keys` instead. |
| `algorithm` | `str` | `"RS256"` | JWT algorithm (RS256, HS256, ES256, etc.) |
| `validate` | `bool` | `True` | Whether to validate JWT tokens |
-| `authorization` | `Optional[bool]` | `None` | Enable RBAC scope checking |
+| `authorization` | `Optional[bool]` | `None` | Enable RBAC scope checking. If left `None` and `scope_mappings` is provided, RBAC is auto-enabled. |
| `token_source` | `TokenSource` | `TokenSource.HEADER` | Where to extract JWT token from |
| `token_header_key` | `str` | `"Authorization"` | Header key for Authorization |
| `cookie_name` | `str` | `"access_token"` | Cookie name for JWT token |
@@ -29,13 +29,14 @@ from agno.os.middleware.jwt import TokenSource
| `user_id_claim` | `str` | `"sub"` | JWT claim name for user ID |
| `session_id_claim` | `str` | `"session_id"` | JWT claim name for session ID |
| `audience_claim` | `str` | `"aud"` | JWT claim name for audience/OS ID |
-| `audience` | `Optional[Union[str, Iterable[str]]]` | `None` | Expected audience claim to validate against the token's audience claim. Defaults to the AgentOS ID. |
+| `audience` | `Optional[Union[str, Iterable[str]]]` | `None` | Expected audience(s) to validate the token's `aud` claim against. Accepts a string or a list of strings; the token matches if its audience matches any of them. Defaults to the AgentOS ID. |
| `verify_audience` | `bool` | `False` | Verify `aud` claim matches AgentOS ID |
| `dependencies_claims` | `Optional[List[str]]` | `None` | Claims to extract for `dependencies` parameter |
| `session_state_claims` | `Optional[List[str]]` | `None` | Claims to extract for `session_state` parameter |
| `scope_mappings` | `Optional[Dict[str, List[str]]]` | `None` | Custom route-to-scope mappings (additive to defaults) |
-| `excluded_route_paths` | `Optional[List[str]]` | See below | Routes to skip JWT/RBAC checks |
+| `excluded_route_paths` | `Optional[List[str]]` | See below | Routes to skip JWT/RBAC checks. |
| `admin_scope` | `Optional[str]` | `"agent_os:admin"` | Scope that grants full admin access |
+| `user_isolation` | `bool` | `False` | Opt in to per-user data isolation. When `True`, AgentOS uses the JWT `sub` claim as the `user_id` for every non-admin caller: reads are scoped to it and writes are coerced to it, so callers can't see or persist other users' rows. Callers holding `admin_scope` bypass it. Requires a database that records `user_id`. |
## TokenSource Enum
@@ -51,6 +52,7 @@ from agno.os.middleware.jwt import TokenSource
[
"/",
"/health",
+ "/info",
"/docs",
"/redoc",
"/openapi.json",