Giving AI Agents a Governed Identity: A Comprehensive Guide to Architecture, Implementation, and Enforcement with CUGA A developer outlined CUGA, an agent identity architecture that registers each AI agent as a first-class identity entity, requires separation-of-duties approval so creators cannot approve their own agents, binds agents to dedicated OAuth clients, and issues short-lived scoped tokens via RFC 8693 token exchange through an identity gateway. The design aims to close security gaps in ungoverned agent deployments, including identity spoofing, over-privileged execution, uncontrolled delegation and the lack of a granular kill switch. Implementing Agent Identity with CUGA Agent. Introduction & Background The Shift Towards Autonomous AI Agents As enterprise AI transitions from stateless chat completion interfaces to autonomous agents capable of reading context, making decisions, executing multi-step workflows, and calling external APIs, traditional application security models begin to break down. Historically, software integration relied on rigid permission boundaries: either a service used a single, static API key for system-to-system operations, or an application borrowed a user’s session token to perform actions. In an agentic architecture, however, LLM-powered agents plan their own actions, dynamically select tools, and interact across complex execution chains. ┌───────────────────────────────────────────────────────────────────────────┐ │ UNGOVERNED AGENT ARCHITECTURE │ │ │ │ User ──────► Agent Runtime ──────► Shared Service Account / API Key │ │ │ │ │ │ ▼ ▼ │ │ Dynamic Tool Call Unrestricted Access │ │ No granular attribution Cannot isolate failures │ └───────────────────────────────────────────────────────────────────────────┘ Without a dedicated, governed identity mechanism, agents present several key security risks: - Identity Spoofing & Blurs: When an agent borrows a shared service account or user password, auditors cannot differentiate between an intentional human action and an agent-generated call. - Over-Privileged Execution Lack of Least Privilege : Agents often run with broad system access, allowing a single prompt injection or hallucinated tool call to inspect, modify, or delete off-limit data. - Uncontrolled Delegation: When an agent acts on behalf of a user, there is often no cryptographic guarantee that the agent’s permission context remains less than or equal to the authorizing user's entitlements. - Lack of Granular Containment Kill Switch Gap : Disabling a compromised agent requires revoking broad service accounts or shared credentials, which risks taking down unaffected services or agent runtimes. What is Governed Agent Identity? An AI agent acts on its own: it reads data, calls tools and sometimes changes things. Agent Identity gives each agent its own verifiable identity in the same way a company gives each employee a badge. The agent is registered, approved by a different person, and then proves who it is on every call. Its core purpose is to answer four questions about every action an agent takes: Agent Identity establishes AI agents as first-class identity entities—similar to issuing an employee badge within an enterprise identity framework. Each agent is registered in a secure registry, reviewed under strict Separation of Duties where the creator cannot approve their own agent , bound to its own OAuth client, and granted short-lived, cryptographically signed tokens. ┌───────────────────────────────────────────────────────────────────────────┐ │ GOVERNED AGENT IDENTITY │ │ │ │ Developer ──► Register ──► Approver Separation of Duties ──► Active │ │ │ │ │ User Token + Agent Credentials ──► RFC 8693 Token Exchange ─────┘ │ │ │ │ │ ▼ │ │ Audit Trail ◄── Identity Gateway ──► Short-Lived Scoped Token │ │ Agent ID Verification │ │ │ ▼ │ │ Protected Business Data │ └───────────────────────────────────────────────────────────────────────────┘ Governed identity answers four critical operational questions for every execution step: | Security Pillar | Operational Requirement | Governed Agent Identity Realization | |---|---|---| | Accountability | Who performed this action? | Every call carries an unforgeable Agent ID. All runtime and lifecycle actions log to a single correlation trail. | | Least Privilege | What is the maximum allowed scope? | Scope ceilings allowed scopes are defined at onboarding. Issued tokens can never exceed this ceiling, even if a tool is exposed. | | Safe Delegation | On whose authority is the agent acting? | Delegated tokens utilize RFC 8693 token exchange sub = User, act.sub = Agent . Scope is bounded by the intersection of user rights and agent rights. | | Immediate Control | Can we isolate a rogue agent instantly? | A centralized kill switch shifts status to SUSPENDED, blocking token issuance and forcing instant rejection at identity gateways. | System Architecture & Lifecycle Governance Component Architecture & Layered Defense A robust governed agent architecture splits responsibility into two distinct runtime layers: LLM Policy Framing and Resource-Level Identity Enforcement. 1. Cuga Policy Framing : Runs before the model or tool invocation. Intent guards filter dangerous prompts e.g., bulk deletion , while tool approval rules freeze execution until human validation is received. 2. Resource-Level Identity Enforcement : Runs inside the backend tool execution path. The tool requests token minting via an IdentityGateway , which checks credentials, validates identity backend status, and asserts token signatures and scope ceilings. Complete Agent Lifecycle State Machine An agent's operational permissions are governed by an explicit state machine. Creation, approval, suspension, and reactivation are managed via formal administrative operations with embedded audit trails. - DEFAULT: The initial state after registration by a developer. The agent has an assigned Agent ID and scope ceiling, but cannot mint tokens or register an OAuth client. - ACTIVE: Set by an independent approver. Dynamic OAuth client registration is unlocked, and short-lived tokens can be issued for authorization. - SUSPENDED Kill Switch : Suspends token issuance immediately. When coupled with strict status validation on resource tools, active tokens are rejected. Access Delegation Mechanics & Scoping Modes GRANT TYPES & ACCESS MODES │ ┌────────────────────────────────────┼────────────────────────────────────┐ ▼ ▼ ▼ Autonomous Mode Delegated Mode Privileged Mode client credentials RFC 8693 Exchange authorization details │ │ │ • Agent acts alone • Agent acts for user • Sensitive operations • Background analytics • HR / Course enrollment • Service restarts • Claims: agent id • Claims: sub=user, act=agent • Short TTL + Step-up human check Governed identities support four primary access modes depending on operational context: - Autonomous Access client credentials : Used for automated, non-interactive processes. The agent authenticates using its OAuth client id and secret, receiving a token scoped to its defined ceiling. - Delegated Access / On-Behalf-Of RFC 8693 Token Exchange : Used when an agent executes tasks on behalf of an authenticated end user. The identity gateway exchanges the user's JWT for a delegated token where sub is the user and act.sub is the agent. - Privileged Action Human-in-the-Loop Scoping : Designed for sensitive operations e.g., service restarts or financial transactions . Requires interactive human approval alongside short-lived tokens carrying explicit authorization details . - Agent Chaining Nested Delegation : When a supervisor agent delegates sub-tasks to a downstream worker agent, tokens nest the actor claim act: worker - supervisor , maintaining complete auditability across multi-agent steps. Implementation with CUGA-Agent Integration The following reference implementation demonstrates how to build a governed agent using Cuga, an Identity Gateway, an isolated Policy Store, and custom Guarded Tools. The demonstration use-case The following is what is implemented containing simulation scenario - Cuga Agent is used for real: agent construction, tool execution, intent-guard and tool-approval policies, human-in-the-loop resume, supervisor delegation. - Agent Identity has no public SDK. The showcase therefore ships a local mock of the IBM Verify identity service MockVerifyBackend : RS256-signed JWTs, agent registry, dynamic client registration, client-credentials and RFC 8693 token exchange . HttpVerifyBackend is a reference implementation of the same interface for a real tenant. It has not been tested against a live tenant ; treat it as a starting point. - Cuga Harness Kit is not a test runner. It scaffolds skills for coding assistants 11 skills, installed with init --targets bob . The showcase runs it into a temporary folder to prove it works, and uses its own harness/scenarios.py 10 conformance scenarios for validation. - The LLM engine is selectable : local Ollama default, granite4.2:latest , llama.cpp llama-server or a cloud OpenAI-compatible API. Pick it in the UI or in .env see LLM engines https://github.com/aairom/cuga-agentidentity llm-engines . Tests and the offline demo use a deterministic fake model, so no LLM is needed to run them. - Live status of the engines: Ollama detection, test and switching were exercised against a real local daemon. llama.cpp and cloud were tested with mocked HTTP only no llama-server and no cloud key were available , so treat them as unverified against a real server. Technical Features of the use-case 1. Agent lifecycle : create, approve a different person , register an OAuth client, deploy, suspend kill switch , reactivate. 2. Four agents covering different identity patterns: - Learning Coach : delegated on-behalf-of user actions, with human approval and a privileged token for expensive enrolments. - Revenue Analyst : autonomous agent with a read-only scope ceiling. - Ops SRE Assistant : privileged actions service restart bound to a short-lived token. - Workplace Supervisor : multi-agent orchestration with a nested actor chain in the token. 3. 4. Defence in depth : Cuga policies before the LLM , then per-tool identity checks after the LLM . The LLM never sees a token. 5. Interactive documentation : six identity flows rendered as sequence diagrams, a token inspector, an audit trail keyed by Agent ID. 6. Pluggable LLM engines : Ollama, llama.cpp and cloud behind one interface, with detection, a settings page with Test connection , runtime switching without a restart, a documented fallback order and error messages that say how to fix the problem. 7. Validation : 118 unit tests, 10 harness conformance scenarios, a CLI demo and a UI smoke test. 8. Operations : structured logging, typed configuration, scripts for start/stop/cleanup, a multi-stage Containerfile. - Identity Models and Gateway Setup : first, we establish core identity models, custom error types, context management, and token validation mechanisms. agent identity/models.py """Domain models shared by the identity layer.""" from future import annotations from dataclasses import dataclass, field from datetime import datetime, timezone from enum import Enum from typing import Any class AgentStatus str, Enum : """Lifecycle states of an agent in the IBM Agent Registry.""" DEFAULT = "DEFAULT" created by the developer, not yet approved ACTIVE = "ACTIVE" approved: may register clients and obtain tokens SUSPENDED = "SUSPENDED" blocks NEW token issuance issued tokens stay valid UNDER REVIEW = "UNDER REVIEW" class Entitlement str, Enum : """Management-plane entitlements names follow the IBM docs .""" READ AGENTS = "readAgents" WRITE AGENTS = "writeAgents" MANAGE AGENT STATUS = "manageAgentStatus" @dataclass frozen=True class Principal: """A human management user developer or approver .""" user id: str display name: str entitlements: frozenset Entitlement def has self, ent: Entitlement - bool: return ent in self.entitlements @dataclass class AgentRecord: """An entry of the Agent Registry.""" agent id: str name: str description: str owner: str user id of the creating developer status: AgentStatus = AgentStatus.DEFAULT allowed scopes: frozenset str = frozenset approved by: str | None = None client id: str | None = None created at: str = field default factory=lambda: datetime.now timezone.utc .isoformat timespec="seconds" metadata: dict str, Any = field default factory=dict @dataclass frozen=True class ClientCredentials: """OAuth client bound to an agent. The secret is returned only once.""" client id: str client secret: str agent id: str - Next, we implement context handling to store end-user session state securely without exposing authorization tokens directly to the LLM model space. agent identity/gateway.py """Identity gateway used by tools. The LLM never sees a token. Tools call :meth: IdentityGateway.authorize , which reads the call context who is the end user, which tokens are available from a ContextVar set by the runtime, mints a just-in-time token for the right grant type, validates it like a resource server would, and returns the validated :class: ~agent identity.tokens.TokenContext . """ from future import annotations import contextvars from dataclasses import dataclass from typing import Any from .audit import AuditLog from .errors import DelegationError, IdentityError from .lifecycle import AgentIdentityService from .logging setup import get logger from .tokens import TokenContext, TokenValidator log = get logger "gateway" @dataclass frozen=True class CallContext: """Per-request identity context propagated to tools via a ContextVar .""" user id: str | None = None user token: str | None = None Set when a supervisor delegates to a sub-agent: the supervisor's own on-behalf-of token becomes the subject of the sub-agent's exchange, which nests the actors in the act claim the transaction/caller chain . supervisor token: str | None = None CALL: contextvars.ContextVar CallContext | None = contextvars.ContextVar "agent identity call", default=None def set call context ctx: CallContext | None - contextvars.Token: return CALL.set ctx def reset call context token: contextvars.Token - None: CALL.reset token def current call context - CallContext | None: return CALL.get class IdentityGateway: """Mints and validates tokens for tool executions.""" def init self, service: AgentIdentityService, issuer: str, public key: Any, strict status check: bool = True - None: self. service = service self. issuer = issuer self. key = public key self. strict = strict status check self. validators: dict str, TokenValidator = {} @property def audit self - AuditLog: return self. service.audit def validator self, audience: str - TokenValidator: """Return and cache the resource-server validator for audience .""" if audience not in self. validators: lookup = lambda aid: self. service.backend.get agent aid .status if self. strict else None self. validators audience = TokenValidator self. key, self. issuer, audience, self. service.audit, status lookup=lookup return self. validators audience def authorize self, , agent name: str, mode: str, "obo" on behalf of the user | "autonomous" audience: str, scopes: list str , resource: str, authorization details: list dict str, Any | None = None, - TokenContext: """Mint + validate a token; raises an :class: IdentityError subclass on denial.""" agent = self. service.backend.find agent agent name try: if mode == "autonomous": token = self. service.agent token agent.agent id, scopes, audience return self.validator audience .validate token, tuple scopes , resource=resource call = current call context subject = call.supervisor token or call.user token if call else None if not subject: raise DelegationError "No end-user context: on-behalf-of access needs a logged-in user" token = self. service.delegated token agent.agent id, subject, scopes, audience, authorization details return self.validator audience .validate token, tuple scopes , require delegation=True, require privileged=bool authorization details , resource=resource, except IdentityError as exc: log.warning "authorize failed agent=%s resource=%s: %s", agent name, resource, exc raise - Defining Guarded Tools: tools assert authorization boundaries via the IdentityGateway prior to interacting with backend resources. agent identity/tools.py """Identity-guarded tools for the example agents. Every tool obtains a token through the :class: IdentityGateway before touching data. Denials are returned as ACCESS DENIED: ... text so the model can relay them faithfully, and are also written to the audit log. """ import json from langchain core.tools import tool from agent identity.models import IdentityError from agent identity.gateway import IdentityGateway COURSES DB = { "SEC301": {"title": "Advanced Cloud Security", "cost": 1200}, "PY101": {"title": "Python Basics", "cost": 300} } def create learning coach tools gateway: IdentityGateway, agent name: str : @tool def list courses - str: """List available training courses, costs, and IDs.""" try: gateway.authorize agent name=agent name, mode="obo", audience="hr-service", scopes= "courses:read" except IdentityError as exc: return f"ACCESS DENIED: {str exc }" return json.dumps COURSES DB @tool def enroll expensive course course id: str - str: """Enroll the user in a course costing over 500 USD. Requires approval.""" course = COURSES DB.get course id.upper if not course: return f"Error: Course {course id} not found." details = { "type": "course enrollment", "course id": course id.upper , "max cost": course "cost" } try: Privileged token authorization with specific authorization details gateway.authorize agent name=agent name, mode="obo", audience="hr-service", scopes= "courses:enroll" , authorization details=details except IdentityError as exc: return f"ACCESS DENIED: {str exc }" return f"Successfully enrolled in {course 'title' } {course id.upper } ." return list courses, enroll expensive course - Constructing the Governed Cuga Agent Environment: next, we initialize the agent with isolated policy stores and human-in-the-loop gates. agents/learning coach.py """Example agent 1 - Learning Coach. Use case: an employee asks an agent to browse and enrol in training courses. Identity pattern: on-behalf-of RFC 8693 - tokens carry sub=