AI Agent Identity: Why Your Current Auth Model Will Break in Production
You’ve deployed a multi-agent AI system. One orchestrator delegates to sub-agents, which call external APIs, which trigger database writes. And somewhere in that chain, something goes wrong — and you have no idea which agent did it, with whose credentials, and whether it was authorized.
This is the AI identity problem. And most teams hit it only after they’re already in production.
The Core Problem
Traditional authentication models were designed for two principals: humans and services. A human logs in via OAuth. A service gets a long-lived API key or service account. The system knows who asked, and what they’re allowed to do.
AI agents break this model in three ways:
1. They act on behalf of users, but aren’t users. An orchestrator agent making API calls shouldn’t have the same permissions as the user who triggered it. The agent might be completing task A — but your service account has permission to do tasks A through Z.
2. They delegate to other agents. When your orchestrator spawns a sub-agent, that sub-agent inherits… what exactly? The original user’s token? A new service account? Nothing? Most current implementations answer “nothing” or “all of the above.”
3. They’re unpredictable. A human’s API calls follow known patterns. An LLM agent might take unexpected action paths — and if your access control is coarse-grained, you discover this only after the damage is done.
What Uber Built: The Actor Chain
Uber’s engineering team tackled this with what they call an actor chain embedded in JWT tokens. The concept is elegant:
Every time a principal delegates to another agent, the delegating principal appends itself to the actor chain in the new token:
# User requests a task
JWT: { sub: "user-123", act: [] }
# Orchestrator agent receives delegation
JWT: { sub: "agent-orchestrator", act: ["user-123"] }
# Sub-agent receives further delegation
JWT: { sub: "agent-summarizer", act: ["user-123", "agent-orchestrator"] }
# Tool/API receives the final call
JWT: { sub: "tool-search-api", act: ["user-123", "agent-orchestrator", "agent-summarizer"] }
Every service that receives this token can now answer three questions:
- Who originally authorized this? (user-123)
- What chain of agents led here? (orchestrator → summarizer)
- What is this specific agent allowed to do? (only what was scoped for this task)
The token is short-lived (minutes, not days) and task-scoped (only the permissions needed for this specific task, not the agent’s full permission set).
What Auth0 Adds: Capability-Scoped Permissions
Auth0’s approach extends this with capability tokens — rather than granting an agent a role, you grant it specific capabilities for a specific task context:
{
"sub": "agent-analyst",
"capabilities": ["read:database-reporting", "write:slack-channel-analytics"],
"scope": "task-id:abc123",
"expires_in": 300,
"act": ["user-456", "agent-orchestrator"]
}
This does three things:
- The agent can only do what the task needs — not everything its “role” allows
- The token expires in 5 minutes — compromise has a tiny blast radius
- The full chain is auditable — every action traces back to the originating user
What This Means for Your Architecture
If you’re building a multi-agent system today, here are the concrete decisions you need to make:
1. Adopt actor chain semantics now.
RFC 9068 (JWT Profile for Access Tokens) already supports the act claim for delegation chains. You don’t need to wait for a standard — it’s here. Implement it even if your agents are still simple.
2. Issue tokens per task, not per agent.
An agent identity is not the same as a task credential. Your agent-analyst gets a fresh, scoped token for each task it executes. When the task is done, the token expires. The agent’s long-term identity credential is separate and never used in API calls.
3. Build a centralized Agent Registry. Know what agents exist, what they’re allowed to do, and which users can invoke them. This registry is the source of truth for your authorization system — not scattered config files.
4. Emit structured audit events at every hop. Every time an agent receives a delegated token and takes an action, emit a structured event: agent ID, actor chain, action taken, resource accessed, outcome. Without this, debugging an incident in a multi-agent system is essentially impossible.
The Minimum Viable Implementation
If you can’t build the full actor chain model yet, start here:
import jwt
import time
def issue_agent_token(
agent_id: str,
user_id: str,
parent_chain: list[str],
capabilities: list[str],
task_id: str,
ttl_seconds: int = 300
):
now = int(time.time())
return jwt.encode({
"sub": agent_id,
"act": parent_chain + [user_id] if not parent_chain else parent_chain,
"cap": capabilities,
"task": task_id,
"iat": now,
"exp": now + ttl_seconds,
}, SECRET_KEY, algorithm="HS256")
Even this minimal version gives you: short-lived tokens, a chain you can audit, and task-scoped capabilities. It’s not the full Uber model — but it’s infinitely better than a shared service account.
The Uncomfortable Truth
Most teams building multi-agent AI systems right now are using shared service accounts with broad permissions and no delegation chain. This works in development. It will cause incidents in production — and when they happen, you won’t know which agent did what, because there’s no audit trail.
The good news: the patterns exist. Actor chains, capability tokens, short-lived credentials — none of this is new technology. We’ve solved these problems in distributed systems. The AI agent layer just requires us to apply them one layer higher.
The time to design your agent identity model is before your first security incident, not after.
This post is part of the AI Engineering series on luonghongthuan.com — practical patterns for engineers building production AI systems.