# Agent Delegation

This guide shows how to model orchestrator to sub-agent delegation with ZeroID using RFC 8693 token exchange.

The key rule is that sub-agents do not borrow the orchestrator's identity. They receive their own token, with their own identity, plus a verifiable record of who delegated authority to them.

### The Delegation Model

At issuance time:

* the **orchestrator** proves it currently holds a valid credential
* the **sub-agent** proves its own identity using a signed assertion
* ZeroID issues a new token where:
  * `sub` is the sub-agent
  * `act.sub` is the orchestrator
  * `scopes` are downscoped to an allowed intersection

This gives you clear attribution without shared credentials.

### Before You Start

You need:

* a registered orchestrator agent
* a registered sub-agent
* a valid access token for the orchestrator
* a private key for the sub-agent
* a registered `public_key_pem` for the sub-agent identity

### Step 1: Register the Orchestrator

Python:

```python
orchestrator = client.agents.register(
    name="Billing Orchestrator",
    external_id="billing-orchestrator",
    sub_type="orchestrator",
    trust_level="first_party",
    created_by="dev@company.com",
)
```

### Step 2: Register the Sub-Agent With a Public Key

Python:

```python
tool_agent = client.agents.register(
    name="Data Fetcher",
    external_id="data-fetcher",
    sub_type="tool_agent",
    trust_level="first_party",
    created_by="dev@company.com",
)
```

If you are using `jwt_bearer` or `token_exchange`, register the sub-agent with a real public key so it can sign an assertion with its private key.

### Step 3: Get an Orchestrator Access Token

```python
orchestrator_token = client.tokens.issue(
    grant_type="api_key",
    api_key=orchestrator.api_key,
)
```

### Step 4: Build the Sub-Agent Assertion

The sub-agent signs a short-lived JWT assertion with:

* `iss = sub-agent WIMSE URI`
* `sub = sub-agent WIMSE URI`
* `aud = ZeroID issuer`

Example shape:

```json
{
  "iss": "spiffe://zeroid.dev/acct-demo/proj-demo/agent/data-fetcher",
  "sub": "spiffe://zeroid.dev/acct-demo/proj-demo/agent/data-fetcher",
  "aud": ["https://auth.zeroid.dev"],
  "iat": 1710000000,
  "exp": 1710000300
}
```

### Step 5: Exchange the Token

If your client was initialized with the orchestrator API key, the Python SDK can use the cached access token automatically:

```python
delegated = client.tokens.exchange(
    actor_token=actor_token,
    scope="data:read",
)
```

If you want to call the token endpoint directly:

```bash
curl http://localhost:8899/oauth2/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
    "subject_token": "<orchestrator-access-token>",
    "actor_token": "<sub-agent-assertion>",
    "scope": "data:read"
  }'
```

### Step 6: Inspect the Result

The delegated token should reflect:

* `sub` as the sub-agent
* `act.sub` as the orchestrator
* `grant_type` as `token_exchange`
* `delegation_depth` incremented from the parent token

That lets downstream systems answer two questions independently:

* Who is acting right now?
* Who delegated that authority?

### Scope Attenuation

ZeroID does not let the orchestrator mint arbitrary power for a sub-agent.

The delegated scope must fit inside:

* the scopes currently held by the orchestrator
* the allowed scopes configured for the sub-agent
* any applicable credential policy

That is what makes delegation safe to automate.

### Design Recommendations

Use a separate identity for each operational role:

* one identity for the orchestrator
* one identity for each tool-facing or specialized sub-agent

Do not:

* reuse the orchestrator identity for all downstream work
* pass the orchestrator's token directly to the sub-agent
* skip registering public keys for identities that should prove themselves cryptographically

### Failure Modes To Expect

Common reasons for token exchange to fail:

* invalid or expired `subject_token`
* malformed or invalid `actor_token`
* unknown sub-agent identity
* missing or incorrect `public_key_pem`
* requested scope exceeds what can be delegated
* policy blocks the grant type or delegation depth

#### What's Next?

* Read [**Credential Policies**](https://docs.highflame.ai/documentation/agent-identity/guides/credential-policies) to control delegation depth and allowed grant types.
* Read [**Downstream Authorization**](https://docs.highflame.ai/documentation/agent-identity/guides/downstream-authorization) to verify delegated tokens in your services.
