When an AI agent hands a customer's task to another agent, and that one to a third, every hop is a chance for the task to drift from what the customer asked for. Purpose-bound delegation checks every entity in the chain against the purpose of the agent the customer started with, and PingAuthorize decides - before a token is issued, and again when it is used.
A customer, Alice, asks Northwind Bank's Bill Pay Assistant to pay her bills. Bill Pay checks her available funds, hands the payments to a routing agent, which may hand payee checks to a third, which calls a directory API. Each hand-off is an RFC 8693 token exchange, and each agent that took part is added to the token's act claim - the newest outermost, the agent Alice started with innermost.
That innermost agent is the origin, and its purpose bounds everything after it. At every hop the whole chain, and the entity being asked for, is judged against the origin's purpose - never against the agent before it. A direct-debit agent may suit a payment router perfectly and still be outside what Alice asked for.
Before a token exists. The authorization server asks PingAuthorize before it issues each token, so an off-purpose party never receives the customer's token at all.
Again when it is used. The receiving API's enforcement point asks again at the call, resolving every entity in the chain afresh: a member dropped since the token was issued is refused there.
Deny, hold or permit. A misfit is denied with a reason the caller can show. Something outside the customer's purpose that serves nothing else is held for the customer to approve. Only the bank's purpose map permits.
Agents, APIs and the authorization server are all federation entities, with their keys and their words published and vouched for by a superior, not configured locally.
The chain grows at the front and the origin never moves. By the last hop the token names three agents, with Bill Pay innermost. The authorization server judges every entity in the chain, and the one it is asked for, before it issues each token; the receiver's enforcement point judges them all again when the token is used.
Three parts
Records in banking terms, a model that reads them, and PingAuthorize deciding
OpenID Federation
Every agent and API is a federation entity
What an entity does is published in its metadata in BIAN v14 terms - the banking industry's service vocabulary. Agents declare it in authorization_details_types (RFC 9396), APIs in authorization_details_types_supported (RFC 9728). Each also states a mission and a purpose statement in clear text, which its superior vouches for by setting them in its Subordinate Statement.
Entity statementsTrust chainsMetadata policy
Small language model
Reads what the words actually describe
A model of a few billion parameters, running inside the bank's network, reads each entity's resolved record - purpose statement first, then mission and description - and reports which BIAN Service Domain the words describe, and whether they show anything the declared codes do not: hidden activity, use for other organisations, or text aimed at the checker. Every adverse claim must quote the record; a quote that is not really there is dropped.
Open weights on Ollamaor a hosted typed decision model
PingAuthorize
Decides on the bank's purpose map
Every decision goes through PingAuthorize 11 in embedded-PDP mode, through the AuthZEN Adapter. Its policy holds the bank's BIAN purpose map - which Service Domains a task may draw on - and the secondary-use list. Inside the map an entity is permitted. Outside it, the model may deny what serves another purpose and can never permit: everything else, a helpful extra included, is held for the customer.
Ten rules, first applicableAuthZEN 1.0
Architecture
Agents act; the authorization server and every API ask PingAuthorize first
Every decision goes through PingAuthorize: the authorization server asks before it issues a token, and each API and agent asks before it acts on one. PingAuthorize gets its evidence from the purpose service, which reads the federation, and asks for the model's judgement only when the federation facts are clean. The authorization server and the enforcement points also take the keys they check - client keys, token-signing keys - from the federation, not from local configuration. In the hosted demo, PingFederate is the authorization server: RFC 8693 token exchange issuing DPoP-bound tokens, each one decided by PingAuthorize before it is issued.
One decision
The model is the last thing asked, and only when it is needed
PayRoute, which Bill Pay handed the payment to, asks for a token for the Payments API. Steps 4 to 6 settle what can be settled without a model: every entity still resolves, no text is aimed at the checker, the origin's purpose is vouched for, and nobody declares a secondary use. In step 8 the model reads each entity's mission and purpose statement in BIAN terms. The answers are cached until the federation statements expire, so the model rarely runs twice. The token in step 11 names Alice as sub, the Payments API as its only audience, and the chain PayRoute ← Bill Pay in act. When PayRoute then calls the Payments API, its enforcement point asks PingAuthorize again.
The policy
Ten rules, first applicable
Rule
What must hold
Who answers
If it fails
1
Every entity in the chain, and the one asked for, resolves to the trust anchor
the federation's resolver
deny
2
No text in a record is aimed at the checker
facts, no model
deny
3
The origin's purpose is vouched for by its superior, and specific
facts, no model
deny
4
No entity declares a secondary-use Service Domain
facts, no model
deny
5
What an entity declares matches what its words describe
the model's reading
deny
6
No entity's words describe a secondary use
the model's reading
deny
7
Every entity serves the origin's purpose
the model, outside the purpose map
deny
8
Every entity can be placed, and the answer is sure
the judge
hold
9
Every entity is inside the bank's purpose map
the purpose map
permit
10
Otherwise
policy
deny
Rules 1 to 4 need no model: they are settled from signatures and declared codes. The model may deny (5 to 7) or leave a hold (8); only the purpose map permits (9). The reason code of the first failing rule goes back to the caller, so an enforcement point can show why.
Outside the purpose map the model denies only what it finds serves another purpose: marketing, profiling, data for other organisations, the bank's own planning, risk or credit decisions, or a different product. Anything else outside the map - a helpful extra, something too vague to place, or no answer in time - is held.
Holding is a decision. Bill reminders serve nothing but the customer, and are not what she asked for. They are neither waved through nor refused outright: they are held, and the customer decides.
Two places to check
At the PDP, or in the federation's policy
Whether an entity's words fit its declared codes can be checked in two places, and the demo runs every scenario both ways. At the PDP, the bank's purpose service runs the model on each record and applies rule 5. In the federation's policy, each superior writes the check into its Subordinate Statement as a metadata policy on purpose_statement: the operator describes, listed in metadata_policy_crit so that a relier which cannot evaluate it must reject the chain rather than skip the check. The trust anchor's resolver has the model and applies the policy once for everyone; an entity whose words fail it does not resolve, and is refused with invalid_metadata. What stays put is who decides fit to the task: PingAuthorize, on the bank's purpose map, either way.
At the PDP
At the federation's resolver
Who runs the model
Each bank, for its own decisions
The trust framework's resolver, once for every relier
Where the rule lives
PingAuthorize's policy: rule 5, "what an entity declares must match what it describes"
The superior's Subordinate Statement: purpose_statementdescribes the codes, critical
What happens to a misfit
Denied by the bank, with the model's quote
Does not resolve at all: rule 1, for every relier, with the resolver's reason
What the PDP trusts
Its own model
The resolver's signature on the resolve response, with the trust chain it was derived from
Fit to the task
The bank's purpose map; outside it, the PDP's model may deny another purpose
The same, at the PDP: the resolver never sees the task
Suits
A bank that wants its own model, prompts and audit trail
A federation whose superiors vouch for purpose, and want one check for everyone
The token
What the token carries
# the token PayRoute receives for the Payments API
{
"iss": "…/e/as",
"sub": "alice@northwind.example",
"aud": "…/e/payments-api",
"client_id": "…/e/payroute",
"scope": "bill_pay",
"act": {
"sub": "…/e/payroute", ← current actor"act": {
"sub": "…/e/bill-pay"← origin
}
},
"cnf": { "jkt": "…" } ← DPoP key
}
Delegation, not impersonation. Alice stays the subject. Each agent that took part is added to act, the newest outermost.
The origin bounds the chain. The innermost actor is the agent Alice started with. Its purpose, as its federation record declares it, is what every later entity is checked against.
Read on purpose. RFC 8693 §4.1 treats earlier actors as informational. For agents, where a request came from is the point, so this design reads the whole chain - and only PingAuthorize's rules read it.
Every name resolves. Each act.sub is a federation entity identifier, so its record, and who vouches for it, can be fetched and checked.
Onward only when approved. When the audience is itself an agent, the token also carries may_act naming it. Only that agent can exchange it onward, and only because PingAuthorize permitted it as the audience.
The record
What an entity statement carries
Every agent and API states its mission and purpose in clear text in its federation metadata, and its superior vouches for them. This is the registry's Subordinate Statement about PayRoute, with purpose checked in the federation's policy and the keys left out.
{
"iss": "…/e/registry", ← its superior signs"sub": "…/e/payroute",
"metadata": {
"oauth_client": {
"display_name": "PayRoute Orchestrator",
"mission": "Get each bill payment to the
biller by the fastest safe route.",
"purpose_statement": "Routes customers' bill
payments to the fastest available payment
rail and confirms settlement.", ← what the model reads"description": "Fast, reliable routing
for bill payments."
}
},
"metadata_policy": {
"oauth_client": {
"authorization_details_types": {
"subset_of": [
"…/initiate_payment_order_initiation",
"…/execute_payment_rail" ] ← codes capped
},
"purpose_statement": {
"describes": "authorization_details_types"
} ← the purpose policy
}
},
"metadata_policy_crit": ["describes"] ← must be understood
}
Mission and purpose, in clear text. The mission says why the entity exists; the purpose statement says what it does and for whom, in words a person and a small model can both read. The description is for people.
Vouched, not self-asserted. The registry sets these in its statement's metadata, which replaces whatever PayRoute wrote about itself (OpenID Federation §6.1.4.2). An entity whose superior does not vouch for its words is marked self-asserted.
Codes capped. The registry's metadata_policy limits the BIAN capabilities PayRoute may declare, whatever it claims.
Purpose as policy. The describes operator says the purpose statement must describe the declared codes and nothing else. Listed in metadata_policy_crit, it cannot be skipped: a relier either evaluates it or does not resolve the entity.
What the model makes of it. Payment Rail, Execute, serving the customer: the policy holds, and PayRoute resolves. The model reads all three fields, so words that give an entity away count wherever they are.
This design's convention.mission, purpose_statement and the describes operator are not defined by OpenID Federation. The standard has description (§5.2.2) and allows federations to add policy operators (§6.1.3.2); the rest is proposed here.
Scenarios
Fifteen scenarios, each run for real
Each is one button in the demo. Alice delegates bill payments to Northwind's Bill Pay Assistant, and every token exchange and every call is decided by PingAuthorize, which asks the model only when it has to. Three of them end differently depending on where purpose is checked: at the PDP the model reads the words and denies; in the federation's policy the entity does not resolve at all, and the model is never asked.
Scenario
What it shows
Decided
Happy path
Bill Pay → PayRoute → Payments API, every entity within the purpose map
permit
Purpose drift
Bill Pay names Spend Insights, which declares a secondary-use Service Domain
denyno model
Codes pass, words do not
QuickPay's codes fit; its purpose statement sells payee data
denymodel · in the federation's policy it does not resolve
Injection neutralised by the federation
The registry's vouched text replaces BillSaver's injection
permit
Wrong resource
PayRoute asks for the Credit Rating API
denymodel
Revoked member
PayRoute dropped while its token is in flight
denyno model
Three agents deep
Bill Pay → PayRoute → Payee Check → Payee Directory API: three agents in act
permit
Revoked in the middle
PayRoute dropped while Payee Check, the current actor, holds a valid token
denyno model
Fits the delegator, not the origin
DebitLink (direct debit mandates) suits PayRoute's purpose, not Bill Pay's
heldmodel
The bank's own marketing
Bill Pay asks Northwind's Offers API, which declares Product Matching
denyno model
Drift three hops down
Payee Check asks for Biller Connect (Customer Relationship Management)
denyno model
Borrowing to pay
PayRoute asks for BillBridge, which lends the shortfall (Consumer Loan): another product
denymodel
Passed on to others
Receipt Relay's words say it passes payment history to credit reporting agencies
denyon the model's quote · in the federation's policy it does not resolve
A helpful extra
BillNudge sends bill reminders: not needed, but serving nothing else
heldmodel
An agent for everything
Money Mate helps with anything to do with money: too broad to place
heldon the model's reading · in the federation's policy it does not resolve
Against the immediate delegator alone. The last scenario also puts the same question to PingAuthorize with the chain cut at PayRoute, which is what a check against the immediate delegator would ask. That answer is PERMIT, and the demo shows it next to the real decision. It is for comparison only; nothing is issued on it.
Recorded runs
Who took part, what was decided at each checkpoint, and where the deny landed
Each timeline is a recorded run of the demo, not a drawing of what should happen. Left to right are the checkpoints the run passed through: the customer's consent, each token exchange at the authorization server, each call at the receiver's enforcement point, and any change the registry made in between. Each box says what was decided, by which rule and by whom; the first deny stops the chain.
All fifteen, both ways, are on the demo's use cases page. The first decision that needs the model to read a record it has not seen takes a few seconds; later ones are answered from the cache.
What drift looks like
Every entity resolves and is in good standing; what is wrong is the purpose
Kind
Example
How it is caught
A secondary use, declared
Spend Insights scores customers for partner offers. Northwind's own Offers API matches customers to products.
Rule 4, from the declared codes. No model.
Drift deep in the chain
Three agents from the origin, Payee Check asks for Biller Connect, which shares customers' details with billers' relationship teams.
Rule 4, still judged against Bill Pay. No model.
A secondary use in the words
QuickPay's codes say payments; its description says it sells payee data.
Rule 5 at the PDP: the model reads the words and quotes them. In the federation's policy, rule 1: QuickPay does not resolve.
Use for other organisations
Receipt Relay sends receipts, and passes payment history on to credit reporting agencies.
Rule 7 at the PDP: the quoted words show data going to other organisations. In the federation's policy, rule 1: it does not resolve.
A different task
BillBridge lends the shortfall. The Credit Rating API.
Rule 7: outside the purpose map, and the model finds it serves another purpose - a different product, or the bank's own credit decisions.
A helpful extra
BillNudge sends bill reminders: not needed, but serving nothing else.
Rule 8: held for the customer to approve.
Fits the delegator, not the origin
DebitLink registers direct-debit mandates: within a payment router's purpose, outside what Alice asked for.
Rule 8: held. Only the origin decides.
Too broad to place
Money Mate helps with anything to do with money.
Rule 8: held at the PDP. In the federation's policy it does not resolve.
Revoked
The registry drops PayRoute while its token is in flight, or while a later agent holds a valid one.
Rule 1 at the receiver's enforcement point, which resolves every entity afresh. A check of the current actor alone would let the call through.
Built on
Standards where they exist, and named conventions where they do not
Standard or product
Used for
OAuth 2.0 Token Exchange, RFC 8693
Each hand-off; act and may_act
Rich Authorization Requests, RFC 9396 §10.2
authorization_details_types: what an agent may ask for, in BIAN terms
Protected Resource Metadata, RFC 9728 §2
authorization_details_types_supported: what an API serves
Embedded PDP; the purpose map, secondary-use list and ten rules as policy
PingFederate
The authorization server in the hosted demo: token exchange that asks PingAuthorize before it issues
Not to claim
What this design does not do
mission, purpose_statement and the describes operator are this design's conventions. OpenID Federation defines the resolve endpoint, description, additional policy operators and metadata_policy_crit, not these. The BIAN-coded metadata, the purpose map and the rules are a design, not a standard.
The chain is read whole by design, contrary to RFC 8693 §4.1. It is safe here because only an authorization server in the federation builds a chain, every act.sub must resolve to the trust anchor, and only PingAuthorize's rules read it.
The model catches misled or careless delegation, and entities whose words contradict their codes. It does not stop a determined liar: with open weights, an attacker can search offline for a description that passes. That is why the words it reads should be vouched for by a superior.
A resolver that applies a model-backed policy becomes something every relier trusts for that check, as it already is for chain resolution. Its signed response proves what resolved; it does not make the model's reading right.
Revocation is as fast as reliers re-resolve. The purpose service resolves fresh each time; a relier that caches statements keeps trusting a dropped member until they expire.
Error rates need an independent, blind-labelled test set. The figures so far are a feasibility check, and where the hold line falls between a helpful extra and a different task is still the model's call.