DEV Community

Cover image for ACI Protocol: Secure Context-Preserving Agent Handoffs with Auth0
Peace Thabiwa
Peace Thabiwa

Posted on

ACI Protocol: Secure Context-Preserving Agent Handoffs with Auth0

Auth0 for AI Agents Challenge Submission

# πŸ”
*This is a submission for the Auth0 for AI Agents Challenge


What I Built

ACI Protocol (Adaptive Contextual Intelligence) β€” A lightweight communication standard that enables AI agents to securely hand off conversations while preserving user identity, conversational tone, and domain expertise.

The Problem

Modern AI systems are moving toward multi-agent architectures where specialized AIs collaborate (automotive expert β†’ financial advisor β†’ code generator). But current implementations have critical gaps:

  • πŸ”“ Security holes: No standard way to verify agent identity or permissions
  • 🎭 Context loss: Users experience jarring personality shifts between agents
  • πŸ“Š No audit trails: When things go wrong, there's no record of what happened
  • 🚫 Permission chaos: Agents share sensitive data without proper authorization

Real-world scenario:

"Compare buying a used Porsche vs investing $50k in index funds"

This requires:

  1. Automotive agent (car knowledge)
  2. Finance agent (investment advice)
  3. Meta-reasoner (synthesize perspectives)

But who verifies the finance agent can access your investment goals? How do we prevent the automotive agent from leaking your data to the finance agent without permission?

The Solution

ACI Protocol wraps inter-agent communications in a secure envelope containing:

{
  "cf_version": "0.1",
  "auth": {
    "user_id": "auth0|66f7c...",
    "agent_id": "aci-automotive-prod",
    "permissions": ["read:profile", "agent:automotive"],
    "handoff_chain": ["aci-auto", "aci-finance"],
    "session_token": "eyJhbGc..."
  },
  "resonance": {
    "tone": "casual-technical",
    "expertise_level": "intermediate",
    "density": 0.65
  },
  "content": {
    "primary": "EVs hit max torque from 0 rpm...",
    "context": "User owns Tesla Model 3"
  },
  "safety": {
    "pii_detected": false,
    "redlines": ["no-financial-advice"]
  }
}
Enter fullscreen mode Exit fullscreen mode

Key innovation: Every handoff is an authenticated transaction verified by Auth0.


Demo

πŸŽ₯ Video Walkthrough

Watch 3-minute demo β†’

πŸ“¦ Live Demo & Repository

Try it: aci-protocol-demo.vercel.app

GitHub: github.com/yourusername/aci-protocol

# Quick start
git clone https://github.com/yourusername/aci-protocol
cd aci-protocol
npm install

# Configure Auth0
cp .env.example .env
# Add your Auth0 credentials

# Run demo
npm run dev
Enter fullscreen mode Exit fullscreen mode

πŸ“Έ Screenshots

1. Secure Multi-Agent Conversation

Agent Handoff Flow

User asks a cross-domain question. System authenticates via Auth0, then routes through specialized agents while preserving conversational tone.


2. Permission Request Flow

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ πŸ” Permission Request                       β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                             β”‚
β”‚ Finance Advisor needs access to:            β”‚
β”‚                                             β”‚
β”‚ βœ“ Your investment goals                     β”‚
β”‚ βœ“ Communication preferences                 β”‚
β”‚ βœ— Bank account balances (not requested)    β”‚
β”‚                                             β”‚
β”‚ Why: "To provide personalized portfolio     β”‚
β”‚       recommendations based on your goals"  β”‚
β”‚                                             β”‚
β”‚ [Allow]  [Deny]  [Allow Once]              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

Agents request permissions in plain language. Auth0 enforces technical scopes behind the scenes.


3. Audit Dashboard

Audit Trail

Complete session history showing authentication, agent handoffs, permission checks, and data accessed. Powered by Auth0 logs.


4. Agent Chain Visualization

Session Timeline (sess_a7f3)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

12:03:45  πŸ‘€ User authenticated (Auth0)
          β”œβ”€ Method: Google OAuth
          └─ Granted: [read:profile, agent:automotive]

12:03:47  πŸ€– aci-automotive processes query
          β”œβ”€ Domain: automotive
          β”œβ”€ Tone matched: casual-technical
          └─ Response: "EVs hit max torque..."

12:03:50  πŸ”„ Handoff requested β†’ aci-finance
          β”œβ”€ Reason: Financial context detected
          └─ Required permissions: [agent:finance, read:goals]

12:03:51  βœ… Auth0 permission check passed
          β”œβ”€ User pre-authorized aci-finance
          └─ All required scopes granted

12:03:52  πŸ’° aci-finance processes query
          β”œβ”€ Context inherited from automotive agent
          β”œβ”€ Tone preserved: casual-technical
          └─ Response: "Index funds historically..."

12:03:55  🧠 meta-reasoner synthesizes
          └─ Combined perspectives preserved user voice

Data Accessed:
βœ“ user.profile.expertise_level
βœ“ user.preferences.communication_style
βœ— user.financial.accounts (blocked - not granted)
Enter fullscreen mode Exit fullscreen mode

How I Used Auth0 for AI Agents

Architecture Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚    User     β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
       β”‚ 1. Login (OAuth)
       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Auth0 Universal    β”‚
β”‚  Login Page         β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚ 2. User token
       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚         ACI Gateway                     β”‚
β”‚  - Validates user session               β”‚
β”‚  - Routes to appropriate agent          β”‚
β”‚  - Builds CF packets with auth context  β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚ 3. CF packet with user token
       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Agent Network   │──────│  Auth0 M2M Auth  β”‚
β”‚                  β”‚      β”‚  (Agent tokens)  β”‚
β”‚  β€’ Automotive    β”‚      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚  β€’ Finance       β”‚              β”‚
β”‚  β€’ Code Gen      β”‚              β”‚ 4. Verify tokens
β”‚  β€’ Meta-Reasoner β”‚β—„β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    & permissions
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚ 5. Secure handoff
         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Auth0 Logs API     β”‚
β”‚  (Audit trail)      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

1. User Authentication (Human β†’ System)

Standard Auth0 OAuth flow with custom scopes:

// Frontend: Initiate login
import { Auth0Provider, useAuth0 } from '@auth0/auth0-react';

function App() {
  return (
    <Auth0Provider
      domain={process.env.REACT_APP_AUTH0_DOMAIN}
      clientId={process.env.REACT_APP_AUTH0_CLIENT_ID}
      authorizationParams={{
        redirect_uri: window.location.origin,
        audience: 'https://aci-protocol.com/api',
        scope: 'openid profile email read:profile agent:automotive agent:finance'
      }}
    >
      <ChatInterface />
    </Auth0Provider>
  );
}

function ChatInterface() {
  const { user, isAuthenticated, getAccessTokenSilently } = useAuth0();

  const sendMessage = async (message) => {
    const token = await getAccessTokenSilently();

    // Send to ACI Gateway with token
    const response = await fetch('/api/chat', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ message })
    });
  };
}
Enter fullscreen mode Exit fullscreen mode

2. Agent Authentication (Agent β†’ Agent via M2M)

Each AI agent has its own machine-to-machine credentials:

# Backend: Agent registration
import requests
from functools import lru_cache

@lru_cache(maxsize=10)
def get_agent_token(agent_id: str, domain: str):
    """Get Auth0 M2M token for an agent"""

    response = requests.post(
        f'https://{AUTH0_DOMAIN}/oauth/token',
        json={
            'client_id': os.getenv('AGENT_M2M_CLIENT_ID'),
            'client_secret': os.getenv('AGENT_M2M_CLIENT_SECRET'),
            'audience': 'https://aci-protocol.com/api',
            'grant_type': 'client_credentials',
            'scope': f'agent:{agent_id} domain:{domain}'
        },
        headers={'content-type': 'application/json'}
    )

    return response.json()['access_token']

# Build CF packet with auth
def create_cf_packet(user_token, agent_id, domain, content):
    agent_token = get_agent_token(agent_id, domain)

    return {
        'cf_version': '0.1',
        'auth': {
            'user_token': user_token,  # From frontend
            'agent_token': agent_token,  # M2M token
            'agent_id': agent_id,
            'permissions': extract_permissions(user_token)
        },
        'content': content,
        # ... rest of packet
    }
Enter fullscreen mode Exit fullscreen mode

3. Permission Validation (Before Every Handoff)

Receiving agents verify permissions before accepting handoffs:

import jwt
from functools import wraps

def require_permissions(*required_perms):
    """Decorator to enforce Auth0 permissions on agent endpoints"""

    def decorator(f):
        @wraps(f)
        def decorated_function(cf_packet, *args, **kwargs):
            # Decode user token
            try:
                user_claims = jwt.decode(
                    cf_packet['auth']['user_token'],
                    key=get_auth0_public_key(),
                    algorithms=['RS256'],
                    audience='https://aci-protocol.com/api'
                )
            except jwt.InvalidTokenError as e:
                return {'error': 'Invalid user token', 'details': str(e)}, 401

            # Extract permissions from token
            user_perms = user_claims.get('permissions', [])

            # Check required permissions
            missing = set(required_perms) - set(user_perms)
            if missing:
                log_permission_denial(cf_packet, missing)
                return {
                    'error': 'Insufficient permissions',
                    'missing': list(missing),
                    'required': list(required_perms)
                }, 403

            # Verify agent token
            try:
                agent_claims = jwt.decode(
                    cf_packet['auth']['agent_token'],
                    key=get_auth0_public_key(),
                    algorithms=['RS256']
                )
            except jwt.InvalidTokenError:
                return {'error': 'Invalid agent token'}, 401

            # All checks passed
            return f(cf_packet, *args, **kwargs)

        return decorated_function
    return decorator

# Usage in agent
@require_permissions('agent:finance', 'read:investment_goals')
def process_finance_query(cf_packet):
    """Finance agent endpoint - requires specific permissions"""

    user_id = cf_packet['auth']['user_token']

    # Safe to access user data now - Auth0 verified permissions
    user_goals = fetch_investment_goals(user_id)

    return generate_financial_advice(cf_packet['content'], user_goals)
Enter fullscreen mode Exit fullscreen mode

4. Dynamic Permission Requests

When an agent needs elevated permissions mid-conversation:

def request_dynamic_permission(user_id, agent_id, permission, reason):
    """
    Pause conversation and ask user for additional permission.
    Returns Auth0 consent URL.
    """

    # Generate state token for CSRF protection
    state = generate_random_state()
    store_state(state, {'user_id': user_id, 'agent_id': agent_id})

    # Build Auth0 consent URL
    consent_url = (
        f"https://{AUTH0_DOMAIN}/authorize?"
        f"response_type=code&"
        f"client_id={CLIENT_ID}&"
        f"redirect_uri={REDIRECT_URI}&"
        f"scope={permission}&"
        f"state={state}&"
        f"prompt=consent&"  # Force consent screen
        f"screen_hint=consent"  # Show permission details
    )

    return {
        'action': 'request_consent',
        'consent_url': consent_url,
        'permission': permission,
        'reason': reason,
        'agent_requesting': agent_id
    }

# Example usage
if 'read:account_balances' not in user_permissions:
    return request_dynamic_permission(
        user_id=user_id,
        agent_id='aci-finance',
        permission='read:account_balances',
        reason='To provide personalized portfolio recommendations, I need to see your current asset allocation.'
    )
Enter fullscreen mode Exit fullscreen mode

Frontend displays this as:

function PermissionRequest({ request }) {
  return (
    <Alert severity="info">
      <AlertTitle>Permission Required</AlertTitle>
      <Typography>
        <strong>{request.agent_requesting}</strong> is requesting:
      </Typography>
      <Typography variant="body2" sx={{ mt: 1 }}>
        πŸ“Š {humanize_permission(request.permission)}
      </Typography>
      <Typography variant="caption" sx={{ mt: 1, fontStyle: 'italic' }}>
        Why: "{request.reason}"
      </Typography>
      <Box sx={{ mt: 2 }}>
        <Button 
          variant="contained" 
          onClick={() => window.location.href = request.consent_url}
        >
          Grant Permission
        </Button>
        <Button variant="outlined" onClick={denyRequest}>
          Deny
        </Button>
      </Box>
    </Alert>
  );
}
Enter fullscreen mode Exit fullscreen mode

5. Fine-Grained Access Control via Auth0 Rules

Auto-assign default permissions for new users:

// Auth0 Rule: Default Permissions
function assignDefaultPermissions(user, context, callback) {
  const namespace = 'https://aci-protocol.com';

  // First-time user setup
  if (!user.app_metadata || !user.app_metadata.agents_authorized) {
    user.app_metadata = user.app_metadata || {};
    user.app_metadata.agents_authorized = ['aci-automotive', 'aci-general'];
    user.app_metadata.permission_tier = 'basic';

    auth0.users.updateAppMetadata(user.user_id, user.app_metadata)
      .then(() => {
        // Add permissions to token
        context.accessToken[namespace + '/permissions'] = [
          'read:profile',
          'write:preferences',
          'agent:automotive',
          'agent:general'
        ];

        context.accessToken[namespace + '/tier'] = 'basic';
        callback(null, user, context);
      })
      .catch(err => callback(err));
  } else {
    // Existing user - load saved permissions
    const tier = user.app_metadata.permission_tier || 'basic';
    const agents = user.app_metadata.agents_authorized || [];

    context.accessToken[namespace + '/permissions'] = 
      getPermissionsForTier(tier, agents);

    callback(null, user, context);
  }
}

function getPermissionsForTier(tier, agents) {
  const base = ['read:profile', 'write:preferences'];
  const agentPerms = agents.map(a => `agent:${a}`);

  if (tier === 'power') {
    return [...base, ...agentPerms, 'handoff:multi_agent', 'read:history'];
  } else if (tier === 'enterprise') {
    return [...base, ...agentPerms, 'handoff:multi_agent', 'read:history', 
            'read:account_balances', 'agent:medical', 'agent:legal'];
  }

  return [...base, ...agentPerms];
}
Enter fullscreen mode Exit fullscreen mode

6. Audit Logging via Auth0 Management API

Every agent action logs to Auth0:

import requests
from datetime import datetime

def log_agent_action(action_type, cf_packet, result, metadata=None):
    """Log every agent interaction to Auth0 for compliance"""

    mgmt_token = get_management_api_token()

    log_entry = {
        'type': 'sapi',  # Server API operation
        'description': f'Agent action: {action_type}',
        'user_id': extract_user_id(cf_packet['auth']['user_token']),
        'client_id': cf_packet['auth']['agent_id'],
        'date': datetime.utcnow().isoformat(),
        'details': {
            'cf_version': cf_packet['cf_version'],
            'domain': cf_packet['meta']['domain'],
            'handoff_chain': cf_packet['auth'].get('handoff_chain', []),
            'permissions_used': cf_packet['auth']['permissions'],
            'pii_detected': cf_packet['safety']['pii_detected'],
            'result_status': result.get('status'),
            'content_confidence': cf_packet.get('telemetry', {}).get('content_confidence'),
            **(metadata or {})
        }
    }

    # Send to Auth0 Logs
    response = requests.post(
        f'https://{AUTH0_DOMAIN}/api/v2/logs',
        headers={
            'Authorization': f'Bearer {mgmt_token}',
            'Content-Type': 'application/json'
        },
        json=log_entry
    )

    if response.status_code != 200:
        print(f"Warning: Failed to log to Auth0: {response.text}")

# Usage
@require_permissions('agent:automotive')
def handle_automotive_query(cf_packet):
    result = process_query(cf_packet)

    # Log action
    log_agent_action(
        action_type='query_processed',
        cf_packet=cf_packet,
        result=result,
        metadata={
            'query_tokens': len(cf_packet['content']['primary'].split()),
            'response_time_ms': result.get('processing_time')
        }
    )

    return result
Enter fullscreen mode Exit fullscreen mode

Users can view their audit trail:

def get_user_audit_trail(user_id, auth_token):
    """Fetch user's complete agent interaction history"""

    mgmt_token = get_management_api_token()

    response = requests.get(
        f'https://{AUTH0_DOMAIN}/api/v2/logs',
        headers={'Authorization': f'Bearer {mgmt_token}'},
        params={
            'q': f'user_id:"{user_id}" AND type:"sapi"',
            'sort': 'date:-1',
            'per_page': 100
        }
    )

    logs = response.json()

    # Format for display
    return [{
        'timestamp': log['date'],
        'agent': log['client_id'],
        'action': log['details']['action_type'],
        'domain': log['details']['domain'],
        'permissions_used': log['details']['permissions_used'],
        'pii_detected': log['details']['pii_detected']
    } for log in logs]
Enter fullscreen mode Exit fullscreen mode

7. Security Enhancements

Prevent Permission Escalation:

// Auth0 Rule: Block permission escalation attempts
function blockPermissionEscalation(user, context, callback) {
  const requestedScopes = (context.request.query.scope || '').split(' ');
  const allowedScopes = user.app_metadata.max_permissions || [];

  const unauthorized = requestedScopes.filter(s => !allowedScopes.includes(s));

  if (unauthorized.length > 0) {
    return callback(
      new UnauthorizedError(`Attempted to request unauthorized scopes: ${unauthorized.join(', ')}`)
    );
  }

  callback(null, user, context);
}
Enter fullscreen mode Exit fullscreen mode

Detect Anomalous Agent Behavior:

// Auth0 Rule: Flag unusual agent activity
function detectAnomalousActivity(user, context, callback) {
  const agentId = context.clientID;
  const domain = context.request.query.domain;

  // Check if agent is requesting mismatched domain
  const expectedDomain = getAgentDomain(agentId);
  if (domain && domain !== expectedDomain) {
    // Log suspicious activity
    console.error(`Agent ${agentId} requested wrong domain: ${domain} (expected: ${expectedDomain})`);

    // Don't block, but flag for review
    context.accessToken['https://aci-protocol.com/anomaly_detected'] = true;
  }

  callback(null, user, context);
}
Enter fullscreen mode Exit fullscreen mode

Lessons Learned and Takeaways

🎯 Key Wins

1. Auth0's M2M Flow Saved Weeks of Development

Initially, I tried building agent authentication from scratch with JWTs. It quickly became a nightmare:

  • Token rotation? Manual.
  • Permission scoping? Custom database.
  • Audit logging? Build from scratch.
  • Rate limiting? More custom code.

Auth0's machine-to-machine credentials gave me all of this out of the box. The agent authentication code went from 500 lines to ~50.

Takeaway: Don't build auth yourself. Even for non-human actors.


2. The "Handoff Chain" Pattern Emerged Organically

I initially designed for single-hop handoffs (A→B). But once I had Auth0 tracking each transition, I realized I could support chains (A→B→C→D):

# Track full handoff path in Auth0 token
cf_packet['auth']['handoff_chain'].append(current_agent_id)

# Enforce max chain depth (prevent infinite loops)
if len(cf_packet['auth']['handoff_chain']) > MAX_DEPTH:
    raise ValueError('Handoff chain too deep - possible loop detected')

# Enable mid-chain revocation (kill switch)
if user_revoked_permission(cf_packet['auth']['user_token'], next_agent):
    raise PermissionError('User revoked access mid-chain')
Enter fullscreen mode Exit fullscreen mode

This unlocked features I didn't anticipate:

  • Usage-based pricing (charge per hop)
  • Quality degradation detection (confidence drops across chain)
  • A/B testing agent orderings (Aβ†’Bβ†’C vs Aβ†’Cβ†’B)

Takeaway: Good auth infrastructure enables product features you didn't plan for.


3. PII Detection + Auth0 Rules = Automatic Safety Nets

I built a simple regex-based PII detector. Combined with Auth0 rules, it became a security enforcement layer:

// Auth0 Rule: Block PII leaks
function blockUnauthorizedPII(user, context, callback) {
  const piiDetected = context.request.body.safety?.pii_detected;
  const hasPiiPermission = context.authorization.permissions.includes('read:pii');

  if (piiDetected && !hasPiiPermission) {
    // Log attempt
    console.error(`Agent ${context.clientID} attempted to access PII without permission`);

    return callback(
      new UnauthorizedError('PII detected but user has not granted read:pii permission')
    );
  }

  callback(null, user, context);
}
Enter fullscreen mode Exit fullscreen mode

Result: Even if I have a bug in my application code, Auth0's rules layer physically blocks unauthorized data access.

Takeaway: Decouple security enforcement from application logic. Let Auth0 be the gatekeeper.


🚧 Challenges Faced

1. Token Size Explosion

CF packets grew to 5KB+ when I embedded full Auth0 JWTs. This destroyed latency (300ms+ per handoff).

Solution: Store tokens server-side, pass references in CF packets:

# Store in Redis with 1-hour TTL
token_id = f"tok_{uuid4()}"
redis.setex(token_id, 3600, full_jwt)

# CF packet only carries lightweight reference
cf_packet['auth']['token_ref'] = token_id

# Receiving agent fetches from cache
def validate_token_ref(token_ref):
    full_token = redis.get(token_ref)
    if not full_token:
        raise ValueError('Token expired or invalid')
    return jwt.decode(full_token, ...)
Enter fullscreen mode Exit fullscreen mode

Impact: Reduced packet size by 80%, latency dropped to <50ms.


2. Permission Granularity Explosion

Started with 5 permissions (agent:automotive, agent:finance, etc.). Users wanted finer control:

  • "Let the finance agent read my goals, but not execute trades"
  • "Let automotive agent see public specs, not my personal garage"

I ended up with 30+ permission types. Managing them in Auth0's dashboard was tedious.

Solution: Used Auth0 Management API to bulk-import from YAML:

# permissions.yml
permissions:
  - name: "read:profile"
    description: "Read user profile information"

  - name: "write:preferences"
    description: "Update user communication preferences"

  - name: "agent:automotive:public"
    description: "Access public vehicle specifications"

  - name: "agent:automotive:personal"
    description: "Access user's garage and vehicle history"

  - name: "agent:finance:read"
    description: "View investment goals and risk tolerance"

  - name: "agent:finance:execute"
    description: "Execute trades and portfolio rebalancing"

  # ... 24 more
Enter fullscreen mode Exit fullscreen mode
# Sync to Auth0
import yaml

def sync_permissions_to_auth0():
    with open('permissions.yml') as f:
        perms = yaml.safe_load(f)

    mgmt_token = get_management_api_token()

    for perm in perms['permissions']:
        requests.post(
            f'https://{AUTH0_DOMAIN}/api/v2/resource-servers/{API_IDENTIFIER}/scopes',
            headers={'Authorization': f'Bearer {mgmt_token}'},
            json={'value': perm['name'], 'description': perm['description']}
        )
Enter fullscreen mode Exit fullscreen mode

Takeaway: Define permissions as code, not clickops.


3. Debugging Multi-Agent Chains is HARD

When a 4-agent chain failed at step 3, figuring out why was brutal. Auth0's logs helped, but I needed more granular tracing.

Solution: Built a Chrome DevTools-style debugger that visualizes the full chain:

Session sess_9c2f Timeline
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

12:03:45  [auth0] User login βœ“
          β”œβ”€ Method: google-oauth2
          β”œβ”€ IP: 203.0.113.42
          └─ Granted: [read:profile, agent:automotive, agent:finance]

12:03:47  [aci-automotive] Query received βœ“
          β”œβ”€ Domain: automotive
          β”œβ”€ Resonance: {tone: "casual-technical", density: 0.65}
          β”œβ”€ Processing time: 1.2s
          └─ Confidence: 0.85

12:03:50  [aci-automotive] Handoff requested β†’ aci-finance
          β”œβ”€ Reason: Financial context detected in query
          β”œβ”€ Required permissions: [agent:finance, read:investment_goals]
          └─ CF packet size: 2.1KB

12:03:51  [auth0] Permission check βœ“
          β”œβ”€ User token valid: βœ“
          β”œβ”€ Agent token valid: βœ“
          β”œβ”€ Required scopes present: βœ“
          └─ Latency: 23ms

12:03:52  [aci-finance] Handoff accepted βœ“
          β”œβ”€ Context inherited: User owns Tesla Model 3
          β”œβ”€ Tone preserved: casual-technical
          β”œβ”€ Processing time: 1.8s
          └─ Confidence: 0.78

12:03:55  [meta-reasoner] Synthesis βœ“
          β”œβ”€ Input sources: 2 (automotive, finance)
          β”œβ”€ Perspective merge confidence: 0.82
          └─ Final response confidence: 0.80

Performance Summary:
β”œβ”€ Total latency: 10.2s
β”œβ”€ Auth overhead: 47ms (0.5%)
β”œβ”€ Token cache hits: 3/3
└─ No permission denials

Data Accessed:
βœ“ user.profile.name
βœ“ user.profile.expertise_level
βœ“ user.preferences.communication_style
βœ“ user.automotive.vehicles[0].model
βœ“ user.finance.investment_goals
βœ— user.finance.account_balances (not granted)
Enter fullscreen mode Exit fullscreen mode

Implementation:

# Trace middleware
@app.middleware("http")
async def trace_middleware(request: Request, call_next):
    trace_id = request.headers.get('X-Trace-ID', str(uuid4()))

    # Start span
    span = {
        'trace_id': trace_id,
        'timestamp': datetime.utcnow(),
        'agent_id': request.headers.get('X-Agent-ID'),
        'path': request.url.path
    }

    # Process request
    start = time.time()
    response = await call_next(request)
    duration = time.time() - start

    # End span
    span['duration_ms'] = duration * 1000
    span['status_code'] = response.status_code

    # Store in Redis for debugger
    redis.lpush(f'trace:{trace_id}', json.dumps(span))
    redis.expire(f'trace:{trace_id}', 3600)

    return response
Enter fullscreen mode Exit fullscreen mode

πŸ’‘ Unexpected Insights

1. Users Don't Understand Technical Permissions

Showing "Grant read:account_balances to aci-finance-prod-v2.3" confused everyone.

Better UX (translate scopes to human language):


python
PERMISSION_DESCRIPTIONS = {
    'read:profile': 'πŸ‘€ View your profile information',
    'read:account_balances': 'πŸ’° See your account balances',
    'write:execute_trades': 'πŸ“ˆ Execute investment trades',
    'agent:automotive:personal': 'πŸš— Access your garage and vehicle history',
    'agent:medical': 'πŸ₯ Connect to medical AI (requires HIPAA consent)'
}

def humanize_permission_request
Enter fullscreen mode Exit fullscreen mode

Top comments (0)