Skip to main content
agent_os_with_service_accounts.py
"""
This example demonstrates service accounts: machine identities for AgentOS.

Coding agents and chat apps connecting to your AgentOS need long-lived credentials.
Humans get JWTs; machines get service accounts - opaque `agno_pat_...` tokens
following the GitHub PAT model. Only the SHA-256 hash is stored in the AgentOS
database, and the plaintext is returned exactly once at creation.

Successful verifications are cached in-process for a short TTL (default 30s), so PAT
auth does not hit the database on every request. Revocation takes effect within that
TTL across workers - immediately on the worker that processes the revoke. Set
`AgnoAPISettings(service_account_cache_ttl_seconds=0)` for strict instant revocation.

Runs executed with a service account attribute to it: sessions and traces created
through a `claude-code` token show `sa:claude-code` as the user.

Flow demonstrated here:
1. An admin (JWT with the admin scope) mints a token for `claude-code`
2. The machine calls the run endpoint with its `agno_pat_...` token
3. The admin lists and revokes tokens; the revoking worker rejects the token at once
"""

from datetime import UTC, datetime, timedelta

import jwt
from agno.agent import Agent
from agno.db.sqlite import SqliteDb
from agno.models.openai import OpenAIResponses
from agno.os import AgentOS
from agno.os.middleware import JWTMiddleware

# ---------------------------------------------------------------------------
# Create Example
# ---------------------------------------------------------------------------

# JWT Secret (use environment variable in production)
JWT_SECRET = "a-string-secret-at-least-256-bits-long"

# Setup database. Service accounts are stored here, next to sessions and memories.
db = SqliteDb(db_file="tmp/service_accounts_demo.db")

# Create agent
assistant_agent = Agent(
    id="assistant-agent",
    model=OpenAIResponses(id="gpt-5.5"),
    db=db,
    instructions="You are a helpful assistant.",
)

agent_os = AgentOS(
    description="AgentOS with service accounts",
    agents=[assistant_agent],
    db=db,
)

# Get the final app
app = agent_os.get_app()

# Add JWT middleware with authorization enabled. Human callers authenticate with
# JWTs; bearer tokens starting with `agno_pat_` authenticate as service accounts
# against the database. Service account scopes are enforced on every request.
app.add_middleware(
    JWTMiddleware,
    verification_keys=[JWT_SECRET],
    algorithm="HS256",
    authorization=True,
)

# ---------------------------------------------------------------------------
# Run Example
# ---------------------------------------------------------------------------

if __name__ == "__main__":
    # Admin JWT, representing a human operator. In production the control plane
    # mints these; here we sign one locally.
    payload = {
        "sub": "demo-admin",
        "scopes": ["agent_os:admin"],
        "exp": datetime.now(UTC) + timedelta(hours=24),
        "iat": datetime.now(UTC),
    }
    admin_token = jwt.encode(payload, JWT_SECRET, algorithm="HS256")

    print("Admin JWT (human operator):")
    print(admin_token)
    print()
    print("1. Mint a token for the claude-code machine identity:")
    print(
        f'   curl -X POST http://localhost:7777/service-accounts -H "Authorization: Bearer {admin_token}"',
        end="",
    )
    print(" -H 'Content-Type: application/json' -d '{\"name\": \"claude-code\"}'")
    print()
    print("   The response contains the plaintext token (agno_pat_...) exactly once.")
    print("   Default scopes: agents:run, teams:run, workflows:run, sessions:read.")
    print("   Default expiry: 90 days. Write/delete/admin scopes require")
    print("   allow_privileged_scopes=true and must be held by the minter.")
    print()
    print(
        "2. Run the agent as the machine (replace agno_pat_... with the minted token):"
    )
    print("   curl -X POST http://localhost:7777/agents/assistant-agent/runs \\")
    print(
        '        -H "Authorization: Bearer agno_pat_..." -F "message=hello" -F "stream=false"'
    )
    print()
    print("   The run's session shows sa:claude-code as the user.")
    print()
    print("3. List and revoke tokens (admin JWT again):")
    print(
        f'   curl http://localhost:7777/service-accounts -H "Authorization: Bearer {admin_token}"'
    )
    print(
        f'   curl -X DELETE http://localhost:7777/service-accounts/<id> -H "Authorization: Bearer {admin_token}"'
    )
    print()
    agent_os.serve(app="agent_os_with_service_accounts:app", port=7777)

Run the Example

1

Set up your virtual environment

uv venv --python 3.12
source .venv/bin/activate
uv venv --python 3.12
.venv\Scripts\activate
2

Install dependencies

uv pip install -U "agno[os]" agno[os] fastmcp openai starlette
3

Export your API keys

export JWT_VERIFICATION_KEY="your_jwt_verification_key_here"
export OPENAI_API_KEY="your_openai_api_key_here"
$Env:JWT_VERIFICATION_KEY="your_jwt_verification_key_here"
$Env:OPENAI_API_KEY="your_openai_api_key_here"
4

Run the example

Save the code above as agent_os_with_service_accounts.py, then run:
python agent_os_with_service_accounts.py
Full source: cookbook/05_agent_os/middleware/agent_os_with_service_accounts.py