Skip to content

Policy reference

Every field of the policy format, every rule type and every match operator.

policy.yaml
version: 1
default: block
rules:
  - id: only-public-repo
    match: { tool: "github_*", args: { repo: { notGlob: "acme/public-app" } } }
    decision: block
    reason: Only acme/public-app is in scope

  - id: hold-comments
    match: { tool: github_post_comment }
    decision: hold
    approvers: { roles: [approver, admin] }
    timeoutSeconds: 300

  - id: interns-read-only
    match: { tool: "github_*", user: { group: intern } }
    except: { tool: [github_list_issues, github_get_issue] }
    decision: block

  - id: allow-in-plan
    match: { inPlan: true }
    decision: allow
Field Value
version Always 1.
default Always block. Applies when no rule matches (no_matching_rule).
rules An ordered list. The first rule that matches decides.

Unknown fields are errors, so a typo never silently does nothing.

Every rule has:

Field Required Meaning
id yes Kebab-case (hold-comments), unique in the policy. Recorded with every decision it makes.
match yes Which calls the rule applies to. All given fields must match.
except no Same shape as match. Calls that match it are excluded from the rule.
decision yes allow, block or hold.
reason no Shown to the model and in the inbox when the rule blocks.
YAML
- id: allow-in-plan
  match: { inPlan: true }
  decision: allow

Runs the tool. Policies are checked after the plan, so an allow rule can never let an off-plan call through. Keep allow-in-plan last: rules after it never apply to calls that are in the plan.

YAML
- id: no-prs
  match: { tool: [github_create_pull_request, github_merge_pull_request] }
  decision: block
  reason: This agent never opens or merges pull requests

Stops the call. The model receives the rule id and reason as an error result.

YAML
- id: hold-large-refunds
  match: { tool: issue_refund, args: { amount: { gte: 100 } } }
  decision: hold
  approvers: { roles: [admin], emails: [finance@acme.dev] }
  timeoutSeconds: 900

Pauses the call until someone approves or rejects it. See Holds.

Field Default Meaning
approvers.roles [] Anyone with one of these roles (admin, approver, member).
approvers.emails [] These people.
approvers.self false The person the agent acts for may approve their own hold.
timeoutSeconds 300 1 to 86,400. The hold expires after this.

At least one of roles, emails or self: true is required, so every hold can be approved by someone.

Field Matches when
tool The tool name matches a glob ("github_*") or one of a list of globs.
args Every listed argument satisfies its condition.
user.email The person’s email matches a glob. Case-insensitive.
user.role The person has this role.
user.group The person is in this group.
inPlan The call is (or, with false, is not) in the signed plan.

Roles and groups come from the team on the server and are snapshotted into the token when the plan is signed.

Operator Matches when the argument
eq equals the value exactly.
in equals one of the values.
notIn does not equal any of the values, or is missing.
glob is a string matching the glob.
notGlob is not a string matching the glob, or is missing.
regex is a string the regular expression matches in full.
lte is a number less than or equal to the value.
gte is a number greater than or equal to the value.

Details:

  • eq and in compare JSON values exactly, ignoring key order. "20" is not equal to 20.
  • glob: * does not cross /, dotfiles are matched, and a leading ! is a literal character, not a negation.
  • regex must match the whole string; it is wrapped in ^(?:…)$.
  • Positive operators (eq, in, glob, regex, lte, gte) match only when the argument is present and has the right type.
  • notIn and notGlob are exact negations, so they also match a missing or wrong-typed argument. In a block rule that is the safe outcome: only-public-repo above also blocks GitHub tools that have no repo argument. Keep this in mind if you use them in an allow rule.

Plan steps use the positive operators only.

Terminal
intentlock policy validate policy.yaml
intentlock policy test --agent github-assistant policy.yaml --replay 50

validate checks the syntax offline. test runs a dry run on the server against sample calls or the agent’s last decisions, and shows which ones would change. The inbox editor does both as you type.