# Holon Caller Mandate — v0.1 (draft)

The [manifest](manifest.md) says what an agent **offers**. The mandate says what an
agent **is allowed to do**: which agents it may call, how much it may spend, and what
may happen to the data it passes on.

It is the piece that lets agents act without a human watching every call, while the
human stays in control. More autonomy is granted by widening a mandate, not by removing it.

Machine-checkable definition: [`schema/holon-mandate.schema.json`](../schema/holon-mandate.schema.json).

```sh
holon mandate check <mandate.yaml> <holon.yaml|dir>...   # may the grantee call these agents?
holon mandate attenuate <parent.yaml> <child.yaml>       # is the sub-mandate within its parent?
holon search [query] --mandate <mandate.yaml>            # rank only what the mandate permits
```

---

## 1. Model

```
alice (human)
  └─ root mandate ──────────► alice/assistant
                                 └─ sub-mandate ──────► holon-labs/invoice-pipeline
                                                           └─ calls ► acme/table-extract
```

- A **root mandate** is issued by a human account to an agent (the grantee).
- The grantee MAY issue **sub-mandates** to the agents it calls. A sub-mandate MUST be
  an [attenuation](#4-attenuation) of its parent: it can narrow everything and widen nothing.
- The gateway checks every call against the caller's mandate **before** the call is made.
  Every call produces a receipt: which agent, under which mandate, the worst case, the
  actual cost, and the decision with its reasons.

This is the capability-token pattern (as in macaroons or UCAN) applied to spending and
delegation between agents.

## 2. Fields

| Field | Rule |
|---|---|
| `issuer` / `grantee` | Who grants, who acts. For a sub-mandate, `issuer` MUST be the parent's grantee. |
| `valid.from` / `valid.until` | Mandates always expire. |
| `budget.total`, `per_call`, `per_day` | Decimal strings. `per_call` bounds a single call's **worst case**. |
| `budget.fx` | Rates into `budget.currency`. A call priced in a currency without a rate is refused. Conversion rounds up. |
| `approval.above` | Calls whose worst case exceeds this are held for the issuer's approval instead of being refused. |
| `allow.capabilities` | Class patterns: `extraction.table`, `extraction.*` (subtree) or `*`. |
| `allow.agents` / `deny.agents` | `namespace/name` or `namespace/*`. Deny wins. |
| `allow.published` | Hosted gateway: `true` lets capability calls be routed to agents published by other authors. Off by default; calling an agent by name is not affected. |
| `require` | `charge_on_success`, `runtimes`, and measured thresholds: `min_success_rate`, `max_latency_p95_s`, `min_runs_30d`, `min_eval_score` ([evaluations](evals.md)). |
| `data` | `max_retention`, `training_use: false`, `allow_egress: false`. |
| `delegation.max_depth` | How many more levels of agents may appear below the direct callee. Defaults to 0. |

## 3. Admission: may this call happen?

For a caller holding mandate **M** and a callee with manifest **A**, the call is:

- **denied** if any of the following fails:
  - M is valid now, and A is not matched by `deny`;
  - A matches `allow` by capability class or by agent;
  - if A declares `calls`: `delegation.max_depth ≥ 1`, and every declared downstream
    capability or agent is itself within `allow`;
  - A's pricing, runtime and data policy meet `require` and `data`;
  - A's measured record meets the thresholds. **No record means the threshold is
    unmet**: an unmeasured agent is not a trusted agent;
  - `worst case ≤ per_call`, and neither `total` nor `per_day` would be exceeded
    counting the worst case.
- **held for approval** if it passes and the worst case exceeds `approval.above`.
- **allowed** otherwise.

`worst case` = price ceiling (`amount`, or `max` for per-unit and quote) + `downstream.cap`
for passthrough pricing, converted into the mandate's currency.

Budgets are charged at the **worst case** when a call starts, and the difference is
released when it settles. Parallel calls therefore cannot overrun a budget together.

## 4. Attenuation

A sub-mandate C of parent P is valid only if:

- `C.parent = P.id` and `C.issuer = P.grantee`;
- C's validity window is inside P's;
- same currency, and every budget ceiling in P exists in C and is not higher;
  the same applies to `approval.above`;
- every `allow` pattern in C is covered by a pattern in P;
- every `deny` pattern in P is kept in C;
- every `require` and `data` condition in P is kept or tightened;
- `C.delegation.max_depth ≤ P.delegation.max_depth − 1`. Each hop consumes one level,
  so delegation chains always terminate.

## 5. Ranking under a mandate

`holon search --mandate` removes the agents the mandate denies (each with its first
reason) and ranks the rest by relevance, then by **cost per successful run**
(`worst case ÷ success rate`), then by p95 latency. List price alone is a poor signal:
a cheap agent that fails half the time costs double.

### Cold start

An unmeasured agent is not trusted, so a mandate with measured thresholds refuses brand-new
agents. Platform [evaluations](evals.md) close the gap. They produce `eval_score` and feed
`success_rate` without counting as usage. A mandate that requires `min_eval_score` (plus, if it
wants, `min_success_rate`) accepts a new agent once it has been evaluated. `min_runs_30d` stays
for mandates that also want a usage history.

## 6. Open questions
- **Signatures:** mandates must be signed by the issuer and chained by hash. v0.1
  specifies content only.
- **Revocation:** a revocation list checked by the gateway, and what happens to calls in flight.
- **Spend state:** `spent.total` and `spent.today` are gateway state. The reference
  CLI takes them as flags (`--spent-total`, `--spent-today`).
- **Data classes:** tagging inputs (PII, health, secrets) and restricting which agents may see each class.
