---
name: Policies
description: Explains how to define structured policies — rules matching on interrupt effect and data — to auto-approve or reject interrupts inside handler blocks.
---

# Policies

Claude Code has an interesting feature. When it asks you for approval for a tool, you can also say "don't ask me about this tool again". You auto-approve that tool for the future. How do you add a feature like that to your agent? By using policies.

## Basic policies

Here is an example of a policy:

```ts
  const policy = {
    "std::read": [{ action: "approve" }],
    "std::write": [{ action: "reject" }],
    "std::shell": [{ action: "reject" }]
  }
```

This policy approves all reads and rejects all writes and shell commands.

To use the policy, you need to check it in a handler block.

```ts
import { checkPolicy } from "std::policy"

handle {
  someFunc()
} with (interrupt) {
  return checkPolicy(policy, interrupt)
}
```

Policies are a structured way to respond to interrupts based on their effect.

The rules of handlers still apply, so if a policy approves an interrupt, but a different handler rejects it, that interrupt will still get rejected.

Here I've defined the policy as a variable, but its just an object. It could just as easily come from a JSON file, or a database.

## Matching on interrupt data

In this example, I am matching on the interrupt effect, but I can additionally match on the interrupt data too. For example, here is a policy that approves all reads from the `/tmp` directory, and rejects all other reads.

```json
{
    "std::read": [
      { "match": { "dir": "/tmp" }, "action": "approve" },
      { "action": "reject" }
    ]
}
```

The key is the interrupt's [effect](/guide/effects), and the value is an array of rules. For the rules, *first match wins*.

### Globs
You can also use *globs*. For example, here is a policy that allows users to read all Markdown files:

```json
{
    "std::read": [
      { "match": { "filename": "*.md" }, "action": "approve" },
      { "action": "reject" }
    ]
}
```

### `rejectMessage`

You can provide a custom rejection message for a rule using the `rejectMessage` field.

```json
{
    "std::write": [
      { "action": "reject", "rejectMessage": "Writes are not allowed by this policy." }
    ]
}
```

This is the equivalent of:

```ts
return reject("Writes are not allowed by this policy.")
```

If the interrupt was raised by a tool call and get rejected, the reject message will be sent to the LLM. This is a good place to tell the LLM what to do instead.

## Running with a policy from the command line

You can also apply policies on the command line, when running an Agency program using `agency run`.

```bash
# A named built-in policy
agency run --policy recommended agent.agency

# A policy JSON file
agency run --policy ./my-policy.json agent.agency

# Inline effect lists
agency run --approve std::read,std::ls --reject std::write agent.agency
```

Notes:

- The built-in names are `recommended`, `minimal`, `with-writes`, and `approve-all`.
- `--approve` and `--reject` take comma-separated [effect](/guide/effects) names. You can also give either flag more than once. Reject takes precedence, so if you have an effect in both approve and reject, it gets rejected.
- The [rules of handlers](/guide/handlers.md#the-rules-of-handlers) still apply. Think of this as just another handler.
- An interrupt that asks for a value, such as `question()` or a draft waiting for review, goes to the prompt under `--interactive` even when an approve rule matches it, so you give the answer its content. Without `--interactive`, an approve rule answers it with no value: the program reads a null answer, which for a draft review means accept and for `question()` means no answer. A reject rule rejects it either way.

### The `--interactive` flag

By default, any interrupts that aren't addressed by your policy, and aren't handled by your agent, will cause a crash. Run with the `--interactive` flag if you want to be prompted to approve or reject those interrupts instead.


## Literal values from TypeScript

```typescript
import { escapeGlob } from "agency-lang/runtime";

const match = { listId: escapeGlob("groceries[1]") };
```

`escapeGlob` escapes pattern characters, including quotes, so they are treated
as part of the value. An empty pattern matches only an empty value.

Policy path normalization still applies: a leading `./` is ignored. In `dir`
rules, `.` also matches the launch directory, and `<agency>` and `<agent-home>`
expand to their configured directories. Escaping does not disable these aliases.
