Policy Language
Policies are JSON documents that define what your agents are allowed to do. They are the core enforcement mechanism — everything in PermitNetworks flows through policy evaluation.
Complete Policy Structure
Every field is annotated below. Required fields are marked with *.
{
"id": "pol_billing_agent", // auto-generated if omitted
"name": "billing-agent-limits", // * human-readable name
"priority": 1, // lower = evaluated first (default: 100)
"enabled": true, // false = policy is disabled but kept
"agents": ["billing-bot"], // null = applies to ALL agents
"description": "Controls what the billing agent can do",
"rules": [ // * at least one rule required
{
"action": "payment.create", // * exact, wildcard, or /regex/
"resource": "account:*", // null = matches any resource
"effect": "allow", // * "allow" | "deny" | "review" | "log"
"conditions": { // null = no conditions (always matches)
"context.amount": { "$lte": 5000 },
"context.currency": { "$in": ["USD", "EUR"] }
}
}
],
"rate_limit": { // null = no rate limiting
"per_second": null,
"per_minute": 60,
"per_hour": 1000,
"per_day": null
},
"budget": { // null = no budget constraint
"limit": 50000,
"unit": "USD",
"period": "monthly",
"threshold_alert": 0.8 // webhook at 80% consumed
}
}Action Matching
The action field in a rule supports three matching modes:
"payment.create"Only matches the exact action string.
"payment.*"Matches any action in the payment namespace.
"*"Matches any action. Use carefully — typically for deny-all rules.
"/^(payment|transfer)\./"Full regex support. Must be wrapped in forward slashes.
Effects
| Effect | SDK response | Budget consumed? | Description |
|---|---|---|---|
allow | decision.effect = "allow" | On confirm() | Action is permitted. Permit token issued. |
deny | decision.effect = "deny" | Never | Action is blocked. No permit token. |
review | decision.effect = "review" | On confirm() | Action is permitted but flagged for human review. |
log | decision.effect = "log" | On confirm() | Action is permitted. Extra audit record created. |
Condition Operators
Conditions evaluate fields from the context object passed in the authorization request. Use dot-notation to access nested fields: context.user.role.
| Operator | Meaning | Example |
|---|---|---|
| $eq | Equal to | { "$eq": "admin" } |
| $ne | Not equal to | { "$ne": "guest" } |
| $gt | Greater than | { "$gt": 500 } |
| $gte | Greater than or equal | { "$gte": 1000 } |
| $lt | Less than | { "$lt": 0.7 } |
| $lte | Less than or equal | { "$lte": 5000 } |
| $in | Value in array | { "$in": ["USD", "EUR"] } |
| $nin | Value not in array | { "$nin": ["DELETE", "DROP"] } |
| $exists | Field is present | { "$exists": true } |
| $regex | Regex match | { "$regex": "^prod-" } |
Multiple conditions on the same rule are combined with AND logic. To express OR, create separate rules with the same effect.
Example Policies
Billing agent: create payments under $5,000
Allow payment creation with a hard dollar cap, then deny everything else.
{
"name": "billing-agent-payments",
"priority": 10,
"agents": ["billing-bot"],
"rules": [
{
"action": "payment.create",
"effect": "allow",
"conditions": {
"context.amount": { "$lte": 5000 },
"context.currency": { "$in": ["USD", "EUR", "GBP"] }
}
},
{
"action": "payment.*",
"effect": "deny"
}
]
}Support bot: read customer data, no writes
Explicitly allow read actions, deny all write/delete operations.
{
"name": "support-bot-readonly",
"priority": 20,
"agents": ["support-agent"],
"rules": [
{
"action": "data.read",
"resource": "customer:*",
"effect": "allow"
},
{
"action": "data.read",
"resource": "order:*",
"effect": "allow"
},
{
"action": "data.write",
"effect": "deny"
},
{
"action": "data.delete",
"effect": "deny"
}
]
}Block all actions outside business hours
Use a time context passed by your application to restrict access.
{
"name": "business-hours-only",
"priority": 5,
"rules": [
{
"action": "*",
"effect": "deny",
"conditions": {
"context.hour_utc": { "$lt": 8 }
}
},
{
"action": "*",
"effect": "deny",
"conditions": {
"context.hour_utc": { "$gte": 20 }
}
}
]
}
// In your application, pass the current UTC hour in context:
// await permit.authorize({
// action: "data.read",
// context: { hour_utc: new Date().getUTCHours() }
// })Large transfers require dual approval
Flag transfers over $10,000 for human review rather than auto-allowing or blocking.
{
"name": "large-transfer-review",
"priority": 8,
"rules": [
{
"action": "payment.create",
"effect": "review",
"conditions": {
"context.amount": { "$gt": 10000 }
}
},
{
"action": "payment.create",
"effect": "allow",
"conditions": {
"context.amount": { "$lte": 10000 }
}
}
]
}Monthly API call budget
Limit a data agent to 10,000 API calls per month with an alert at 80%.
{
"name": "data-agent-api-budget",
"priority": 30,
"agents": ["data-processor"],
"rules": [
{
"action": "api.*",
"effect": "allow"
}
],
"budget": {
"limit": 10000,
"unit": "api_calls",
"period": "monthly",
"threshold_alert": 0.8
}
}Priority and Conflicts
Policies are evaluated in ascending priority order (1 before 100). The first matching rule wins — evaluation stops.
If two rules from different policies match the same request with conflicting effects, the lower-priority-number policy wins. This returns a policy.conflict warning in the response metadata.
If no rule matches at all, the default effect is deny with reason policy.not_found.