Policy Management
Control agent execution with policies
Policy Management
Policies define what actions an agent can execute, providing fine-grained control over agent behavior. Think of policies as execution guardrails that work alongside intent verification.
What are Policies?
A policy is a set of rules that determines:
- Which MCPs and actions are allowed/denied
- Time-based access restrictions
- Rate limits
- IP whitelisting
- Tool-level permissions
Policy Structure
{
"allow": ["analytics-mcp/*", "data-mcp/fetch_*"],
"deny": ["data-mcp/delete_*", "admin-mcp/*"],
"allowed_tools": ["read_file", "analyze", "aggregate"],
"rate_limit": 100,
"ip_whitelist": ["10.0.0.0/8", "192.168.1.0/24"],
"time_restrictions": {
"allowed_hours": [9, 10, 11, 12, 13, 14, 15, 16, 17],
"allowed_days": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
},
"priority": 50
}Policy Fields
| Field | Type | Description | Example |
|---|---|---|---|
allow | list[str] | Allowed MCP/action patterns (glob) | ["data-mcp/*"] |
deny | list[str] | Denied MCP/action patterns (glob) | ["data-mcp/delete_*"] |
allowed_tools | list[str] | Whitelisted tool names | ["read_file", "analyze"] |
rate_limit | int | Max requests per hour | 100 |
ip_whitelist | list[str] | Allowed IPs/CIDR ranges | ["10.0.0.0/8"] |
time_restrictions | object | Time-based access control | See below |
priority | int | Policy priority (0-100, higher wins) | 50 |
Time Restrictions
{
"allowed_hours": [9, 10, 11, 12, 13, 14, 15, 16, 17], // 0-23
"allowed_days": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
}Creating Policies
Method 1: Programmatic (SDK)
Define policies directly in your code:
# Restrictive policy for production agent
policy = {
"allow": ["analytics-mcp/*", "data-mcp/fetch_*"],
"deny": ["data-mcp/delete_*"],
"allowed_tools": ["read_file", "analyze", "aggregate"],
"rate_limit": 100,
"ip_whitelist": ["10.0.0.0/8"],
"time_restrictions": {
"allowed_hours": [9, 10, 11, 12, 13, 14, 15, 16, 17],
"allowed_days": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
}
}
token = client.get_intent_token(
plan_capture=plan,
policy=policy,
validity_seconds=3600
)Method 2: Visual Policy Builder (ArmorIQ Canvas)
Create policies using the drag-and-drop interface at platform.armoriq.ai/dashboard/policies:
Steps:
- Click "Canvas" button to open visual builder
- Drag users, MCPs, and agents onto canvas
- Connect entities with edges (connections)
- Click edge to configure permissions visually
- Use "Browse Tools" to select allowed tools from MCP
- Set IP restrictions, time windows, rate limits
- Save policy with name and priority
Stored policies do not need fetching. Policies authored in the dashboard are held against your organization and evaluated by the platform during enforcement, so an ordinary call is already subject to them:
# Stored org policies apply automatically - nothing to pass
token = client.get_intent_token(
plan_capture=plan,
validity_seconds=3600
)There is no policy_id parameter. get_intent_token accepts plan_capture,
policy and validity_seconds only. The policy argument is an inline dict
that constrains this one token; it cannot reference a stored policy by id.
Policy Evaluation
How Policies are Applied
When you request an intent token, ArmorIQ:
- Loads applicable policies (user, agent, organization level)
- Merges policies by priority (higher priority wins)
- Evaluates plan actions against merged policy
- Rejects token if any action violates policy
- Embeds policy hash in token
At invocation time, ArmorIQ:
- Extracts policy from token
- Checks if action matches allow/deny patterns
- Verifies time restrictions (if any)
- Checks rate limits
- Validates IP address (if whitelist exists)
- Allows or denies request
Allow/Deny Pattern Matching
Policies use glob patterns for flexible matching:
policy = {
"allow": [
"data-mcp/*", # All data-mcp actions
"analytics-mcp/fetch_*" # Only fetch actions in analytics-mcp
],
"deny": [
"data-mcp/delete_*", # No delete actions
"admin-mcp/*" # No admin actions at all
]
}Matching Rules:
*matches any stringdata-mcp/*matchesdata-mcp/fetch,data-mcp/analyze, etc.data-mcp/fetch_*matchesdata-mcp/fetch_users,data-mcp/fetch_orders, etc.- Deny takes precedence over allow
Priority Resolution
When multiple policies apply:
# User-level policy (priority 30)
user_policy = {
"allow": ["data-mcp/*"],
"priority": 30
}
# Agent-level policy (priority 60)
agent_policy = {
"deny": ["data-mcp/delete_*"],
"priority": 60
}
# Organization-level policy (priority 90)
org_policy = {
"allow": ["analytics-mcp/*"],
"priority": 90
}
# Merged result:
# - data-mcp/* allowed (user policy)
# - data-mcp/delete_* denied (agent policy, higher priority)
# - analytics-mcp/* allowed (org policy, highest priority)Policy Examples
Example 1: Read-Only Agent
# Agent can only read data, no writes
readonly_policy = {
"allow": [
"data-mcp/fetch_*",
"data-mcp/query_*",
"analytics-mcp/analyze_*"
],
"deny": [
"data-mcp/insert_*",
"data-mcp/update_*",
"data-mcp/delete_*"
],
"rate_limit": 1000
}Example 2: Business Hours Only
# Agent only works during business hours
business_hours_policy = {
"allow": ["*"],
"time_restrictions": {
"allowed_hours": [9, 10, 11, 12, 13, 14, 15, 16, 17],
"allowed_days": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
}
}Example 3: High-Security Agent
# Agent with strict security constraints
secure_policy = {
"allow": ["finance-mcp/fetch_balance", "finance-mcp/calculate_*"],
"deny": ["finance-mcp/transfer_*", "finance-mcp/withdraw_*"],
"ip_whitelist": ["10.0.0.0/8"], # Internal network only
"rate_limit": 50,
"time_restrictions": {
"allowed_hours": [9, 10, 11, 12, 13, 14, 15, 16, 17],
"allowed_days": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
}
}Example 4: Development Agent
# Permissive policy for development
dev_policy = {
"allow": ["*"],
"deny": ["production-mcp/*"], # No production access
"rate_limit": 10000
}Policy Composition
You can compose policies for different scenarios:
def get_policy(environment: str, role: str) -> dict:
"""Get policy based on environment and role."""
base_policy = {
"rate_limit": 100,
"priority": 50
}
# Environment-specific
if environment == "production":
base_policy["ip_whitelist"] = ["10.0.0.0/8"]
base_policy["time_restrictions"] = {
"allowed_hours": list(range(9, 18)),
"allowed_days": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
}
# Role-specific
if role == "admin":
base_policy["allow"] = ["*"]
base_policy["rate_limit"] = 1000
elif role == "analyst":
base_policy["allow"] = ["data-mcp/fetch_*", "analytics-mcp/*"]
base_policy["deny"] = ["data-mcp/delete_*"]
elif role == "viewer":
base_policy["allow"] = ["data-mcp/fetch_*"]
base_policy["deny"] = ["*"]
return base_policy
# Usage
policy = get_policy(environment="production", role="analyst")
token = client.get_intent_token(plan_capture=plan, policy=policy)Testing Policies
1. Dry Run Validation
There is no dry_run parameter. get_intent_token takes plan_capture,
policy and validity_seconds only. To test a policy, mint a short-lived
token and catch the block.
from armoriq_sdk import PolicyBlockedException
try:
token = client.get_intent_token(
plan_capture=plan,
policy=test_policy,
validity_seconds=60, # short window; discard it after the check
)
print("✓ Policy allows this plan")
except PolicyBlockedException as e:
print(f"✗ Blocked by policy ({e.enforcement_action}): {e.reason}")2. Policy Simulation
# Test if action would be allowed
def simulate_policy(policy: dict, mcp: str, action: str) -> bool:
"""Check if action would be allowed by policy."""
full_action = f"{mcp}/{action}"
# Check deny patterns
for pattern in policy.get("deny", []):
if fnmatch.fnmatch(full_action, pattern):
return False
# Check allow patterns
for pattern in policy.get("allow", []):
if fnmatch.fnmatch(full_action, pattern):
return True
return False
# Usage
policy = {"allow": ["data-mcp/*"], "deny": ["data-mcp/delete_*"]}
print(simulate_policy(policy, "data-mcp", "fetch_data")) # True
print(simulate_policy(policy, "data-mcp", "delete_all")) # FalsePolicy Management Best Practices
1. Start Restrictive, Then Relax
# Start with minimal permissions
initial_policy = {
"allow": ["data-mcp/fetch_data"],
"deny": ["*"]
}
# Add permissions as needed
expanded_policy = {
"allow": ["data-mcp/fetch_*", "analytics-mcp/analyze"],
"deny": ["data-mcp/delete_*"]
}2. Use Environment-Specific Policies
policies = {
"development": {
"allow": ["*"],
"deny": ["production-mcp/*"],
"rate_limit": 10000
},
"staging": {
"allow": ["*"],
"deny": ["production-mcp/*"],
"rate_limit": 1000
},
"production": {
"allow": ["data-mcp/fetch_*", "analytics-mcp/*"],
"deny": ["data-mcp/delete_*"],
"rate_limit": 100,
"ip_whitelist": ["10.0.0.0/8"]
}
}
env = os.getenv("ENVIRONMENT", "development")
policy = policies[env]3. Version Your Policies
policy_v1 = {
"version": "1.0.0",
"allow": ["data-mcp/*"],
"deny": []
}
policy_v2 = {
"version": "2.0.0",
"allow": ["data-mcp/*", "analytics-mcp/*"],
"deny": ["data-mcp/delete_*"]
}
# Use version in metadata
token = client.get_intent_token(
plan_capture=plan,
policy=policy_v2,
metadata={"policy_version": "2.0.0"}
)4. Monitor Policy Violations
from armoriq_sdk import PolicyBlockedException
# Log policy blocks for security monitoring
try:
token = client.get_intent_token(plan_capture=plan, policy=policy)
except PolicyBlockedException as e:
logger.error("Policy blocked the plan: %s", e, extra={
"user_id": user_id,
"agent_id": agent_id,
"enforcement_action": e.enforcement_action,
"reason": e.reason,
"metadata": e.metadata,
"policy": policy,
})
raisePolicyBlockedException carries enforcement_action, reason and metadata.
The TypeScript equivalent adds matchedPolicy. See
Error Handling for the full attribute table.
Common Policy Patterns
Pattern 1: Separation of Concerns
# Data team: Read-only access to data
data_team_policy = {
"allow": ["data-mcp/fetch_*", "data-mcp/query_*"],
"deny": ["data-mcp/insert_*", "data-mcp/delete_*"]
}
# Analytics team: Read + compute
analytics_team_policy = {
"allow": ["data-mcp/fetch_*", "analytics-mcp/*"],
"deny": ["data-mcp/insert_*", "data-mcp/delete_*"]
}
# Admin team: Full access
admin_team_policy = {
"allow": ["*"],
"deny": []
}Pattern 2: Progressive Permissions
# Level 1: Basic access
level1_policy = {
"allow": ["data-mcp/fetch_public_*"],
"rate_limit": 100
}
# Level 2: Intermediate access
level2_policy = {
"allow": ["data-mcp/fetch_*", "analytics-mcp/basic_*"],
"rate_limit": 500
}
# Level 3: Advanced access
level3_policy = {
"allow": ["data-mcp/*", "analytics-mcp/*"],
"deny": ["data-mcp/delete_*"],
"rate_limit": 1000
}Next Steps
- get_intent_token() - Apply policies to tokens
- Security Model - Understand verification
- Token Lifecycle - How tokens work