# π
*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:
- Automotive agent (car knowledge)
- Finance agent (investment advice)
- 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"]
}
}
Key innovation: Every handoff is an authenticated transaction verified by Auth0.
Demo
π₯ Video Walkthrough
π¦ 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
πΈ Screenshots
1. Secure Multi-Agent Conversation
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] β
βββββββββββββββββββββββββββββββββββββββββββββββ
Agents request permissions in plain language. Auth0 enforces technical scopes behind the scenes.
3. Audit Dashboard
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)
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) β
βββββββββββββββββββββββ
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 })
});
};
}
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
}
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)
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.'
)
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>
);
}
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];
}
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
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]
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);
}
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);
}
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')
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);
}
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, ...)
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
# 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']}
)
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)
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
π‘ 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


Top comments (0)