Seal

Policy authoring

Match rules, condition operators, verdicts, approvers, timeouts, and escalation — the full policy schema.

A policy decides, for each approval request, whether a human is needed and who reviews it. A request references a policy by its policyKey; the policy with the highest version for that key and org is evaluated.

Shape

{
  "match": { "action": "wire.transfer", "agent": "finance-bot" },
  "rules": [
    {
      "when": [{ "field": "payload.amount", "op": "gt", "value": 1000 }],
      "then": "require_approval"
    },
    { "when": [], "then": "allow" }
  ],
  "approvers": {
    "channel": "slack",
    "users": ["U0123ABC"],
    "teams": [],
    "escalation": [{ "channel": "email", "targets": ["oncall@acme.dev"], "timeoutSeconds": 1800 }],
    "timeoutSeconds": 900,
    "onTimeout": "escalate"
  }
}

Match

The policy is only applied if match matches the request. Both fields are optional; an omitted field matches anything.

FieldMatches when
actionthe request's action equals this value
agentthe request's agent equals this value

If match does not match, the engine fails closed to require_approval.

Rules

rules is an ordered list. The first rule whose conditions all pass wins, and its then verdict is returned. A rule with an empty when array always passes — use it as a catch-all final rule.

then is one of allow, deny, or require_approval. If no rule matches, the fallthrough verdict is require_approval.

Conditions

Each condition is { field, op, value }. field is a dot-path evaluated against the request root, so payload.amount, context.tier, action, and agent are all reachable. A missing field never passes.

opPasses when
eqfield deep-equals value
neqfield does not deep-equal value
gtfield and value are both finite numbers and field > value
ltfield and value are both finite numbers and field < value
containsfield is a string containing value, or an array containing value
regexfield is a string matching the value pattern

gt / lt never coerce. A string "5000" is not greater than 1000 — it fails closed. Send numbers as JSON numbers.

Approvers

When the verdict is require_approval, the approvers block decides who is notified and how long they have.

FieldMeaning
channelslack, email, or dashboard — where the request is delivered
userschannel-specific reviewer ids (e.g. Slack user ids)
teamschannel-specific team ids
timeoutSecondshow long the request stays pending before it times out
onTimeoutdeny (default) or escalate when the timeout elapses
escalationordered next steps, each { channel, targets, timeoutSeconds }

If onTimeout is escalate, the request moves to the next escalation step instead of being denied. When the steps run out, it is denied — Seal never leaves a request pending forever.

Managing policies

Policies are stored per org and versioned. They are authored in the dashboard; for a local dev instance, pnpm db:seed installs a sample policy under the key payments.high_value.

On this page