Agent Delegation In development

Agent delegation lets one principal — usually an AGENT or SERVICE_ACCOUNT — act under the authority of another principal — usually a USER — for a bounded set of actions, resources, and time.

It exists because two practical patterns are common in modern SaaS:

  • A support automation needs to read one customer's invoice to resolve a ticket without permanently widening anyone's permissions.
  • An AI assistant needs to act on a user's behalf for a single task, with the user's explicit consent and an audit trail tying the action back to both the agent and the user.

Delegation is built on three pieces:

ConceptPurpose
principal_delegation access requestAsks for the right to act for a user. Routed through approval rules.
Durable delegated grantLong-lived authorization that lets a subject mint many delegated sessions until expiry/revocation.
Delegated sessionShort-lived session that combines subject identity with actingFor metadata and intersected permissions.

Read Federated Principal Auth first; agents must authenticate themselves before they can ask to act for anyone.

The mental model

agent authenticates as itself        (OIDC, AWS IAM, etc.)
  -> agent requests delegation        (principal_delegation access request)
  -> grantor (user) approves          (or approval rule routes to a delegate)
  -> session-only OR durable grant    (depends on grantMode)
  -> agent mints delegated session    (short-lived, intersected permissions)
  -> agent calls protected APIs       (audited as agent acting-for user)

The agent never "becomes" the user. The delegated session always identifies the agent as the subject and the user as actingFor. Audit logs preserve both.

Request delegation

POST /access-requests
Content-Type: application/json

{
  "requester": { "type": "AGENT", "id": "support-bot" },
  "type": "principal_delegation",
  "target": {
    "grantor":  { "type": "USER", "id": "alice" },
    "actions":  ["invoice:read", "ticket:comment"],
    "resources": [
      "xrn:saaslayer:billing:*:tn_acme:invoice/inv_123",
      "xrn:saaslayer:support:*:tn_acme:ticket/TKT-123"
    ],
    "scope": "tn_acme",
    "grantMode": "session",
    "maxSessionDuration": 1800
  },
  "reason": "Investigate failed invoice sync for ticket TKT-123"
}

Required fields:

  • requester — typed {type,id}. AGENT and SERVICE_ACCOUNT are allowed in addition to USER.
  • target.grantor — the principal whose authority the requester wants to borrow.
  • target.actions — non-empty list of action strings.
  • target.grantMode — "session" or "durable". See below.
  • target.scope — the scope the delegated session will run under.

Optional:

  • target.resources — pins delegation to specific resources rather than all matching ones.
  • target.subject — defaults to the requester; set explicitly only when the requester is a broker for another subject.
  • target.maxGrantDuration — caps grant lifetime when grantMode = "durable".
  • target.maxSessionDuration — caps minted session lifetime.

grantMode: "session"

Approval is exchanged once for a single short-lived session. No durable grant row is created. Use this for one-shot tasks where the agent should not be able to come back later without a new request.

grantMode: "durable"

Approval creates a DELEGATED_GRANT row. The agent can mint multiple delegated sessions while the grant remains active and unexpired. Use this for recurring agent work the grantor has consented to over a window of time.

Approval routing

Approval rules decide who can approve a delegation request. Configure them per request type and scope:

POST /approval-rules
Content-Type: application/json

{
  "scope": "tn_acme",
  "requestType": "principal_delegation",
  "target": {
    "grantorType": "USER"
  },
  "approvers": [
    { "type": "self_grantor" },
    { "type": "role", "id": "role-tenant-admin" }
  ],
  "requireCount": 1
}

Routing options the matcher understands:

  • The grantor approves themselves (self_grantor).
  • A group, role, tenant admin, or org admin approves on the grantor's behalf when the grantor is unavailable.
  • Stricter rules can require multiple approvers for sensitive scopes or actions.

Use approval rules for policy. Avoid hard-coding "alice approves alice" — model it as self_grantor so the rule generalizes.

Approve, deny, or exchange

POST /access-requests/req_01J.../approve
POST /access-requests/req_01J.../deny
POST /access-requests/req_01J.../exchange

approve records an approval vote. When the approval threshold is reached:

  • grantMode = "session" requests stay pending an exchange call by the subject.
  • grantMode = "durable" requests are marked approved, but a manual approval does not create a DELEGATED_GRANT today. Durable grants are created by standing-policy auto-approval (when the deployment configures the grant manager) and by AAuth consent.

exchange is callable by the subject (not the grantor). It is single-use and idempotent by request ID + subject: a repeated call while the minted session is still active returns the same session metadata; after that session expires, exchange returns already_exchanged.

Durable delegated grants

When a durable grant exists, the subject mints sessions on demand:

POST /delegated-grants/dgrant_01J.../sessions
Content-Type: application/json

{
  "durationSeconds": 900,
  "permissions": {
    "statements": [
      {
        "effect": "can",
        "actions":   ["invoice:read"],
        "resources": ["xrn:saaslayer:billing:*:tn_acme:invoice/inv_123"]
      }
    ]
  }
}

Validation at mint time:

  1. Caller's session subject must equal the grant's subject.
  2. Grant must be active and unexpired.
  3. Requested duration must be ≤ maxSessionDuration on the grant.
  4. Effective permissions are recomputed as an intersection (see below).
  5. The mint is audited.

List grants by subject or grantor:

GET /delegated-grants?subjectType=AGENT&subjectId=support-bot
GET /delegated-grants?grantorType=USER&grantorId=alice

The caller may list grants where it is the named subject or grantor. Listing another principal's grants requires ?scope=<scope> and delegated-grant:manage-others in that scope, and returns only grants in that scope.

Route permissions

Every delegation route requires a saaslayer:roles permission, then the handler checks the caller against the record:

RouteRoute permissionWho passes the record check
GET /delegated-grants, GET /delegated-grants/:iddelegated-grant:readthe grant's subject or grantor, else delegated-grant:manage-others in the grant scope
DELETE /delegated-grants/:iddelegated-grant:revokethe grantor, else delegated-grant:manage-others in the grant scope
POST /delegated-grants/:id/sessionsdelegated-grant:mint-sessionthe grant's subject only
GET /standing-delegation-policies, GET /standing-delegation-policies/:iddelegation-rule:readthe owner, grantor or creator, else delegation-rule:manage-others in the policy scope
POST /standing-delegation-policiesdelegation-rule:createsee the creation rules below
DELETE /standing-delegation-policies/:iddelegation-rule:revokethe owner or the direct grantor (opt-out), else delegation-rule:manage-others in the policy scope

The acting principal (revoker, creator) is always the authenticated caller, never a body field.

Permission intersection

A delegated session never has more authority than the smallest of:

effective = subject's current permissions
          ∩ grantor's current permissions
          ∩ approved delegation constraints (actions, resources, scope)
          ∩ optional session policy

Key consequences:

  • If the grantor loses a permission, new delegated sessions reflect that loss. Existing live sessions are not automatically revoked — they live until expiry or explicit revocation.
  • Subject permissions matter too. An agent that lost the invoice:read capability cannot mint a session with invoice:read, even if the grantor still has it.
  • Optional permissions on the mint call can only further restrict, never expand.

Recompute is on every mint. Reusable principal-scope permission documents keep the cost bounded — the engine does not rebuild the full RBAC graph on each renewal.

Delegated session shape

{
  "type": "session",
  "id": "sess_01J...",
  "attributes": {
    "token": "sess_<jwt>",
    "sessionKind": "delegated_principal",
    "subject":   { "type": "AGENT", "id": "support-bot" },
    "actingFor": { "type": "USER",  "id": "alice" },
    "sourceDelegatedGrantId": "dgrant_01J...",
    "permissionsMode": "intersection",
    "scope": "tn_acme",
    "expiresAt": "2026-05-17T18:15:00.000Z"
  }
}

Use the token exactly like any other sess_<jwt>:

Authorization: Bearer sess_<jwt>

The authorizer attaches both subject and actingFor to the request context. Application code that audits or filters by acting-user can read actingFor to attribute the action correctly.

Standing delegation policies

Standing delegation policies auto-approve matching principal_delegation requests. They exist for repeated low-risk agent work where re-approving every request is friction without security value.

They are intentionally explicit and a little uncomfortable to create:

POST /standing-delegation-policies
Content-Type: application/json

{
  "owner":   { "type": "USER",  "id": "alice" },
  "subject": { "type": "AGENT", "id": "support-bot" },
  "actions":   ["ticket:read", "ticket:comment"],
  "resources": ["xrn:saaslayer:support:*:tn_acme:ticket/*"],
  "scope": "tn_acme",
  "maxGrantDuration":  86400,
  "maxSessionDuration": 900,
  "confirm": "CONFIRM"
}

Rules:

  • confirm must be the exact string "CONFIRM". Anything else fails closed.
  • A user-owned policy must be owned by the caller and can only delegate the caller's own authority (no grantorSelector, no other grantor).
  • An admin-managed policy (WORKSPACE, ORG or GROUP owner) requires delegation-rule:manage-others in the policy scope.
  • System policies cannot be created over the API.
  • A policy wider than the creator's own permissions is refused at creation.
  • The policy is auditable, listable, and revocable by the owner.
  • Auto-approval needs the deployment to configure the standing-delegation and delegated-grant managers on the access-request manager. Without them, matching requests are not auto-approved.
  • Every auto-approval still writes a complete access-request record so the timeline shows what was approved, when, and by which policy.

Admin-managed standing policies are also supported and use subjectSelector / grantorSelector (e.g., "support-agents group acting for supportable-users group"). Even when an admin creates the policy, the grantor gets a loud notification the first time it fires for them, and the grantor (or an authorized admin) can revoke or opt out per the deployment's compliance posture.

Audit distinguishes:

  • user_owned_standing_delegation
  • admin_managed_standing_delegation
  • system_policy_standing_delegation

Warning. Standing delegation widens an agent's blast radius. Use it only when you trust the subject, the resource scope is narrow, and the maximum grant/session durations are short.

Revocation

DELETE /delegated-grants/dgrant_01J...

The grantor can revoke. Anyone else needs delegated-grant:manage-others in the grant's scope (for example a tenant or org admin role). The revoker recorded on the grant is the authenticated caller; a revokedBy body field is ignored. Revoking an already revoked grant returns it unchanged.

Revocation blocks future session minting from that grant. Live sessions minted before revocation remain valid until their own expiresAt unless explicitly revoked via the SESSION row.

For high-risk incidents, mass-revoke all sessions acting for a principal using the ACTING_FOR#… GSI on the SESSION table.

Audit and observability

Every delegation-related write is auditable:

  • Access request created, approved, denied, expired.
  • Standing policy created or revoked.
  • Durable grant created, used to mint a session, revoked.
  • Session minted, used, revoked, expired.

Audit never collapses a delegated session into "the user did it." The actor is the agent; actingFor is metadata. Reports that need a user-centric view should aggregate over actingFor themselves.

When not to use delegation

  • If the agent should always have the permission, assign it directly to the agent. Delegation adds complexity that pays off only when authority comes from another principal.
  • If the workload is just a service account doing its own job, use a direct principal session instead.
  • If the request will happen exactly once and never recur, use grantMode: "session" rather than "durable". Durable grants exist for recurring patterns, not one-offs.
  • Federated Principal Auth — how the agent or service account authenticates itself in the first place.
  • Access Requests — the broader access-request and approval framework.
  • Event Delivery — notification transports for approval prompts and grant lifecycle events.