Designing Identity and Delegation for Production AI Agents
Production AI agents need explicit human identity, managed agent identity, trusted workload identity, narrow delegation, and an evidence chain that survives every authorization hop.
On this page
- Give every agent a managed identity
- The running workload has to prove which agent it represents
- Carry the user and agent identities together
- Delegation needs to be represented as data
- Authority should narrow at every delegation hop
- Validate the complete delegation chain
- Revocation should flow down the delegation tree
- Tool binding is part of agent identity policy
- Keep trusted execution context outside the model
- Keep identity lifecycle independent from individual runs
- Keep an identity and delegation audit chain
- Keep these invariants through implementation changes
A token proves that some caller is authenticated. It does not tell a production system which agent is acting, whose authority that agent carries, or how much of that authority should survive when work is delegated to another agent.
Consider a reconciliation agent investigating payment P9831 for user U1841 in tenant T42. The logical agent is reconciliation_agent_v12. It can read payment history and, under the right conditions, request a refund.
During the investigation it delegates log analysis to investigation_agent_v4.
At that point there are several identities involved:
user_subject = U1841
agent_principal = reconciliation_agent_v12
child_agent = investigation_agent_v4
tenant = T42
workload_identity = agent-pod-a81f
If all of these collapse into one service account or one user token, the downstream system loses the information needed to tell who is actually acting and what authority was delegated.
For production agents, I would keep four concepts separate:
Identity
Who is acting?
Delegation
On whose behalf is it acting?
Standing capability
What kinds of actions may this agent request?
Action authorization
May this exact action execute now?
This article stays with the first three. Authorization for the exact write has its own concurrency, replay, and recovery problems.
Give every agent a managed identity#
An agent should have a stable logical identity with an owner, tenant, purpose, and lifecycle.
AgentIdentity {
agent_id
owner
tenant
purpose
allowed_tool_classes
created_at
disabled_at
status
}
For the running example:
agent_id =
reconciliation_agent
agent_version =
12
I would not create a completely new identity for every deployment. The logical identity represents the automation. The version belongs in execution and policy context so an investigation can still answer which implementation made a decision.
That gives us two useful dimensions:
agent_principal =
reconciliation_agent
agent_version =
12
The identity registry should also answer operational questions that become important later:
Who owns this agent?
Is it still active?
Which tenant does it belong to?
Which tool classes may it request?
Which deployment versions are currently trusted?
Which delegations were created from it?
A shared identity such as prod-agent-service loses most of this information.
The running workload has to prove which agent it represents#
The application should not be able to claim its identity through an ordinary request header.
This is not sufficient:
X-Agent-Name:
reconciliation_agent
A compromised workload could put any value there.
The binding between a production workload and the registered agent identity should come from infrastructure.
Agent Definition
|
v
Deployment Platform
|
v
Workload Attestation
|
v
Short Lived Workload Credential
|
v
agent_principal =
reconciliation_agent
The underlying mechanism can use cloud workload federation, SPIFFE, OIDC workload credentials, or another managed identity system. The important property is that the authorization infrastructure can verify which workload received the identity.
For one execution we may now have:
agent_principal =
reconciliation_agent
agent_version =
12
workload_identity =
agent-pod-a81f
These values mean different things.
The agent identity identifies the automation.
The version identifies the implementation.
The workload identity identifies the running instance presenting the request.
That distinction becomes useful during rollout, compromise, audit, and revocation.
Carry the user and agent identities together#
The agent is usually not acting only for itself.
In the payment example:
user_subject =
U1841
agent_principal =
reconciliation_agent
agent_version =
12
workload_identity =
agent-pod-a81f
tenant =
T42
The user identity tells us what authority can be delegated.
The agent identity limits what the automation is expected to do.
The workload identity proves which process is acting.
Passing only the user token gives the agent whatever authority that credential already carries. Passing only the agent identity removes user context that the payment system may need to determine whether U1841 can operate on P9831.
Both identities need to survive the execution path.
A protected resource should be able to distinguish:
U1841 directly requested this action
from:
reconciliation_agent
acting for U1841
requested this action
Those are not equivalent security events.
A production action carries human authority, a logical agent identity, and the identity of the running workload. The workload proves its agent binding through trusted infrastructure instead of declaring its own identity.
Delegation needs to be represented as data#
Now the reconciliation agent delegates investigation work.
The parent agent may have:
payments.read
payments.refund
The child only needs:
payments.read
Do not pass the parent's complete credential to the child.
Represent the delegation explicitly.
Delegation {
delegation_id
parent_principal
child_principal
delegated_scopes
resource_constraints
parent_delegation_id
created_at
expires_at
version
status
}
For example:
delegation_id = D41
parent_principal =
reconciliation_agent
child_principal =
investigation_agent_v4
delegated_scopes =
payments.read
resource_constraints =
payment P9831
The child now has authority for the investigation without receiving refund capability.
This is especially important when agents can dynamically create subagents. Process creation should not imply authority delegation.
Authority should narrow at every delegation hop#
The effective authority of a child should be an intersection of the authority available from every relevant source.
effective_authority = intersection(
user_authority,
agent_capability,
parent_delegation,
child_tool_binding,
resource_constraints,
current_policy
)
Never take the union of those sets.
Suppose the user can read and refund payments, the parent agent can read and refund, but the delegation only contains payments.read.
The child gets payments.read.
It does not regain payments.refund because the user or parent happens to have it.
A delegation chain may look like:
U1841
|
v
reconciliation_agent
|
| payments.read
v
investigation_agent_v4
If another child is created later, its authority is derived from the authority currently held by investigation_agent_v4, not from the original user's full permissions.
This gives the chain a monotonic property. Delegation can preserve authority or remove it. It does not create new authority.
Delegation only preserves or reduces authority. The child receives the intersection of user authority, agent capability, delegation scope, tool binding, and resource constraints, never the union.
Validate the complete delegation chain#
Checking only the last delegation object is not enough.
For a protected request, validate:
delegation not expired
parent identity still active
parent delegation still active
child identity matches
tenant boundary is valid
requested scope is contained
resource constraints are preserved
maximum chain depth is respected
no delegation cycle exists
The parent relationship must also have integrity.
A child process should not be able to submit:
parent_principal =
finance_admin_agent
delegated_scopes =
payments.admin
and have the platform trust that lineage.
Delegation should be issued or signed by trusted authorization infrastructure and referenced by an immutable identifier.
Revocation should flow down the delegation tree#
Suppose the chain is:
D1
|
+-- D2
|
+-- D3
If D1 is revoked, authority derived from D2 and D3 should normally disappear.
There are several ways to implement this. A delegation record can retain parent_delegation_id, the authorization service can maintain ancestry indexes, or a delegation version or epoch can invalidate a branch efficiently.
The exact implementation depends on scale.
The invariant matters more:
A child delegation cannot remain valid after the authority it depends on has disappeared.
Otherwise revoking the parent agent leaves old subagents operating with authority that no longer has a valid origin.
Tool binding is part of agent identity policy#
The identity system should also know which capabilities an agent is designed to request.
For reconciliation_agent:
allowed:
payments.lookup
payments.history
payments.refund
not allowed:
payments.change_bank_account
fraud.disable_controls
admin.create_user
For investigation_agent_v4:
allowed:
payments.lookup
payments.history
not allowed:
payments.refund
This is stronger than relying on the model prompt to say:
Do not issue refunds.
Tool binding is enforced outside the model.
A compromised or confused investigation agent can generate payments.refund as text. The infrastructure still refuses to expose or execute that capability.
Tool binding answers which actions the agent may request. It does not decide whether a particular refund is allowed. That decision belongs to runtime authorization.
Keep trusted execution context outside the model#
Some later authorization decisions may depend on what the agent has done.
For example:
external document read
untrusted instruction observed
payment write proposed
If the authorization system needs to know that untrusted input appeared earlier, the fact should be produced by infrastructure.
A tool gateway or context layer can classify an external result:
trust_class =
UNTRUSTED_EXTERNAL
The runtime can then maintain:
AuthorizationContext {
previous_tool_classes
untrusted_input_seen
writes_in_current_run
cumulative_value
risk_flags
}
The model does not get to set:
untrusted_input_seen = false
That would make the model part of its own security boundary.
These facts can later influence runtime authorization without sending an entire conversation transcript into the policy system.
Keep identity lifecycle independent from individual runs#
An agent identity may live for months while one run lasts minutes.
A delegation may live for fifteen minutes.
A workload credential may live for an hour.
These lifetimes should not be confused.
For example:
agent identity
months
agent version
days or weeks
workload credential
minutes or hours
delegation
minutes
run
minutes or hours
Disabling the logical agent should prevent future protected actions even if one workload process is still alive.
Deploying version 13 should not automatically invalidate version 12 unless policy says so.
Ending run R9831 should not delete the agent identity.
Keeping those lifecycles separate makes rollout and revocation much easier to reason about.
Keep an identity and delegation audit chain#
For protected actions, retain enough information to reconstruct how authority reached the executing workload.
IdentityEvidence {
user_subject
agent_principal
agent_version
workload_identity
delegation_chain
tenant
tool_binding_version
created_at
}
An investigation should be able to answer:
Who started the work?
Which agent received the task?
Which workload executed it?
Did another agent delegate the operation?
What authority was delegated?
Was the delegation still valid?
Which tool binding allowed the capability?
For the P9831 investigation, the chain might be:
U1841
|
| starts R9831
v
reconciliation_agent v12
|
| delegation D41
| payments.read
v
investigation_agent_v4
That chain should come from trusted identity and delegation state, not from reconstructing application logs after an incident.
Keep these invariants through implementation changes#
-
Every production agent has a managed identity separate from the human it acts for.
-
A running workload proves its binding to an agent identity through trusted infrastructure.
-
User identity, agent identity, agent version, and workload identity remain distinguishable.
-
Delegation preserves or reduces authority at every hop.
-
A child agent cannot manufacture authority that is absent from its parent delegation.
-
Resource constraints survive delegation unless policy explicitly narrows them further.
-
Revoking parent authority invalidates authority derived from it.
-
Tool binding limits which capabilities an agent may request and is enforced outside model reasoning.
-
Trusted execution context used for authorization is produced by infrastructure rather than self reported by the model.
-
Agent identity, workload credentials, delegations, and individual runs have independent lifecycles.
-
Every protected action can be traced back through the workload, agent, delegation chain, and originating human authority.
Keep reading
Continue with a guided sequence of free production engineering essays.
Find your next reading pathRelated reading
Continue this path
OpenAI Decisions API vs Jev: Designing Systems Around Decisions Instead of Generation
A technical comparison of OpenAI Decisions API and Jev, and how to design a decision layer for routing, tool selection, risk, abstention, calibration and AI agent control.
Read essayDesigning Production AI Systems with Jev
Jev is useful when an AI system needs fast bounded decisions around generative work. The production design still needs state construction, versioned questions, policy, calibration, observability, and clear boundaries around when to act, escalate, or collect more evidence.
Read essayA Skill Is Not a File. It Is a Deploy: Designing Agent Skills Infrastructure
Production Agent Skills are deployments, not files. They need immutable versions, controlled rollout, revocation, trust boundaries, progressive resolution, and a runtime path designed for scale.
Read essay