Docs/Authority/Chain Delegation

Chain Delegation

Chain delegation enables hierarchical permission management for multi-agent systems. An orchestrator agent holds a root mandate and delegates narrower scopes to child agents, enforcing the principle of least privilege.


The Problem

In multi-agent systems, every agent typically runs with the same ambient OS permissions. If any agent is compromised (prompt injection, jailbreak), it can access resources beyond its role.

Chain delegation solves this by:


How It Works

┌─────────────────────────────────────────────────────────────────────┐
│              POST /v1/authorize (multi-scope root mandate)           │
│   Orchestrator scopes:                                               │
│     - action: browser.* | resource: https://www.amazon.com/*         │
│     - action: fs.*      | resource: **/workspace/data/**             │
│   mandate_token: eyJhbGci... (depth=0, TTL=300s)                     │
└───────────────────────────────┬─────────────────────────────────────┘
                                │
        ┌───────────────────────┴───────────────────────┐
        ▼                                               ▼
┌───────────────────────┐                     ┌───────────────────────┐
│  POST /v1/delegate    │                     │  POST /v1/delegate    │
│  target: agent:scraper│                     │  target: agent:analyst│
│  action: browser.*    │                     │  action: fs.write     │
│  resource: amazon.com │                     │  resource: workspace/ │
│                       │                     │                       │
│  ✓ Subset of scope 1  │                     │  ✓ Subset of scope 2  │
└───────────────────────┘                     └───────────────────────┘

The orchestrator requests a multi-scope mandate covering all capabilities it needs to delegate. Child agents receive derived mandates with narrower scopes that are cryptographically linked to the parent.


API Reference

POST /v1/delegate

Creates a derived mandate for a child agent.

Request:

{
  "parent_mandate_token": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9...",
  "target_agent_id": "agent:scraper",
  "requested_action": "browser.navigate",
  "requested_resource": "https://www.amazon.com/dp/*",
  "intent_hash": "scrape:product-page",
  "ttl_seconds": 300
}

Response (Success):

{
  "mandate_token": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9...",
  "mandate_id": "m_200d9756fd87d9bd",
  "expires_at": 1710000300,
  "delegation_depth": 1,
  "delegation_chain_hash": "sha256:7f3a2b1c..."
}

Response (Error):

{
  "code": "DELEGATION_EXCEEDS_SCOPE",
  "message": "Requested scope (fs.write, *) exceeds parent scope (browser.*, https://*)"
}

Multi-Scope Authorization

Request authorization for multiple scopes in a single call:

curl -X POST http://127.0.0.1:8787/v1/authorize \
  -H "Content-Type: application/json" \
  -d '{
    "principal": "agent:orchestrator",
    "scopes": [
      {"action": "browser.*", "resource": "https://www.amazon.com/*"},
      {"action": "fs.*", "resource": "**/workspace/data/**"}
    ],
    "intent_hash": "orchestrate:ecommerce:run-123"
  }'

The response includes scopes_authorized showing which scopes matched:

{
  "allowed": true,
  "mandate_token": "m_abc123...",
  "scopes_authorized": [
    {"action": "browser.*", "resource": "https://www.amazon.com/*", "matched_rule": "allow-browser-https"},
    {"action": "fs.*", "resource": "**/workspace/data/**", "matched_rule": "allow-workspace-fs"}
  ]
}

Key Properties

PropertyDescription
Scope NarrowingChild scope must be ⊆ at least one parent scope (OR semantics for multi-scope parents)
TTL CappingChild TTL is always capped to the parent's remaining TTL
Cascade RevocationRevoking a parent mandate invalidates all derived child mandates (O(1) via HashSet)
Cryptographic Linkingdelegation_chain_hash ties each child to its parent for audit trail
Depth LimitsMax delegation depth (default: 5) prevents infinite chains

Sidecar CLI Configuration

The sidecar automatically supports chain delegation. Key CLI arguments:

./predicate-authorityd \
  --policy-file policy.yaml \
  --mandate-ttl-s 300 \
  run
FlagDescriptionDefault
--policy-filePolicy file (JSON/YAML) with rulesRequired
--mandate-ttl-sDefault mandate TTL in seconds300
--portHTTP port for the sidecar8787
--hostBind host127.0.0.1
--log-levelLogging level (trace/debug/info/warn/error)info

Important: CLI arguments must come before the subcommand:

# Correct
./predicate-authorityd --port 9000 --policy-file policy.yaml run

# Wrong
./predicate-authorityd run --port 9000

Scope Validation

The sidecar validates that child scopes are subsets of parent scopes using wildcard matching:

Action Matching

Parent ActionChild ActionResult
browser.*browser.navigate✓ Valid
browser.*fs.write✗ Rejected
fs.*fs.read✓ Valid
*any.action✓ Valid

Resource Matching

Parent ResourceChild ResourceResult
https://www.amazon.com/*https://www.amazon.com/dp/B123✓ Valid
/workspace/data//app/workspace/data/reports/analysis.json✓ Valid
/workspace//etc/passwd✗ Rejected
https://*http://internal:8080✗ Rejected (scheme mismatch)

Error Codes

CodeHTTPDescription
INVALID_PARENT_MANDATE401Parent mandate signature verification failed
PARENT_MANDATE_EXPIRED401Parent mandate has expired
PARENT_MANDATE_REVOKED403Parent mandate has been revoked
DELEGATION_EXCEEDS_SCOPE403Requested scope is not a subset of parent scope
MAX_DELEGATION_DEPTH_EXCEEDED403Delegation chain exceeds maximum depth (default: 5)

Example: CrewAI Multi-Agent Delegation

Here's how chain delegation works in a CrewAI e-commerce demo:

1. Orchestrator Requests Root Mandate

curl -X POST http://127.0.0.1:8787/v1/authorize \
  -H "Content-Type: application/json" \
  -d '{
    "principal": "agent:orchestrator",
    "scopes": [
      {"action": "browser.*", "resource": "https://www.amazon.com/*"},
      {"action": "fs.*", "resource": "**/workspace/data/**"}
    ],
    "intent_hash": "orchestrate:ecommerce-monitoring"
  }'

Response:

{
  "allowed": true,
  "mandate_token": "eyJhbGciOiJFUzI1NiIs...",
  "mandate_id": "m_bc3a42ef63d45fc0"
}

2. Orchestrator Delegates to Scraper

curl -X POST http://127.0.0.1:8787/v1/delegate \
  -H "Content-Type: application/json" \
  -d '{
    "parent_mandate_token": "eyJhbGciOiJFUzI1NiIs...",
    "target_agent_id": "agent:scraper",
    "requested_action": "browser.*",
    "requested_resource": "https://www.amazon.com/*",
    "intent_hash": "scrape:amazon-products"
  }'

Response:

{
  "mandate_token": "eyJhbGciOiJFUzI1NiIs...",
  "mandate_id": "m_200d9756fd87d9bd",
  "expires_at": 1710000300,
  "delegation_depth": 1,
  "delegation_chain_hash": "sha256:7f3a..."
}

3. Orchestrator Delegates to Analyst

curl -X POST http://127.0.0.1:8787/v1/delegate \
  -H "Content-Type: application/json" \
  -d '{
    "parent_mandate_token": "eyJhbGciOiJFUzI1NiIs...",
    "target_agent_id": "agent:analyst",
    "requested_action": "fs.write",
    "requested_resource": "**/workspace/data/reports/**",
    "intent_hash": "analyze:price-report"
  }'

Response:

{
  "mandate_token": "eyJhbGciOiJFUzI1NiIs...",
  "mandate_id": "m_079e1228ae8dc257",
  "expires_at": 1710000300,
  "delegation_depth": 1,
  "delegation_chain_hash": "sha256:9c2d..."
}

Console Output

[Delegation] Requesting multi-scope root mandate for orchestrator...
  ✓ Root mandate issued: bc3a42ef63d45fc0 (depth=0)

[Delegation] Delegating to agent:scraper...
  ✓ Scraper mandate: 200d9756fd87d9bd (depth=1)

[Delegation] Delegating to agent:analyst...
  ✓ Analyst mandate: 079e1228ae8dc257 (depth=1)

Cascade Revocation

When a parent mandate is revoked, all derived mandates become invalid:

# Revoke the orchestrator's mandate
curl -X POST http://127.0.0.1:8787/v1/revoke \
  -H "Content-Type: application/json" \
  -d '{"mandate_id": "m_bc3a42ef63d45fc0"}'

Now any attempt by agent:scraper or agent:analyst to use their derived mandates will fail:

{
  "code": "PARENT_MANDATE_REVOKED",
  "message": "Parent mandate has been revoked"
}

The sidecar maintains an O(1) revocation cache (HashSet) for instant revocation checks.


Performance

MetricTargetActual
Authorization latency< 1ms p990.2-0.8ms
Delegation issuance< 10ms p99~5ms
Revocation check< 1μsO(1) HashSet
Memory footprint< 50MB~15MB idle

Resources