Concepts

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

FieldTypeDescriptionExample
allowlist[str]Allowed MCP/action patterns (glob)["data-mcp/*"]
denylist[str]Denied MCP/action patterns (glob)["data-mcp/delete_*"]
allowed_toolslist[str]Whitelisted tool names["read_file", "analyze"]
rate_limitintMax requests per hour100
ip_whitelistlist[str]Allowed IPs/CIDR ranges["10.0.0.0/8"]
time_restrictionsobjectTime-based access controlSee below
priorityintPolicy 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:

  1. Click "Canvas" button to open visual builder
  2. Drag users, MCPs, and agents onto canvas
  3. Connect entities with edges (connections)
  4. Click edge to configure permissions visually
  5. Use "Browse Tools" to select allowed tools from MCP
  6. Set IP restrictions, time windows, rate limits
  7. 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:

  1. Loads applicable policies (user, agent, organization level)
  2. Merges policies by priority (higher priority wins)
  3. Evaluates plan actions against merged policy
  4. Rejects token if any action violates policy
  5. Embeds policy hash in token

At invocation time, ArmorIQ:

  1. Extracts policy from token
  2. Checks if action matches allow/deny patterns
  3. Verifies time restrictions (if any)
  4. Checks rate limits
  5. Validates IP address (if whitelist exists)
  6. 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 string
  • data-mcp/* matches data-mcp/fetch, data-mcp/analyze, etc.
  • data-mcp/fetch_* matches data-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"))  # False

Policy 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,
    })
    raise

PolicyBlockedException 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

On this page