Skip to main content
A policy is a rule attached to a board. Policies are substrate-as-code: they live in the board’s JSON file, are versioned with your repo, and are read fresh on every call. Substrate v0.7.0 ships two policy classes — one that blocks writes and one that never does. Every policy carries id, name, description, priority, enabled, and version. Set enabled: false to keep a policy as living documentation without enforcing it.

transition_guard

A transition_guard engages when a task moves from a matching from_group to a matching to_group. Use "*" as a wildcard on either side. When engaged, the move is blocked with transition_blocked unless every condition in require passes — conditions are evaluated against the task as it would look after the move.
require is an implicit all_of. A "*" → done guard is the idiomatic definition-of-done gate — it fires no matter which column a task ships from.

agent_responsibility

An agent_responsibility policy never blocks. When its when conditions match a written task, the policy appears in that write’s policies_fired list with its message. Omit or leave when empty and the policy always matches.

Conditions

A leaf condition takes a field reference, an operator, and a value:
Field references resolve literal-first, then against custom_data. Use task.<name> for custom fields. Built-in field references are task.group_id, task.title, task.description, and task.parent_id.

Operators

Operators never throw. A type mismatch, bad operand, or invalid regex evaluates to “condition not met” rather than crashing a write. String operators coerce scalars but not arrays — use has_any or has_all for string_list fields.

Compound conditions

Nest conditions with all_of, any_of, or none_of:
A misspelled key is a validation error, not a silently-ignored field. Policy definitions are validated strictly at create_policy / update_policy time and on load.

What a gate actually guarantees

A transition_guard is a real structural rail — it blocks the move and returns transition_blocked at write time. But the field it checks is self-attested. The agent sets tests_passing: true itself; nothing runs your tests.
A gate is a confession step, not a control. It records that the claim was made and blocks until it is. An agent that would skip review under pressure can also set the flag under pressure. The exception is a human_only field: an agent’s write tools structurally refuse to set it, so a guard requiring it genuinely waits on a person. See Fields and Schema. Two habits follow from this:

Prove your rails fire

Run substrate validate and exercise your guards before relying on them. Definitions that parse don’t always do what you expect at runtime.

Dry-run instead of guessing

Call check_transition before attempting a move. It returns allowed, from_group, to_group, and the blocking policy with its message — no side effects, no version bump.

Reading the result

A successful write returns an ok: true envelope with the updated entity state and any policies_fired:
A blocked write returns an error envelope with code transition_blocked. See Envelopes and Errors for the full error shape.

See it run

Walk through a complete session — creation, blocked moves, gate satisfaction, human approval, and revocation — with real request and response payloads.