Stage 7 · Master
ChatOps & On-Call Copilots
Agents With Guardrails
Approval steps, allow-lists, and blast-radius limits for autonomous actions.
Agent vs Copilot
A copilot suggests actions for a human to execute. An agent takes actions autonomously. In operational contexts, the spectrum is wide — from pure suggestion to fully autonomous remediation. Most teams start with copilots and gradually add autonomy as trust builds.
| Mode | Autonomy | Risk | Use Case |
|---|---|---|---|
| Copilot | Suggests only | None | New tools, unproven workflows |
| Supervised agent | Acts with approval | Low | Moderate-impact operations |
| Autonomous agent | Acts independently | Medium | Well-understood, reversible actions |
| Fully autonomous | No human in loop | High | Only for critical, well-tested paths |
Guardrail Layers
Defense in depth means multiple independent layers of protection. No single guardrail is sufficient.
- Input validation — Reject prompts that attempt injection or ask for disallowed actions.
- Command classification — Categorize actions by risk level before execution.
- Scope restrictions — Limit which namespaces, services, or resources the agent can access.
- Approval gates — Require human confirmation for medium and high-risk actions.
- Blast-radius limits — Cap the number of resources affected by a single action.
- Rate limiting — Prevent the agent from taking too many actions too quickly.
- Audit logging — Record every action for post-hoc review.
Approval Gates
from enum import Enum
from typing import Optional
class RiskLevel(Enum):
SAFE = "safe"
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
CRITICAL = "critical"
class ApprovalGate:
def __init__(self):
self.auto_execute = {RiskLevel.SAFE, RiskLevel.LOW}
self.require_approval = {RiskLevel.MEDIUM, RiskLevel.HIGH}
self.block = {RiskLevel.CRITICAL}
def check(self, action, risk_level):
if risk_level in self.block:
return {"approved": False, "reason": "Action is blocked by policy"}
if risk_level in self.auto_execute:
return {"approved": True, "reason": "Auto-approved: low risk"}
if risk_level in self.require_approval:
return {"approved": None, "reason": "Requires human approval"}
return {"approved": False, "reason": "Unknown risk level"}
gate = ApprovalGate()
# Example
result = gate.check("kubectl delete pod checkout-abc123", RiskLevel.HIGH)
# {"approved": None, "reason": "Requires human approval"}The gate returns three states: approved, denied, or pending approval. The application handles each state differently.
Blast-Radius Limits
class BlastRadiusLimit:
def __init__(self, max_pods=5, max_services=1, max_namespaces=1):
self.max_pods = max_pods
self.max_services = max_services
self.max_namespaces = max_namespaces
def validate(self, command):
"""Check if a command affects too many resources."""
affected = self.parse_affected_resources(command)
violations = []
if affected["pods"] > self.max_pods:
violations.append(f"Would affect {affected['pods']} pods (max: {self.max_pods})")
if affected["services"] > self.max_services:
violations.append(f"Would affect {affected['services']} services (max: {self.max_services})")
if affected["namespaces"] > self.max_namespaces:
violations.append(f"Would affect {affected['namespaces']} namespaces (max: {self.max_namespaces})")
return {
"allowed": len(violations) == 0,
"violations": violations,
"affected": affected
}
def parse_affected_resources(self, command):
"""Parse command to estimate affected resource count."""
# Implementation depends on command type
# For kubectl delete pod NAME, it is 1 pod
# For kubectl delete pods -l app=checkout, count matching pods
passBlast-radius limits prevent the agent from affecting too many resources at once. If the agent wants to delete 50 pods, the limit blocks it and requires human override.
Implementation
class OpsAgent:
def __init__(self, gate, blast_radius, audit_log):
self.gate = gate
self.blast_radius = blast_radius
self.audit_log = audit_log
def execute(self, user_request, channel_id):
# Generate command
command = generate_command(user_request)
# Classify risk
risk = classify_command(command)
# Check blast radius
radius_check = self.blast_radius.validate(command)
if not radius_check["allowed"]:
return self.report_violation(radius_check)
# Check approval gate
gate_result = self.gate.check(command, risk)
if gate_result["approved"] is False:
return gate_result["reason"]
if gate_result["approved"] is None:
# Wait for human approval
approved = await_approval(channel_id, command)
if not approved:
return "Command cancelled by user"
# Execute
self.audit_log.log(command=command, risk=risk, user=user_request)
return execute_command(command)The agent flow is: generate, classify, validate blast radius, check approval, execute, and log. Every step is independent and auditable.
Start with copilot mode (suggest only). After weeks of accurate suggestions, move to supervised agent (approve medium-risk). Only after months of proven reliability, allow autonomous execution of safe commands.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.