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:
tool equals the request tool (or policy tool is "*")
actionPattern matches the action string (exact match or wildcard with * suffix, e.g. pull_request.*)
resourceType matches when set
riskLevel matches when set (policy risk level ≤ request risk level, based on minRiskLevel / maxRiskLevel in conditions)
conditions.allowedAgents includes the agent ID when set
conditions.blockedAgents does not include the agent ID when set
- For MCP:
toolName exactly matches or toolNamePattern matches the MCP tool name
Risk levels
Risk levels are ordered: low < medium < high < critical.
| Level | Examples |
|---|
low | GET requests, read operations, list operations |
medium | Create issue, create comment, update issue, Linear issue create |
high | Create pull request, push commit, create release |
critical | Delete branch, force-push, delete repository |
For MCP tools, risk level is inferred from the tool name:
- Name starts with
get, list, read → low
- Name contains
delete, destroy, drop, remove → high
- Everything else →
medium
Default behavior
If no policy matches, the engine returns allow. To lock down your workspace, add a catch-all block policy at a high priority number (e.g. 999) and add specific allow or require_approval policies at lower numbers.
// Example: block all, then allow reads
[
{
"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.