# Holon Gateway — v0 (dev)

Every call between agents goes through the gateway. It turns the two contracts,
the [manifest](manifest.md) (what an agent offers) and the [mandate](mandate.md)
(what a caller may do), into enforced behaviour. Each call produces a receipt.

The reference implementation (`src/gateway.mjs`) is a **local dev gateway**. It
runs agents from `agent.mjs` files, keeps state in a JSON file, and has no
authentication. The rules below are the ones a production gateway must also follow. The
hosted mode (§7) adds accounts, API keys, Postgres and an append-only journal.

## 1. Lifecycle of a call

```
request ─► resolve ─► admit ─► check input ─► reserve ─► run ─► check output ─► settle ─► receipt
```

1. **Resolve.** `agent: ns/name[@range]` picks the highest listed version in range.
   `capability: class` ranks every implementer under the caller's mandate (by cost per
   successful run) and picks the best one the mandate allows.
2. **Admit.** `checkCall(mandate, manifest)` returns allow, approve or deny
   ([mandate §3](mandate.md#3-admission-may-this-call-happen)). `approve` creates a pending
   approval bound to the mandate, the exact agent version and a hash of the input. Inside a delegated
   run nobody can approve, so it becomes a deny.
3. **Check input.** The input must validate against `interface.input`. Otherwise the call
   is `rejected_input` and nothing is reserved or billed.
4. **Reserve.** The worst case is reserved in the mandate's budget and held on the
   payer's account. Parallel calls see each other's reservations, so together they can't
   overrun a budget.
5. **Run.** The call has a hard timeout of `runtime.limits.timeout_s` (default 60s). If the agent delegates, the gateway
   derives a sub-mandate (§3) and gives the agent `ctx.call`.
6. **Check output.** The output must validate against `interface.output`. Otherwise the call
   fails with `invalid_output`.
7. **Settle.** Only the real cost is charged, the rest of the hold is released,
   and the run is added to the agent's measured record.

## 2. What gets billed

| Outcome | Billed? |
|---|---|
| Output valid | Yes |
| Declared error with `billed: true`, under `charge_on: attempt` | Yes |
| Declared error, any other case | No |
| Undeclared error, crash, timeout, invalid output | **Never** |
| Denied, pending approval, rejected input | Never: nothing ran |

Price actually charged: `per_run` = `amount`. `per_unit` = `amount × ctx.units(n)`,
capped at `max`. `quote` = `ctx.quote(x)`, capped at `max`. Everything is converted into
the mandate's currency at the mandate's `fx` rate, rounded up.

### Caller cap: `max_cost`

A caller MAY send `max_cost` with a call: its own ceiling, in the mandate's currency. When it is
lower than the agent's listed worst case:

- admission, approval and budget checks use `max_cost` instead. A per-unit agent listed at
  "up to 3.00" can be called for a 0.20 job without asking the human;
- the charge is capped at `max_cost`. If the agent reports more units, it absorbs the overrun;
- the agent receives the cap (in its own currency) and SHOULD refuse up front with a declared,
  non-billed error when its estimate exceeds it (`over_budget` by convention);
- for an orchestrator, delegated calls get whatever the cap leaves after the agent's own price;
- an approval is bound to the `max_cost` it was requested with;
- a `per_run` price is fixed: a `max_cost` below it is denied, it never buys the run for less.

### Split

```
amount ─► 10% platform fee (rounded down)
       └► creator share ─► minus royalty to the upstream agent ─► minus its royalty to its own upstream …
```

Royalties are computed on each level's revenue, as specified in
[manifest §8](manifest.md#lineage-forks-that-pay-upstream). Money is conserved exactly:
every micro-unit taken from the payer lands in some account.

## 3. Delegation

When an agent that declares `calls` is run, the gateway derives its sub-mandate:

| Field | Value |
|---|---|
| `issuer` → `grantee` | the caller's grantee → the agent being run |
| `allow` | exactly the agent's declared `calls` (capabilities and agent ids) |
| `budget.total` | `downstream.cap` (or the agent's own price for `included`) |
| `require`, `data`, `deny`, `approval`, `fx` | inherited unchanged |
| `delegation.max_depth` | parent − 1 |

The derived sub-mandate is checked with `checkAttenuation` before use, and it can never widen its parent.
Each downstream call must also match one of the agent's declared `calls`, and its worst
case must fit that entry's `max_cost`. When resolving a capability, candidates that
don't fit the slot are skipped.

**Who pays downstream calls.** With `passthrough`, the original payer pays, out of the
hold already reserved for the orchestrator's worst case, which includes the cap. With
`included`, the orchestrator's own account pays, with its own hold.

A run is not settled until all of its downstream calls have settled, including calls
still in flight after a timeout.

## 4. Receipts

A receipt is written for every request, including denied ones:

```json
{
  "id": "r4", "parent": null, "mandate": "alice/bookkeeping-q4", "caller": "alice/assistant",
  "agent": "holon-labs/invoice-pipeline@0.4.0", "status": "succeeded", "approval": "a3",
  "worst_case": "3.10", "currency": "EUR", "payer": "alice",
  "sub_mandate": "alice/bookkeeping-q4.r4",
  "billed": true, "cost": { "own": "0.10", "downstream": "0.092", "total": "0.192" },
  "splits": [{ "to": "holon", "amount": "0.01" }, { "to": "holon-labs", "amount": "0.09" }],
  "children": ["r5", "r6", "…"], "duration_ms": 10
}
```

Receipts never contain inputs or outputs: the gateway itself keeps no data from a call. The input
attached to a pending approval is deleted once the approval is used or rejected.

## 5. HTTP API (`holon serve`)

| Method | Path | |
|---|---|---|
| `GET` | `/v0/agents?q=&capability=&mandate=` | Ranked search. With `mandate`: costs converted, denied agents listed with a reason |
| `GET` | `/v0/agents/{ns}/{name}[@range]` | Manifest, measured record, versions |
| `POST` | `/v0/calls` | `{mandate, agent \| capability, input, max_cost?}` → `{output, receipt}` |
| `GET` | `/v0/approvals` | Pending approvals |
| `POST` | `/v0/approvals/{id}` | `{}` to approve and run, or `{"decision":"reject"}` |
| `GET` | `/v0/receipts[?mandate=]`, `/v0/receipts/{id}` | Receipt trees |
| `GET` | `/v0/mandates/{id}` | Mandate and its spent, reserved and remaining budget |
| `GET` | `/v0/accounts` | Balances |

`POST /v0/calls` returns 200 when the call succeeded, 202 when it is pending approval,
403 when denied, 422 when the input is rejected, and 502 when the agent failed.

## 6. Runtimes

### MCP agents (`runtime.kind: mcp`): the Holon MCP agent profile

An agent can be any MCP server reachable over Streamable HTTP. The gateway connects to
`runtime.endpoint` and calls the tool named `runtime.tool`:

| Direction | Carries |
|---|---|
| request `arguments` | the call's input, already validated against `interface.input` |
| request `_meta["holon/max_cost"]` | `{amount, currency}`: the ceiling for this run, when the caller set one |
| result `structuredContent` | the output (else the first text block, parsed as JSON), validated against `interface.output` |
| result `isError` + `structuredContent.error.code` | a failure. Billing follows the code as declared in `interface.errors`; unknown codes are never billed |
| result `_meta["holon/units"]` | units consumed, for `per_unit` pricing (integer) |
| result `_meta["holon/quote"]` | the price, for `quote` pricing (decimal string) |
| request `_meta["holon/files"]` | hosted gateway: for each `holon://files/…` handle in the input, `{url, content_type, size, sha256, expires}`. The `url` is a signed link valid 15 minutes; the agent checks size and SHA-256 after download |

The hard timeout aborts the MCP request, so the agent sees the cancellation. An unreachable
endpoint fails with `runtime_unavailable`, which is never billed.

**Authentication.** An author's server may require a token: the author sets it once with
`PUT /v0/agents/{ns}/{name}/credential` (`{"token": "…"}`, human key) or `holon credential set`,
and the hosted gateway then sends `Authorization: Bearer <token>` on every request to that
agent's `runtime.endpoint`, for all its versions, evaluations included. The token is write-only:
no endpoint returns it, the author's view only says whether one is set. It is stored encrypted
(AES-256-GCM, key derived from `HOLON_FILE_SECRET`: changing that secret means authors set their
tokens again). `DELETE` on the same path removes it. It can be set before the first version is
published, so the evaluation at publish time gets through.

`agents/host.mjs` is a reference host: it serves every `agents/*/tool.mjs` over one endpoint
following this profile. The two agents in `agents/` are real: `holon-labs/csv-profile`
(deterministic) and `holon-labs/summarize` (Claude, billed per 1k tokens).

Remote runtimes are **attested, not enforced**: the gateway cannot see what the author's server
does with the data. The Agent Record says so.

### Webhook agents (`runtime.kind: webhook`)

For an endpoint that is not an MCP server: an n8n workflow (Webhook node, then Respond to
Webhook), a Make scenario, a small API.

| Direction | Carries |
|---|---|
| request | `POST runtime.endpoint`, body = the input as JSON, already validated against `interface.input` |
| request headers | `x-holon-agent: <id>@<version>`, `x-holon-call: <receipt id>`, and `Authorization: Bearer <token>` when the author set an endpoint credential |
| answer, 2xx | a JSON body: the output, validated against `interface.output` |
| answer with `{"error": {"code", "message"}}` | a failure, any status. Billing follows the code as declared in `interface.errors`; unknown codes are never billed |
| anything else | a non-2xx status or a body that is not JSON: `agent_error`, never billed |

Redirects are not followed; published webhooks go through the same guarded client as MCP agents.
A webhook agent is priced `per_run` or `free`: its answer has no channel for units or a quote.

**Drafting a listing.** `POST /v0/agents/draft` (human key) with `{name, endpoint, input, summary?,
price?}` calls the endpoint once with the example input (with the credential already set for
`<handle>/<slug of name>`, if any) and returns a draft manifest (`manifest` and `yaml`): schemas
describing the example input and the answer, license `proprietary`, the answer as a successful
example. Nothing is published or stored; the author reviews it, then publishes it. The console's
"Connect a webhook" form does both steps.

### Dev runtime

For other runtime kinds, the dev gateway loads `agent.mjs` next to each `holon.yaml`:

```js
export default async function run(input, ctx) {
  const r = await ctx.call({ capability: 'extraction.table' }, { file });  // or ctx.call('ns/name@^1', input)
  if (!r.ok) throw ctx.error('no_table_found');                            // a declared error code
  ctx.units(3);            // per_unit pricing
  return output;           // validated against interface.output
}
```

`container` and `a2a` runtimes will plug in through the same `loadHandler` option.

## 7. Hosted gateway (`holon serve --hosted`)

The same gateway, for other people: accounts, API keys, and the MCP server over Streamable
HTTP. Postgres is the source of truth (`DATABASE_URL`; without it, PGlite, an embedded
Postgres, in `--db`). One process keeps the gateway state in memory, rebuilt from Postgres at
start-up, and writes every change back before the API answers. A call reserves its worst case
before it awaits the agent, so concurrent calls cannot overspend and a slow agent does not block
anyone else. If a write fails, the process stops: memory would be ahead of the truth.

### The journal

Every movement of money is a **journal entry**: two or more lines, one per account, that sum
to exactly zero, in integer micro-units. Balances are sums of lines. Postgres enforces it:

- an entry whose lines do not sum to zero is refused at commit;
- journal rows can never be updated or deleted (a correction is a new entry);
- each entry has a unique idempotency key: `signup:<handle>`, `settle:<receipt>`,
  `admin-credit:<uuid>`. The same movement cannot be recorded twice.

Money entering the platform comes from an `external:*` account (today only
`external:demo-credit`), so the sum over every account is always zero. A settlement and the
receipt it pays for are written in the same transaction. Holds are not journaled: they only
exist while a call runs.

| Entry | Lines (example) |
|---|---|
| sign-up credit | `external:demo-credit` −5.00, `bob` +5.00 |
| settlement of a 0.035 call to a fork | `bob` −0.035, `holon` +0.0035, `lena` +0.026775, `acme` +0.004725 |

Every finished run (succeeded or failed) also lands in `call_events`, append-only: agent,
status, error, latency, cost billed, whether it was delegated. The Agent Record is computed
from there: this is what the platform measures.

### Idempotent calls

`POST /v0/calls` accepts an `Idempotency-Key` header (per API key). A call re-sent with a key
already used is not run again: the response carries `"replayed": true` and the original
receipt (outputs are never stored, so `output` is null). A key whose call is still running gets
`409`. A restart marks the keys of calls it interrupted: their outcome is unknown (the call may
have been billed just before), so they also get `409`, telling the caller to check
`GET /v0/receipts` before sending the call again with a new key. A key is never run twice.

### Principals

| Key | Who | May |
|---|---|---|
| `hlk_h_…` human | the issuer | create mandates and agent keys, call under their mandates, see their receipts and balance, **decide on approvals** |
| `hlk_a_…` agent | a grantee | act under **one** mandate: search, call, read its receipts and approvals. Never decide on an approval |
| admin (`HOLON_ADMIN_KEY`) | the operator | top up demo credit, see all balances. Never decides on someone's approval |
| none | anyone | read the catalogue, sign up |

Keys are shown once and stored as SHA-256 hashes. Every resource is scoped: a principal sees
its own mandates (and the sub-mandates derived from them), receipts, approvals and balance, and
gets `404` for anyone else's.

### Accounts

Two ways in, both giving demo credit (`HOLON_SIGNUP_CREDIT`, default 5.00 EUR), a starter
mandate `<handle>/sandbox`, a human key and an agent key. There are no real payments yet:
credit is demo money.

- **Sign in with GitHub** (required to publish). `GET /v0/auth/github` sends the browser to
  GitHub with a random `state`, also set in an `HttpOnly`, `SameSite=Lax` cookie; the callback
  accepts it once, from that browser only, within 10 minutes. The gateway reads the GitHub
  account (stable id, login, primary verified email), never keeps the GitHub token, and sends
  the browser to `/console#login=<code>`: a one-time code, valid 2 minutes, that the console
  trades with `POST /v0/auth/exchange` for the result. No key ever travels in a URL. The first
  sign-in creates the account with handle = the GitHub login in lowercase; an account made
  earlier with the same email, which GitHub has now verified, is linked instead. Each sign-in
  mints a new human key that **expires after 30 days**; the agent key does not expire. A
  disabled account gets no new key.
- **Email** (`POST /v0/signup {handle, email}`): for callers. The email is not verified, and
  such an account cannot publish until it signs in with GitHub (same email: linked).

Handles are 2 to 39 characters, like GitHub logins. Handles that would collect other people's
money are refused: every agent author's namespace in the registry, the platform account, and a
reserved list (with the project's former names). Signups are rate-limited per address, and every
key per minute.

### Approvals and attended clients

An approval form (MCP elicitation) is only safe when a human is at the client. Each agent key
says whether it is `ask_in_client`: the starter key is (it is meant for Claude Code or Claude
Desktop); keys from `POST /v0/keys` are not unless asked. Without it, a held call stays
`pending_approval` and only the human key decides: `POST /v0/approvals/{id}` with `{}` (approve
and run), `{"run": false}` (grant, the agent re-submits with `approval`), or
`{"decision": "reject"}`.

### Restarts

At start-up, calls that were in flight are closed as `failed` with `gateway_restarted`, never
billed; holds and reservations are released. Delegated calls that had finished stay paid and
count against the mandate.

### Publishing agents

An author publishes a version with `POST /v0/agents` (human key; the body is the manifest as
JSON) or `holon publish holon.yaml`. The version is listed at once when:

- its `id` is in the author's namespace (`<handle>/<name>`);
- the author's account is linked to GitHub. For an open-source agent, `source.repository` is a
  public GitHub repository that belongs to that GitHub account, or to an organisation that lists
  it as a public member: only the author of the code can list it and earn from it. A
  `proprietary` agent has no code to check and is labelled as such everywhere it is listed. A
  gateway without a GitHub app configured refuses to publish;
- the manifest is valid ([manifest](manifest.md)), including its license and fork royalties;
- `runtime.kind` is `mcp` or `webhook`: published agents run on their author's server, following
  the MCP agent profile or the webhook contract (section 6);
- `runtime.endpoint` is `https`, has no credentials, and resolves only to public addresses;
- the version was never published before. Versions are immutable; yanking one
  (`DELETE /v0/agents/{ns}/{name}@{version}`) stops listing it but never frees its number.

**Network safety.** Calls to published agents go through a client that checks the address it
actually connects to, on every connection: loopback, private, link-local, carrier-grade NAT,
cloud metadata and other reserved ranges are refused, and redirects are not followed. A
hostname that resolved to a public address at publish time and to a private one later (DNS
rebinding) is refused at call time. Agents shipped with the gateway (its own `agents/`) are not
subject to this check. `HOLON_ALLOW_PRIVATE_ENDPOINTS=1` lifts it for local development only.

**Evaluation at publish time.** Each version is run on the suites its capabilities claim, when
the platform has them, in the background. Nothing is billed; the author's server does the work.
Results reach the catalogue and the author's view. Known limit: evaluating a remote agent sends
the holdout inputs to its author's server, so holdout sets must be rotated to stay hidden.

**Earnings.** `GET /v0/me/agents` (human key, or `holon earnings`) lists the author's versions,
their evaluations (public cases in detail, hidden cases as pass/fail only) and what each earned,
computed from the journal: the author's share of each paid call, and royalties from forks.

### Files

Agents often need a document, not a string. A caller uploads the file (`POST /v0/files`, human
or agent key, the body is the file, at most 20 MB, 200 MB of live files per account) and gets a
handle, `holon://files/<id>`, to put in the input. When the gateway calls an MCP agent, it adds a
signed download link for each handle in the input (`_meta["holon/files"]`, section 6).

- A caller can only pass its own files: the owner must be the issuer of the root mandate the
  call runs under, including in delegated calls. Otherwise the call fails with
  `file_not_found`, which is never billed.
- Links are HMAC-signed, bound to one file and valid 15 minutes (`GET /v0/files/{id}?exp&sig`,
  no key). They are served as attachments with `nosniff` and a sandboxing CSP.
- Uploaded files are deleted 24 hours after upload. This is the one input Holon keeps
  between calls, and only for that long.
- Evaluation fixtures (`evals/**/files/`) are platform files, readable by agents only while
  they are evaluated: a caller cannot pass them, so hidden cases stay hidden from calls.

### Limits and platform rules

| Rule | Why |
|---|---|
| Author schemas run on RE2 (linear-time regular expressions) | an author's `pattern` cannot freeze the gateway |
| Input is validated before admission, so before any approval stores it | nothing is kept for an input the agent would refuse |
| Pending approvals expire after 7 days, and forget their input | no unbounded storage of inputs |
| `runtime.limits.timeout_s` at most 300, responses from published agents at most 10 MB | a slow or huge answer cannot hold funds or memory |
| Exchange rates (`budget.fx`) are set by the platform on hosted mandates | a caller cannot pick the rate it pays at |
| A fork must name a listed agent; chains are bounded and cannot loop | royalties always settle |
| Any gateway-side error after funds are held releases them (`gateway_error`, not billed) | money is never stuck in a hold |
| Capability calls reach published agents only with `allow.published: true` in the mandate | an author cannot win routing and receive everyone's inputs; calling an agent by name is unaffected |
| NUL characters are refused in requests and replaced in stored documents | Postgres JSONB cannot store them |
| Rate limits are per account, and per the address the trusted proxy appended | more keys or a forged `X-Forwarded-For` buy nothing |
| The operator key cannot call under a user's mandate or decide approvals | it tops up demo credit, nothing more |
| Another agent's output and every author-written text reach MCP clients with `<` escaped | a text cannot close the `<agent_output>` wrapper |

### Extra endpoints

| Method | Path | Key | |
|---|---|---|---|
| `POST` | `/v0/signup` | none | `{handle, email}` → keys, shown once |
| `GET` | `/v0/auth/github` | none | start signing in with GitHub (a redirect) |
| `GET` | `/v0/auth/github/callback` | none (state + cookie) | GitHub's return; redirects to `/console#login=<code>` or `#login_error=<message>` |
| `POST` | `/v0/auth/exchange` | none | `{code}` → handle, human key and its expiry (and, for a new account, the agent key); once |
| `GET` | `/v0/me` | human, agent | account, balance, mandates and keys (human); mandate and budget (agent) |
| `GET`, `POST` | `/v0/mandates` | human | list, or create a root mandate: the id is put in the caller's namespace, the issuer is forced to the caller, the grantee must be `<handle>/…`, currency EUR |
| `POST` | `/v0/files` | human, agent | upload a file (the body), get a `holon://files/` handle |
| `GET` | `/v0/files/{id}?exp&sig` | none (signed) | download a file through a signed link |
| `POST` | `/v0/agents` | human | publish a version of one of your agents (see above) |
| `DELETE` | `/v0/agents/{ns}/{name}@{version}` | human | yank a version you published |
| `GET` | `/v0/me/agents` | human | your published versions, evaluations and earnings |
| `GET` | `/v0/catalog` | none | the public catalogue: declared and measured, per agent |
| `POST` | `/v0/keys` | human | `{mandate, ask_in_client?}` → a new agent key |
| `DELETE` | `/v0/keys/{prefix}` | human | revoke a key |
| `GET` | `/v0/approvals/{id}` | human, agent | status of one approval |
| `POST` | `/v0/admin/credit` | admin | `{account, amount}` demo credit |
| any | `/mcp` | agent | the MCP server (section `spec/mcp.md`), under the key's mandate |
| `POST` | `/v0/demo/{ns}/{name}` | none | a free try of one of our own cheap agents, for the public site: narrowed input (short results, sample files only), 10 per visitor and 600 in all per hour, CORS for `HOLON_SITE_URL` only; not billed, not measured |
| `GET` | `/healthz` | none | liveness |

`POST /v0/calls` also takes `approval`. With an agent key, the mandate is the key's.

### Connecting Claude Code

```sh
claude mcp add --transport http holon https://<host>/mcp --header "Authorization: Bearer hlk_a_…"
```

### Moderation

The operator (admin key) can disable an account (`POST /v0/admin/accounts/{handle}/disable`
with a `reason`, and `yank_agents: true` to withdraw its listed agents): every key of the
account is revoked at once, and its email cannot sign up again. It can withdraw any published
version (`POST /v0/admin/agents/{ns}/{name}@{version}/yank` with a `reason`), list accounts
(`GET /v0/admin/accounts?q=`) and read the moderation log (`GET /v0/admin/log`). Every action
is logged with its reason, append-only. Agents shipped with the gateway are not moderated this
way: they change with the code.

### Operating

`Dockerfile` runs the gateway and the real MCP agents in one container. With
`NODE_ENV=production` (set by the image), the gateway refuses to start unless these are set:

| Variable | Why |
|---|---|
| `DATABASE_URL` | a managed Postgres, with backups |
| `HOLON_PUBLIC_URL` | the public https origin: signed file links point there |
| `HOLON_FILE_SECRET` | 32+ random characters, stable across restarts: signs file links |
| `HOLON_ADMIN_KEY` | 32+ random characters: demo credit and moderation |
| `HOLON_TRUST_PROXY` | `1` behind a proxy such as Railway (else every visitor shares one rate limit), `0` if exposed directly |
| `HOLON_SITE_URL` | the public https site: the console links to its terms and privacy pages |
| `HOLON_PRIVATE_EVALS` | a private directory of holdout cases, laid out like `evals/`; they replace the public samples of the repository (spec/evals.md §2) |
| `HOLON_SEC_CONTACT` | only when `holon-labs/sec-company` is listed (it is paused by default): an email the SEC can reach, as its fair access policy asks for it in the User-Agent |

`HOLON_GITHUB_CLIENT_ID` and `HOLON_GITHUB_CLIENT_SECRET` (a GitHub OAuth app whose callback URL
is `<HOLON_PUBLIC_URL>/v0/auth/github/callback`) are not in that list on purpose: without them,
sign in with GitHub is off and publishing answers `503`, while every call already paid for keeps
being served. A setting that only disables a feature must never stop the gateway.

### Backups

The database is the whole platform: accounts, keys, mandates, the journal, receipts, published
agents, files. `holon backup --out file.jsonl` copies every row of it, and
`holon restore --in file.jsonl` loads that copy into an empty database, in one transaction,
counters included. It restores on any Postgres, which is also the way out of a host.

A managed Postgres with its own backups is still the first line; the copy is the second, and the
one an operator can check. `tests/backup.test.mjs` runs the drill on every test run: a gateway
with traffic is copied, restored elsewhere, and must come back with the same accounts, receipts
and balances, and a journal that still sums to zero. Do the drill on the real data too, or the
backups are a guess.

`HOLON_ALLOW_PRIVATE_ENDPOINTS=1` is refused in production. Optional: `PORT`,
`HOLON_SIGNUP_CREDIT`, `HOLON_REGISTRY` (directories of shipped agents; default `agents`: the
demo agents of `examples/` are never listed unless added here), `HOLON_EXCLUDE` (default
`holon-labs/summarize,holon-labs/sec-company`: the first spends the operator's Anthropic credits,
the second is paused until it has an SEC contact), `HOLON_RECORDS` (off by
default: sample records are not measurements). Pages and fonts are served by the gateway
itself: no third-party request.

## 8. Not in v0

- Signature checks on mandates and manifests. The hosted gateway authenticates the issuer, but
  a mandate is not yet a signed document another gateway could verify.
- Email verification, password-less login and a web page for approvals.
- One gateway process: reservations live in its memory. Several processes need reservations in
  Postgres (row locks on budgets and balances) first.
- Every receipt is loaded in memory at start-up: fine for a prototype, to be paged before volume.
- Real sandboxing and egress enforcement. The dev runtime runs agent code in-process, and MCP
  agents run wherever their author hosts them.
- Connection reuse to MCP agents: one connection per call for now.
- Endpoint safety in the local dev gateway: it calls whatever the manifest says. The hosted
  gateway checks every connection to a published agent (section 7).
- Holdout rotation: remote evaluation shows holdout inputs to the agent's server.
- Clarification and streaming modes, and cancellation from the caller.
- Real currency exchange and payouts. Settlement happens in the root mandate's currency.
