Stage 7 · Master
Ops Agents & Automation Tools
Read-Only First
Separate observe, propose, and execute phases with dry-run commands, diffs, and human confirmation.
Why Read-Only First?
The safest agents are the ones that observe before they act. A read-only-first pattern separates the agent into three phases: observe (gather information), propose (suggest actions), and execute (take action with approval). This prevents the agent from jumping straight to destructive operations.
The Three Phases
class ReadOnlyFirstAgent:
def __init__(self, tools, planner):
self.read_tools = [t for t in tools if t.risk_level == "safe"]
self.write_tools = [t for t in tools if t.risk_level != "safe"]
self.planner = planner
def run(self, goal, channel_id):
# Phase 1: Observe (read-only)
observations = self.observe(goal)
# Phase 2: Propose (suggest actions)
proposal = self.propose(goal, observations)
# Present proposal to human
approved = present_proposal(channel_id, proposal)
# Phase 3: Execute (only if approved)
if approved:
return self.execute(proposal["actions"])
return {"status": "proposal_rejected"}
def observe(self, goal):
"""Gather information using read-only tools."""
observations = []
for tool in self.read_tools:
result = tool.execute()
observations.append({"tool": tool.name, "result": result})
return observations
def propose(self, goal, observations):
"""Generate a proposal based on observations."""
return self.planner.plan_actions(goal, observations)
def execute(self, actions):
"""Execute approved actions."""
results = []
for action in actions:
result = action["tool"].execute(**action["arguments"])
results.append(result)
return resultsThe observe phase uses only read-only tools. The propose phase generates a plan. The execute phase runs only approved actions.
Dry-Run Commands
def kubectl_with_dry_run(command, namespace):
"""Run a kubectl command with dry-run first."""
# Phase 1: Dry run
dry_run_cmd = f"{command} --dry-run=client -n {namespace}"
dry_result = execute_command(dry_run_cmd)
# Phase 2: Show what would happen
if dry_result.success:
return {
"would_execute": command,
"dry_run_output": dry_result.output,
"requires_confirmation": True
}
return {"error": dry_result.error}
# Usage in proposal
proposal = {
"action": "Scale deployment checkout to 5 replicas",
"command": "kubectl scale deployment checkout --replicas=5 -n production",
"dry_run": kubectl_with_dry_run(
"kubectl scale deployment checkout --replicas=5",
"production"
),
"risk_level": "medium"
}Dry-run shows exactly what would happen without actually doing it. This gives the human full visibility before confirming.
Diff Preview
def show_config_diff(new_config, current_config):
"""Show the diff between current and proposed config."""
import difflib
current_lines = json.dumps(current_config, indent=2).splitlines()
new_lines = json.dumps(new_config, indent=2).splitlines()
diff = difflib.unified_diff(
current_lines, new_lines,
fromfile="current",
tofile="proposed",
lineterm=""
)
return "\n".join(diff)
# Example output:
# --- current
# +++ proposed
# @@ -1,5 +1,5 @@
# {
# - "replicas": 3,
# + "replicas": 5,
# "image": "checkout:latest"
# }Diffs are the most intuitive way for humans to understand what will change.
Graduated Autonomy
| Phase | Agent Can | Human Must |
|---|---|---|
| Read-only | Query, search, analyze | Nothing |
| Propose | Suggest commands, show diffs | Review and approve |
| Execute safe | Restart pods, clear caches | Nothing (auto-approved) |
| Execute risky | Scale, config changes | Explicitly approve |
| Execute destructive | Delete resources | Approve with confirmation |
Start with read-only + propose for all actions. After weeks of accurate proposals, auto-approve safe actions. After months, auto-approve medium-risk actions. Never auto-approve destructive actions.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.