> ## Documentation Index
> Fetch the complete documentation index at: https://docs.substrate.42.pe/llms.txt
> Use this file to discover all available pages before exploring further.

# Write Substrate Policies: Guards and Responsibilities

> The policy DSL for Substrate — transition guards that block moves and agent responsibilities that surface reminders at write time.

A policy is the mechanism by which a board author's intent reaches an agent at the exact moment it matters. There are two types: `transition_guard` blocks a move until conditions are met, and `agent_responsibility` fires a message into the write envelope without blocking. Both share the same outer shape; everything that differs lives in `definition`.

## The Policy Envelope

```json theme={null}
{
  "id": "gate-done",
  "name": "Definition of done",
  "description": "Nothing reaches Done unheard.",
  "type": "transition_guard",
  "definition": { "…": "…" },
  "priority": 0,
  "enabled": true,
  "version": 1,
  "created_by_agent": "you",
  "created_at": "2026-01-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z",
  "archived_at": null
}
```

<Warning>
  `type` is immutable after creation — `update_policy` will not change it. Decide the type before you create the policy.
</Warning>

## transition\_guard — Blocks a Move

<ParamField path="from_group" type="string" required>
  A group id, or `"*"` to match any source group.
</ParamField>

<ParamField path="to_group" type="string" required>
  A group id, or `"*"` to match any destination group. Use `"*"` → `done` for a definition-of-done gate.
</ParamField>

<ParamField path="require" type="Condition[]">
  An implicit `all_of`. Each condition is evaluated against the task as it would look **after** the move — so you can require fields that are set in the same update that moves the task.
</ParamField>

<ParamField path="on_failure_message" type="string | null">
  What the agent is told when the move is blocked. Write it as an instruction, not a description — it is the agent's next action.
</ParamField>

```json theme={null}
{
  "from_group": "spec",
  "to_group": "build",
  "require": [{ "field": "task.plan_approved", "op": "eq", "value": true }],
  "on_failure_message": "Set plan_approved=true once the plan is signed off."
}
```

The guard engages when the move matches; when engaged, the move fails with `transition_blocked` unless every `require` condition passes.

Use `"*"` → `done` for a definition-of-done gate that catches every path into the final column.

## agent\_responsibility — Advises, Never Blocks

<ParamField path="when" type="Condition[]">
  Conditions that trigger the message. Absent or empty matches always — the message fires on every write to this board.
</ParamField>

<ParamField path="message" type="string" required>
  Surfaced in the write's `policies_fired` envelope. Write it as a direct instruction to the agent reading the response.
</ParamField>

```json theme={null}
{
  "when": [{ "field": "task.group_id", "op": "eq", "value": "build" }],
  "message": "Write tests during development, not after."
}
```

## Conditions

A leaf condition is `{ field, op, value?, values? }`. Field references resolve **literal-first**, then `custom_data` — so a custom field is `task.<name>`, and built-ins like `task.group_id`, `task.title`, `task.description`, and `task.parent_id` work too.

### Operator Reference

| Group          | Operators                                                               | Operand  |
| -------------- | ----------------------------------------------------------------------- | -------- |
| Existence      | `exists`, `not_exists`, `is_empty`, `not_empty`                         | none     |
| Equality       | `eq`, `neq`                                                             | `value`  |
| Sets           | `in`, `not_in`                                                          | `values` |
| Numeric        | `gt`, `gte`, `lt`, `lte`                                                | `value`  |
| String         | `contains`, `not_contains`, `starts_with`, `ends_with`, `matches_regex` | `value`  |
| String (multi) | `matches_any_keyword`                                                   | `values` |
| Array          | `has_any`, `has_all`                                                    | `values` |

### Compound Conditions

Nest compounds freely:

```json theme={null}
{ "all_of": [ … ] }
{ "any_of": [ … ] }
{ "none_of": [ … ] }
```

<Note>
  Operators never throw. A type mismatch, bad operand, or unparseable regex evaluates to `false` — a malformed policy can never crash a write. The trade-off is that a wrong `field` path reads as "condition not met" rather than as an error, which is why you validate. String operators coerce scalars but not arrays: a `string_list` field will not match `contains`. Use `has_any` / `has_all` instead. `matches_regex` compiles author-supplied patterns — patterns over 1000 chars, non-strings, and anything that throws are treated as non-match.
</Note>

## Validation at Author Time

Definitions are validated **strictly** against their type at `create_policy` / `update_policy` time and on load. A misspelled key or unknown operator produces a `schema_violation` error — it is not silently ignored. This catches shape errors, but not a wrong `from_group`, a wrong `to_group`, or a field path that resolves to nothing. For those, see [Validate Gates](/authoring/validate-gates).

## Priority and enabled

`priority` orders evaluation among policies on the same board — lower numbers run first. Use it when the order of `policies_fired` messages matters.

`enabled: false` keeps a policy in the file as documentation without enforcing it — useful for gates you're still drafting or have temporarily suspended.
