# Policies

> Every coordination policy rule, its default, and how org defaults and repo overrides combine.

A policy decides how Comity coordinates your agents: whether intents are required, how serious each kind of conflict is, what blocks an edit, which paths are protected, and what gets posted to Slack. Defaults warn rather than block.

## Where policies live

- **Org default**: applies to every repository.
- **Repo override**: rules set for one repository; everything else follows the org default.

The effective policy for a repo is Comity's defaults, overlaid with the org default, overlaid with the repo override. Objects merge key by key (for example `severity`); arrays and plain values replace.

Owners and admins edit policies in the dashboard under **Settings → Coordination policy** (pick the scope: **Org default** or a repository), or with the API. Everyone else sees the policy read-only. Each save creates a new version and a `policy.updated` event in the audit log. Agents pick up a change on their next hook call.

### With the API

```sh
curl -s https://api.comity.so/v1/policies?repo=acme/api \
  -H "Authorization: Bearer $COMITY_TOKEN"
```

```json
{
  "repo_name": "acme/api",
  "org": { "version": 3, "rules": { "require_intent": "auto", "ignore_paths": ["dist/**"] }, "updated_at": "2026-10-12T09:14:03.000Z", "updated_by": "0199…" },
  "repo": null,
  "effective": { "require_intent": "auto", "intent_ttl_hours": 24, "…": "…" }
}
```

`PUT /v1/policies` (org default) or `PUT /v1/policies?repo=<owner/name>` stores a new version for that scope. It needs an owner or admin with the `policy:write` scope (the dashboard has it; tokens from `comity login` don't).

```sh
curl -s -X PUT https://api.comity.so/v1/policies \
  -H "Authorization: Bearer $COMITY_TOKEN" -H "Content-Type: application/json" \
  -d '{"rules": {"require_intent": "auto", "ignore_paths": ["dist/**", "**/*.lock"], "severity": {"path_overlap": "warn"}}}'
```

> [!WARNING]
> The `rules` you PUT become the whole stored rule set for that scope; they are not merged with the previous version. Send every rule you want to keep overriding. `{"rules": {}}` on a repo removes all its overrides. The dashboard re-reads the stored rules and sends only your edits merged into them.

The merged result is validated; an invalid value returns `400 validation` with the failing field.

## Rules

### require_intent

`"off"` | `"auto"` | `"required"`. Default **`"auto"`**.

- `auto`: the first prompt of a session auto-drafts a private intent (titled from the prompt's first line). Plans become the intent's plan. The agent confirms it with `intent_declare`, keeping its number.
- `required`: like `auto`, and an edit (`pre_edit`) by a session without a declared intent is **blocked** with: "This repository's policy requires a declared intent before editing. Declare one with the intent_declare MCP tool, then retry."
- `off`: no auto-drafts.

### intent_ttl_hours

Integer > 0. Default **`24`**. A draft, active or blocked intent with no activity for this long, and no active session working on it, becomes `expired`.

### session_idle_minutes

Integer > 0. Default **`30`**. A session with a hook event within this window is `active`; quieter sessions are `idle`. MCP tools without `session_id` act as your most recent session within this window.

### severity

Severity per conflict kind: `"info"` | `"warn"` | `"block"`.

| Kind | Default |
|---|---|
| `path_overlap` | `info` |
| `symbol_overlap` | `warn` |
| `contract_break` | `warn` |
| `textual_merge` | `warn` |
| `duplicate_work` | `warn` |
| `dependency_order` | `warn` |
| `protected_path` | `block` |

`textual_merge` and `duplicate_work` are accepted but no detector produces them today. See [Conflicts](/docs/collaboration/conflicts).

### block_contract_break_when_criticality

Array of `"low"` | `"normal"` | `"high"` | `"frozen"`. Default **`["high", "frozen"]`**. A contract break escalates to a block only when the contract's criticality is in this list.

### block_min_confidence

Number 0–1. Default **`0.85`**. A finding needs at least this confidence to block; below it, it is downgraded to a warning.

### exclusive_max_files

Integer > 0. Default **`50`**. An exclusive path claim (intent `mode: "exclusive"`) only blocks others when it covers at most this many files; a broader claim is treated as shared.

### protected_paths

Array of `{ glob, owners, fail_closed }`. Default **`[]`**.

| Field | Type | Default | Notes |
|---|---|---|---|
| `glob` | string | | Required. Repo-relative glob, e.g. `db/migrations/**`. |
| `owners` | string[] | `[]` | `user:<email>`. Other prefixes (such as `team:`) are rejected with a 400. |
| `fail_closed` | boolean | `false` | Stored, but not enforced: hooks always fail open. |

An edit under a protected path produces a `protected_path` conflict (severity `block` by default).

```json
{ "protected_paths": [{ "glob": "db/migrations/**", "owners": ["user:diego@acme.com"] }] }
```

### exclusive_by_default

Array of globs. Default **`[]`**. Paths matching these are treated as exclusively claimed by whoever edits them first.

### ignore_paths

Array of globs. Default **`[]`**. Ignored by every detector: generated files, lockfiles, build output.

### override

| Field | Default | Notes |
|---|---|---|
| `override.allowed_roles` | `["owner", "admin"]` | Roles that may override a conflict. Any of `owner`, `admin`, `member`, `viewer`, `billing`. |
| `override.require_reason` | `true` | When `true`, an override needs a reason of 3–2,000 characters (after trimming). When `false`, the reason is optional. |

### notify

Slack notifications. See [Integrations → Slack](/docs/admin/integrations#slack).

| Field | Default | Notes |
|---|---|---|
| `notify.slack_events` | `["conflict.detected:block", "hook.blocked"]` | `<event type>` or `<event type>:<info\|warn\|block>`; a severity suffix matches that severity and above. |
| `notify.slack_min_severity` | `"warn"` | Events that carry a severity are only posted at or above this. |

### fail_mode

`"open"` | `"closed"`. Default **`"open"`**. Stored, but not enforced: hooks always fail open (the dashboard labels it "Fail closed is saved but not enforced yet"). See [How it works → Fail-open](/docs/how-it-works#fail-open).

### briefing_max_tokens

Integer 200–4,000. Default **`1500`**. Size budget of a briefing (about 4 characters per token).

### share_session_status

Boolean. Default **`true`**. Teammates see each session's latest status (an excerpt of the agent's last message at the end of a turn, plus files touched) in briefings and search. Individual developers can also opt out with `COMITY_SHARE_STATUS=0`.

### cross_repo_contracts

`"org"` | `"projects"` | `"off"`. Default **`"org"`**. Which other repos this repo's contracts (HTTP routes, workspace packages) are matched against:

- `org`: every repo in the org whose policy is also `org`, plus repos in a shared project.
- `projects`: only repos that share a [project](/docs/admin/dashboard#work).
- `off`: none.

Both repos of a pair must allow it.

### arrival_notices

Boolean. Default **`true`**. When another developer's agent starts a session in this repo, teammates' agents with a live session here get a one-time notice. See [Messaging](/docs/collaboration/messaging#arrival-notices).

## Full default policy

```json
{
  "require_intent": "auto",
  "intent_ttl_hours": 24,
  "session_idle_minutes": 30,
  "severity": {
    "path_overlap": "info",
    "symbol_overlap": "warn",
    "contract_break": "warn",
    "textual_merge": "warn",
    "duplicate_work": "warn",
    "dependency_order": "warn",
    "protected_path": "block"
  },
  "block_contract_break_when_criticality": ["high", "frozen"],
  "block_min_confidence": 0.85,
  "exclusive_max_files": 50,
  "protected_paths": [],
  "exclusive_by_default": [],
  "ignore_paths": [],
  "override": { "allowed_roles": ["owner", "admin"], "require_reason": true },
  "notify": { "slack_events": ["conflict.detected:block", "hook.blocked"], "slack_min_severity": "warn" },
  "fail_mode": "open",
  "briefing_max_tokens": 1500,
  "share_session_status": true,
  "cross_repo_contracts": "org",
  "arrival_notices": true
}
```
