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

  1. The delegate agent exists, is active, and holds (or can mint) a credential (UC-026).
  2. 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).
  3. An AgentDelegationGrant exists (or is created in this flow) naming the subject, the allowed scope subset, the budget allocation, the parentPlanId, and an expiry.
  4. The caller exchanging via POST /api/agents/token/exchange presents a valid subject token (the credential to be exchanged) plus the delegate's authentication.
  5. 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

  1. A delegated AgentCredential is issued with OnBehalfOfSubjectId set, effective scopes = the granted subset, and a reference to the carried budget allocation.
  2. 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).
  3. Introspection of the delegated credential includes on_behalf_of (the subject) alongside the delegate (sub).
  4. An AuditLog entry records the full actor chain (delegate ⇽ subject) and the carried budget allocation.

Failure Postconditions

  1. No delegated credential is issued when the requested scope exceeds the subject's grant or the budget exceeds parent headroom.
  2. A structured RFC 7807 Problem Details (or OAuth error) response is returned; the exchange yields no token.
  3. A revoked or expired delegation grant yields no exchange; existing delegated credentials introspect as inactive.
  4. 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

  1. The caller submits the GrantDelegation command: subject id (user or agent), allowed scope subset, budget allocation amount, parentPlanId, and expiry.
  2. The handler confirms the named scopes are a subset of the subject's own grant (no escalation).
  3. 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.
  4. The handler persists an AgentDelegationGrant (subject, scope subset, budget allocation, parentPlanId, expiry).
  5. The handler writes a GrantDelegation audit event recording the subject, scopes, and carried budget.
  6. 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

  1. 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.
  2. 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.
  3. AM confirms (via UC-033) that the carried budget is within the grant's allocation and the parent plan's remaining headroom.
  4. AM mints a delegated AgentCredential with OnBehalfOfSubjectId = subject, effective scopes = the granted subset, and a reference to the carried budget allocation.
  5. The credential token is protected via AgentCredentialTokenProtector (purpose AgentCredentials) and persisted.
  6. AM writes an ExchangeDelegationCredential audit event recording the full actor chain (delegate ⇽ subject) and the carried budget.
  7. 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

  1. The caller submits the grant id via RevokeDelegation.
  2. The handler marks the AgentDelegationGrant revoked (sets RevokedAt) and releases the carved budget allocation back to the parent plan (UC-033).
  3. The handler writes a RevokeDelegation audit event with the acting principal and the released budget.
  4. 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

  1. A parent agent holds a plan and grants a sub-agent a delegation (scopes + budget carved off the parent plan via UC-033).
  2. 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.
  3. 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.
  4. 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.
  5. 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