# Set Policy

> The policy operation on whisper.agents: the arguments that write resolver rules, the read-back with no arguments, and the scope the call requires.

*Source: https://www.whisper.security/docs/control-plane/operations/policy*
*Published: 2026-09-19*
*Last updated: 2026-09-19*

---
`policy` writes the resolver rules for the account and reads them back. It needs `dns:policy:write`. A call that also carries the `whale` key needs `dns:whale:write` beside it.

Policy is held per account. It applies to every agent and every device on that account, not to one address at a time.

## Write

```whisper-call
CALL whisper.agents({op: 'policy', args: {allow: ['api.example.com'], block: ['ads.example.com'], default: 'deny'}})
```

Send only the arguments you are changing.

| Argument | Contract |
|---|---|
| `default` | `allow` or `deny`. What happens to a name no rule and no verdict covers. |
| `block` | Domain suffixes to refuse. |
| `allow` | Domain suffixes to permit. An explicit `allow` wins over a `block`. |
| `bundles` | Category and geography rules, which travel here rather than in `block`. [Resolver policy](/docs/network/resolver/policy) lists the catalogue. |
| `mode` | `graph-only`, `hybrid` or `always-forward`. How much of resolution the graph decides. |
| `retention` | How long ordinary query logs are kept, counted in days. Set it to zero to keep none. |
| `allow_sources` | Prefixes allowed to use the account's dedicated resolver address. A source outside the list gets the answer an unconfigured address would get. |

`block` and `allow` match on domain suffix, so a rule on `example.com` also covers every name under it.

Policy narrows and does not widen. An `allow` entry cannot lift a verdict the graph has already reached about a malicious name, so the strongest rule in force is the one that applies. [Resolver policy](/docs/network/resolver/policy) sets out what each value does at resolution time.

## Read

Send an empty `args` map to read the policy in force.

```whisper-call
CALL whisper.agents({op: 'policy', args: {}})
```

The answer has columns `key` and `value`, one row per setting.

| Key | Holds |
|---|---|
| `default` | The default action. |
| `mode` | The resolution mode. |
| `block`, `allow` | One row per name on each list. |
| `bundle` | One row per bundle in force, in its canonical spelling. |
| `allow_sources` | The source prefixes allowed on the resolver address. |
| `nesting_opt_out` | Whether identities may egress behind this account's own. See [Nested identities](/docs/identity/nested). |
| `east_west_on_fault`, `whale_approval`, `node_key_enrolment` | Fleet settings, described in [Managed settings](/docs/whalenet/settings). |
| `calm.*` | The sensor's footprint settings, described in [What the sensor costs](/docs/endpoint/operate/footprint). |

The lists come back one entry per row rather than as a single row holding a list, so a name you wrote and a name already in force read the same way.

## Errors

| Code | Status | Meaning |
|---|---|---|
| `BAD_ARGS` | 400 | `default` or `mode` is not one of the accepted values, or a list holds something that is not a domain suffix. |
| `FORBIDDEN_SCOPE` | 403 | The key does not carry `dns:policy:write`. See [Keys and scopes](/docs/control-plane/auth). |
| `ANONYMOUS_WRITE` | 403 | The call carried no key. |
