Policy Design Patterns
Best practices and proven patterns for writing effective MeshGuard policies that scale.
Why Patterns Matter
Writing AI agent policies is a new discipline. Unlike traditional access control (RBAC for users, IAM for cloud resources), agent policies must handle:
- Autonomous decision-making: Agents act without human approval for each action
- Dynamic contexts: The same agent might need different permissions at different times
- Complex delegation: Agent A asks Agent B to do something on A's behalf
- High velocity: Agents can attempt thousands of actions per minute
Without established patterns, policy authors tend to either:
- Over-permit: Grant broad access to "make it work," creating security holes
- Over-restrict: Lock everything down, making agents useless
This guide presents battle-tested patterns that balance security with functionality.
The Foundation: Deny-by-Default
Every effective policy starts with the same foundation: deny everything unless explicitly permitted.
The Pattern
name: foundation-policy
version: 1
rules:
# Catch-all: deny everything by default
- action: "*"
effect: deny
reason: "Action not explicitly permitted"
# Then add specific allows
- action: "read:public_data"
effect: allowWhy It Works
With deny-by-default:
- New actions are blocked until reviewed
- Forgotten permissions don't create vulnerabilities
- You can audit exactly what's allowed
The Anti-Pattern: Allow-by-Default
# ❌ DANGEROUS: Don't do this
rules:
- action: "*"
effect: allow
- action: "admin:*"
effect: deny # Trying to block specific thingsThis seems simpler but fails because:
- You must anticipate every dangerous action
- New features are automatically permitted
- One missed deny creates a vulnerability
Real-World Example: Customer Service Agent
A customer service agent needs to read customer data and create tickets. With deny-by-default:
name: customer-service-foundation
version: 1
rules:
# Start with deny-all
- action: "*"
effect: deny
reason: "Customer service agent has limited permissions"
# Explicitly allow required actions
- action: "read:customer_profile"
effect: allow
- action: "read:order_history"
effect: allow
- action: "write:support_ticket"
effect: allow
# Note: agent cannot write:customer_profile, delete anything,
# or access billing — because we didn't allow itRole-Based Patterns
Different agents have different jobs. Role-based patterns group permissions by function.
The Pattern
Define policies for roles, then assign agents to roles:
name: role-customer-service-basic
version: 1
description: "Basic customer service capabilities"
rules:
- action: "*"
effect: deny
- action: "read:customer_profile"
effect: allow
- action: "read:order_history"
effect: allow
- action: "write:support_ticket"
effect: allow
- action: "write:internal_note"
effect: allowname: role-customer-service-senior
version: 1
description: "Senior CS with refund authority"
extends: role-customer-service-basic
rules:
# Inherits all basic permissions, plus:
- action: "write:refund"
effect: allow
conditions:
- "request.amount <= 100"
- action: "write:account_credit"
effect: allow
conditions:
- "request.amount <= 50"Why It Works
Role-based patterns provide:
- Consistency: All agents in a role have the same permissions
- Maintainability: Update the role, update all agents
- Clarity: "What can this agent do?" → "What role is it?"
Role Hierarchy Example
┌─────────────────────────────────────────────────────────┐
│ admin-agent │
│ Can: Everything including policy management │
└─────────────────────────────────────────────────────────┘
│
│ extends
▼
┌─────────────────────────────────────────────────────────┐
│ senior-cs-agent │
│ Can: Basic CS + refunds up to $100 + credits │
└─────────────────────────────────────────────────────────┘
│
│ extends
▼
┌─────────────────────────────────────────────────────────┐
│ basic-cs-agent │
│ Can: Read customer data, create tickets │
└─────────────────────────────────────────────────────────┘The Anti-Pattern: Copy-Paste Policies
# ❌ BAD: Duplicated policies for each agent
name: agent-alice-policy
rules:
- action: "read:customer_profile"
effect: allow
# ... 20 more rules
name: agent-bob-policy
rules:
- action: "read:customer_profile"
effect: allow
# ... same 20 rules, slightly differentThis creates:
- Drift: Policies diverge over time
- Maintenance burden: Updates require touching every policy
- Audit confusion: Why is Bob different from Alice?
Time-Based Patterns
Some actions should only be allowed during certain time windows.
The Pattern
Use time conditions to restrict when actions are permitted:
name: business-hours-policy
version: 1
rules:
- action: "*"
effect: deny
# Allow customer data access only during business hours
- action: "read:customer_*"
effect: allow
conditions:
- "time.hour >= 9 AND time.hour < 18" # 9 AM to 6 PM
- "time.weekday IN [1,2,3,4,5]" # Monday-Friday
# After hours: read-only access to knowledge base
- action: "read:knowledge_base"
effect: allowUse Cases
1. Maintenance Windows
Allow destructive operations only during scheduled maintenance:
name: database-maintenance-policy
version: 1
rules:
# Normal operations always allowed
- action: "read:database_*"
effect: allow
- action: "write:database_*"
effect: allow
# Destructive operations only in maintenance windows
- action: "delete:database_*"
effect: allow
conditions:
- "time.weekday == 7" # Sunday only
- "time.hour >= 2 AND time.hour < 6" # 2-6 AM
- action: "admin:database_*"
effect: allow
conditions:
- "time.weekday == 7"
- "time.hour >= 2 AND time.hour < 6"2. Trading Hours
Financial agents restricted to market hours:
name: trading-agent-policy
version: 1
rules:
# Market data always readable
- action: "read:market_*"
effect: allow
# Trades only during market hours (9:30 AM - 4 PM ET)
- action: "write:trade_*"
effect: allow
conditions:
- "time.hour >= 9 AND (time.hour < 16 OR (time.hour == 9 AND time.minute >= 30))"
- "time.weekday IN [1,2,3,4,5]"
- "NOT calendar.is_market_holiday"3. Escalation Windows
Allow autonomous action during off-hours when humans aren't available:
name: incident-response-policy
version: 1
rules:
# During business hours: alert humans
- action: "notify:oncall_team"
effect: allow
conditions:
- "time.hour >= 9 AND time.hour < 18"
# After hours: agent can take autonomous action
- action: "execute:incident_remediation"
effect: allow
conditions:
- "time.hour < 9 OR time.hour >= 18" # Off hours
- "incident.severity IN ['critical', 'high']"The Anti-Pattern: Hardcoded Time Checks
# ❌ BAD: Hardcoding times without timezone awareness
conditions:
- "time.hour >= 9" # Which timezone? Server? User? Agent?Always specify timezone explicitly:
# ✅ GOOD: Explicit timezone
conditions:
- "time.hour >= 9 AND time.hour < 18"
- "time.timezone == 'America/New_York'"Resource-Based Patterns
Different resources require different protection levels.
The Pattern
Classify resources, then apply policies based on classification:
name: resource-classification-policy
version: 1
rules:
- action: "*"
effect: deny
# Public resources: broad access
- action: "read:*"
effect: allow
conditions:
- "resource.classification == 'public'"
# Internal resources: verified agents
- action: "read:*"
effect: allow
conditions:
- "resource.classification == 'internal'"
- "agent.trust_tier IN ['verified', 'trusted', 'privileged']"
# Confidential resources: trusted agents only
- action: "read:*"
effect: allow
conditions:
- "resource.classification == 'confidential'"
- "agent.trust_tier IN ['trusted', 'privileged']"
# Restricted resources: privileged agents only
- action: "read:*"
effect: allow
conditions:
- "resource.classification == 'restricted'"
- "agent.trust_tier == 'privileged'"Resource Hierarchies
Protect parent-child relationships:
name: database-resource-policy
version: 1
rules:
# All agents can query analytics database
- action: "read:database.analytics.*"
effect: allow
# Only trusted agents can query production
- action: "read:database.production.*"
effect: allow
conditions:
- "agent.trust_tier IN ['trusted', 'privileged']"
# Only privileged agents can write to production
- action: "write:database.production.*"
effect: allow
conditions:
- "agent.trust_tier == 'privileged'"
# Nobody can drop production tables without explicit approval
- action: "admin:database.production.drop_table"
effect: deny
reason: "Table drops require manual approval process"Data-Type Policies
Apply rules based on data types:
name: pii-protection-policy
version: 1
rules:
# PII can be read but not exported
- action: "read:*.pii_*"
effect: allow
conditions:
- "agent.trust_tier IN ['trusted', 'privileged']"
- action: "export:*.pii_*"
effect: deny
reason: "PII cannot be exported by agents"
# Anonymized data can be exported
- action: "export:*.anonymized_*"
effect: allow
# Financial data requires additional logging
- action: "*:*.financial_*"
effect: allow
conditions:
- "agent.trust_tier IN ['trusted', 'privileged']"
audit:
level: enhanced
notify: ["compliance@company.com"]The Anti-Pattern: Flat Resource Permissions
# ❌ BAD: No resource distinction
rules:
- action: "read:database"
effect: allow
- action: "write:database"
effect: allowThis allows reading/writing anything in any database. Classify and protect.
Conditional Patterns
Real-world policies often need multiple conditions working together.
The Pattern: AND Conditions
All conditions must be true:
rules:
- action: "write:refund"
effect: allow
conditions:
- "agent.trust_tier == 'trusted'" # Must be trusted
- "request.amount <= 100" # Must be small
- "customer.account_age_days >= 30" # Must be established customerThe Pattern: OR Conditions
Any condition being true is sufficient:
rules:
- action: "read:confidential_docs"
effect: allow
conditions:
any:
- "agent.department == 'legal'"
- "agent.department == 'executive'"
- "agent.has_role == 'auditor'"Complex Conditionals
Combine AND and OR:
rules:
- action: "write:large_transaction"
effect: allow
conditions:
all:
- "request.amount > 10000"
any:
- "agent.trust_tier == 'privileged'"
- all:
- "agent.trust_tier == 'trusted'"
- "request.has_dual_approval == true"This allows large transactions if:
- Agent is privileged, OR
- Agent is trusted AND has dual approval
Real-World Example: Data Processing Agent
A data processing agent that handles ETL pipelines:
name: data-processor-policy
version: 1
description: "ETL pipeline agent with environment-aware permissions"
rules:
- action: "*"
effect: deny
# Read from any source
- action: "read:source.*"
effect: allow
# Write to staging in any environment
- action: "write:staging.*"
effect: allow
# Write to production only if:
# - Pipeline has been tested (staging success)
# - It's during approved deployment window
# - Data validation passed
- action: "write:production.*"
effect: allow
conditions:
all:
- "pipeline.staging_status == 'success'"
- "pipeline.validation_status == 'passed'"
- any:
- "deploy_window.is_active == true"
- "request.emergency_override == true AND agent.trust_tier == 'privileged'"
# Delete operations only in non-production
- action: "delete:*"
effect: allow
conditions:
- "resource.environment != 'production'"Anti-Patterns to Avoid
Learn from common mistakes.
Anti-Pattern 1: The "God Agent"
# ❌ TERRIBLE: One agent that can do everything
name: super-agent-policy
rules:
- action: "*"
effect: allowWhy it's bad:
- No audit granularity
- Compromise means total access
- Impossible to debug behavior
Fix: Create specific roles even if one team manages the agent.
Anti-Pattern 2: Permission Creep
# Policy version 1
rules:
- action: "read:orders"
effect: allow
# Version 2: "Just add this one thing"
rules:
- action: "read:orders"
effect: allow
- action: "write:orders" # Added
effect: allow
# Version 47: "I don't know what this agent can do anymore"
rules:
- action: "read:*"
effect: allow
- action: "write:*"
effect: allow
- action: "delete:*" # How did this get here?
effect: allowFix: Regular policy reviews. Document why each permission exists.
Anti-Pattern 3: Overly Broad Wildcards
# ❌ BAD: Wildcards without constraints
rules:
- action: "read:customer_*"
effect: allow # Matches customer_pii, customer_credit_card, etc.Fix: Be explicit or add conditions:
# ✅ GOOD: Explicit actions
rules:
- action: "read:customer_profile"
effect: allow
- action: "read:customer_orders"
effect: allow
# Explicitly not including customer_pii
# ✅ ALSO GOOD: Wildcard with exclusions
rules:
- action: "read:customer_*"
effect: allow
conditions:
- "NOT action MATCHES 'read:customer_pii'"
- "NOT action MATCHES 'read:customer_payment_*'"Anti-Pattern 4: Deny Without Reason
# ❌ BAD: No context for denials
rules:
- action: "admin:*"
effect: denyFix: Always explain why:
# ✅ GOOD: Reason helps debugging and auditing
rules:
- action: "admin:*"
effect: deny
reason: "Admin operations require manual approval. Contact platform-team@company.com"Anti-Pattern 5: Testing in Production
Writing policies directly against production resources without testing.
Fix: Use a staged approach (see next section).
Testing and Iterating on Policies
Policies should be developed like software: write, test, deploy, monitor.
Stage 1: Dry-Run Mode
Test policies without enforcement:
name: new-policy-v1
version: 1
enforcement: dry_run # Log decisions but don't enforce
rules:
- action: "write:*"
effect: denyIn dry-run mode:
- All actions are allowed (as if policy didn't exist)
- Decisions are logged with "would have been denied" markers
- You can analyze impact before enforcing
Stage 2: Shadow Testing
Run the new policy alongside the old one:
# Both policies evaluate, only old one enforces
shadow_config = {
"active_policy": "existing-policy-v5",
"shadow_policy": "new-policy-v1",
}Compare decisions:
- Where do they agree?
- Where do they disagree?
- Are the disagreements intentional?
Stage 3: Gradual Rollout
Enforce for a subset of agents first:
name: new-policy-v1
version: 1
enforcement: enabled
rollout:
percentage: 10 # Only 10% of matching agents
# OR
agents: ["test-agent-1", "test-agent-2"] # Specific agents onlyStage 4: Full Deployment
After validation, remove rollout restrictions:
name: new-policy-v1
version: 1
enforcement: enabled
# rollout removed = applies to all matching agentsMonitoring Policy Performance
Track key metrics:
# In your policy config
monitoring:
alert_on:
- denial_rate > 20% # Too many denials might indicate policy is too strict
- denial_rate < 0.1% # Too few denials might indicate policy is too permissive
- unknown_actions > 10/hr # Actions hitting catch-all deny (need explicit rules)Policy Versioning
Always version your policies:
name: customer-service-policy
version: 12 # Increment on every change
changelog:
- version: 12
date: 2026-01-25
author: platform-team
changes: "Added refund limits for fraud prevention"
- version: 11
date: 2026-01-10
changes: "Expanded read access to order history"Real-World Scenario: Admin Agent
An admin agent needs powerful capabilities but must be tightly controlled.
name: admin-agent-policy
version: 1
description: "System administration agent with enhanced logging"
# Require privileged trust tier
required_trust_tier: privileged
rules:
- action: "*"
effect: deny
reason: "Admin actions must be explicitly permitted"
# System health: always allowed
- action: "read:system.*"
effect: allow
# Configuration changes: allowed with logging
- action: "write:config.*"
effect: allow
audit:
level: enhanced
include_diff: true # Log what changed
# Service restarts: allowed during maintenance windows
- action: "admin:service.restart"
effect: allow
conditions:
- "time.is_maintenance_window == true"
audit:
level: enhanced
notify: ["oncall@company.com"]
# User management: requires dual approval
- action: "admin:user.*"
effect: allow
conditions:
- "request.approval_count >= 2"
audit:
level: enhanced
notify: ["security@company.com"]
# Destructive operations: never automated
- action: "admin:*.delete"
effect: deny
reason: "Destructive admin operations require manual execution"
- action: "admin:*.drop"
effect: deny
reason: "Destructive admin operations require manual execution"Putting It All Together
Here's how patterns combine in practice:
name: comprehensive-agent-policy
version: 3
description: "Production policy for customer-facing agents"
# Foundation: Deny by default
default_effect: deny
# Role-based: Inherit from base role
extends: role-customer-service-basic
rules:
# Resource-based: Different data, different rules
- action: "read:customer.public_*"
effect: allow
- action: "read:customer.private_*"
effect: allow
conditions:
- "agent.trust_tier IN ['trusted', 'privileged']"
# Time-based: Refunds only during business hours
- action: "write:refund"
effect: allow
conditions:
- "agent.trust_tier == 'trusted'"
- "request.amount <= 100"
- "time.hour >= 9 AND time.hour < 18"
- "time.timezone == 'America/New_York'"
# Conditional: Large refunds need approval
- action: "write:refund"
effect: allow
conditions:
- "request.amount > 100 AND request.amount <= 500"
- "request.manager_approval == true"
# Monitoring
monitoring:
alert_on:
- denial_rate > 15%
- "action:write:refund denial_count > 50/day"Key Takeaways
- Start with deny-by-default — it's the only safe foundation
- Use roles — group permissions by function, not by agent
- Add time constraints — not everything should be allowed 24/7
- Classify resources — different data needs different protection
- Test before deploying — use dry-run and shadow modes
- Monitor and iterate — policies are never "done"
- Document everything — reasons, changelogs, and decisions
Good policy design is the difference between AI agents that enhance your business and AI agents that become liabilities.
Next Steps
Ready to dive deeper? Explore these related concepts:
- What is Agent Governance? — Understand the foundations
- Trust Tiers Explained — Learn how trust levels enable graduated autonomy
Design Better Policies Today
MeshGuard provides the tools to implement these patterns at scale — policy versioning, dry-run testing, real-time monitoring, and comprehensive audit logs.
