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.

Diagram showing the three identities involved in one production AI agent action: human user U1841, the logical reconciliation agent version 12, and the running workload agent-pod-a81f. The workload proves its binding to the agent through trusted infrastructure before the combined identity is passed into authorization.

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.

Diagram showing authority narrowing from user U1841 to reconciliation_agent_v12 and then through delegation D41 to investigation_agent_v4. The child receives only payments.read for payment P9831, while payments.refund is rejected because delegated authority is calculated by intersection rather than union.

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#

  1. Every production agent has a managed identity separate from the human it acts for.

  2. A running workload proves its binding to an agent identity through trusted infrastructure.

  3. User identity, agent identity, agent version, and workload identity remain distinguishable.

  4. Delegation preserves or reduces authority at every hop.

  5. A child agent cannot manufacture authority that is absent from its parent delegation.

  6. Resource constraints survive delegation unless policy explicitly narrows them further.

  7. Revoking parent authority invalidates authority derived from it.

  8. Tool binding limits which capabilities an agent may request and is enforced outside model reasoning.

  9. Trusted execution context used for authorization is produced by infrastructure rather than self reported by the model.

  10. Agent identity, workload credentials, delegations, and individual runs have independent lifecycles.

  11. Every protected action can be traced back through the workload, agent, delegation chain, and originating human authority.