| Internet-Draft | Agent Delegation Chain | October 2026 |
| Asor | Expires 18 April 2027 | [Page] |
AI agents increasingly delegate tasks to other agents. Each delegation should convey only a subset of the delegating party's authority, that subset should be bounded in scope, magnitude, and time, and any enforcement point should be able to verify -- offline, with no call to an authorization server -- that a token presented at hop N carries authority no greater than the token at hop N-1, back to a trusted root, and that every hop acts under the same accountable principal. OAuth 2.0 Token Exchange (RFC 8693) models two-party delegation and records prior actors in a nested "act" claim, but that claim is informational only and cannot enforce attenuation across a chain of depth two or more. This document defines the Agent Delegation Chain: a profile of OAuth 2.0 JWT access tokens (RFC 9068) that carries authority as Rich Authorization Requests (RFC 9396), binds the chain to one principal and names the agent acting at each hop, links each delegation to its parent by a cryptographic byte-commitment, and specifies a deterministic offline verification algorithm that enforces monotonic attenuation, bounded depth, monotonic expiry, and principal invariance. It reuses existing JOSE, proof-of-possession (RFC 9449), and status-list machinery (the OAuth Status List draft) and introduces no new cryptography.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 18 April 2027.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
An AI agent that receives a task from a user or another agent frequently decomposes it and delegates sub-tasks to further agents or tools. In production deployments this produces delegation chains of depth greater than two. The security requirement is a single invariant: the authority exercised at any hop MUST be a subset of the authority granted at the hop that delegated to it, and this MUST be verifiable by the enforcement point that ultimately honors a tool call, without that point contacting an authorization server.¶
OAuth 2.0 Token Exchange [RFC8693] defines delegation and impersonation semantics and a nestable "act" claim. However, [RFC8693] Section 4.1 states that a recipient considers only the top-level claims and the current (outermost) actor; the nested chain is history for audit, not enforced authority. Token Exchange also normally contacts the authorization server at each hop. Neither property supports offline, enforced, depth >= 2 attenuation.¶
Cross-domain identity chaining (see [I-D.ietf-oauth-identity-chaining]) and transaction tokens address related but distinct problems (crossing trust domains and propagating immutable context within one domain, respectively) and explicitly do not provide chained cryptographic attenuation.¶
Capability systems that do provide offline attenuation -- macaroons [Macaroons], Biscuit [Biscuit] -- either verify with a shared secret (macaroons: symmetric HMAC, so every verifier holds the minting key) or use a non-IETF wire format (Biscuit: protocol buffers with an embedded Datalog engine). The historical standards-track ancestor is SDSI/SPKI [RFC2693].¶
This document fills the gap by reusing the JOSE/OAuth stack and adding only the chain linkage, the principal binding, and the subsumption-enforcing verification algorithm. It is designed as the attenuation mechanism satisfying requirements R1 and R5 of [I-D.reece-wimse-cross-org-delegation] and as a companion to [I-D.ietf-wimse-aims], whose placement of the acting agent in "client_id" and the on-behalf-of party in "sub" this profile follows. It shares its approach with, and is intended to converge with, [I-D.niyikiza-oauth-attenuating-agent-tokens] and [I-D.coetzee-oauth-spt-txn-tokens]. Its offline verification model is complementary to the online Delegation Server and synchronous revocation model in [I-D.sweeney-wimse-credential-delegation]. The two are halves of one design, separated by a single axis: whether a server is in the path. The in-token mechanisms this document requires -- constraints carried in the token, parent-hash verification, and a child expiry bounded by its parent's -- are how a verifier with no network reconstructs what such a server would otherwise know first-hand; where a server is in every hop, they are redundant rather than absent. The verifier-side evaluation rules of [I-D.jackson-wimse-evaluation] describe the receiving half of the same problem; this document cites that work informatively, and no requirement here depends on it.¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
An OAuth 2.0 JWT access token [RFC9068] profiled by this document, carrying the authority granted to one agent at one hop of a chain.¶
An ordered sequence of Delegation Tokens DT_0, DT_1, ... DT_n, where DT_0 is the root, and each DT_i (i > 0) is linked to DT_{i-1} by the mechanism in Section 5. A chain is one path. An agent that holds tokens issued along several paths holds several chains; see Section 6.3.¶
The party accountable for the chain: the party whose grant every token in the chain acts under. Carried in "sub", identical at every hop (Section 3).¶
The agent that holds, and acts under, one Delegation Token. Carried in "client_id", which names the agent acting at each hop.¶
The set of permitted scope values carried by an "agent_delegation" authorization detail in the "authorization_details" claim [RFC9396], together with the constraints in Section 4.2.¶
The construction of a child Delegation Token whose Authority is a subset of its parent's, per the subsumption rules in Section 4.3.¶
The component that verifies a Delegation Chain and permits or denies an action.¶
Any component that runs the algorithm of Section 6, whether or not it is presented with an action. An Enforcement Point is a Verifier that is presented with an action and decides it.¶
A Delegation Token is a JWT [RFC7519] signed with JWS [RFC7515] using a fully-specified algorithm [RFC9864]. Implementations MUST support Ed25519 [RFC8032] [RFC8037] and MAY support ES256 and, for post-quantum readiness, ML-DSA [RFC9964]. The token uses the "application/at+jwt" header type of [RFC9068] and includes the claims that profile requires: "iss", "exp", "aud", "sub", "client_id", "iat", and "jti". The protected header SHOULD contain "c14n": "JCS" as an informational label. Verifiers MUST NOT rely on this field. The canonicalization of the protected header and payload JSON is the JSON Canonicalization Scheme (JCS) [RFC8785]: producers MUST serialize both with JCS before base64url encoding, and verifiers MUST reject either decoded byte string unless it is exactly the JCS serialization of the parsed object. Verifiers MUST also reject duplicate object member names, non-finite numbers, and lone UTF-16 surrogates. Because JCS numbers are IEEE 754 binary64 values, an integer whose magnitude exceeds 2^53-1 is not represented exactly; producers MUST NOT emit one, and verifiers MUST reject a token carrying one as malformed.¶
The [RFC9068] claims carry the following meaning in a Delegation Token:¶
REQUIRED. The Principal. [RFC9068] Section 2.2 makes "sub" the resource owner where one exists; in a Delegation Chain the resource owner is the party whose grant the chain acts under, and the same value MUST appear in every DT_i of the chain. A verifier denies a chain in which any DT_i carries a "sub" that differs from DT_0's (Section 6, step 3). A chain whose DT_0 carries no "sub", or a "sub" that is not a non-empty string, is malformed.¶
REQUIRED. The Acting Agent at this hop, as a string. It identifies which software exercised the authority; "sub" identifies whose grant it exercised. The value MAY be a workload identifier as defined by [I-D.ietf-wimse-identifier]; this document places no requirement on its format beyond being a string.¶
REQUIRED. The audience, as [RFC7519] Section 4.1.3 defines it: a string, or an array of one or more strings. A token whose "aud" is absent, null, or an empty array is malformed. The Enforcement Point applies the audience check of [RFC9068] Section 4 to DT_n (Section 6, step 8); this document places no requirement on the audience of DT_i for i < n beyond presence and shape.¶
REQUIRED, as in [RFC9068]. "exp" is additionally subject to the monotonic expiry rule of Section 4.3. "jti" identifies the token instance for the purposes of Section 5 and of per-lifetime constraints (Section 4.2).¶
In addition, a Delegation Token contains:¶
REQUIRED. An array of authorization detail objects [RFC9396] expressing the Authority. See Section 4.¶
REQUIRED. A confirmation claim [RFC7800] binding the token to the holder's key, proven per DPoP [RFC9449]. See Section 7.¶
REQUIRED. A non-negative integer; the position of this token in the chain. DT_0 has del_depth 0.¶
REQUIRED in DT_0. A positive integer; the maximum permitted chain length. MUST NOT be increased by any child (see Section 4.3).¶
REQUIRED in every DT_i with i > 0; MUST be absent in DT_0. The base64url-encoded SHA-256 digest of the parent token's JWS Signing Input ([RFC7515] Section 5.1), i.e. of the exact bytes "ASCII(BASE64URL(parent JOSE Header)) || '.' || ASCII(BASE64URL(parent JWS Payload))". This is the byte-commitment that binds a child to one specific parent and prevents chain splicing.¶
A Delegation Chain answers two questions that a holder key alone does not: who is accountable for what the chain does, and which agents took part. The first is answered by "sub", which names the Principal and is invariant along the chain: a child token is issued under the same grant as its parent, so it names the same Principal, and a token that names a different one is not a member of the chain. The second is answered by "client_id", which names the agent acting at each hop. Together they make a chain auditable as well as verifiable.¶
The "par_hash" commitment (Section 5) already prevents a child from being re-parented onto a different token. It does not, by itself, prevent a child from carrying a different "sub" than its parent, because the child is a new token with its own payload. The invariance check in step 3 of Section 6 is what closes that gap, and it is a comparison of two strings, not new machinery.¶
This revision defines no transition mode for chains shaped by the previous revision. A chain whose tokens carry no "client_id" or no "cnf", or whose "aud" is null, is malformed under this revision. A verifier that also accepts such chains under the previous revision's rules does so outside this document and reports which revision's rules it applied.¶
Each child token commits to its parent by "par_hash" (Section 3). This binds the child to the parent's exact serialized bytes, so a child cannot be re-parented onto a different (e.g. broader) token: doing so changes the parent's Signing Input and thus its SHA-256 digest, which no longer matches the child's "par_hash". The digest is over the already-serialized JWS Signing Input; the protected header and payload that form that input are JCS [RFC8785] as required by Section 3.¶
The commitment is to a parent token instance, not to a parent identity. A child's chain carries the parent instance the child commits to. When a parent is re-issued -- for example refreshed before expiry with a new "jti", "iat", and "exp" -- the re-issued token is a different token, outside the chain of any child issued under the previous instance. Re-issuing a parent therefore does not invalidate its existing children: they continue to verify with the instance they were issued under until that instance expires or a token in their chain is revoked (Section 8), and a child presented together with a re-issued parent instead of the instance it commits to is denied at step 2 of Section 6. The operational consequence for the refresh rate is in Section 9.5. A parent identity that survives re-issuance is not defined by this document.¶
An Enforcement Point presented with a Delegation Chain DT_0 ... DT_n and an attempted action A, and holding the trusted root public key(s), MUST perform the following, denying on the first failure:¶
Verify the JWS signature of every DT_i using a fully-specified algorithm [RFC9864]. The verifier MUST hold a local list of accepted algorithms, and MUST deny the chain if the "alg" of any DT_i is not on that list, even where the signature verifies. The verifier MUST take the algorithm from its own configuration for the key, and MUST deny if the "alg" header names a different one. DT_0 MUST verify under a trusted root key.¶
For each i > 0, compute SHA-256 of DT_{i-1}'s JWS Signing Input and compare, in constant time, to DT_i's "par_hash". Any mismatch: deny.¶
Check chain shape, depth first. Depth: DT_0.del_depth == 0; DT_i.del_depth == i; n < DT_0.del_max_depth. Then principal: DT_0.sub is a non-empty string, and for every i, DT_i.sub is identical to DT_0.sub. Otherwise deny.¶
For each i > 0, verify DT_i.Authority <= DT_{i-1}.Authority under rules 1, 2, 3, and 5 of Section 4.3. A token carrying a second detail, or a detail of a type other than "agent_delegation", was rejected as malformed when it was parsed (Section 4.4). Any violation: deny.¶
Check time: for every i, nbf (if present) <= now <= exp, and exp is monotonic non-increasing along the chain (rule 4 of Section 4.3). Otherwise deny.¶
Verify holder binding: the presenter proves possession of the key in DT_n.cnf via a valid DPoP proof [RFC9449] bound to this request. Otherwise deny.¶
Check revocation: consult the Token Status List [I-D.ietf-oauth-status-list] reference in each DT_i (if present) against a cached list. A cached status answer is trusted for its time to live, so the window in which a revoked token is still honored is that time to live and no longer. A status reference that is present but whose list cannot be fetched, or whose cached answer has outlived its time to live and cannot be refreshed, is UNKNOWN, and the verifier MUST deny: unknown is not not-revoked. If any DT_i is revoked, deny (and, by local policy, treat the whole subtree as revoked). A token issued with no status reference is a choice made at issuance (Section 8) and is not denied on that account.¶
Authorize A against DT_n: confirm that the Enforcement Point is identified by DT_n's "aud" as [RFC9068] Section 4 requires; then check A against DT_n.Authority: its scope, every per-action constraint ("max", "min", "one_of", "not_one_of", "prefix", "rank"), and every cumulative constraint ("max_lifetime", "max_subtree"). A cumulative constraint is checked where the running total it is measured over is held: by the Enforcement Point itself, or by a component that holds that total and authorizes A on the Enforcement Point's behalf (Section 9.4). The total reaches the check through a channel separate from the request's own parameters, never from the request: a cumulative constraint is evaluated against that total only, and a per-action constraint against the request only, so a name the two share cannot let one stand in for the other. An Enforcement Point that neither holds the running total for a cumulative constraint DT_n carries nor has A authorized by a component that does MUST deny. Permit only if A is within DT_n.Authority on every count.¶
The algorithm is deterministic, side-effect free, and requires no network call except the (cacheable, offline-checkable) status list of step 7. The one exception is the cumulative-constraint check of step 8, which belongs to the component holding the running total and lies outside that guarantee. The order of steps 1 to 5 is the order in which a chain presented without an action can be checked (Section 6.1); steps 6 and 8 need the request, and step 7 needs the status list.¶
A Verifier is not always an Enforcement Point. An auditor re-checking a recorded chain, an issuer validating a parent before minting a child, or a component that holds a chain but no request, can run steps 1 to 5 and, if a status list is available, step 7. Table 1 states what such a Verifier may and may not conclude.¶
| Property | Chain alone (steps 1-5, 7) | With the request (steps 6, 8) |
|---|---|---|
| Every token is well-formed, JCS-canonical, and signed under an accepted algorithm | yes | yes |
| DT_0 is signed under a trusted root key | yes | yes |
| Each child commits to the parent instance presented with it | yes | yes |
| Depth is bounded and the Principal is unchanged along the chain | yes | yes |
| Each hop's Authority is a subset of its parent's | yes | yes |
| Every token is within its validity window at the instant checked | yes | yes |
| No token in the chain is revoked, within the status list's time to live | yes, if the list is reachable | yes, if the list is reachable |
| The presenter holds the key DT_n is bound to | no | yes |
| A specific action is permitted | no | yes |
| A per-lifetime total is within bound | no | only where the total is held; otherwise step 8 denies |
| A per-subtree total is within bound | no | only where the total is held (Section 9.4); otherwise step 8 denies |
The "subset" row holds for the constraint types the Verifier implements. A constraint type it does not implement fails closed under the rule in Section 4.3, so it can turn a "yes" into a "no" and never the reverse.¶
A Verifier that has not performed step 6 has established that the chain is valid, not that the party presenting it is its holder, and MUST NOT report the chain as authorizing anything. A Verifier that has not performed step 8 has established the leaf Authority as a set and MUST NOT report any action as permitted. Such a Verifier SHOULD report its result as the chain's validity together with the steps it did not perform.¶
Independent implementations are compared by their outcomes on shared test vectors (Appendix B), and the comparison is only as good as the agreement on how a denial is named. A verifier SHOULD report a denial under the name that Table 2 gives for the check that failed, which is the vocabulary the test vectors declare; since the algorithm denies on the first failure, one denial carries one name. A verifier MAY add detail of its own beside the name. The names are not carried in any token and have no effect on the wire.¶
| Name | Step | Meaning |
|---|---|---|
| malformed | parse | a token is not three base64url parts, a claim this document requires is absent or of the wrong shape, an integer's magnitude exceeds 2^53-1, a scope is invalid, a constraint is malformed or repeated, a detail carries an unknown member, a token carries more than one detail, or a detail type is not "agent_delegation" |
| non_canonical | parse | a decoded header or payload is not the JCS serialization of what it parses to |
| duplicate_member | parse | a JSON object repeats a member name |
| non_finite | parse | a number is NaN or an infinity, which JSON and JCS do not represent |
| signature_invalid | 1 | a signature does not verify, the algorithm is not on the verifier's list, the algorithm differs from the one configured for the key, or DT_0 is not under a trusted root key |
| par_hash_mismatch | 2 | a child's commitment does not match the parent presented with it |
| depth_invalid | 3 | a del_depth is out of sequence or the chain exceeds del_max_depth |
| principal_altered | 3 | a token's "sub" differs from DT_0's |
| not_narrower | 4 | a child's Authority is not subsumed by its parent's under rules 1, 2, 3, or 5 |
| expired | 5 | a token is outside its validity window, or its expiry exceeds its parent's (rule 4) |
| holder_binding_failed | 6 | the proof of possession is absent or invalid for this request |
| revoked | 7 | a token in the chain is revoked |
| status_unknown | 7 | a status reference is present and its current status cannot be established |
| audience_mismatch | 8 | DT_n's "aud" does not identify the Enforcement Point |
| scope_not_granted | 8 | the action's scope is not covered by DT_n's scopes |
| ceiling_exceeded | 8 | the action violates a constraint of DT_n |
| unknown_constraint | 8 | DT_n carries a constraint of a type the verifier does not implement |
This document defines the verification of one chain. An agent may hold Delegation Tokens issued along more than one path from one or more roots, and so hold more than one chain. For each request the presenter presents one chain, the Enforcement Point verifies that chain alone, and the action is permitted only if that chain permits it. An Enforcement Point MUST NOT combine the Authority of tokens from different chains, and a chain that fails verification has no effect on the evaluation of another chain the same agent presents. A presenter holding several valid chains chooses which to present; one valid chain that permits the action suffices. [I-D.jackson-wimse-evaluation] treats the same situation from the verifier's side, in its rule that each delegation path is evaluated independently and fails closed on its own; this document cites that rule informatively and does not depend on it.¶
Step 1 assumes the verifier already holds the trusted root key or keys. This document says nothing about how a verifier comes to hold that key, how the key is bound to the party it is trusted as, or under which algorithm that binding was made; the algorithm rule of step 1 reaches every token in the chain and stops at the root key. A chain can be post-quantum at every hop and still rest on a root key whose provenance the verifier cannot check. That is a property of every delegation chain design, not of this one, and the author's view is that it belongs to the working group rather than to this document. It is recorded here as open and is not solved here.¶
Every Delegation Token is sender-constrained by a "cnf" claim [RFC7800] carrying the JWK thumbprint of the holder's key, proven per request with DPoP [RFC9449]. mTLS-bound tokens [RFC8705] MAY be used where the transport is controlled end-to-end. A captured token is therefore unusable without its bound private key, which mitigates replay of intermediate tokens. The "cnf" claim is present in every token of a chain, and a Verifier that sees no request (Section 6.1) checks its presence and shape but cannot perform step 6.¶
Delegation Tokens are RECOMMENDED to be short-lived (seconds to minutes for leaf and execution tokens), so that expiry is the common revocation path. For earlier revocation of longer-lived delegations, each token MAY carry a Token Status List [I-D.ietf-oauth-status-list] reference; revoking a token's status entry, and by local policy the entries of its descendants, revokes a delegation or a whole sub-chain while preserving offline verification (the status list is itself cacheable and offline-checkable). An online revocation endpoint [RFC7009] MAY additionally be offered.¶
Whether a token carries a status reference is decided at issuance. A token issued without one can be revoked only by expiry. A token issued with one is subject to step 7 of Section 6 as stated there: a cached answer is trusted for its time to live and no longer, and an answer that cannot be obtained or refreshed is unknown, which denies. A deployment chooses between the two at issuance with that consequence in view.¶
Short lifetimes mean re-issuance. A parent that is re-issued is a new token instance (Section 5); its existing children are not invalidated by the re-issuance, and a deployment that wants them invalidated revokes the previous instance. The rate at which this happens is an operational property that Section 9.5 states.¶
Deployments that require synchronous cascading revocation MAY issue and exercise Delegation Tokens through an online Delegation Server as specified by [I-D.sweeney-wimse-credential-delegation]. The server can evaluate current delegation-tree state and refuse a revoked root or descendant immediately. This online bridge complements, and does not replace, the offline verification algorithm in Section 6: an Enforcement Point that receives a chain MUST still verify its signatures, linkage, attenuation, depth, principal, and expiry.¶
Attenuation produces a new child token but does not, by itself, invalidate the parent. A party holding the parent still holds the parent's (broader) authority. This is mitigated by three mechanisms that MUST be considered together: (a) tokens are short-lived and holder-bound (Section 7), so a leaked parent is both time-boxed and non-replayable without its key; (b) the "par_hash" byte-commitment (Section 5) prevents splicing a child onto a different parent; and (c) status-list revocation (Section 8) allows early invalidation of a parent and its subtree. Deployments that require immediate parent invalidation on delegation MUST use short TTLs and status lists accordingly.¶
Without the byte-commitment, an attacker could present a valid child together with a broader token as its purported parent. Step 2 of Section 6 prevents this: the child's "par_hash" digests the parent's exact Signing Input, so only the intended parent verifies. The same check is what denies a child presented with a re-issued parent rather than the instance it was issued under (Section 5).¶
Because each hop's authority is the meet of parent and request (Section 4.3), a child cannot be induced (e.g. by prompt injection) to exercise authority the parent lacked. The principal invariance check (step 3 of Section 6) adds that a child cannot be induced to act under a different grant than its parent: a token that names another Principal is not a member of the chain.¶
"del_max_depth" (checked in step 3) bounds chain length; deployments SHOULD set it low (e.g. 5). Fan-out (a parent delegating to many children) is not limited by the token format and MUST be bounded by the issuing infrastructure if required.¶
Quantity constraints compose across siblings, and subsumption is checked per edge. A "max" constraint bounds one authorized action and nothing else (Section 4.2). A parent holding {"key": "max_spend", "max": 100} can mint any number of children each carrying max_spend 100; every parent-child edge satisfies C <= P, and a subtree of N children can spend N x 100. The same holds at a single enforcement point and across several: against a root ceiling of 100, two valid children can each authorize 60 at two different gateways; both chains verify, each gateway sees a request within bound, and the combined spend is 120. Individually permitted delegations compose into an outcome nobody permitted. The "max_subtree" type names that total, and a chain-only verifier checks only that a child's "max_subtree" is no wider than its parent's; verification is side-effect free by design, so a signed ceiling names a limit while nothing in verifying it reserves against that limit or sees another gateway's balance. A signed ceiling alone provides none of the protection an aggregate budget needs.¶
An aggregate budget over a consequential resource therefore needs machinery outside the token, and that machinery has two separable parts. Allocation -- deciding how much of a parent's budget each child may consume -- requires coordination: an allocator that splits a budget at delegation time only keeps the total under bound if it is a single writer, since two issuers allocating offline from the same budget can each allocate the same units. Spending afterwards does not require coordination once allocations are disjoint: each enforcement point debits its own durable balance for the allocation it holds and contacts no shared service per action. Two deployment shapes follow, and a deployment that enforces aggregate budgets uses one of them:¶
Shared accounting: one ledger, consulted by every enforcement point, that checks and debits atomically at authorization time. A ledger that is read at authorization and debited afterwards, or debited by more than one writer without coordination, does not close the hole; the check and the debit are one operation or the budget is not enforced. An online Delegation Server through which every action is exercised, as in [I-D.sweeney-wimse-credential-delegation], is one place such accounting can be held.¶
Coordinated allocation with local enforcement: a single-writer allocator reserves disjoint allocations for the children (for example 60 units to the child acting at gateway A and 40 to the child acting at gateway B, leaving none at the parent, since allocating to a child reduces what remains available to the parent), and each enforcement point enforces its own allocation locally against a durable balance. This path has to protect against three hazards that a signed ceiling does not: duplicate allocation of the same units to two children; local rollback, where an enforcement point loses or reverts a debit it has already honored; and premature reuse of allocated units before the action that consumed them is settled.¶
Either way, the division is the one this document is built on: the offline chain bounds each action and produces the audit trail; the online half holds the aggregates.¶
Offline verification means an enforcement point may honor a token that has been revoked but whose status-list update it has not yet fetched. Deployments trade this window against TTL: shorter TTLs bound the exposure, and step 7 of Section 6 fixes the window at the cached answer's time to live. This is the standard status-list trade-off and MUST be documented for each deployment.¶
The lifetimes Section 8 recommends have a second cost, which is on the issuing side rather than the verifying side. A token that lives for seconds to minutes and is still needed must be re-issued, so a platform with many live agent instances sustains a refresh rate proportional to the number of live instances, continuously, not only at startup. That rate is a property of the deployment and a deployment MUST size its authorization server for it, and for the resynchronisation case in which many instances refresh at once after an outage of the issuer or of the network between them. Offline verification is preserved under these lifetimes; offline operation is not, because an agent whose issuer is unreachable stops receiving tokens when its current ones expire.¶
The refresh rate counts every token in a chain, not only the roots, and grows with fan-out. Rule 4 of Section 4.3 and step 5 of Section 6 cap a child's "exp" at its parent's when the child is issued, so at the recommended lifetimes every child that keeps working is re-issued at least once per parent lifetime, whichever instance of the parent it commits to (Section 5). A deployment that revokes the previous parent instance at refresh adds a burst on top of that rate: every child of that instance at once. Both the sustained rate and the burst belong in the sizing above, and the fan-out bound of Section 9.4 is what bounds them.¶
Macaroons [Macaroons] verify with the root secret, precluding public offline verification at an untrusted edge; this document uses public-key signatures. Algorithms are fully specified [RFC9864] and agile via the JOSE "alg" registry, with a migration path to ML-DSA [RFC9964]. The algorithm list of step 1 in Section 6 is the agility mechanism on the verifying side. A deployment that retires an algorithm, for example a quantum-vulnerable one after a transition date, removes it from the verifier's list, and chains that still use it are then denied whatever their remaining lifetime. Because every hop's "alg" sits in a protected header that the child's "par_hash" covers, a verifier holding the full chain sees every hop's algorithm, and a parent whose algorithm was swapped after the fact no longer matches its child's commitment.¶
DT_0's Authority is asserted by whoever holds the root key. Every guarantee in this document begins downstream of that minting: each child is a deterministic attenuation of DT_0, and verification can establish that no hop exceeded the root, never that the root was right. Approval policy therefore belongs at root issuance. A deployment that requires a human decision before an agent may act with consequential authority places that decision where DT_0 is minted, not at a later hop, because no later hop can widen what DT_0 granted and no verification step can tighten it. The Principal named in DT_0's "sub" is the party that decision is taken on behalf of, and the "client_id" of each hop records which agent acted under it.¶
This document requests registration, in the JSON Web Token Claims registry, of: "del_depth", "del_max_depth", and "par_hash" (with the semantics in Section 3). The claims "sub", "client_id", "aud", and "cnf" are already registered and are used with their registered meanings as profiled in Section 3.¶
It requests a new "Agent Delegation Constraint Types" registry with the following initial entries, each recorded with its measurement scope: "max" (per action), "max_lifetime" (per token lifetime), "max_subtree" (per subtree), "min" (per action), "one_of" (per action), "not_one_of" (per action), "prefix" (per action), and "rank" (per action). The registration policy is Specification Required; a registration states the type's value shape, its measurement scope, and its subsumption rule in the form of Section 4.3. Unknown types fail closed per Section 4.2.¶
This document does not request registration of an "authorization_details" type value. [RFC9396] maintains no registry of type values, and recommends a collision-resistant namespace under the designer's control; see Section 4.4.¶
This section records the status of known implementations of this document at the time of its posting, as described in [RFC7942]. The description of implementations here is intended to assist the IETF in its decision processes in progressing drafts to RFCs. It is not an endorsement of any implementation, and the listing is not a claim that any implementation conforms to this revision in full. Please note that this section is to be removed before this document is published as an RFC.¶
Two reference implementations are maintained by the author under the Apache License 2.0: "attenu-guard", in Python, which mints Delegation Tokens and runs the verification algorithm of Section 6, and "attenu-guard-ts", in TypeScript, which runs the verification algorithm and mints nothing. At the time of posting, both released libraries (attenu-guard 0.20.0 and attenu-guard-ts 0.14.0) implement this revision as an opt-in profile beside the previous one, which remains their default: the claim rules of Section 3 at parse, the accepted-algorithm list of step 1, principal invariance, the three-form scope grammar, the two cumulative constraint types with their totals supplied through a channel separate from the request, the "order" member of "rank", one constraint per (key, type), the closed detail and constraint objects, and the audience check of step 8, under a single-signer trust model. Both check the presence and shape of "cnf" and perform neither step 6 (holder binding) nor step 7 (status list), which both document as not performed. The vector set of Appendix B for this revision has thirty-five files; the Python library generates and scores it on every test run, and the TypeScript library vendors it byte for byte and scores it in its test suite. The previous revision's twenty files ship in both libraries unchanged.¶
Two implementations independent of the author's code have run the twenty test vectors of the previous revision and published their results, as reported by their authors: the conformance harness of the Cred delegation protocol, whose published result of 2026-10-07 reports 20 of 20, with no failures and no declared gaps, at a pinned commit of the Cred SDK; and a standalone Node.js verifier that imports no code from either reference implementation, which reported 20 of 20 with verdicts and declared reasons both matching, under the boundary its author states: the vectors' HS256 single-signer, public-test-key profile, and steps 1 to 5 only. Neither result is reproduced here, and neither author has reported a run against the vectors of this revision.¶
This work builds directly on, and seeks convergence with, [I-D.niyikiza-oauth-attenuating-agent-tokens], [I-D.coetzee-oauth-spt-txn-tokens], [I-D.sweeney-wimse-credential-delegation], [I-D.ietf-wimse-aims], and the requirements of [I-D.reece-wimse-cross-org-delegation]. The capability-token lineage of [Macaroons], [Biscuit], and [RFC2693] is gratefully acknowledged. The "min" constraint type was added after Amr Hassan, author of [I-D.hamr-oauth-agent-delegation], observed that -00 had no comparator for a floor tightened upward; the duration-typed "tenureMin" axis of that document is the motivating example.¶
The following contributions were made in review of the -01 and shaped this revision. Naming a contributor records where their contribution landed; it does not imply that they endorse this document.¶
Wes Jackson: the binding of the chain to an accountable Principal in "sub", distinct from the holder key, and the naming of the Acting Agent at each hop in "client_id" (Section 3, Section 3.1); the observation that quantity constraints compose across siblings while subsumption is checked per edge, that "max" is therefore per token, and that per-action, per-lifetime, and per-subtree limits are enforced by different machinery and belong in distinct registered types (Section 4.2, Section 9.4); the atomic check-and-debit requirement on shared accounting and the single-writer constraint on delegation-time allocation (Section 9.4); root issuance as the policy decision point (Section 9.7); the scope-vocabulary limit (Section 4.5); the questions that led to the process-every-detail rule and the per-type interoperability statement (Section 4.4); and the per-path evaluation rule answered in Section 6.3.¶
Iman Schrock: the two-gateway example in which two verified chains overspend a root ceiling, the distinction between allocation, which needs coordination, and spending within disjoint allocations, which does not, the two deployment shapes and the three hazards of the allocation path (Section 9.4), the compatibility point that led the scope grammar to keep structured literals as their own form, and the request to state the residual ambiguity of that grammar beside it (Section 4.1).¶
Girish Konda: the sustained refresh rate implied by the recommended lifetimes and the sizing it requires of the authorization server, and the requirement that a verifier tell a fresh status answer from a stale one (Section 9.5, Section 6 step 7).¶
Jijie Wei: the question of what "par_hash" commits to when a parent is re-issued, answered in Section 5, and the counting of child re-issuance in the refresh rate (Section 9.5).¶
Kieran Sweeney: the proposal to admit scope strings issued by OAuth providers, which became the opaque scope form, and the vectors that exercise it (Section 4.1, Appendix B).¶
Venkata Karunakar Uppalapati: the rule that a verifier rejects a chain carrying an algorithm outside its local policy even where the signature verifies, now step 1 of Section 6 and the agility text in Section 9, and the trust-anchor provenance gap recorded in Section 6.4.¶
A permissively licensed reference implementation (the "attenu-guard" library) and a set of offline-verification test vectors (chains that MUST verify and adversarial chains that MUST be rejected, each with the outcome name of Section 6.2 it is expected to produce) are published with this draft. They are intended for interoperability testing across independent implementations.¶
The twenty vector files published with the -01 are unchanged by this revision and remain available under their -01 names: independent implementations have vendored them, and a byte-stable set is worth more than a corrected one. They do not conform to this revision: their tokens carry no "cnf" and no "client_id", and their "aud" is null. The vector set published with this revision is a second, separately named set. It carries "cnf", "client_id", and a well-formed "aud" on every token, and adds vectors for: a chain in which a child's "sub" differs from the root's (principal_altered); a child presented with a re-issued parent rather than the instance it commits to (par_hash_mismatch); a wildcard scope over an opaque child scope and an opaque scope over a wildcard child, both not_narrower, and an opaque scope covering a byte-identical child, which verifies, contributed by Kieran Sweeney; a detail carrying two constraints with the same key and type (malformed); a "rank" whose "order" differs from its parent's (not_narrower); a "max_lifetime" under a parent "max" of the same key (not_narrower, by the no-cross-type rule); a token whose "alg" is not on the verifier's list (signature_invalid); and a token carrying an authorization detail of a type the verifier does not implement (malformed). The -01 set's coverage of scope and ceiling widening, parent splicing, depth and expiry violations, wildcard errors, RFC 8785 number and string forms, non-finite values, duplicate member names, and the informational "c14n" label is re-minted in the new set under this revision's claim layout.¶
"sub" is defined as the Principal, identical at every hop; "client_id" is added to the required claims as the Acting Agent at each hop; and step 3 of the verification algorithm denies a chain in which any token's "sub" differs from the root's. A new subsection explains the split.¶
"aud" is required to be a string or non-empty array of strings; null is malformed. The audience check of RFC 9068 Section 4 is applied to the leaf token in step 8.¶
Step 1 requires a verifier-held list of accepted algorithms, denies any token whose "alg" is not on it even where the signature verifies, and takes the algorithm from the verifier's configuration for the key rather than from the header. The algorithm agility text says how an algorithm is retired.¶
The scope grammar gains a third form, "opaque-scope", the RFC 6749 scope token without the asterisk, so that provider-issued scope strings can be carried; classification is by bytes alone in a stated order; an opaque scope covers only a byte-identical child and no wildcard covers it; the residual ambiguity for dotted provider strings is stated beside the grammar.¶
Two constraint types are added, "max_lifetime" and "max_subtree", so that per-action, per-lifetime, and per-subtree bounds are distinct registered types; "max" is defined as per action only; no inference is made across types; a chain-only verifier checks only that a child's cumulative bounds are no wider than its parent's.¶
The "rank" type carries its ordering on the wire in an "order" member, and a child's ordering must be identical to its parent's.¶
A detail may not carry two constraints with the same key and type; a verifier rejects a token that does.¶
Three closure rules are stated as MUSTs: a token carries exactly one "agent_delegation" detail; that detail carries no members other than "type", "scopes", and "constraints"; and a constraint object carries no members other than "key", its type member, and the members its type defines. A token that breaks any of them is malformed.¶
The covering relation is bound to the authorization detail type; a verifier must evaluate every detail or reject the token, and an unimplemented type is a rejection; under this revision a token carries exactly one detail, of type "agent_delegation", and chains carrying several types are left to a later revision; a resource server is pointed to RFC 9728 to declare the types it supports.¶
The IANA request for an "authorization_details" type value is withdrawn, since RFC 9396 keeps no such registry; whether the type value becomes a URI is recorded as open.¶
The scope-vocabulary limit is recorded as an open issue in its own subsection, with the interface a companion document is expected to meet and the rule under which such a document is cited.¶
The chain linkage section states that "par_hash" commits to a parent token instance, and what that means for re-issued parents and their children.¶
Step 8 denies an action under a cumulative constraint when no component holds the running total it is measured over.¶
An integer whose magnitude exceeds 2^53-1 is malformed.¶
The revision states that it defines no transition mode for chains shaped by the previous revision.¶
Step 7 states that a cached status answer is trusted for its time to live, that an unobtainable or unrefreshable status is unknown and denies, and that a token issued without a status reference is a choice made at issuance.¶
A table states what a verifier that sees no request may and may not conclude, and a table names the verification outcomes the test vectors use.¶
Several chains reaching one agent are addressed: each chain is verified alone and never combined with another.¶
Trust-anchor provenance is recorded as an open issue.¶
The fan-out security consideration gains the sibling and two-gateway examples, the allocation/spending distinction, the two deployment shapes for aggregate budgets, and the three hazards of the allocation path.¶
The revocation-latency security consideration gains the sustained refresh rate, the resynchronisation case, the sizing requirement, the counting of child re-issuance, and the revoke-at-refresh burst.¶
Root issuance is stated as the policy decision point in a new security consideration.¶
draft-klrc-aiagent-auth, now replaced, is re-pointed to draft-ietf-wimse-aims in the introduction and the acknowledgments; draft-jackson-wimse-evaluation, draft-ietf-wimse-identifier, RFC 6749, RFC 7942, and RFC 9728 are added as references.¶
An Implementation Status section is added.¶
The test-vector appendix records that the -01 set stays byte-stable and does not conform to this revision, and describes the second set that does.¶
Step 8 and the constraint vocabulary state that a running total reaches a cumulative constraint through a channel separate from the request, that cumulative and per-action constraints are evaluated against different inputs, and that a per-action "max" on a running count bounds nothing.¶