# 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.

{% hint style="warning" %}
**Do not pass the orchestrator's token directly to sub-agents.** Sub-agents must obtain their own token via `tokens.delegate()`. Sharing tokens removes auditability — you lose the ability to attribute actions to the specific agent that performed them, and there is no `delegation_depth` to enforce in Cedar policies.
{% endhint %}

### 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.delegate(
    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**](/agent-identity-zeroid/guides/credential-policies.md) to control delegation depth and allowed grant types.
* Read [**Downstream Authorization**](/agent-identity-zeroid/guides/downstream-authorization.md) to verify delegated tokens in your services.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.highflame.ai/agent-identity-zeroid/guides/agent-delegation.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
