Skip to main content
The policy engine receives an action request and returns one of three decisions: allow, block, or require_approval. Policies are matched in priority order — the first match wins.

Policy structure

Policies are stored in the policies table. The TypeScript shape used by the engine:
interface Policy {
  id: string;
  workspaceId: string;
  name: string;
  tool: string;              // "github" | "linear" | "slack" | "notion" | "internal_api" | "mcp"
  actionType?: string | null; // legacy field, use actionPattern
  actionPattern: string;     // glob-style, e.g. "pull_request.*" or "*"
  resourceType?: string | null;
  riskLevel?: "low" | "medium" | "high" | "critical" | null;
  mode: "allow" | "block" | "require_approval";
  conditions: PolicyConditions;
  priority: number;          // lower number = higher priority
  enabled: boolean;
  toolName?: string | null;  // MCP only: exact tool name match
  toolNamePattern?: string | null; // MCP only: wildcard pattern, e.g. "delete_*"
}

interface PolicyConditions {
  minRiskLevel?: "low" | "medium" | "high" | "critical";
  maxRiskLevel?: "low" | "medium" | "high" | "critical";
  allowedAgents?: string[];
  blockedAgents?: string[];
  allowedResourceTypes?: string[];
  blockedResourceTypes?: string[];
  customRules?: Record<string, unknown>;
}

Evaluation order

Matching logic

A policy matches when all of the following are true:
  1. tool equals the request tool (or policy tool is "*")
  2. actionPattern matches the action string (exact match or wildcard with * suffix, e.g. pull_request.*)
  3. resourceType matches when set
  4. riskLevel matches when set (policy risk level ≤ request risk level, based on minRiskLevel / maxRiskLevel in conditions)
  5. conditions.allowedAgents includes the agent ID when set
  6. conditions.blockedAgents does not include the agent ID when set
  7. For MCP: toolName exactly matches or toolNamePattern matches the MCP tool name

Risk levels

Risk levels are ordered: low < medium < high < critical.
LevelExamples
lowGET requests, read operations, list operations
mediumCreate issue, create comment, update issue, Linear issue create
highCreate pull request, push commit, create release
criticalDelete branch, force-push, delete repository
For MCP tools, risk level is inferred from the tool name:
  • Name starts with get, list, readlow
  • Name contains delete, destroy, drop, removehigh
  • Everything else → medium

Default behavior

If no policy matches, the engine returns require_approval — the action is queued for a human reviewer and is not forwarded automatically. This is a safe default: agents cannot take write actions without either an explicit allow policy or human sign-off.
Exception: repos.delete (repository deletion) is always blocked outright when no policy matches, regardless of the default behavior above. Repository deletion is treated as an unrecoverable destructive action.
To allow unmatched actions without approval, add an explicit allow policy with actionPattern: "*" at a high priority number. To block them, add a block catch-all instead:
// Example: allow reads, gate PRs, block everything else explicitly
[
  {
    "name": "Allow all reads",
    "tool": "github",
    "actionPattern": "read",
    "mode": "allow",
    "priority": 10
  },
  {
    "name": "Require approval for PRs",
    "tool": "github",
    "actionPattern": "pull_request.create",
    "mode": "require_approval",
    "priority": 20
  },
  {
    "name": "Block everything else",
    "tool": "github",
    "actionPattern": "*",
    "mode": "block",
    "priority": 999
  }
]
Lower priority numbers are evaluated first. Set your most specific rules at low numbers, your catch-all rules at high numbers.