---
title: "Policy"
description: "Every policy.json field, how to choose each command's operation, land and confirm gate, and the coverage rules lint enforces against the manifest."
canonical: https://harnesscontextprotocol.io/docs/concepts/policy
---

# Policy

> Every policy.json field, how to choose each command's operation, land and confirm gate, and the coverage rules lint enforces against the manifest.

Policy states what each command and each trigger does: its side-effect class,
where its data may land, and whether a write must ask first. Who may cause it
is the runtime's business ([Authorization](/docs/concepts/access#authorization)),
and what runs without asking is the user's or operator's call
([Access](/docs/concepts/access)). Write one row per command and per trigger in
`policy.json`, choosing each value as below.

## Policy file

```json title="policy.json"
{
  "surface_id": "mail",
  "version": 1,
  "verbs": {
    "mail.list":    { "operation": "read",  "privacy": { "land": "personal" } },
    "mail.get":     { "operation": "read",  "privacy": { "land": "personal" } },
    "mail.send":    { "operation": "write", "privacy": { "land": "personal" }, "confirm": true },
    "mail.connect": { "operation": "write", "privacy": { "land": "personal" } },
    "notes.create": { "operation": "write", "privacy": { "land": "brain" } }
  },
  "stimuli": {
    "new_item":         { "operation": "read", "privacy": { "land": "personal" } },
    "binding.degraded": { "operation": "read", "privacy": { "land": "personal" } }
  }
}
```

<TypeTable
  type={{
    surface_id: { description: 'Must equal the manifest id. Legacy name for pack_id.', type: 'string', required: true },
    version: { description: 'Policy file version. 1 for HCP/1.x.', type: '1', default: '1' },
    verbs: { description: 'One entry per command key in the manifest. Coverage is checked both ways.', type: 'Record<string, PolicyEntry>', required: true },
    stimuli: { description: 'One entry per stimulus ref in the manifest. Empty object if none.', type: 'Record<string, PolicyEntry>', default: '{}' },
  }}
/>

Each entry, for a command or a trigger, has these fields:

<TypeTable
  type={{
    operation: { description: 'Side-effect class.', type: "'read' | 'write'", required: true },
    'privacy.land': { description: 'Where data or side effects may land.', type: "'personal' | 'brain' | 'local'", required: true },
    confirm: { description: 'Writes only. true: the command asks before it runs, unless the user\'s access mode is run. "always": it asks in every mode; only an exact allow rule written by the user pre-approves it. See Access.', type: 'boolean | "always"' },
  }}
/>

Unknown fields and unknown operation or land tokens fail parse. A trigger is
always `read`, since Sense cannot write, but its land still decides where the
sink records it.

## Operation

| Ask | If yes | Examples |
|-----|--------|----------|
| Would the caller need to undo anything afterwards? | `write` | `send`, `create`, `setup.apply` |
| Does it only inspect, list, validate, or report, even for a prospective write? | `read` | `list`, `status`, `validate`, `doctor` |
| Is it a dry-run of a write? | Still `write`, and it must not persist | |
| Does it establish or refresh a Binding? | `write`, and it needs `pack.connect` at runtime | `connect` |

## Land

| Land | Data belongs to | Runtime also checks |
|------|-----------------|---------------------|
| `personal` | One actor: a personal mailbox, the user's own artifacts | The Principal's own scope |
| `brain` | A shared project or team: team artifacts | Project membership, with `project_id` set; tenant membership alone is not enough |
| `local` | A local workspace directory (cwd jail), not a tenant. HCP/1.1 | The sandbox's working directory |

Products map `brain` to whatever they call a shared work context, and packs
never invent a fourth land. Choose by where the data belongs, never by adapter
or Binding mode, since a `local` Binding pack can have `personal` land and a
`user` Binding pack `brain` land. Arguments like `--entity` or `--workspace`
refine accounts within Binding scope and never select the tenant, which comes
from the authenticated Principal's session
([L5](/docs/introduction/design-principles)).

## Confirm

Set `confirm: true` on any write that must not fire from a hallucinated turn,
such as transfers, sends to external parties, deletes, publishes and anything
with cost. Leave it off idempotent writes an agent should retry, such as
`setup.apply`, `sync` and `categorize`. Use `confirm: "always"` for the few
commands that must never run unattended, even in access mode `run`: payments,
irreversible deletes, anything that leaves the company. Users can still
pre-approve those with an exact allow rule and limits
([Access](/docs/concepts/access#confirm-always)).

The runtime enforces the gate before any Adapter runs. The JSON envelope must
carry `confirm: true` (or `apply: true`), and the CLI face takes `--confirm` or
`--apply` in its `cli[]`.

```json
{
  "ok": false,
  "pack_id": "ledger",
  "verb_key": "transfer",
  "code": "confirm_required",
  "error": "confirm required",
  "preview": {
    "command": "transfer",
    "args": { "amount": "2.80", "payee": "demo-transit" },
    "cli": "ledger transfer 2.80 --to demo-transit --confirm"
  },
  "hint": "Nothing ran. Re-run with confirm=true only if the user has approved this exact action with these values …"
}
```

No Invoker can skip it: a UI shows an approval, an agent shows the user the
`preview` and waits, and a person at the terminal types the flag. `error` is
the conformance string, and the reference runtime adds `code`, `preview` and
`hint`. An agent may pass `confirm=true` without asking again only when the
user's own words approve that exact action with those values ("pay 2.80 to Demo
Transit"). "Pay the fare" names no amount, so the agent shows the preview
first.

## Coverage rules

Lint fails when a manifest command or stimulus ref has no Policy row, or when
Policy names a key the manifest lacks:

```text
verb "transfer" missing from policy.json          # manifest has it, Policy lacks it
policy verb "refund" not in pack manifest         # Policy has it, manifest lacks it
stimulus "new_item" missing from policy.json      # declared trigger without a row
policy stimulus "old_event" not in stimulusTriggers
policy surface_id "ledgr" !== "ledger"
```

## How grants scale Policy

Policy says what a command does, and capability grants say who may run it.

```text
Policy operation  ──►  required capability
read              ──►  verb.exec.read
write             ──►  verb.exec.write
(connect command) ──►  pack.connect
(trigger)         ──►  sense.enable
```

A runtime may grant per `pack_id` or per `verb_key`. A multi-tenant product must
not collapse read, write and connect into one boolean, because sending mail
must never imply re-linking the mailbox.

## What stays out

Roles, user lists, allow-lists, tenant ids, API keys, rate limits, schedules
and host paths stay out. Every runtime, UI and author must understand each knob
Policy carries, so it keeps two axes and one gate: a runtime with serious RBAC
layers its rules over these rows without the pack knowing, and a runtime with
none still enforces the confirm gate.

## Related

- [Specification: Policy](/docs/specification/policy)
- [Schemas](/docs/reference/schemas)

## Sitemap

- [Documentation](https://harnesscontextprotocol.io/docs)
- [Packs](https://harnesscontextprotocol.io/packs)
- [Glossary](https://harnesscontextprotocol.io/docs/reference/glossary)
- [llms.txt](https://harnesscontextprotocol.io/llms.txt)
- [llms-full.txt](https://harnesscontextprotocol.io/llms-full.txt)
- [OpenAPI](https://harnesscontextprotocol.io/openapi.json)
- [MCP](https://harnesscontextprotocol.io/mcp)
