# Delegating credentials to sub-agents

When your agent spawns a worker — a VM, a sub-process, a CI runner — it can share a subset of its permissions without exposing its own identity.

## When to delegate

- **Multi-agent workflows**  
  Parent agent orchestrates, sub-agents execute specific API calls.
- **Ephemeral VMs**  
  Spin up a worker VM that needs GitHub access for 2 hours, then auto-expires.
- **CI / CD pipelines**  
  Give your pipeline scoped API access without storing long-lived secrets.
- **Sandboxed execution**  
  Run untrusted code with minimal permissions — it can't escalate beyond its delegate scope.

## Create a scoped delegate

A scoped delegate gets a fixed subset of the parent's approved scopes. No dynamic expansion — what you give is what it gets.

```terminal
$ keychains delegates add pr_a1b2c3 --scopes "github::repo" --ttl 3600
> Delegate created: del_x7y8z9
> Mode: scoped
> Scopes: github::repo
> Expires in: 1 hour
> Ship KEYCHAINS_DELEGATE_ID and KEYCHAINS_DELEGATE_KEY to the remote system
```

## Create a wildcard delegate

A wildcard delegate starts with zero scopes and accumulates them as the user approves. The parent's scopes act as a hard ceiling.

```terminal
$ keychains delegates add pr_a1b2c3 --wildcard --ttl 7200
> Delegate created: del_w1c2a3
> Mode: wildcard
> Scopes: (none — approved on demand)
> Parent ceiling: github::repo, google::gmail.readonly
```

Note: The wildcard delegate can only request scopes the parent has already approved. Requesting a scope outside the parent's set returns a hard `scope_refused` error — no approval URL.

## The two-tier scope check

When a wildcard delegate requests a scope, two checks happen:

- **Tier 1 — Parent Ceiling**  
  Is the scope in the parent's `approvedScopes`? If no: **hard refuse**. No approval URL.
- **Tier 2 — Delegate Approval**  
  Scope is in parent but not the delegate? **Ask user** — returns 403 with approval URL.

## Using delegates from code

```typescript
import { mintDelegateToken, proxyFetch } from '@keychains.dev/machine-sdk';
// On the remote system — env vars shipped from parent
const { token } = await mintDelegateToken(
  process.env.KEYCHAINS_DELEGATE_ID,
  process.env.KEYCHAINS_DELEGATE_KEY
);
const res = await proxyFetch('https://api.github.com/user/repos', { token });
```

Warning: Delegate private keys are secrets. Always use short TTLs and revoke delegates as soon as the task completes.

## Revoking a delegate

Revoke from the CLI, SDK, or dashboard. Takes effect immediately.

```terminal
$ keychains delegates rm del_x7y8z9
> Delegate del_x7y8z9 revoked
```

Tip: Revoking the parent permission automatically revokes all its delegates via cascade revocation.

## Delegation mode matrix

| Parent | Delegate | Behavior |
| --- | --- | --- |
| scoped | scoped | Fixed subset. No expansion. |
| scoped | wildcard | Accumulates up to parent's fixed set. |
| wildcard | scoped | Fixed subset of parent's dynamic pool. |
| wildcard | wildcard | Dynamic accumulation. Parent is hard ceiling.
