The first plan for hooking an AI agent into an enterprise system is usually this: let the user log in once and hand the resulting token to the agent to use. It runs, but it leaves three questions you cannot answer — whose permissions did the agent exercise? Can the agent keep working after the user revokes its authorization? And when something goes wrong, how do you tell a human action from an agent action?
Agents are principals, not tools
The first step is to model the agent as its own principal. Its tokens carry principal_type=machine — the same kernel as human users, a different principal type. An agent has its own credentials, its own roles, its own call quota.
01// Agent 不再伪装成 USER,它有独立的 subject_type02{03 "sub": "agent:sales-copilot",04 "principal_type": "machine",05 "tenant_id": "acme",06 "roles": ["order:read", "customer:read"],07 "quota": { "tool_calls_per_min": 120 }08}The delegation chain: intersect permissions, never union them
When an agent acts on behalf of a user, it goes through an RFC 8693 Token Exchange to obtain a fresh token. The crux is how permissions are computed: the new token’s permissions are the intersection of “the app permissions granted to the agent” and “the permissions that user actually holds”, then trimmed to a ceiling by maxPermissionLevel.
This point matters enormously. Take the union and the agent becomes a permission amplifier — it ends up holding permissions the user never had. Take the intersection and the agent can never do more than the user who delegated to it.
01val appPerms = appPermissionResolver.resolve(clientId)02val userPerms = userPermissionResolver.resolve(subjectToken.sub)0304// 交集:Agent 不能比被委托的用户做得更多05val granted = appPerms.intersect(userPerms)06 .filter { it.level <= maxPermissionLevel }0708// act claim 记录委托关系,多跳场景可嵌套09val act = ActClaim(sub = subjectToken.sub, nested = subjectToken.act)Tool-level authorization lives in the code
Which tools an agent may call is not written in a document for people to obey — it is written on annotations and enforced by the framework. The agent_tools registry records each tool’s ownership and permission requirements, and every call is verified against them before it runs.
01@RequiresToolPermission("order:refund")02@PostMapping("/tools/order-refund/invoke")03fun invokeRefund(@RequestBody req: ToolRequest): ToolResult {04 // 到这里说明调用方持有该工具权限,且未超配额05 return toolExecutionService.execute(req)06}What it costs
- Every agent must be registered and assigned roles up front — no “get it running first, sort it out later”
- The delegation chain adds one more token exchange: a longer path and more complex troubleshooting
- Nested act claims in multi-hop delegation need careful handling, or the intermediate delegation relationships get lost
What these costs buy is this: for every data access an agent makes, you can answer “who authorized it, which tool was used, which permissions were exercised”. In compliance-driven settings, that ability to answer is the price of admission.