# Policy language

> Rules that decide allow, hold or veto, plus budgets and preconditions.

Reading time: 12 min. Source: https://www.voidagent.tech/docs/policy

A policy is an ordinary module, not a DSL. It exports a default policy object, it is type checked by your own toolchain, and it can import whatever it needs. Rules are evaluated in order and the first match decides.

> The import at the top of this sample names a package inside the product repository, at version 0.1.0, that is private and unpublished. It resolves from a clone of that repository and cannot be installed from a registry, so the sample shows a shape rather than a line you can paste.

## Shape

```void.policy.ts
import { policy, R } from "@void/policy";

export default policy({
  skip: (call) => call.effect === "read",
  rules: [ /* ... */ ],
  budget: { irreversible: { perAgentPerDay: 2 }, onExhausted: "read_only" },
});
```

## The call object

| field | type | meaning |
| --- | --- | --- |
| call.tool | string | fully qualified tool id, for example stripe.refunds.create |
| call.effect | read \| write | computed from the adapter's tool map |
| call.class | R0 \| R1 \| R2 \| R3 | reversibility class computed at preflight |
| call.domain | string | logical grouping, for example payments or crm |
| call.records | number | estimated records affected |
| call.systems | string[] | systems the call will change |
| call.agent | string | agent identity that made the call |
| call.args | object | the arguments, after redaction |

## Decisions

- allow: forward the call, record it, keep the compensation plan.
- hold: pause and require an approver. The call is never sent until a human decides.
- veto: refuse before the write. The agent receives a structured refusal it can reason about.

## Preconditions

A rule can require something to be true before allowing the call. The most common precondition is a captured snapshot, which turns an otherwise risky bulk update into an R0.

```void.policy.ts
{
  when: (call) => call.records > 100 && call.class <= R.R1,
  then: "allow",
  require: ["snapshot"],
  reason: "wide write, snapshot first",
}
```

## Budgets

```void.policy.ts
budget: {
  irreversible: {
    perAgentPerDay: 2,
    perWorkspacePerDay: 8,
    // optional: a different cap for a specific agent
    overrides: { "growth-outbound": 6 },
  },
  onExhausted: "read_only",   // or hold_for_human, veto
}
```

> Budgets are enforced at preflight, so an agent never discovers it is out of budget halfway through a multi step write.

## Testing a policy

```shell
$ void policy test --fixtures ./fixtures
  + 42 fixtures, 42 decisions matched expectations
  + 0 rules unreachable
  ! rule 'bulk external send' never matched in fixtures
```

---

This document is the markdown form of https://www.voidagent.tech/docs/policy, published for agents that read
text rather than a rendered page. It is generated from the same data the page
renders, so the two cannot disagree.

