# Agent policy for sites

> For site operators: which agents may do what, per-agent quotas and prices, handling of unverified claims, and declared tools.

A site's **agent policy** decides what each verified agent may do. It is a
typed, validated JSON object stored with the site; unknown fields are
rejected.

> [!IMPORTANT]
> The policy engine is complete and tested, but there is no console screen,
> admin API field or CLI command to edit a site's policy yet. The admin API
> returns it (`agentPolicy` on the site), and an operator sets it directly
> in the site's stored configuration. A management screen is on the
> roadmap. Sites without a policy keep the default behaviour below.

## Example

```json
{
  "agents": {
    "chatgpt":    {"actions": ["agent_submit"], "perMinute": 30},
    "perplexity": {"actions": ["*"], "price": {"amount": "0.01", "currency": "USD", "unit": "request"}}
  },
  "default":      {"actions": []},
  "unverified":   "challenge",
  "crawlerPrice": {"amount": "0.005", "currency": "USD"},
  "tools":        []
}
```

| Field | Meaning |
| --- | --- |
| `agents` | keyed by the verified agent name (from `agents.json` or its directory entry); each grant has `actions` (site actions, `"*"` for all), an optional `perMinute` quota and an optional `price` |
| `default` | the grant for verified agents without an entry |
| `unverified` | what happens when a User-Agent claims an AI assistant, AI crawler or search engine and no signature proves it: `challenge` (default), `block`, `charge` (402 with the crawler price) or `count` |
| `crawlerPrice` | the price for verified agents with no entry on a site with no `default`, and for `unverified: "charge"` |
| `tools` | [declared tools](/docs/agent-tools) published in the manifest |

Quotas are per site and per agent, on top of the agent's own quota in
`agents.json`. The maximum policy size is 16 KiB.

## Registering agents

Agents are known to AgentGate through `agents.json` (or `AGENTS_FILE`):

- `agents`: local keys, as printed by `agentgate keygen NAME`
  (`{"name", "public_key", "per_minute"}`);
- `directories`: allow-listed `Signature-Agent` origins whose key directory
  AgentGate fetches, each with a `name` and `per_minute`. The repository's
  `agents.example.json` lists ChatGPT agent, Google-Agent, Cloudflare
  Browser Run and Browserbase, checked on 2026-09-24;
- `registries`: optional lists of directory URLs and signature agent cards,
  imported at startup.

## Labels and default rules

The policy adds labels; default rules act on them, before the browser
checks:

| Label | Default rule |
| --- | --- |
| `agentgate:agent:policy:allowed` | allow (skips browser challenges, not content blocks) |
| `agentgate:agent:policy:denied` | block `403` |
| `agentgate:agent:policy:quota_exceeded` | block `429`, `Retry-After: 60` |
| `agentgate:agent:policy:unverified:challenge` / `block` / `charge` / `count` | challenge / `403` / `402` / none |
| `agentgate:agent:payment:required`, `exact_mismatch`, `max_too_low` | `402` |
| `agentgate:agent:payment:invalid_value`, `conflicting`, `unsigned` | `400` |
| `agentgate:agent:payment:accepted`, `agentgate:agent:policy:unlisted` | none |

Rule sets loaded from your own `rules.json` do not get these default rules
automatically.

## Limits

- On the form, a Web Bot Auth signature earns `policy:allowed` only when it
  binds `@method` and `@path` and carries a nonce, which is consumed.
- The price-offer check requires some `web-bot-auth` signature to cover the
  offer header; it does not tie that to the signature that verified, which
  only differs when a request carries several.
- No third-party agent has yet exercised pay per crawl, handoff or receipts
  against an AgentGate gateway.
