Use Case 027: On-Behalf-Of Delegation
Overview
| Property |
Value |
| Use Case ID |
UC-027 |
| Use Case Name |
On-Behalf-Of Delegation |
| Module |
Agent Identity — Identity Backend |
| Priority |
High |
| Status |
✅ Implemented |
| Version |
1.0 |
| Last Updated |
June 15, 2026 |
Implementation status (agent-identity release, June 2026). Implemented: RFC 8693 token exchange at POST /api/agents/token/exchange, the AgentDelegationGrant envelope, scope intersection (requested ∩ agent ceiling ∩ grant scopes), issued-credential expiry capped to the grant, and the on-behalf-of subject surfaced through introspection (AgentCredential.OnBehalfOfSubjectId). Grant management is exposed in the Web agent detail page and at /api/agents/{id}/delegations. This phase is scope-only; plan-budget delegation rides on UC-033.
Description
This use case describes how an agent acts on behalf of another subject — a human user or a parent agent — through RFC 8693 OAuth 2.0 token exchange, gated by an AgentDelegationGrant. The delegation grant is a single envelope that carries both planes that matter for a delegated call: the identity scopes the subject permits the agent to use, and a plan-budget allocation carved off the subject's parent plan. One primitive bounds both planes, so a downstream call cannot exceed either the subject's own scopes or the subject's remaining budget headroom. Every delegated credential records the full actor chain (agent acting on-behalf-of subject, with the carried budget allocation) so the audit trail describes the complete chain of accountability.
This is part of the agent-identity release. The SDK master design is riptide-sdk/docs/plans/AGENT-IDENTITY-ARCHITECTURE.md; the Application Manager-side plan is docs/internal/agent-identity-plan.md (Section 1, plus §9 Reuse — "AgentDelegationGrant envelope" — and §10 Resolved decisions, "Identity scopes and budget travel together"). This UC owns the identity / envelope side of delegation: the grant, the RFC 8693 exchange, and the actor-chain audit. The mechanics of how plan budget is carved, tracked, and reconciled live in UC-033 (Agent Plan & Budget Delegation); this UC references that allocation but does not re-specify plan accounting. Governance is strict: a delegation's scopes must be a subset of the subject's own grant (no privilege escalation), and its budget allocation must be ≤ the parent plan's remaining headroom.
Actors
| Actor |
Description |
Role |
| Parent agent / subject |
The user or agent on whose behalf the delegated call is made; grants the delegation |
Primary |
| Delegate (sub-)agent |
The agent that exchanges its credential to act on-behalf-of the subject |
Primary |
| Owner / Admin |
Human owner who creates/revokes delegation grants from the admin UI; holds agents.write |
Supporting |
| Application Manager API |
X-Api-Key-authenticated REST API (AgentsController) hosting the exchange endpoint |
Supporting |
| Application Manager Web |
Bootstrap 5 + Tabler MVC admin UI for managing delegation grants |
Supporting |
| Plan-budget manager |
Carves and tracks the plan-budget allocation referenced by the grant (UC-033) |
Supporting |
| AgentCredentialTokenProtector |
IDataProtector (purpose AgentCredentials) protecting the exchanged credential |
Supporting |
| AuditLog |
Append-only audit trail recording the full actor chain and carried budget |
Supporting |
| Resource server / LLM gateway |
Verifier that honors the delegated token and enforces the carried budget downstream |
External |
Preconditions
- The delegate agent exists, is active, and holds (or can mint) a credential (UC-026).
- The subject (user or parent agent) has an own grant defining the scopes it may delegate, and — for budget delegation — an active parent
AgentPlan with remaining headroom (UC-033).
- An
AgentDelegationGrant exists (or is created in this flow) naming the subject, the allowed scope subset, the budget allocation, the parentPlanId, and an expiry.
- The caller exchanging via
POST /api/agents/token/exchange presents a valid subject token (the credential to be exchanged) plus the delegate's authentication.
- The requested delegated scopes are a subset of the subject's own grant; the requested budget is ≤ the parent plan's remaining headroom.
Postconditions
Success Postconditions
- A delegated
AgentCredential is issued with OnBehalfOfSubjectId set, effective scopes = the granted subset, and a reference to the carried budget allocation.
- The credential's scopes are ⊆ the subject's own grant; the carried budget is ≤ the parent plan's remaining headroom (UC-033 carves the sub-plan allocation).
- Introspection of the delegated credential includes
on_behalf_of (the subject) alongside the delegate (sub).
- An
AuditLog entry records the full actor chain (delegate ⇽ subject) and the carried budget allocation.
Failure Postconditions
- No delegated credential is issued when the requested scope exceeds the subject's grant or the budget exceeds parent headroom.
- A structured RFC 7807 Problem Details (or OAuth
error) response is returned; the exchange yields no token.
- A revoked or expired delegation grant yields no exchange; existing delegated credentials introspect as inactive.
- Failed exchanges are audited (with the attempted actor chain) without leaking token material.
Primary Flow Sequence
sequenceDiagram
participant Subject as Subject (user/parent agent)
participant Delegate as Delegate Agent
participant API as AM API (exchange endpoint)
participant Grants as IAgentDelegationGrantRepository
participant Plan as Plan-Budget Manager (UC-033)
participant Audit as AuditLog
Subject->>API: GrantDelegation (subject, scope subset, budget alloc, parentPlanId, expiry)
API->>Plan: Verify budget ≤ parent plan remaining headroom
Plan-->>API: Headroom OK; reserve sub-plan allocation
API->>Grants: Persist AgentDelegationGrant
API->>Audit: Record GrantDelegation (subject, scopes, budget)
Delegate->>API: POST /api/agents/token/exchange (subject_token, requested scope, grant id)
API->>Grants: Load grant; check scope ⊆ subject grant, not expired/revoked
API->>Plan: Confirm carried budget ≤ remaining allocation
alt scope or budget exceeds grant
API-->>Delegate: 400 invalid_scope / invalid_target
API->>Audit: Record denied exchange (actor chain)
else within grant
API->>API: Mint delegated credential (OnBehalfOfSubjectId, scopes, budget ref)
API->>Audit: Record ExchangeDelegationCredential (delegate ⇽ subject, budget)
API-->>Delegate: 200 { access_token, scope, on_behalf_of, expires_in }
end
UC-027a: Grant Delegation (Scopes + Budget)
Triggers
- An owner/admin (or the subject agent's orchestration) creates a delegation grant via
GrantDelegation, naming the subject, the allowed scope subset, the budget allocation, the parent plan, and an expiry.
Basic Flow
- The caller submits the
GrantDelegation command: subject id (user or agent), allowed scope subset, budget allocation amount, parentPlanId, and expiry.
- The handler confirms the named scopes are a subset of the subject's own grant (no escalation).
- The handler invokes the plan-budget manager (UC-033) to confirm the allocation is ≤ the parent plan's remaining headroom and to carve the sub-plan allocation.
- The handler persists an
AgentDelegationGrant (subject, scope subset, budget allocation, parentPlanId, expiry).
- The handler writes a
GrantDelegation audit event recording the subject, scopes, and carried budget.
- The grant id is returned for use in subsequent exchanges.
Alternative Flows
- A1: Scope exceeds subject grant — A requested scope is outside the subject's own grant; reject with
invalid_scope; nothing persisted; audited.
- A2: Budget exceeds parent headroom — Allocation > parent plan remaining; reject with
insufficient_budget; no carve; audited (delegated to UC-033 enforcement).
- A3: Subject inactive — Subject user/agent disabled or revoked; reject; no grant.
- A4: No parent plan — Budget delegation requested but no active parent
AgentPlan; reject (scopes-only grants are permitted where budget is omitted, matching the Phase 2 scopes-only delegation).
UC-027b: Exchange Credential On-Behalf-Of a Subject
Triggers
- A delegate agent calls
POST /api/agents/token/exchange (RFC 8693) presenting the subject token and naming the delegation grant, to obtain a credential that acts on the subject's behalf.
Basic Flow
- The delegate authenticates (UC-026
private_key_jwt + DPoP) and POSTs the RFC 8693 exchange: subject_token, subject_token_type, the grant id, and the requested delegated scope.
- AM resolves the
AgentDelegationGrant, confirms it is not expired/revoked, and that the requested scope is a subset of both the grant and the subject's own grant.
- AM confirms (via UC-033) that the carried budget is within the grant's allocation and the parent plan's remaining headroom.
- AM mints a delegated
AgentCredential with OnBehalfOfSubjectId = subject, effective scopes = the granted subset, and a reference to the carried budget allocation.
- The credential token is protected via
AgentCredentialTokenProtector (purpose AgentCredentials) and persisted.
- AM writes an
ExchangeDelegationCredential audit event recording the full actor chain (delegate ⇽ subject) and the carried budget.
- AM returns the delegated token with
on_behalf_of in its claims; downstream introspection (UC-026c) surfaces the subject.
Alternative Flows
- A1: Scope exceeds grant — Requested scope outside the grant or the subject's own grant; reject
invalid_scope; audited.
- A2: Budget exceeds allocation — Carried budget over the grant allocation or parent remaining; reject
insufficient_budget.
- A3: Grant expired/revoked — No exchange; existing delegated credentials introspect inactive.
- A4: Subject token invalid —
subject_token not live (revoked/expired per UC-026c); reject invalid_target.
UC-027c: Revoke Delegation
Triggers
- An owner/admin revokes a delegation grant via the admin UI or
RevokeDelegation; revoking the subject or parent plan cascades.
Basic Flow
- The caller submits the grant id via
RevokeDelegation.
- The handler marks the
AgentDelegationGrant revoked (sets RevokedAt) and releases the carved budget allocation back to the parent plan (UC-033).
- The handler writes a
RevokeDelegation audit event with the acting principal and the released budget.
- Outstanding delegated credentials minted under the grant introspect as
active: false (their owning grant is revoked).
Alternative Flows
- A1: Already revoked — Idempotent;
RevokedAt unchanged; no error.
- A2: Grant not found — RFC 7807
404; no change.
- A3: Parent plan aborted/completed — Cascades: dependent grants are treated as revoked; allocations release.
UC-027d: Sub-Agent Chains & Actor-Chain Audit
Triggers
- A parent agent delegates to a sub-agent, which may itself delegate further; each hop must preserve no-escalation and produce an auditable actor chain.
Basic Flow
- A parent agent holds a plan and grants a sub-agent a delegation (scopes + budget carved off the parent plan via UC-033).
- The sub-agent exchanges to obtain a delegated credential; the credential's scopes ⊆ the parent's, and its budget ≤ the parent plan's remaining headroom.
- The sub-agent makes a downstream call; the gateway records a
LlmCallLedger/AuditLog row carrying the full actor chain (sub-agent ⇽ parent agent ⇽ originating subject) and the carried budget allocation.
- If the sub-agent delegates again, each further hop re-applies the subset/headroom checks against the immediately-preceding grant — the chain can only narrow, never widen.
- Aborting or revoking any ancestor in the chain cascades downward: dependent grants and credentials become inactive and unused budget releases up the plan tree.
sequenceDiagram
participant Parent as Parent Agent
participant API as AM API
participant Sub as Sub-Agent
participant GW as LLM Gateway
participant Audit as AuditLog
Parent->>API: GrantDelegation (sub-agent, scopes ⊆ parent, budget ⊆ parent plan)
API->>Audit: Record GrantDelegation (parent ⇽ subject, budget carve)
Sub->>API: POST /api/agents/token/exchange (grant id, scope)
API->>API: scope ⊆ grant? budget ≤ remaining?
API->>Audit: Record ExchangeDelegationCredential (sub ⇽ parent ⇽ subject)
API-->>Sub: 200 delegated token (on_behalf_of=parent, sub-plan budget)
Sub->>GW: Downstream call with delegated token + DPoP
GW->>GW: Enforce scopes + carried sub-plan budget
GW->>Audit: Ledger/audit row with full actor chain + carried budget
Note over Audit: One trail describes sub ⇽ parent ⇽ subject
Alternative Flows
- A1: Escalation attempt mid-chain — A hop requests a scope its parent grant lacks; reject
invalid_scope; chain does not widen.
- A2: Budget overdraw mid-chain — A hop's allocation exceeds the immediate parent's remaining; reject
insufficient_budget.
- A3: Ancestor revoked — Revoking/aborting an ancestor cascades; all descendant grants/credentials introspect inactive; allocations release up the tree.
- A4: Cycle prevention — A delegation that would create a cycle in the plan/grant tree is rejected.
API Endpoints
| Method |
Path |
Auth |
Purpose |
| POST |
/api/agents/token/exchange |
private_key_jwt + DPoP (delegate) |
RFC 8693 on-behalf-of exchange; carries scopes + budget allocation (UC-027b) |
| POST |
/api/agents/{id}/delegations |
X-Api-Key |
Create a delegation grant (GrantDelegation, UC-027a) |
| DELETE |
/api/agents/{id}/delegations/{gid} |
X-Api-Key |
Revoke a delegation grant (RevokeDelegation, UC-027c) |
Representative example — POST /api/agents/token/exchange
Request:
{
"grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
"subject_token": "eyJhbGciOiJFUzI1NiIsInR5cCI6ImF0K2p3dCJ9...",
"subject_token_type": "urn:ietf:params:oauth:token-type:access_token",
"requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
"delegation_grant_id": "01971f4c-9a2b-7c3d-8e4f-5a6b7c8d9e0f",
"scope": "billing:read mcp:invoice-server:get_invoice",
"budget_allocation": { "amount": 2.50, "currency": "USD", "parent_plan_id": "01971f3b-..." }
}
Response (200 OK):
{
"access_token": "eyJhbGciOiJFUzI1NiIsInR5cCI6ImF0K2p3dCJ9...",
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
"token_type": "DPoP",
"expires_in": 300,
"scope": "billing:read mcp:invoice-server:get_invoice",
"on_behalf_of": {
"subject_id": "01971e9a-1234-7d4e-8f90-1a2b3c4d5e6f",
"subject_type": "agent"
},
"budget_allocation": { "amount": 2.50, "currency": "USD", "sub_plan_id": "01971f5d-..." },
"actor_chain": ["agent:delegate", "agent:parent", "user:originating-subject"]
}
Business Rules
| Rule ID |
Description |
| BR-1 |
A delegation's scopes must be a subset of the subject's own grant — no privilege escalation at any hop. |
| BR-2 |
A delegation's budget allocation must be ≤ the parent plan's remaining headroom (carve enforced by UC-033). |
| BR-3 |
Identity scopes and budget allocation travel together in one AgentDelegationGrant envelope. |
| BR-4 |
A delegated credential records OnBehalfOfSubjectId; introspection surfaces on_behalf_of. |
| BR-5 |
Every exchange records the full actor chain (delegate ⇽ subject, plus any ancestors) and the carried budget in AuditLog. |
| BR-6 |
Exchange uses RFC 8693 token exchange; the delegate authenticates with private_key_jwt + DPoP (UC-026). |
| BR-7 |
Revoking a grant releases its carved budget back to the parent plan and invalidates dependent credentials. |
| BR-8 |
Revoking/aborting any ancestor cascades: descendant grants and credentials become inactive; allocations release up the plan tree. |
| BR-9 |
Delegation chains can only narrow (scope ⊆, budget ≤) at each hop; cycles are rejected. |
| BR-10 |
Scopes-only grants are permitted (budget omitted), matching Phase 2 delegation before budget delegation lands in Phase 3. |
Data Requirements
AgentDelegationGrant
| Field |
Type |
Constraints |
| Id |
Guid (UUIDv7) |
Primary key |
| DelegateAgentId |
Guid |
FK → AgentIdentity (the agent receiving the delegation), required, indexed |
| SubjectId |
Guid |
The user or agent on whose behalf the delegate acts, required |
| SubjectType |
enum (User, Agent) |
Required |
| AllowedScopes |
string (space/JSON) |
Subset of the subject's own grant; required |
| BudgetAllocationAmount |
decimal? |
Null for scopes-only grants; ≤ parent plan remaining headroom when set |
| BudgetCurrency |
string? |
ISO 4217; required when BudgetAllocationAmount is set |
| ParentPlanId |
Guid? |
FK → AgentPlan (UC-033); required when budget is delegated |
| SubPlanId |
Guid? |
FK → carved sub-plan (UC-033); set on grant/exchange |
| ExpiresAt |
DateTimeOffset |
Required; bounds the grant lifetime |
| RevokedAt |
DateTimeOffset? |
Null until revoked; non-null ⇒ inactive |
| CreatedBy |
string? |
Acting principal (admin id or subject orchestration) |
| CreatedAt |
DateTimeOffset |
Set on grant |
Security Considerations
- Authentication: The delegate authenticates with
private_key_jwt + DPoP (UC-026); the subject_token must itself be live (introspection per UC-026c). Grant management endpoints require a valid X-Api-Key.
- Authorization / capabilities: Creating and revoking grants requires
agents.write. The exchange enforces scope ⊆ subject grant and budget ≤ parent headroom at issue time — no escalation, mirroring the identity scope-ceiling rule reused for cost caps.
- Data protection: The exchanged credential's token material is protected by
AgentCredentialTokenProtector (purpose AgentCredentials); no subject or delegate private key is stored.
- No-escalation invariant: Each hop can only narrow scopes and budget; cycles are rejected. Ancestor revocation cascades to descendants so a compromised or stale chain cannot continue acting.
- Audit: Every grant, exchange, and revoke records the full actor chain (delegate ⇽ subject ⇽ ancestors) and the carried budget allocation in
AuditLog; failed exchanges are audited with the attempted chain and no token material.
Testing Scenarios
| Test ID |
Scenario |
Expected Result |
| T-1 |
Grant delegation with scopes ⊆ subject grant and budget ≤ parent headroom |
AgentDelegationGrant persisted; sub-plan carved; audit row written |
| T-2 |
Grant with a scope outside the subject's own grant |
Rejected invalid_scope; nothing persisted (BR-1) |
| T-3 |
Grant with budget > parent plan remaining headroom |
Rejected insufficient_budget; no carve (BR-2) |
| T-4 |
Grant against an inactive/revoked subject |
Rejected; no grant (UC-027a A3) |
| T-5 |
Scopes-only grant (budget omitted) with no parent plan |
Permitted; grant created without budget (BR-10, UC-027a A4) |
| T-6 |
Exchange on-behalf-of with scope ⊆ grant and live subject token |
200 delegated token; on_behalf_of present; OnBehalfOfSubjectId set |
| T-7 |
Exchange requesting a scope outside the grant |
Rejected invalid_scope; no token (UC-027b A1) |
| T-8 |
Exchange carrying budget > grant allocation |
Rejected insufficient_budget; no token (UC-027b A2) |
| T-9 |
Exchange against an expired/revoked grant |
Rejected; no token (UC-027b A3) |
| T-10 |
Exchange with an invalid/expired subject_token |
Rejected invalid_target; no token (UC-027b A4) |
| T-11 |
Introspect a delegated credential |
Response includes on_behalf_of (subject) and the delegate sub (BR-4) |
| T-12 |
Revoke a grant, then exchange |
Exchange rejected; dependent credentials introspect inactive (BR-7) |
| T-13 |
Revoke a grant releases carved budget |
Parent plan remaining headroom increases by the released amount (BR-7) |
| T-14 |
Revoke an already-revoked grant |
Idempotent; RevokedAt unchanged (UC-027c A1) |
| T-15 |
Revoke a non-existent grant |
RFC 7807 404; no change |
| T-16 |
Sub-agent chain: parent → sub-agent exchange + downstream call |
Ledger/audit row carries full actor chain sub ⇽ parent ⇽ subject (BR-5) |
| T-17 |
Escalation attempt mid-chain (sub-agent requests scope parent lacks) |
Rejected invalid_scope; chain does not widen (BR-9, UC-027d A1) |
| T-18 |
Budget overdraw mid-chain |
Rejected insufficient_budget (UC-027d A2) |
| T-19 |
Revoke/abort an ancestor in the chain |
Descendant grants/credentials introspect inactive; budget releases up the tree (BR-8, UC-027d A3) |
| T-20 |
Delegation that would form a cycle |
Rejected (BR-9, UC-027d A4) |
| T-21 |
Verify carried budget is recorded in the audit actor-chain entry |
Audit row shows delegate ⇽ subject and the carried allocation (BR-5) |
| T-22 |
Exchange without a valid delegate private_key_jwt / DPoP |
invalid_client / invalid_dpop_proof; no token (BR-6) |
| T-23 |
Parent plan aborted while a grant is outstanding |
Dependent grant treated as revoked; allocation released (UC-027c A3) |
| T-24 |
Effective delegated scopes are the intersection of request and grant |
Token scopes never exceed the grant (BR-1) |
- UC-025: Agent Identity Registration & Lifecycle — provides the delegate agent, its ceiling, and the public keys the exchange authenticates against.
- UC-026: Agent Credential Issuance — the mint/token/introspect/revoke primitives delegation builds on;
OnBehalfOfSubjectId is set here.
- UC-032: Cost-Governance Budget Headroom — the viable-headroom checks bounding what budget a grant may carry.
- UC-033: Agent Plan & Budget Delegation — owns the plan-budget carve/track/reconcile mechanics this UC references for the budget plane.
- UC-006: Role-Based Access Control — the capability model gating grant creation/revocation.
- UC-010: Activity Logging & Audit Trail — the
AuditLog surface the actor-chain delegation records fold into.
- UC-016: Tenant Provisioning & Management — tenant scoping that bounds the subjects and plans participating in delegation.
Revision History
| Version |
Date |
Author |
Changes |
| 1.0 |
June 9, 2026 |
Platform Architecture Team |
Initial draft |