How it works

What the purpose-bound delegation demo is made of, and how each decision is reached.

A customer asks an AI agent to pay their bills. That agent hands parts of the job to other agents, and they call APIs. Every hand-off is a chance for the task to drift from what the customer asked for. Here, every entity in the chain is checked against the purpose of the agent the customer started with, the origin, before it gets a token and again when it uses one.

Three things make that possible. Every agent and API is an OpenID Federation entity whose record says what it does in BIAN terms, the banking industry's service vocabulary. A small language model reads those records and reports what their words actually describe. PingAuthorize makes every decision, using the bank's own rules.

1The architecture

Agents act. The authorization server and every API ask PingAuthorize before anything happens. PingAuthorize asks the purpose service for evidence, and only uses the model when the evidence needs reading.

Scroll sideways to see the whole diagram.

WHO ACTS WHO DECIDES WHAT IT DECIDES ON Alice the customer Agents each a federation entity and a PEP Bill Pay · origin PayRoute Payee Check Spend Insights QuickPay Helper BillSaver DebitLink BillBridge Biller Connect Receipt Relay BillNudge Money Mate APIs each behind a PEP Payments API Customer Position API Credit Rating API Payee Directory API Offers API Authorization server RFC 8693 token exchange DPoP · private_key_jwt PingAuthorize AuthZEN servlet embedded PDP Policy rules, first applicable BIAN purpose map secondary-use list decides every checkpoint OpenID Federation Trust anchor Northwind Bank Accredited Fintech Registry mission and purpose, vouched BIAN-coded capabilities resolve endpoint applies the policy Purpose service Federation facts · no model Purpose judge · the model judge only when facts are clean Small language model ministral-3:3b · Ollama runs inside the network delegates token exchange DPoP-bound call AuthZEN AuthZEN AuthZEN facts, judge resolves chains classifies records client keys
Every decision goes through PingAuthorize (the orange path): 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 PEPs also take the keys they check (client keys, token-signing keys) from the federation, not from local configuration.

2Where purpose is checked

Whether an entity's words fit its declared codes can be checked in two places, and the demo runs every scenario both ways: the switch is above its scenarios. At the PDP, the bank's purpose service runs the model on each record. In the federation's policy, each superior writes the check into its Subordinate Statement as a metadata policy, and the trust anchor's resolver runs the model to apply it: an entity whose words fail it does not resolve, for anyone. What stays put is who decides fit to the task: PingAuthorize, on the bank's purpose map, either way.

Scroll sideways to see the whole diagram.

AT THE PDP · THE BANK RUNS THE MODEL IN THE FEDERATION'S POLICY · THE RESOLVER APPLIES IT AS, or an API's PEP asks before it acts PingAuthorize decides, on the bank's purpose map Purpose service + SLM reads each entity's words, judges fit OpenID Federation statements and chains, no model AuthZEN facts, then judge resolves chains AS, or an API's PEP asks before it acts PingAuthorize decides, on the bank's purpose map Purpose service takes resolved codes as read, judges fit Resolver + SLM applies the policy: describes needs the model AuthZEN facts, then judge resolve, or refuse
In the federation's policy, the check is part of resolution itself. The superior's Subordinate Statement says, as a metadata policy, that the member's purpose_statement must describes its declared codes, and lists describes in metadata_policy_crit. OpenID Federation lets a federation add operators like this (6.1.3.2); a critical one a relier cannot evaluate invalidates the chain (3.1), so a relier without a model resolves through the trust anchor's resolve endpoint (8.3), where the model is. The resolver applies the policy once for everyone and signs what resolved, or refuses (8.9, invalid_metadata). The resolver never sees the task, so fit to the task stays with the bank.
At the PDPAt the federation's resolver
Who runs the modelEach bank, for its own decisionsThe trust framework's resolver, once for every relier
Where the rule livesPingAuthorize's policy: rule 5, "what an entity declares must match what it describes"The superior's Subordinate Statement: purpose_statement describes the codes, critical
What happens to a misfitDenied by the bank, with the model's quoteDoes not resolve at all: rule 1, for every relier, with the resolver's reason
What the PDP trustsIts own modelThe resolver's signature on the resolve response, with the trust chain it was derived from
Fit to the taskThe bank's purpose map; outside it, the PDP's model may deny another purposeThe same, at the PDP: the resolver never sees the task
SuitsA bank that wants its own model, prompts and audit trailA federation whose superiors vouch for purpose, and want one check for everyone

3One decision, step by step

PayRoute, which Bill Pay handed the payment to, asks for a token for the Payments API. This is what happens between the request and the token.

Scroll sideways to see the whole diagram.

PayRoute Authorization server PingAuthorize Purpose service Federation Model 1 token exchange, DPoP 2 resolve PayRoute's keys 3 AuthZEN + act chain 4 facts 5 resolve every entity 6 clean 7 judge 8 classify records 9 in the purpose map 10 PERMIT + reason 11 token
The model is the last thing asked, and only when it is needed. Steps 4 to 6 settle what can be settled without it: 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, or, under the federation's policy, the resolver already did that before the entity would resolve in step 5, and the purpose service takes its codes as read. Either way 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 PEP asks PingAuthorize again (steps 3 to 10): a member dropped since the token was issued is refused at that point.

4Who is in a decision, and where the denies land

For one diagram per scenario, drawn from recorded runs, see Use cases. This section is the general picture.

Every decision has the same cast. The customer is the subject, and is not judged. The entities judged are the ones in the token's act chain, from the current actor down to the origin, plus the audience being asked for. The authorization server asks at a token exchange and a PEP asks at a call; PingAuthorize decides, with the purpose service, the federation's resolver and the model behind it. The ten decisions run in order, and the first that fails is the reason the caller sees.

Scroll sideways to see the whole diagram.

WHO IS JUDGED WHO ASKS, WHO DECIDES THE DECISIONS, IN ORDER Alice the subject; not judged The act chain, outermost first: Current actor outermost act: the client asking Earlier actors every hop in between Origin innermost act: bounds the chain Audience the agent or API asked for Each is resolved fresh in the federation, read in BIAN terms, and checked against the origin's purpose, never a neighbour's. The AS, or a PEP at a token exchange, or at a call AuthZEN: subject, audience, act chain PingAuthorize ten rules, first applicable; the reason code goes back facts, then judge Purpose service facts: no model; judge: the reading, the purpose map resolves reads; fit Resolver resolves, or refuses the policy operator needs the model Model reads records; fit outside the purpose map Outcome DENY at the AS: no token is issued DENY at a PEP: the call is refused HOLD: denied, for the customer to approve PERMIT: a token issued, or a call served rule who answers if it fails 1 Every entity resolves the resolver DENY 2 No text aimed at the checker facts DENY 3 Origin vouched, and specific facts DENY 4 No declared secondary use facts DENY 5 Words match the codes model reads DENY 6 No described secondary use model reads DENY 7 Serves the origin's purpose model, outside map DENY 8 Can be placed, and is sure judge HOLD 9 Inside the purpose map purpose map PERMIT 10 Otherwise policy DENY Rules 1 to 4 need no model. The model may deny (5 to 7) or leave a hold (8); only the purpose map permits (9).
Everything in the chain is judged against the origin, and the first failing rule decides. The audience is judged before a token is issued for it, so an off-purpose party never receives the customer's token at all. Rules 1 to 4 are settled from signatures and codes; 5 to 7 from the model's reading, or, outside the purpose map, its judgement; 8 holds what cannot be settled; 9 permits from the bank's purpose map. Under the federation's policy, rule 1 also carries the words-fit-codes check: an entity that fails it does not resolve.

Scroll sideways to see the whole diagram.

Alice delegates to Bill Pay; then each agent exchanges the token for its next hop (the AS decides), and calls it (the receiver's PEP decides). Bill Pay origin PayRoute agent Payee Check agent Payee Directory API API Hop 1 Bill Pay asks for its next hop AT THE AS · BEFORE ANY TOKEN 4 Purpose drift · Spend Insights 4 The bank's own marketing · Offers API 5 Codes pass, words do not · QuickPay ¹ 7 Passed on to others · Receipt Relay ¹ 8 A helpful extra · BillNudge 8 An agent for everything · Money Mate ¹ AT THE PEP · THE CALL nothing lands here Hop 2 PayRoute asks for its next hop AT THE AS · BEFORE ANY TOKEN 7 Wrong resource · Credit Rating API 7 Borrowing to pay · BillBridge 8 Fits the delegator, not the origin · DebitLink AT THE PEP · THE PAYMENTS API 1 Revoked member · PayRoute dropped, token still in flight Hop 3 Payee Check asks for its next hop AT THE AS · BEFORE ANY TOKEN 4 Drift three hops down · Biller Connect AT THE PEP · THE DIRECTORY API 1 Revoked in the middle · PayRoute dropped; Payee Check still a member Through every checkpoint every entity inside the purpose map EACH AS, THEN EACH PEP 9 Happy path 9 Injection neutralised 9 Three agents deep The number is the rule that decided. ¹ Under the federation's policy these three do not resolve at all: rule 1, at the AS, and the model is never asked.
Every deny lands before a token exists, except revocation. Along the happy chain, each agent's request for its next hop is decided at the authorization server: a misfit is refused there and no token is ever issued for it. The two revocations are different: PayRoute already holds a valid token when the registry drops it, so the refusal comes at the receiver's PEP, which resolves every entity in the chain afresh when the token is used. Under the federation's policy, the three entities whose words fail the codes do not resolve at all, so they fall to rule 1 at the AS with the model never asked.
Reason the caller seesRuleDecided byLands atTokenModelScenarios
chain_invalid1The federation resolver: signatures, and the purpose policyAS, or a PEPNone at the AS; at a PEP, one in flight is refusedNot askedRevoked member; Revoked in the middle; under the policy also Codes pass, words do not; Passed on to others; An agent for everything
secondary_use_declared4Facts, from the declared codesASNone issuedNot askedPurpose drift; The bank's own marketing; Drift three hops down
declared_vs_described5The judge, on the model's readingASNone issuedRead the recordCodes pass, words do not (at the PDP)
inconsistent7A rule on the reading (use for other organisations), or the model outside the mapASNone issuedRead; judged fitPassed on to others (at the PDP); Wrong resource; Borrowing to pay
held_for_customer_approval8The judge: unsure, not needed, too broad, or no answer in timeASNone issuedRead; judged fitA helpful extra; Fits the delegator, not the origin; An agent for everything (at the PDP)
prescreen_flagged, origin_gate, secondary_use_described, no_rule_applied2, 3, 6, 10Facts, the judge, the policy's defaultAS or PEP——Not shown by a scenario; the policy build tests each

5What the token carries

The token PayRoute gets in step 11. The whole delegation chain travels with it, and PingAuthorize reads all of it.

{
  "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 impersonationAlice stays the subject. Each agent that took part is added to act, the newest outermost.
  • The origin bounds the chainThe 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 purposeThe token exchange standard, RFC 8693, 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 resolvesEach act.sub is a federation entity identifier, so its record, and who vouches for it, can be fetched and checked.
  • Onward only when approvedWhen 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.

6What 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, as the demo serves it, with its keys left out. The demo's "Entity statements" view shows every entity's, with the resolver's response beside it.

{
  "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 SLM resolves
      "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 textThe 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-assertedThe 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 cappedThe registry's metadata_policy limits the BIAN capabilities PayRoute may declare, whatever it claims.
  • Purpose as policyThe 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. The trust anchor's resolver evaluates it with the model, so reliers resolve through the resolver.
  • What the SLM makes of itPayment 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 demo's conventionmission, purpose_statement and the describes operator are not defined by OpenID Federation. The standard's informational metadata has description (5.2.2), and it allows federations to add policy operators (6.1.3.2); the rest is proposed here.

7Deeper chains

A chain is as long as the task needs. Each token exchange puts the agent that made it outermost in act, and the origin stays innermost. At every hop the whole chain is judged against the origin, not against the agent before it.

Scroll sideways to see the whole diagram.

THE CHAIN THE TOKEN FOR THE HOP · act JUDGED AGAINST BILL PAY Alice customer Bill Pay Assistant origin PayRoute Orchestrator agent Payee Check agent Payee Directory API API delegates hands on hands on calls delegation grant may_act Bill Pay Bill Pay · origin PayRoute Bill Pay · origin Payee Check PayRoute Bill Pay · origin innermost at every hop its own purpose: vouched, specific PayRoute PayRoute, Payee Check PayRoute, Payee Check, the API at the AS, then at the receiver's PEP
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 PEP judges them all again when the token is used.

Revoked in the middle

The registry drops PayRoute while Payee Check holds a valid token. A check of the current actor alone, which is what RFC 8693 asks of a token's consumer, sees only Payee Check, still a member in good standing, and lets the call through. Resolving every agent in the chain finds PayRoute gone, and the call is refused without asking the model.

Fits the delegator, not the origin

PayRoute asks for a token for DebitLink, which registers direct debit mandates. Judged against PayRoute, a payment router, that is within purpose, and PingAuthorize says so when the demo asks it that way. Judged against Bill Pay, it is outside the purpose map: the customer asked for bills to be paid, not for billers to hold standing authority over the account. So it is held for the customer to approve. Only the origin decides.

8What drift looks like

Drift is anything in the chain doing something other than the task the customer delegated. Every entity below resolves in the federation and is in good standing: what is wrong is the purpose, not the membership. Each is a scenario in the demo.

KindExampleHow it is caught
A secondary use, declaredSpend Insights scores customers for partner offers (Purpose drift). Northwind's own Offers API matches customers to products (The bank's own marketing).Rule 4, from the declared codes. No model.
Drift deep in the chainThree agents from the origin, Payee Check asks for Biller Connect, which shares customers' details with billers' relationship teams (Drift three hops down).Rule 4, still judged against Bill Pay. No model.
A secondary use in the wordsQuickPay's codes say payments; its description says it sells payee data (Codes pass, words do not).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 organisationsReceipt Relay sends receipts, and passes payment history on to credit reporting agencies (Passed on to others).Rule 7 at the PDP: the quoted words show data going out to other organisations. In the federation's policy, rule 1: it does not resolve.
A different taskBillBridge lends the shortfall (Borrowing to pay). The Credit Rating API (Wrong resource).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 extraBillNudge sends bill reminders (A helpful extra).Rule 8: outside the map, and the model finds the task does not need it but that it serves no other purpose. Held for the customer, not refused.
Fits the delegator, not the originDebitLink suits PayRoute, a payment router, but not paying Alice's bills (Fits the delegator, not the origin).Rule 8: held, because the check is against the origin, whose purpose map has no mandates. Against PayRoute alone it would be permitted.
Too broad to placeMoney Mate helps with anything to do with money (An agent for everything).Rule 8 at the PDP: it cannot be shown to serve the task, so it is held for the customer. In the federation's policy, words that broad describe nothing, and it does not resolve.

Outside the purpose map the model may deny only what serves another purpose: marketing, profiling, data for other organisations, the bank's own planning, risk or credit decisions, or a different product. Everything else outside the map, including a helpful extra, is held for the customer. Nothing the model says can permit it.

9The rules, in order

PingAuthorize applies the first rule that matches, and its reason is what the caller sees. The first four rules need no model: when one of them decides, the model is never asked. The rule names are the policy's own.

#RuleDecided byShown in
1Every entity must resolve in the federationSignatures, and the federation's policyRevoked member; Revoked in the middle; in the federation's policy also Codes pass, words do not; Passed on to others; An agent for everything
2No text may instruct the checkerPre-screenPasses in Injection neutralised: the registry's words replace BillSaver's first
3The origin's purpose must be vouched for and specificSuperior's statementPasses in every scenario: Northwind Bank vouches for Bill Pay
4No entity may declare a secondary-use Service DomainDeclared codesPurpose drift; The bank's own marketing; Drift three hops down
5What an entity declares must match what it describesThe model's readingCodes pass, words do not
6No entity may describe a secondary-use Service DomainThe model's reading— a backstop: rules 4 and 5 catch these first
7Every entity must serve the origin's purposeThe model's readingWrong resource; Borrowing to pay; Passed on to others
8Hold what cannot be placedAn extra, unsure, or no answerAn agent for everything; A helpful extra; Fits the delegator, not the origin
9Permit when every entity serves the origin's purposeThe purpose mapHappy path; Injection neutralised; Three agents deep
10Otherwise denyDefault—

Rules 4 and 6 use the bank's secondary-use list, such as Customer Behavior Insights. Rule 9 permits only when every entity's Service Domains, declared and described, are inside the purpose map for the origin's task. Rule 7 denies an entity whose words show use for other organisations, or one outside the map that the model finds serves another purpose. Everything else outside the map is held (rule 8): what the model thinks fits, a helpful extra the task does not need, anything too vague to place, and a model that does not answer in time. Rule 6 is a backstop: as the services stand, rules 4 and 5 catch a secondary use first.

10Who vouches for whom

The federation is where the words the model reads come from, and who stands behind them.

Scroll sideways to see the whole diagram.

Trust anchor vouches for Northwind Bank the bank Accredited Fintech Registry accredits, issues trust marks Bill Pay Assistant · origin Authorization server Payments API Customer Position API Credit Rating API Payee Directory API Offers API · the bank's own marketing PayRoute Orchestrator Payee Check Spend Insights · declares a secondary use QuickPay Helper · its own words BillSaver · own words replaced DebitLink · suits PayRoute, not Bill Pay BillBridge, Biller Connect, Receipt Relay, BillNudge, Money Mate · more drift
A superior's word replaces the member's own. Each superior signs a statement about each of its members. The statement can set the member's mission, purpose statement and description, which then replace whatever the member wrote about itself, and caps the BIAN capabilities it may declare. That is why BillSaver's attempt to instruct the checker never reaches the model: the registry's purpose statement is the one that resolves. QuickPay's purpose statement is its own, and it says more than its codes do. Any party can check a record by walking these statements up to the trust anchor, whose keys it holds in advance.

11What the model does, and what it doesn't

The model

  • Reads one record at a time, its mission, purpose statement and description, and names the BIAN Service Domain they describe. It runs at the PDP, or in the federation's resolver as the describes policy operator.
  • Points out activity the codes don't declare, and use for other organisations, quoting the words. A claim whose quote is not really in the record is dropped.
  • Flags text that addresses the checker.
  • Outside the purpose map, may deny what serves another purpose. It never permits: a helpful extra is held for the customer.
  • Runs inside the private network, so no decision goes to an outside model provider.

Not the model

  • Whether an entity is a member at all: federation signatures.
  • What a purpose may draw on: the bank's BIAN purpose map, held in policy.
  • What counts as secondary use: a policy list.
  • The decision itself: PingAuthorize's rules.
  • Keys and tokens: standard OAuth and federation checks.

Small models are reliable at placing a description in a fixed vocabulary, and unreliable at open-ended "does this serve that purpose?" judgements. So the model does the first job, and the bank's purpose map does most of the second, with the model allowed only to deny, and only for another purpose.

Choosing the model: what these jobs ask of a model, and how a typed decision model would do them.

12Standards used

13How it is deployed

One private network: the federation, the authorization server, PingAuthorize, the purpose service, the model, this site, and every agent and API of the story as a service of its own, with its own keys and its own PEP. Only this site and the federation face the internet. The agents and APIs, PingAuthorize, the purpose service and the model can be reached only from inside; the federation's resolver reaches the model the same way. What's deployed reads each service live: versions, the policy package, the model's digest, and whether each is answering.

Run the demo