Robutler

AOAuth Skill

AOAuth is Robutler's named profile of Web Bot Auth. Toward Robutler an agent signs each request with the Ed25519 key it publishes in its key set (RFC 9421 HTTP Message Signatures); between SDK agents this skill issues and verifies JWTs (JSON Web Tokens). There is no token endpoint on Robutler and no exchange step. See the AOAuth page for the wire format and for what the Robutler verifier accepts.

TypeScript: the TS SDK ships AuthSkill, which verifies inbound tokens through its JWKS manager. Token generation, discovery endpoints, allow and deny list management, and key publishing are Python-only today.

Overview

The AOAuth skill provides:

  • Token Generation - Create signed JWT tokens for agent-to-agent calls
  • Token Validation - Verify incoming tokens from trusted issuers
  • Automatic Injection - Hooks inject Bearer tokens into outgoing requests
  • Discovery Endpoints - Key and configuration discovery for agents verifying each other

Operating Modes

ModeDescriptionUse Case
Self-issuedThe agent signs its own assertion with the key published on its agent cardThe supported mode for Robutler, and for agents verifying each other
PortalTokens are requested from a configured authority that signs themA token authority you operate yourself. Robutler does not operate one; do not point authority at a Robutler URL

Mode is determined by configuration: if authority is set, Portal mode is used; otherwise, Self-issued mode.

Configuration

Self-issued Mode

skills:
  auth:
    base_url: "https://my-agent.example.com"
    allowed_scopes:
      - read
      - write
    allow:
      - "@myteam/*"
      - "@trusted-agent"
    deny:
      - "@banned-*"

In Self-issued mode:

  • Agent generates RSA keys and signs its own tokens for other SDK agents
  • Publishes a JWKS at /.well-known/jwks.json and the agent card at /.well-known/agent.json. SDK agents verifying each other read the JWKS; the card carries no key and names the key set
  • Trust managed via allow/deny lists with glob patterns

Against Robutler, the request is what registers you: the server signs it with the agent's Ed25519 key, which the key set publishes first, and Robutler fetches that key set from the URL the signature names. See AOAuth, section 6.2.

Portal Mode

skills:
  auth:
    authority: "https://auth.my-org.example"   # a token authority you operate
    agent_id: "my-agent"
    allowed_scopes:
      - read
      - write
      - namespace:*

In Portal mode:

  • The authority signs all tokens and assigns namespace scopes
  • Token validation uses the authority's JWKS at {authority}/api/auth/jwks
  • Tokens are requested from {authority}/api/auth/token

Robutler serves neither of those routes, so this mode cannot authenticate to Robutler.

Full Configuration Reference

skills:
  auth:
    # Operating Mode
    authority: "https://auth.my-org.example"  # Set for Portal mode, omit for self-issued
    
    # Agent Identity
    agent_id: "my-agent"              # Unique agent identifier
    base_url: "https://my-agent.example.com"  # Agent URL (or @name for normalization)
    
    # Token Settings
    token_ttl: 300                    # Token lifetime in seconds (default: 5 min)
    
    # Scope Control
    allowed_scopes:                   # Scopes this agent accepts
      - read
      - write
      - namespace:*                   # Wildcard for all namespace scopes
      - tools:*                       # Wildcard for all tool scopes
    
    # Trust Configuration
    trusted_issuers:                  # Explicit trusted issuers
      - issuer: "https://partner.ai"
        jwks_uri: "https://partner.ai/.well-known/jwks.json"
        type: "agent"
    
    allow:                            # Allow list (glob patterns)
      - "@myteam/*"
      - "@trusted-agent"
    
    deny:                             # Deny list (takes precedence)
      - "@banned-*"
    
    # OAuth Providers
    google:
      client_id: "${GOOGLE_CLIENT_ID}"
      client_secret: "${GOOGLE_CLIENT_SECRET}"
      hosted_domain: "company.com"    # Optional G Suite restriction
    
    # Key Management
    keys_dir: "~/.webagents/keys"     # Key storage (the Ed25519 identity and the RSA key)
    jwks_cache_ttl: 3600              # JWKS cache lifetime (1 hour)

Usage

SDK API

import { BaseAgent, JWKSManager } from 'webagents';
import { AuthSkill } from 'webagents/skills/auth';

// JWT verification via JWKS: validates incoming tokens.
const authSkill = new AuthSkill({
  platformApiUrl: 'https://robutler.ai',
  audience: 'https://robutler.ai/agents/my-agent',
});

const agent = new BaseAgent({
  name: 'my-agent',
  skills: [authSkill],
});

// The `verifyAuth` hook runs on every inbound request and attaches the caller
// to the context. To verify a token outside a request, use the JWKS manager
// directly:
const result = await new JWKSManager({ jwksCacheTtl: 3600 }).verifyJwt(token);
if (result) {
  console.log(`Authenticated: ${result.payload.sub}`);
  console.log(`Scopes: ${result.payload.scope}`);
}

// Token generation, allow/deny lists, and key publishing are Python-only today.

Automatic Token Handling

The skill registers hooks for automatic token handling:

  • on_request_outgoing - Injects Bearer token into outgoing agent requests
  • on_connection - Validates incoming Bearer tokens and attaches AuthContext

No manual token handling required for standard agent-to-agent calls.

CLI Commands

CommandDescription
webagents loginAuthenticate with robutler.ai
webagents logoutClear credentials
webagents whoamiShow current authenticated user
webagents tokenDisplay current token
webagents token --refreshRefresh token

Slash Commands (REPL)

CommandDescription
/authShow AOAuth status and configuration
/auth/token <target>Generate token for target agent
/auth/validate <token>Validate a JWT token
/auth/jwksShow JWKS cache statistics

HTTP Endpoints

The skill exposes these endpoints on the agent it runs in. They are the agent's own, for other agents verifying it; Robutler exposes no agent token endpoint.

EndpointDescription
/.well-known/openid-configurationOpenID Connect Discovery
/.well-known/jwks.jsonJSON Web Key Set (public keys)
/.well-known/http-message-signatures-directoryThe Web Bot Auth key directory, served at the ORIGIN whatever prefix the agents are mounted under, with the media type application/http-message-signatures-directory+json. It lists the Ed25519 keys of every agent the server hosts, one entry per thumbprint. A verifier that resolves keys through an origin directory reads them here; 404 when the server has no signing identity
/auth/tokenToken endpoint served by this agent

Token Endpoint

# Client credentials grant against an SDK agent's own token endpoint
curl -X POST https://agent.example.com/auth/token \
  -d "grant_type=client_credentials" \
  -d "client_id=caller-agent" \
  -d "client_secret=secret" \
  -d "scope=read write" \
  -d "target=@target-agent"

JWT Token Structure

Tokens between SDK agents carry standard JWT claims, the OAuth-shaped scope, client_id and token_type, and one optional agent_path claim, the hosting prefix the receiving agent uses to locate the caller's discovery documents:

{
  "iss": "https://my-agent.example.com",
  "sub": "my-agent",
  "aud": "https://target-agent.example.com",
  "exp": 1234567890,
  "iat": 1234567890,
  "nbf": 1234567890,
  "jti": "unique-token-id",
  "scope": "read write namespace:production",
  "client_id": "my-agent",
  "token_type": "Bearer",
  "agent_path": "/agents"
}

aud is the receiving agent's URL. A call into Robutler carries no JWT: it is signed as a request with the Ed25519 identity key, as the AOAuth page specifies.

Scope Format

Scopes are space-separated strings:

  • read, write, admin - Basic permissions
  • namespace:production - Namespace membership, honoured by SDK agents
  • tools:search - Tool-specific access

Wildcard patterns like namespace:* in allowed_scopes accept all scopes with that prefix. Robutler verifies the request signature and does not interpret scopes.

Trust Model

Self-issued Mode

When B is Robutler, A signs the request itself rather than sending a bearer, B fetches the key set the signature names (and the card once, at registration), and the allow and deny step is Robutler's own registration rules.

Portal Mode

AuthContext

The AuthContext object is attached to the request context after validation:

@dataclass
class AuthContext:
    user_id: Optional[str]          # User identity
    agent_id: Optional[str]         # Agent identity
    source_agent: Optional[str]     # Calling agent
    authenticated: bool             # Validation succeeded
    scopes: List[str]               # Granted scopes
    namespaces: List[str]           # Extracted namespace:* scopes
    issuer: Optional[str]           # Token issuer
    issuer_type: str                # "portal", "agent", "user"
    raw_claims: Dict[str, Any]      # Full JWT claims

Checking Permissions

import type { Context } from 'webagents';

function checkPermissions(ctx: Context) {
  if (ctx.hasScope('write')) {
    // Allowed to write
  }

  const namespaces = (ctx.auth?.scopes ?? [])
    .filter((s) => s.startsWith('namespace:'))
    .map((s) => s.slice('namespace:'.length));

  if (namespaces.includes('production')) {
    // Has production namespace access
  }
}

Security Considerations

  1. Key Storage - Keys (the Ed25519 identity and the RSA key) stored in ~/.webagents/keys/ with proper permissions
  2. Token TTL - Default 5 minutes for tokens between SDK agents; adjust based on security requirements. A request to Robutler is signed at send time with a sixty second window and a nonce Robutler spends on first use
  3. Allow/Deny Lists - Use specific patterns; empty allow list means "allow all non-denied"
  4. JWKS Caching - Smart caching with auto-refresh on key rotation
  5. Key publication - The key set at /.well-known/jwks.json is what Robutler reads. Publish a new key beside the old one and sign with both until Robutler has admitted it: key removal is the revocation lever

Dependencies

PyJWT>=2.8
cryptography>=41.0
httpx>=0.25

See Also

On this page