AI Agent Identity: Delegated Authorization Beyond OAuth 2.1

AI agent identity means giving the agent its own standing account, separate from both the human it works for and the workload it runs on. A service account is a shared, long-lived credential built for deterministic workloads, so it carries no consent record and no link to the person whose authority the agent is using. Microsoft's own agent identity documentation says identity models designed for human users and applications prove insufficient for agents, which are often created dynamically and destroyed at high volume.
Split the single actor field into two: the authority, meaning the human whose delegated rights made the action valid, and the actor, meaning the software that actually performed the mutation. Storing only the human is a false statement in an immutable log, and storing only the agent leaves the row with no traceable authority. In practice you want the actor id, the on-behalf-of subject, the delegation chain, a consent reference and the token jti in the same audit row.
The data model does. OAuth 2.0 Token Exchange, RFC 8693, has been Standards Track since January 2020 and defines the nested act claim for delegation plus the may_act claim for stating in advance that one party may become the actor for another. What it does not define is a front-channel consent screen naming a specific agent, or a way for the agent to prove at the token endpoint that it is the actor the user consented to.
No. Two individual submissions are circulating on the IETF datatracker, neither with a working group behind it. draft-oauth-ai-agents-on-behalf-of-user reached revision 02 and expired on 27 February 2026, while draft-araut-oauth-transaction-tokens-for-agents is active at revision 02 and expires on 23 November 2026. Anything you build on those parameters today is a bet, so model your internal delegation on RFC 8693 instead.
Entra Agent ID gives each agent a special service principal that holds no credentials of its own. Agents are created from a reusable agent identity blueprint, and the blueprint holds the federated identity credentials, certificates or client secrets and acquires tokens on behalf of each agent identity. When the agent acts for a person, the subject of the token is the user while the actor is the agent identity, which is RFC 8693 delegation semantics inside a vendor product.

Key Takeaway
An AI agent needs its own standing identity plus a per-request delegation token naming the human whose authority it borrowed. OAuth 2.0 Token Exchange, RFC 8693, already models this with the nested act claim, but no agent-specific extension has been standardised, so every audit trail design in 2026 is a bet on one vendor.
Take a question any ERP tenant will eventually ask: can an agent clear purchase orders below a finance manager's signing limit while she is away for two weeks. The permission half takes a minute to answer, because capping an amount and a document type is what an authorization policy is for. The field that stops you is approverId, which holds exactly one string, and which the audit log treats as the answer to who approved this.
I maintain hierarchical-approval, an npm library that runs multi-level approval chains and appends an immutable audit entry with an old and new state diff on every mutation. This post is about what that one field has to become when the approver is an agent, what OAuth already standardised for it in 2020, and why the extension everyone links to has expired. I have read the specs closely; I have not shipped an agent approver to production, and the honest state of this in 2026 is that nobody has a settled answer.
The question who approved this purchase order splits in two the moment an agent is involved, and a single actor column can only answer one half. Compliance wants the authority: which human being's delegated signing power made this approval valid. Security wants the actor: which piece of software performed the mutation, so it can be revoked, rate-limited or investigated. A human approver collapses both into the same id, which is why the schema got away with one field for years.
// hierarchical-approval: the call that closes an approval level.
await engine.approve(instance.id, { approverId: 'mgr-1' });
// The audit entry it appends names ONE actor. Now an agent runs
// that approval while mgr-1 is on leave. Both fills are wrong.
// Wrong: name the human. The log asserts that a person reviewed
// a document she never opened, and revocation cannot target the
// agent without disabling the manager.
{ approverId: 'mgr-1' }
// Wrong: name the agent. The signing limit that makes this
// approval valid is attached to mgr-1, and no agent id inherits
// it. The row no longer proves anyone had the authority.
{ approverId: 'svc-approval-bot' }Neither fill above survives an audit conversation. Naming the human is a factual misstatement inside an immutable log, and immutability is the entire point of that log. Naming the agent leaves the row with no traceable authority, because signing limits in every ERP I have worked on hang off an org chart of people. The fix is not a better value for the field. It is a second field.
Both existing identity constructs were designed for something an agent is not, and each fails in a different direction. A service account is a shared, long-lived credential for a deterministic workload; an agent is created for a task and may not exist tomorrow. Microsoft's agent identity documentation is blunt that identity models designed for human users and applications prove insufficient here, and describes agents as created dynamically and sometimes destroyed thousands of times per day. The specific failures that matter to an approval log:
This is the part the permission literature keeps skipping. Cerbos, OpenFGA and every policy engine I have written about answer what the caller may do, and they take the caller's identity as given. When the caller is an agent running on someone's behalf, the identity is the unsolved part, and no amount of policy expressiveness repairs an actor field that was already ambiguous when the policy engine received it.
OAuth 2.0 Token Exchange, RFC 8693, has been a Standards Track RFC since January 2020, and it already models this exactly. It draws the distinction an ERP audit needs: with delegation semantics, principal A still has its own identity separate from B, and it is explicitly understood that while B may have delegated some of its rights to A, any actions taken are being taken by A representing B. Impersonation, by contrast, makes A indistinguishable from B inside that rights context. The delegation chain is a nested act claim.
// RFC 8693 section 4.1: delegation is a NESTED "act" claim.
// Read it outside-in. "sub" is whose authority is being used;
// each "act" is a party that acted, most recent first.
{
"iss": "https://sso.example.co.id",
"aud": "https://erp.example.co.id",
"sub": "mgr-1",
"act": {
"sub": "agent://approvals/finance-v1",
"act": {
"sub": "https://orchestrator.example.co.id"
}
}
}
// Section 4.4 covers the permission side. "may_act" states, in
// advance, that one party is allowed to BECOME the actor for
// another. Put it on the user's own token and you have a
// delegation grant you can revoke without deleting the agent.
{
"sub": "mgr-1",
"may_act": { "sub": "agent://approvals/finance-v1" }
}So the token format has not been the missing piece for six years. What RFC 8693 does not provide is a front-channel moment where the user sees which agent is asking and consents to that agent specifically, plus a way for the agent to prove at the token endpoint that it is the actor named in that consent. Token exchange happens back-channel, between services. Closing that gap is what the 2026 drafts are for.
Two individual submissions are circulating, and both reuse sub and act rather than inventing a new shape. draft-oauth-ai-agents-on-behalf-of-user, from authors at WSO2, adds requested_actor to the authorization request, which must uniquely identify the actor and be understood by the authorization server, and actor_token to the token request, whose sub claim must identify the agent. draft-araut-oauth-transaction-tokens-for-agents, from an author at Amazon, takes the other half of the problem: propagating agent identity through a call graph, holding sub and act immutable for the whole transaction and adding an agentic_ctx claim that carries current_actor, originator and a hop count.
# draft-oauth-ai-agents-on-behalf-of-user-02
# requested_actor rides on the AUTHORIZATION request, so the
# consent screen can name the agent instead of only the app.
GET /authorize?response_type=code
&client_id=s6BhdRkqt3
&scope=purchase_order.approve
&requested_actor=actor-finance-v1
&redirect_uri=https%3A%2F%2Fapp.example.co.id%2Fcb
# actor_token rides on the TOKEN request, so the agent proves it
# is the actor the user consented to. The draft requires its sub
# claim to identify the agent.
POST /token
grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&actor_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
# The access token the draft shows coming back:
{ "sub": "user-456", "azp": "s6BhdRkqt3", "act": { "sub": "actor-finance-v1" } }
# draft-araut-oauth-transaction-tokens-for-agents-02 keeps sub and
# act immutable for the whole transaction and adds agentic_ctx for
# the parts that change as the call graph deepens:
{
"sub": "user:[email protected]",
"act": { "sub": "agent-identity-1" },
"agentic_ctx": {
"current_actor": "agent-identity-1",
"originator": "agent-identity-1",
"chain_metadata": { "hop_count": 1 }
}
}Read the status line before building on either. Both are individual submissions with no IETF stream and no working group behind them. The on-behalf-of draft sits at revision 02 and expired on 27 February 2026. The transaction tokens draft sits at revision 02, is active, and expires on 23 November 2026 — and it is itself a rename of an earlier draft that reached revision 06 under a different filename. That is what an unsettled area looks like on the datatracker, and it is worth saying plainly instead of writing about these parameters as though they had landed.
Do not wire requested_actor or agentic_ctx into a production authorization server on the strength of a draft. Both can change parameter names or vanish at the next revision, and one has already lapsed once. Model your internal delegation on RFC 8693's act and may_act, which are Standards Track and will not move, then treat the draft parameters as a wire format you may have to adapt to.
Four delegation patterns are running in production somewhere in 2026, and they are not variations on a theme. They put different values in the token subject, so they support different audit claims and fail in different places. Choosing one is a bet on where the industry lands.

| Pattern | Token subject and actor | What the audit row can prove | Where it breaks |
|---|---|---|---|
| User-delegated | sub is the user, act is the agent | Both the authority and the actor, from one token | Needs an interactive consent moment, so a scheduled overnight run has nothing to attach itself to |
| Autonomous | sub is the agent, no act claim | What acted, and nothing about whose authority | Approval limits hang off people, so the agent needs a limit invented for it and governed separately |
| Hybrid orchestrated | sub is the user, act nests once per hop | The whole chain, if the log can store an array | Most audit schemas have one actor column, and hop depth is unbounded unless policy caps it |
| Scoped impersonation | sub is the user, no act, scopes narrowed | Nothing about the agent at all | The row is indistinguishable from the human's, so the agent cannot be revoked or investigated alone |
The row I would have to defend to an auditor is the third one, and it has the least tooling behind it. A single actor column cannot store a chain of unknown depth, so either the log grows a JSON column or the depth gets capped in policy. I would cap it at two hops, because two is the most I can explain in a meeting. That is not a technical argument, but it is the argument that decides it.
Microsoft Entra Agent ID is the most complete implementation I have read, and it follows neither draft. An agent identity there is a special service principal, and the detail that matters is that it holds no credentials of its own. It is created from a reusable template called an agent identity blueprint, and the blueprint is what holds the federated identity credentials, certificates or client secrets, then acquires tokens on behalf of each agent identity it created.
The claim shape lands where RFC 8693 put it. The documentation describes three token cases: the agent requests tokens whose subject is the agent identity, receives tokens whose audience is the agent identity, and requests user tokens where the subject of the token is a user while the actor is the agent identity. Each agent identity also carries a sponsor, recording the human user or group accountable for it, which is an org-chart answer to accountability sitting alongside the per-request delegation. Agent identities are single-tenant, though a blueprint can be multitenant and create tenant-local identities elsewhere.
Two consequences follow from that credential model, and both are my reading rather than the documentation's words. First, the blueprint is the object an attacker wants, because it holds the credentials for every agent identity beneath it, which turns the number of blueprints into a security boundary decision rather than a tidiness one. Second, this is a vendor model, not an interoperable one: an agent identity issued in one Entra tenant means nothing to a resource server that does not federate with it. Microsoft states that Agent ID is available for all Microsoft Entra customers, while extending the wider Entra security features to agents requires the separate Agent 365 licence, so the bet is commercial as well as architectural.
An agent identity in a directory says which agent this is. It does not say which running process currently holds the credential, and in a container fleet those are separate questions. SPIFFE is the mature answer to the second one. A workload gets a SPIFFE ID shaped as a URI, spiffe://acme.com/billing/payments, where the first component is a trust domain acting as the cryptographic root of trust and the rest identifies the workload. It proves that ID with an SVID: an X.509-SVID carrying the ID and a short-lived private key, or a JWT-SVID, which the SPIFFE documentation itself flags as carrying replay risk.
The property worth stealing is how the workload obtains it. The Workload API does not require that a calling workload have any knowledge of its own identity, or possess any authentication token when calling the API — identity comes from attestation of what the platform can observe about the process, not from a secret shipped next to it. That is the right posture for an agent, which is exactly the sort of short-lived process you do not want holding a long-lived key. Three systems, then, answer three questions: SPIFFE says which process, the agent identity says which agent, and the delegation token says on whose authority. An ERP audit row needs all three, and today they arrive from three different places.
Record the token's jti and the consent grant id in the audit row, not only the actor ids. Ids get reused when an agent is recreated from the same blueprint, and consent is revocable, so a year later the grant record may be the only artefact that can prove the delegation existed at the moment of approval.
Here is what I would put into an ERP approval log today, given that nothing is standardised. It is deliberately boring: six flat fields, all derivable from a token that follows RFC 8693, none of them depending on a draft parameter that may not survive.

// One approval row, six fields instead of one.
type AgentApprovalAudit = {
actorType: 'human' | 'agent';
actorId: string; // agent://approvals/finance-v1
onBehalfOf: string | null; // mgr-1; null is legal only for 'human'
delegationChain: string[]; // flattened from the token's nested act
consentRef: string; // the grant this delegation was made under
tokenJti: string; // the exact credential, checkable a year later
};
// hierarchical-approval already exposes the refusal point, so the
// rule lives in front of the log rather than in a reviewer's head.
const engine = new ApprovalEngine({
adapter: pgAdapter,
tenantId: 'acme',
authorizationPolicy: {
authorize: async (ctx) => {
const d = delegationFor(ctx.actorId); // parsed from the bearer token
if (d.actorType === 'agent' && !d.onBehalfOf)
return 'Agent actor with no delegation subject';
if (d.actorType === 'agent' && d.delegationChain.length > 2)
return 'Delegation chain too deep to approve';
if (d.actorType === 'agent' && !d.consentRef)
return 'No consent grant recorded for this agent';
},
},
});The refusal matters more than the schema. An agent action with no delegation subject is not an approval, it is an unattributed mutation, and letting it reach an immutable log means it is wrong permanently. hierarchical-approval already exposes an authorizationPolicy hook that runs before the mutation, so the check sits in front of the write rather than in a quarterly review.
hierarchical-approval is my open-source npm library for multi-level ERP approval chains, with an immutable audit log, an authorization policy hook and pluggable audit adapters.
npmjs.com/package/hierarchical-approvalThe rule worth carrying: never let an agent's action produce a log row with one actor in it. Store the actor and the authority as separate fields from the first migration, even while you are still using a plain service account, because retrofitting a second actor column into an immutable audit table is the expensive version of this problem. The standards are unsettled and will keep moving. The shape of the row is already knowable.
Sources and further reading