Learn how to define what your AI agent can and cannot do through access controls, action policies, rate limits, and scope boundaries. Master the art of balancing agent capability with security and trust.

This article is part of the free-to-read AI Agent Handbook
Environment Boundaries and Constraints
In the previous sections, we explored how our assistant perceives its environment and takes actions within it. But here's a crucial question: should your agent have unlimited access to everything? Can it read any file, call any API, or execute any command?
The answer, of course, is no. Just as you wouldn't give a new employee access to every system on day one, your AI agent needs clearly defined boundaries. These constraints aren't limitations in a negative sense. They're protective guardrails that make your agent safer, more predictable, and easier to trust.
Let's explore how to define what your agent can and cannot do, and why these boundaries matter for building reliable AI systems.
Why Boundaries Matter
Think about a physical assistant working in your office. You might give them access to your calendar and email, but probably not your bank account or medical records. You'd want them to schedule meetings, but not delete important files. These natural boundaries exist because they match the assistant's role and minimize risk.
Your AI agent needs the same kind of thoughtful constraints. Without them, several problems can emerge:
Accidental damage: An agent trying to "help" by cleaning up files might delete something important. We've all seen overzealous automation go wrong. Clear boundaries prevent these well-intentioned mistakes.
Security risks: If your agent can access sensitive data, what happens if someone tricks it through prompt injection? Or if it logs information it shouldn't? Limiting access reduces the blast radius of any security issue.
Unpredictable behavior: When an agent has too many options, its decision-making becomes harder to reason about. Constraints actually make behavior more predictable by reducing the possibility space.
User trust: People are more comfortable with agents that have clear, limited permissions. "This agent can read your calendar and send emails" is much easier to trust than "This agent can do anything on your computer."
Let's see how to implement these boundaries in practice.
Types of Environment Constraints
When designing your agent's environment, you'll typically work with several categories of constraints. Each serves a different purpose in keeping your agent safe and effective.
Access Constraints
Access constraints define what data and resources your agent can reach. These are your first line of defense.
For our personal assistant, you might specify:
- Read access: Calendar events, contact list, recent emails
- Write access: Calendar events, draft emails (but not sent emails)
- No access: File system, browser history, system settings
Here's how you might implement this in code:
1## Example (Claude Sonnet 4.5)
2## Using Claude Sonnet 4.5 for its superior reasoning about permissions
3
4class AgentEnvironment:
5 def __init__(self, allowed_resources):
6 self.allowed_resources = set(allowed_resources)
7 self.access_log = []
8
9 def check_access(self, resource_type, operation):
10 """Verify if the agent can perform this operation"""
11 permission = f"{resource_type}:{operation}"
12
13 if permission in self.allowed_resources:
14 self.access_log.append({
15 "resource": resource_type,
16 "operation": operation,
17 "allowed": True
18 })
19 return True
20 else:
21 self.access_log.append({
22 "resource": resource_type,
23 "operation": operation,
24 "allowed": False
25 })
26 return False
27
28## Set up environment with specific permissions
29env = AgentEnvironment(allowed_resources=[
30 "calendar:read",
31 "calendar:write",
32 "email:read",
33 "email:draft" # Note: not email:send
34])
35
36## Agent tries to access calendar
37if env.check_access("calendar", "write"):
38 print("Agent can modify calendar")
39
40## Agent tries to send email
41if not env.check_access("email", "send"):
42 print("Agent cannot send emails without approval")1## Example (Claude Sonnet 4.5)
2## Using Claude Sonnet 4.5 for its superior reasoning about permissions
3
4class AgentEnvironment:
5 def __init__(self, allowed_resources):
6 self.allowed_resources = set(allowed_resources)
7 self.access_log = []
8
9 def check_access(self, resource_type, operation):
10 """Verify if the agent can perform this operation"""
11 permission = f"{resource_type}:{operation}"
12
13 if permission in self.allowed_resources:
14 self.access_log.append({
15 "resource": resource_type,
16 "operation": operation,
17 "allowed": True
18 })
19 return True
20 else:
21 self.access_log.append({
22 "resource": resource_type,
23 "operation": operation,
24 "allowed": False
25 })
26 return False
27
28## Set up environment with specific permissions
29env = AgentEnvironment(allowed_resources=[
30 "calendar:read",
31 "calendar:write",
32 "email:read",
33 "email:draft" # Note: not email:send
34])
35
36## Agent tries to access calendar
37if env.check_access("calendar", "write"):
38 print("Agent can modify calendar")
39
40## Agent tries to send email
41if not env.check_access("email", "send"):
42 print("Agent cannot send emails without approval")This simple permission system ensures your agent can only touch what you've explicitly allowed. Notice how we separate read and write operations. This granularity matters because reading data is generally safer than modifying it.
Action Constraints
Beyond what your agent can access, you need to control what actions it can take. Some operations are inherently more risky than others.
Consider these action tiers:
Safe actions (can happen automatically):
- Reading information
- Generating text or summaries
- Performing calculations
- Searching within allowed data
Moderate actions (might need confirmation):
- Creating calendar events
- Drafting emails
- Saving notes or files
- Making API calls to external services
High-risk actions (always need confirmation):
- Sending emails or messages
- Deleting data
- Making purchases
- Changing system settings
Here's how you might implement action constraints:
1## Example (Claude Sonnet 4.5)
2## Using Claude Sonnet 4.5 for its ability to reason about action safety
3
4class ActionPolicy:
5 def __init__(self):
6 self.safe_actions = {"read", "search", "calculate", "summarize"}
7 self.confirm_actions = {"create", "draft", "save"}
8 self.restricted_actions = {"send", "delete", "purchase"}
9
10 def requires_confirmation(self, action):
11 """Check if this action needs user approval"""
12 if action in self.safe_actions:
13 return False
14 elif action in self.confirm_actions:
15 return "optional" # Could be configured per user
16 elif action in self.restricted_actions:
17 return True
18 else:
19 return True # Unknown actions require confirmation by default
20
21policy = ActionPolicy()
22
23## Agent wants to send an email
24action = "send"
25if policy.requires_confirmation(action):
26 print(f"Action '{action}' requires user confirmation")
27 # In a real system, you'd prompt the user here
28 user_approved = input("Approve this action? (yes/no): ")
29 if user_approved.lower() == "yes":
30 print("Proceeding with send...")
31 else:
32 print("Action cancelled")1## Example (Claude Sonnet 4.5)
2## Using Claude Sonnet 4.5 for its ability to reason about action safety
3
4class ActionPolicy:
5 def __init__(self):
6 self.safe_actions = {"read", "search", "calculate", "summarize"}
7 self.confirm_actions = {"create", "draft", "save"}
8 self.restricted_actions = {"send", "delete", "purchase"}
9
10 def requires_confirmation(self, action):
11 """Check if this action needs user approval"""
12 if action in self.safe_actions:
13 return False
14 elif action in self.confirm_actions:
15 return "optional" # Could be configured per user
16 elif action in self.restricted_actions:
17 return True
18 else:
19 return True # Unknown actions require confirmation by default
20
21policy = ActionPolicy()
22
23## Agent wants to send an email
24action = "send"
25if policy.requires_confirmation(action):
26 print(f"Action '{action}' requires user confirmation")
27 # In a real system, you'd prompt the user here
28 user_approved = input("Approve this action? (yes/no): ")
29 if user_approved.lower() == "yes":
30 print("Proceeding with send...")
31 else:
32 print("Action cancelled")Notice the three-tier system. This gives you flexibility. A power user might configure their agent to automatically create calendar events, while a cautious user might want to review every action.
Rate and Resource Constraints
Even for allowed actions, you might want to limit how often or how much your agent can do something. This prevents runaway behavior and controls costs.
1## Example (GPT-5)
2## Using GPT-5 for this straightforward rate limiting example
3
4from datetime import datetime, timedelta
5
6class RateLimiter:
7 def __init__(self, max_calls, time_window):
8 self.max_calls = max_calls
9 self.time_window = time_window # in seconds
10 self.calls = []
11
12 def can_proceed(self, action):
13 """Check if we're within rate limits"""
14 now = datetime.now()
15
16 # Remove old calls outside the time window
17 cutoff = now - timedelta(seconds=self.time_window)
18 self.calls = [call for call in self.calls if call > cutoff]
19
20 # Check if we're under the limit
21 if len(self.calls) < self.max_calls:
22 self.calls.append(now)
23 return True
24 else:
25 return False
26
27## Limit API calls to 10 per minute
28api_limiter = RateLimiter(max_calls=10, time_window=60)
29
30## Agent tries to make an API call
31if api_limiter.can_proceed("api_call"):
32 print("Making API call...")
33else:
34 print("Rate limit exceeded. Please wait.")1## Example (GPT-5)
2## Using GPT-5 for this straightforward rate limiting example
3
4from datetime import datetime, timedelta
5
6class RateLimiter:
7 def __init__(self, max_calls, time_window):
8 self.max_calls = max_calls
9 self.time_window = time_window # in seconds
10 self.calls = []
11
12 def can_proceed(self, action):
13 """Check if we're within rate limits"""
14 now = datetime.now()
15
16 # Remove old calls outside the time window
17 cutoff = now - timedelta(seconds=self.time_window)
18 self.calls = [call for call in self.calls if call > cutoff]
19
20 # Check if we're under the limit
21 if len(self.calls) < self.max_calls:
22 self.calls.append(now)
23 return True
24 else:
25 return False
26
27## Limit API calls to 10 per minute
28api_limiter = RateLimiter(max_calls=10, time_window=60)
29
30## Agent tries to make an API call
31if api_limiter.can_proceed("api_call"):
32 print("Making API call...")
33else:
34 print("Rate limit exceeded. Please wait.")Rate limiting is especially important for actions that cost money (like API calls) or could annoy users (like sending notifications). It's a safety net that prevents your agent from going haywire.
Scope Constraints: Defining the Sandbox
Beyond individual permissions, you can define broader scope constraints that limit where your agent operates. Think of this as creating a sandbox for your agent to play in.
File System Boundaries
If your agent needs file access, restrict it to specific directories:
1## Example (Claude Sonnet 4.5)
2## Using Claude Sonnet 4.5 for its careful reasoning about file system safety
3
4import os
5from pathlib import Path
6
7class FileSystemBoundary:
8 def __init__(self, allowed_paths):
9 # Convert to absolute paths and normalize
10 self.allowed_paths = [
11 Path(p).resolve() for p in allowed_paths
12 ]
13
14 def is_allowed(self, file_path):
15 """Check if a file path is within allowed boundaries"""
16 try:
17 file_path = Path(file_path).resolve()
18
19 # Check if the path is under any allowed directory
20 for allowed in self.allowed_paths:
21 try:
22 file_path.relative_to(allowed)
23 return True
24 except ValueError:
25 continue
26
27 return False
28 except Exception:
29 return False
30
31## Agent can only access files in these directories
32fs_boundary = FileSystemBoundary(allowed_paths=[
33 "/home/user/documents/agent_workspace",
34 "/home/user/notes"
35])
36
37## Test some paths
38test_paths = [
39 "/home/user/documents/agent_workspace/data.txt", # Allowed
40 "/home/user/notes/meeting.txt", # Allowed
41 "/home/user/.ssh/id_rsa", # Not allowed
42 "/etc/passwd" # Definitely not allowed
43]
44
45for path in test_paths:
46 allowed = fs_boundary.is_allowed(path)
47 print(f"{path}: {'✓ Allowed' if allowed else '✗ Blocked'}")1## Example (Claude Sonnet 4.5)
2## Using Claude Sonnet 4.5 for its careful reasoning about file system safety
3
4import os
5from pathlib import Path
6
7class FileSystemBoundary:
8 def __init__(self, allowed_paths):
9 # Convert to absolute paths and normalize
10 self.allowed_paths = [
11 Path(p).resolve() for p in allowed_paths
12 ]
13
14 def is_allowed(self, file_path):
15 """Check if a file path is within allowed boundaries"""
16 try:
17 file_path = Path(file_path).resolve()
18
19 # Check if the path is under any allowed directory
20 for allowed in self.allowed_paths:
21 try:
22 file_path.relative_to(allowed)
23 return True
24 except ValueError:
25 continue
26
27 return False
28 except Exception:
29 return False
30
31## Agent can only access files in these directories
32fs_boundary = FileSystemBoundary(allowed_paths=[
33 "/home/user/documents/agent_workspace",
34 "/home/user/notes"
35])
36
37## Test some paths
38test_paths = [
39 "/home/user/documents/agent_workspace/data.txt", # Allowed
40 "/home/user/notes/meeting.txt", # Allowed
41 "/home/user/.ssh/id_rsa", # Not allowed
42 "/etc/passwd" # Definitely not allowed
43]
44
45for path in test_paths:
46 allowed = fs_boundary.is_allowed(path)
47 print(f"{path}: {'✓ Allowed' if allowed else '✗ Blocked'}")This prevents your agent from accidentally (or maliciously) accessing sensitive files. Notice how we use resolve() to handle symbolic links and relative paths. Security boundaries need to be airtight.
Network Boundaries
Similarly, you might restrict which external services your agent can contact:
1## Example (Claude Sonnet 4.5)
2## Using Claude Sonnet 4.5 for security-conscious network boundary logic
3
4from urllib.parse import urlparse
5
6class NetworkBoundary:
7 def __init__(self, allowed_domains, blocked_domains=None):
8 self.allowed_domains = set(allowed_domains)
9 self.blocked_domains = set(blocked_domains or [])
10
11 def can_access(self, url):
12 """Check if the agent can access this URL"""
13 try:
14 domain = urlparse(url).netloc
15
16 # Check blocklist first
17 if domain in self.blocked_domains:
18 return False
19
20 # Check allowlist
21 if self.allowed_domains:
22 return domain in self.allowed_domains
23
24 # If no allowlist, allow by default (except blocked)
25 return True
26
27 except Exception:
28 return False
29
30## Agent can only access specific APIs
31net_boundary = NetworkBoundary(
32 allowed_domains=[
33 "api.weather.com",
34 "api.calendar.google.com",
35 "api.openai.com"
36 ],
37 blocked_domains=[
38 "malicious-site.com"
39 ]
40)
41
42## Test some URLs
43if net_boundary.can_access("https://api.weather.com/forecast"):
44 print("✓ Can fetch weather data")
45
46if not net_boundary.can_access("https://random-website.com"):
47 print("✗ Cannot access arbitrary websites")1## Example (Claude Sonnet 4.5)
2## Using Claude Sonnet 4.5 for security-conscious network boundary logic
3
4from urllib.parse import urlparse
5
6class NetworkBoundary:
7 def __init__(self, allowed_domains, blocked_domains=None):
8 self.allowed_domains = set(allowed_domains)
9 self.blocked_domains = set(blocked_domains or [])
10
11 def can_access(self, url):
12 """Check if the agent can access this URL"""
13 try:
14 domain = urlparse(url).netloc
15
16 # Check blocklist first
17 if domain in self.blocked_domains:
18 return False
19
20 # Check allowlist
21 if self.allowed_domains:
22 return domain in self.allowed_domains
23
24 # If no allowlist, allow by default (except blocked)
25 return True
26
27 except Exception:
28 return False
29
30## Agent can only access specific APIs
31net_boundary = NetworkBoundary(
32 allowed_domains=[
33 "api.weather.com",
34 "api.calendar.google.com",
35 "api.openai.com"
36 ],
37 blocked_domains=[
38 "malicious-site.com"
39 ]
40)
41
42## Test some URLs
43if net_boundary.can_access("https://api.weather.com/forecast"):
44 print("✓ Can fetch weather data")
45
46if not net_boundary.can_access("https://random-website.com"):
47 print("✗ Cannot access arbitrary websites")Network boundaries are crucial for preventing data leakage and ensuring your agent only communicates with trusted services.
Training vs. Production Environments
As you develop your agent, you'll work in different environments with different constraints. Understanding this distinction helps you test safely and deploy confidently.
Training and Testing Environments
When you're developing and testing your agent, you want an environment that:
- Mimics production but uses fake or sanitized data
- Allows experimentation without real consequences
- Provides detailed logging for debugging
- Can be reset easily when things go wrong
Here's a simple way to distinguish environments:
1## Example (GPT-5)
2## Using GPT-5 for this straightforward environment configuration
3
4import os
5
6class EnvironmentConfig:
7 def __init__(self, mode="development"):
8 self.mode = mode
9 self.setup_environment()
10
11 def setup_environment(self):
12 """Configure based on environment mode"""
13 if self.mode == "development":
14 self.database = "test_db"
15 self.api_calls_enabled = False
16 self.require_confirmations = False
17 self.verbose_logging = True
18 self.use_real_data = False
19
20 elif self.mode == "staging":
21 self.database = "staging_db"
22 self.api_calls_enabled = True
23 self.require_confirmations = True
24 self.verbose_logging = True
25 self.use_real_data = False
26
27 elif self.mode == "production":
28 self.database = "prod_db"
29 self.api_calls_enabled = True
30 self.require_confirmations = True
31 self.verbose_logging = False
32 self.use_real_data = True
33
34 def can_perform_action(self, action):
35 """Check if action is allowed in this environment"""
36 if self.mode == "development":
37 # In development, log but allow most things
38 print(f"[DEV] Would perform: {action}")
39 return True
40 else:
41 # In production, actually check permissions
42 return self.check_real_permissions(action)
43
44## Initialize for development
45config = EnvironmentConfig(mode="development")
46
47print(f"Running in {config.mode} mode")
48print(f"Using database: {config.database}")
49print(f"Real API calls: {config.api_calls_enabled}")1## Example (GPT-5)
2## Using GPT-5 for this straightforward environment configuration
3
4import os
5
6class EnvironmentConfig:
7 def __init__(self, mode="development"):
8 self.mode = mode
9 self.setup_environment()
10
11 def setup_environment(self):
12 """Configure based on environment mode"""
13 if self.mode == "development":
14 self.database = "test_db"
15 self.api_calls_enabled = False
16 self.require_confirmations = False
17 self.verbose_logging = True
18 self.use_real_data = False
19
20 elif self.mode == "staging":
21 self.database = "staging_db"
22 self.api_calls_enabled = True
23 self.require_confirmations = True
24 self.verbose_logging = True
25 self.use_real_data = False
26
27 elif self.mode == "production":
28 self.database = "prod_db"
29 self.api_calls_enabled = True
30 self.require_confirmations = True
31 self.verbose_logging = False
32 self.use_real_data = True
33
34 def can_perform_action(self, action):
35 """Check if action is allowed in this environment"""
36 if self.mode == "development":
37 # In development, log but allow most things
38 print(f"[DEV] Would perform: {action}")
39 return True
40 else:
41 # In production, actually check permissions
42 return self.check_real_permissions(action)
43
44## Initialize for development
45config = EnvironmentConfig(mode="development")
46
47print(f"Running in {config.mode} mode")
48print(f"Using database: {config.database}")
49print(f"Real API calls: {config.api_calls_enabled}")In development mode, your agent might print what it would do instead of actually doing it. This lets you test logic without consequences. When you move to production, those same actions become real.
Production Constraints
Production environments need stricter boundaries:
1## Example (Claude Sonnet 4.5)
2## Using Claude Sonnet 4.5 for production-grade safety logic
3
4class ProductionEnvironment:
5 def __init__(self):
6 self.safety_checks_enabled = True
7 self.audit_logging = True
8 self.rate_limits = {
9 "email": {"max": 50, "window": 3600}, # 50 per hour
10 "api_call": {"max": 1000, "window": 3600},
11 "file_write": {"max": 100, "window": 3600}
12 }
13
14 def execute_action(self, action_type, action_data):
15 """Execute an action with production safety checks"""
16
17 # Check rate limits
18 if not self.check_rate_limit(action_type):
19 return {
20 "success": False,
21 "error": "Rate limit exceeded"
22 }
23
24 # Perform safety checks
25 if self.safety_checks_enabled:
26 safety_result = self.run_safety_checks(action_type, action_data)
27 if not safety_result["safe"]:
28 self.log_security_event(action_type, safety_result)
29 return {
30 "success": False,
31 "error": f"Safety check failed: {safety_result['reason']}"
32 }
33
34 # Log for audit
35 if self.audit_logging:
36 self.log_action(action_type, action_data)
37
38 # Execute the actual action
39 result = self.perform_action(action_type, action_data)
40 return result
41
42 def run_safety_checks(self, action_type, action_data):
43 """Run safety checks before executing"""
44 # Check for suspicious patterns
45 if action_type == "email" and "urgent" in str(action_data).lower():
46 # Might be a phishing attempt
47 return {"safe": False, "reason": "Suspicious email content"}
48
49 # Add more checks as needed
50 return {"safe": True}
51
52 def log_action(self, action_type, action_data):
53 """Log actions for audit trail"""
54 # In a real system, this would write to a secure log
55 print(f"[AUDIT] {action_type}: {action_data}")
56
57 def check_rate_limit(self, action_type):
58 """Check if action is within rate limits"""
59 # Simplified for example
60 return True
61
62 def perform_action(self, action_type, action_data):
63 """Actually perform the action"""
64 return {"success": True, "message": "Action completed"}
65
66 def log_security_event(self, action_type, details):
67 """Log security events for review"""
68 print(f"[SECURITY] Blocked {action_type}: {details['reason']}")
69
70## Production environment with full safety checks
71prod_env = ProductionEnvironment()
72
73## Try to execute an action
74result = prod_env.execute_action(
75 "email",
76 {"to": "user@example.com", "subject": "Meeting notes"}
77)
78print(result)1## Example (Claude Sonnet 4.5)
2## Using Claude Sonnet 4.5 for production-grade safety logic
3
4class ProductionEnvironment:
5 def __init__(self):
6 self.safety_checks_enabled = True
7 self.audit_logging = True
8 self.rate_limits = {
9 "email": {"max": 50, "window": 3600}, # 50 per hour
10 "api_call": {"max": 1000, "window": 3600},
11 "file_write": {"max": 100, "window": 3600}
12 }
13
14 def execute_action(self, action_type, action_data):
15 """Execute an action with production safety checks"""
16
17 # Check rate limits
18 if not self.check_rate_limit(action_type):
19 return {
20 "success": False,
21 "error": "Rate limit exceeded"
22 }
23
24 # Perform safety checks
25 if self.safety_checks_enabled:
26 safety_result = self.run_safety_checks(action_type, action_data)
27 if not safety_result["safe"]:
28 self.log_security_event(action_type, safety_result)
29 return {
30 "success": False,
31 "error": f"Safety check failed: {safety_result['reason']}"
32 }
33
34 # Log for audit
35 if self.audit_logging:
36 self.log_action(action_type, action_data)
37
38 # Execute the actual action
39 result = self.perform_action(action_type, action_data)
40 return result
41
42 def run_safety_checks(self, action_type, action_data):
43 """Run safety checks before executing"""
44 # Check for suspicious patterns
45 if action_type == "email" and "urgent" in str(action_data).lower():
46 # Might be a phishing attempt
47 return {"safe": False, "reason": "Suspicious email content"}
48
49 # Add more checks as needed
50 return {"safe": True}
51
52 def log_action(self, action_type, action_data):
53 """Log actions for audit trail"""
54 # In a real system, this would write to a secure log
55 print(f"[AUDIT] {action_type}: {action_data}")
56
57 def check_rate_limit(self, action_type):
58 """Check if action is within rate limits"""
59 # Simplified for example
60 return True
61
62 def perform_action(self, action_type, action_data):
63 """Actually perform the action"""
64 return {"success": True, "message": "Action completed"}
65
66 def log_security_event(self, action_type, details):
67 """Log security events for review"""
68 print(f"[SECURITY] Blocked {action_type}: {details['reason']}")
69
70## Production environment with full safety checks
71prod_env = ProductionEnvironment()
72
73## Try to execute an action
74result = prod_env.execute_action(
75 "email",
76 {"to": "user@example.com", "subject": "Meeting notes"}
77)
78print(result)Notice how production adds layers of protection: rate limiting, safety checks, audit logging. These aren't needed in development, but they're essential when real users and real data are involved.
Implementing a Complete Boundary System
Let's bring everything together into a cohesive boundary system for our personal assistant. This combines all the constraint types we've discussed:
1## Example (Claude Sonnet 4.5)
2## Using Claude Sonnet 4.5 for its ability to reason about complex permission systems
3
4class AgentBoundarySystem:
5 def __init__(self, config):
6 self.access_policy = config.get("access_policy", {})
7 self.action_policy = config.get("action_policy", {})
8 self.rate_limiters = config.get("rate_limiters", {})
9 self.scope_boundaries = config.get("scope_boundaries", {})
10 self.environment_mode = config.get("environment", "production")
11
12 def can_execute(self, action_request):
13 """
14 Check if an action can be executed given all constraints.
15 Returns (allowed, reason) tuple.
16 """
17 action_type = action_request["type"]
18 resource = action_request.get("resource")
19
20 # Check access permissions
21 if not self.check_access_permission(resource, action_type):
22 return False, "Access denied: insufficient permissions"
23
24 # Check action policy
25 if self.requires_confirmation(action_type):
26 if not action_request.get("user_confirmed"):
27 return False, "Action requires user confirmation"
28
29 # Check rate limits
30 if not self.check_rate_limit(action_type):
31 return False, "Rate limit exceeded"
32
33 # Check scope boundaries
34 if not self.within_scope(action_request):
35 return False, "Action outside allowed scope"
36
37 return True, "Action allowed"
38
39 def check_access_permission(self, resource, action):
40 """Check if we have permission for this resource/action combo"""
41 if resource not in self.access_policy:
42 return False
43 return action in self.access_policy[resource]
44
45 def requires_confirmation(self, action_type):
46 """Check if action needs user confirmation"""
47 return self.action_policy.get(action_type, {}).get("confirm", False)
48
49 def check_rate_limit(self, action_type):
50 """Check rate limits for this action type"""
51 if action_type in self.rate_limiters:
52 return self.rate_limiters[action_type].can_proceed(action_type)
53 return True
54
55 def within_scope(self, action_request):
56 """Check if action is within allowed scope"""
57 # Check file system boundaries
58 if "file_path" in action_request:
59 fs_boundary = self.scope_boundaries.get("filesystem")
60 if fs_boundary and not fs_boundary.is_allowed(action_request["file_path"]):
61 return False
62
63 # Check network boundaries
64 if "url" in action_request:
65 net_boundary = self.scope_boundaries.get("network")
66 if net_boundary and not net_boundary.can_access(action_request["url"]):
67 return False
68
69 return True
70
71## Configure the boundary system
72config = {
73 "access_policy": {
74 "calendar": ["read", "write"],
75 "email": ["read", "draft"],
76 "contacts": ["read"]
77 },
78 "action_policy": {
79 "send": {"confirm": True},
80 "delete": {"confirm": True},
81 "read": {"confirm": False}
82 },
83 "rate_limiters": {
84 "email": RateLimiter(max_calls=50, time_window=3600)
85 },
86 "scope_boundaries": {
87 "filesystem": FileSystemBoundary(["/home/user/agent_workspace"]),
88 "network": NetworkBoundary(["api.weather.com", "api.calendar.google.com"])
89 },
90 "environment": "production"
91}
92
93boundary_system = AgentBoundarySystem(config)
94
95## Test various actions
96test_actions = [
97 {
98 "type": "read",
99 "resource": "calendar",
100 "description": "Read calendar events"
101 },
102 {
103 "type": "send",
104 "resource": "email",
105 "description": "Send email",
106 "user_confirmed": False
107 },
108 {
109 "type": "write",
110 "resource": "calendar",
111 "description": "Create calendar event"
112 }
113]
114
115for action in test_actions:
116 allowed, reason = boundary_system.can_execute(action)
117 status = "✓ Allowed" if allowed else "✗ Blocked"
118 print(f"{status}: {action['description']} - {reason}")1## Example (Claude Sonnet 4.5)
2## Using Claude Sonnet 4.5 for its ability to reason about complex permission systems
3
4class AgentBoundarySystem:
5 def __init__(self, config):
6 self.access_policy = config.get("access_policy", {})
7 self.action_policy = config.get("action_policy", {})
8 self.rate_limiters = config.get("rate_limiters", {})
9 self.scope_boundaries = config.get("scope_boundaries", {})
10 self.environment_mode = config.get("environment", "production")
11
12 def can_execute(self, action_request):
13 """
14 Check if an action can be executed given all constraints.
15 Returns (allowed, reason) tuple.
16 """
17 action_type = action_request["type"]
18 resource = action_request.get("resource")
19
20 # Check access permissions
21 if not self.check_access_permission(resource, action_type):
22 return False, "Access denied: insufficient permissions"
23
24 # Check action policy
25 if self.requires_confirmation(action_type):
26 if not action_request.get("user_confirmed"):
27 return False, "Action requires user confirmation"
28
29 # Check rate limits
30 if not self.check_rate_limit(action_type):
31 return False, "Rate limit exceeded"
32
33 # Check scope boundaries
34 if not self.within_scope(action_request):
35 return False, "Action outside allowed scope"
36
37 return True, "Action allowed"
38
39 def check_access_permission(self, resource, action):
40 """Check if we have permission for this resource/action combo"""
41 if resource not in self.access_policy:
42 return False
43 return action in self.access_policy[resource]
44
45 def requires_confirmation(self, action_type):
46 """Check if action needs user confirmation"""
47 return self.action_policy.get(action_type, {}).get("confirm", False)
48
49 def check_rate_limit(self, action_type):
50 """Check rate limits for this action type"""
51 if action_type in self.rate_limiters:
52 return self.rate_limiters[action_type].can_proceed(action_type)
53 return True
54
55 def within_scope(self, action_request):
56 """Check if action is within allowed scope"""
57 # Check file system boundaries
58 if "file_path" in action_request:
59 fs_boundary = self.scope_boundaries.get("filesystem")
60 if fs_boundary and not fs_boundary.is_allowed(action_request["file_path"]):
61 return False
62
63 # Check network boundaries
64 if "url" in action_request:
65 net_boundary = self.scope_boundaries.get("network")
66 if net_boundary and not net_boundary.can_access(action_request["url"]):
67 return False
68
69 return True
70
71## Configure the boundary system
72config = {
73 "access_policy": {
74 "calendar": ["read", "write"],
75 "email": ["read", "draft"],
76 "contacts": ["read"]
77 },
78 "action_policy": {
79 "send": {"confirm": True},
80 "delete": {"confirm": True},
81 "read": {"confirm": False}
82 },
83 "rate_limiters": {
84 "email": RateLimiter(max_calls=50, time_window=3600)
85 },
86 "scope_boundaries": {
87 "filesystem": FileSystemBoundary(["/home/user/agent_workspace"]),
88 "network": NetworkBoundary(["api.weather.com", "api.calendar.google.com"])
89 },
90 "environment": "production"
91}
92
93boundary_system = AgentBoundarySystem(config)
94
95## Test various actions
96test_actions = [
97 {
98 "type": "read",
99 "resource": "calendar",
100 "description": "Read calendar events"
101 },
102 {
103 "type": "send",
104 "resource": "email",
105 "description": "Send email",
106 "user_confirmed": False
107 },
108 {
109 "type": "write",
110 "resource": "calendar",
111 "description": "Create calendar event"
112 }
113]
114
115for action in test_actions:
116 allowed, reason = boundary_system.can_execute(action)
117 status = "✓ Allowed" if allowed else "✗ Blocked"
118 print(f"{status}: {action['description']} - {reason}")This complete system checks every aspect of an action before allowing it. It's comprehensive but still readable and maintainable.
Practical Considerations
When implementing boundaries for your agent, keep these principles in mind:
Start restrictive, then relax: It's easier to grant new permissions than to revoke them. Begin with tight constraints and loosen them as you gain confidence in your agent's behavior.
Make boundaries visible: Users should know what your agent can and cannot do. Don't hide limitations. Instead, communicate them clearly: "I can read your calendar but I'll ask before creating events."
Log boundary violations: When your agent tries to do something it can't, log it. These logs reveal where your constraints might be too tight or where your agent's logic needs improvement.
Test boundaries explicitly: Write tests that verify your agent respects its boundaries. Try to make it access forbidden resources or exceed rate limits. Your boundary system should catch these attempts.
Consider context: Some boundaries might change based on context. Your agent might have more permissions during work hours than at night, or more access when you're actively interacting with it than when it's running autonomously.
Here's a simple context-aware boundary system:
1## Example (Claude Sonnet 4.5)
2## Using Claude Sonnet 4.5 for context-aware permission logic
3
4from datetime import datetime
5
6class ContextAwareBoundaries:
7 def __init__(self, base_permissions):
8 self.base_permissions = base_permissions
9
10 def get_permissions(self, context):
11 """Get permissions based on current context"""
12 permissions = self.base_permissions.copy()
13
14 # More permissions during work hours
15 if self.is_work_hours():
16 permissions["email"].append("send")
17 permissions["calendar"].append("delete")
18
19 # More permissions when user is actively present
20 if context.get("user_present"):
21 permissions["files"] = ["read", "write"]
22
23 return permissions
24
25 def is_work_hours(self):
26 """Check if it's currently work hours"""
27 now = datetime.now()
28 return 9 <= now.hour < 17 and now.weekday() < 5
29
30## Base permissions (always available)
31base_perms = {
32 "calendar": ["read"],
33 "email": ["read", "draft"],
34 "contacts": ["read"]
35}
36
37boundaries = ContextAwareBoundaries(base_perms)
38
39## Get permissions for current context
40context = {"user_present": True}
41current_permissions = boundaries.get_permissions(context)
42
43print("Current permissions:", current_permissions)1## Example (Claude Sonnet 4.5)
2## Using Claude Sonnet 4.5 for context-aware permission logic
3
4from datetime import datetime
5
6class ContextAwareBoundaries:
7 def __init__(self, base_permissions):
8 self.base_permissions = base_permissions
9
10 def get_permissions(self, context):
11 """Get permissions based on current context"""
12 permissions = self.base_permissions.copy()
13
14 # More permissions during work hours
15 if self.is_work_hours():
16 permissions["email"].append("send")
17 permissions["calendar"].append("delete")
18
19 # More permissions when user is actively present
20 if context.get("user_present"):
21 permissions["files"] = ["read", "write"]
22
23 return permissions
24
25 def is_work_hours(self):
26 """Check if it's currently work hours"""
27 now = datetime.now()
28 return 9 <= now.hour < 17 and now.weekday() < 5
29
30## Base permissions (always available)
31base_perms = {
32 "calendar": ["read"],
33 "email": ["read", "draft"],
34 "contacts": ["read"]
35}
36
37boundaries = ContextAwareBoundaries(base_perms)
38
39## Get permissions for current context
40context = {"user_present": True}
41current_permissions = boundaries.get_permissions(context)
42
43print("Current permissions:", current_permissions)Context-aware boundaries make your agent more flexible while maintaining safety. The agent has more freedom when it makes sense (during work hours, when you're present) and less when risks are higher.
Balancing Safety and Capability
The art of setting boundaries is finding the right balance. Too restrictive, and your agent can't help effectively. Too permissive, and you risk security issues or unpredictable behavior.
Here are some guidelines for finding that balance:
Match boundaries to use cases: If your agent's job is to manage your calendar, it needs write access to calendars. But it probably doesn't need to access your file system. Let the agent's purpose guide your constraints.
Layer your defenses: Don't rely on a single boundary. Combine access controls, action policies, rate limits, and scope boundaries. If one layer fails, others provide backup protection.
Make risky actions reversible: When possible, design your system so mistakes can be undone. Draft emails instead of sending them immediately. Create calendar events that can be easily deleted. This gives you a safety net.
Provide escape hatches: Sometimes your agent needs to do something outside its normal boundaries. Provide a way for users to grant temporary elevated permissions for specific tasks, with clear warnings about the risks.
Review and adjust: Your boundaries aren't set in stone. As you learn how your agent behaves in practice, refine the constraints. Maybe some restrictions are too tight. Maybe others need to be stricter.
Summary
Environment boundaries and constraints are how you make your AI agent safe, predictable, and trustworthy. They define what your agent can access, what actions it can take, and where it can operate.
We've covered several types of constraints:
Access constraints limit what data and resources your agent can reach. You control read and write permissions separately, giving your agent only the access it needs for its job.
Action constraints govern what your agent can do. Safe actions happen automatically, while risky actions require confirmation. This prevents accidents and gives users control over high-stakes operations.
Rate and resource constraints prevent runaway behavior by limiting how often your agent can perform certain actions. This controls costs and prevents your agent from overwhelming external services.
Scope constraints define the sandbox where your agent operates. File system and network boundaries ensure your agent only touches approved resources.
Environment-specific constraints differ between development and production. Testing environments allow experimentation with fake data, while production environments enforce strict safety checks and audit logging.
The key is finding the right balance. Start with tight constraints and relax them as you gain confidence. Make boundaries visible to users. Log violations to understand where constraints might need adjustment. And always layer your defenses so no single failure compromises security.
With well-designed boundaries, your agent becomes something users can trust. They know what it can do, what it can't do, and that it will ask before taking risky actions. This trust is essential for building agents that people actually want to use.
In the next chapter, we'll explore how agents can plan complex, multi-step tasks. Planning requires reasoning about sequences of actions, and those actions will all respect the boundaries we've established here. The constraints we've built become the safe foundation on which more sophisticated agent behaviors can operate.
Glossary
Access Constraint: A rule that limits what data or resources an agent can read or modify. Access constraints typically distinguish between read and write permissions for different resource types.
Action Policy: A set of rules defining which actions an agent can perform automatically and which require user confirmation. Actions are typically categorized by risk level.
Audit Logging: The practice of recording all significant agent actions for later review. Audit logs help with debugging, security monitoring, and compliance.
Boundary System: The complete set of constraints and permissions that define what an agent can and cannot do in its environment. A boundary system combines access controls, action policies, rate limits, and scope restrictions.
Context-Aware Permissions: Permissions that change based on the current situation, such as time of day, user presence, or the specific task being performed. Context awareness allows more flexible security.
Production Environment: The real-world setting where an agent operates with actual user data and real consequences. Production environments require stricter safety checks than development environments.
Rate Limiting: A constraint that limits how often an agent can perform certain actions within a time window. Rate limiting prevents runaway behavior and controls costs.
Scope Boundary: A constraint that defines where an agent can operate, such as which directories it can access or which network domains it can contact. Scope boundaries create a sandbox for agent operations.
Training Environment: A safe setting for developing and testing an agent, typically using fake or sanitized data. Training environments allow experimentation without real-world consequences.
Quiz
Ready to test your understanding? Take this quick quiz to reinforce what you've learned about environment boundaries and constraints for AI agents.
Reference

About the author: Michael Brenndoerfer
All opinions expressed here are my own and do not reflect the views of my employer.
Michael currently works as an Associate Director of Data Science at EQT Partners in Singapore, where he drives AI and data initiatives across private capital investments.
With over a decade of experience spanning private equity, management consulting, and software engineering, he specializes in building and scaling analytics capabilities from the ground up. He has published research in leading AI conferences and holds expertise in machine learning, natural language processing, and value creation through data.
Related Content

Scaling Up without Breaking the Bank: AI Agent Performance & Cost Optimization at Scale
Learn how to scale AI agents from single users to thousands while maintaining performance and controlling costs. Covers horizontal scaling, load balancing, monitoring, cost controls, and prompt optimization strategies.

Managing and Reducing AI Agent Costs: Complete Guide to Cost Optimization Strategies
Learn how to dramatically reduce AI agent API costs without sacrificing capability. Covers model selection, caching, batching, prompt optimization, and budget controls with practical Python examples.

Speeding Up AI Agents: Performance Optimization Techniques for Faster Response Times
Learn practical techniques to make AI agents respond faster, including model selection strategies, response caching, streaming, parallel execution, and prompt optimization for reduced latency.
Stay updated
Get notified when I publish new articles on data and AI, private equity, technology, and more.

