Design patterns for building autonomous coding agents. Covers tool integration, permission systems, browser automation, and human-in-the-loop workflows. Use when building AI agents, designing tool APIs, implementing permission systems, or creating autonomous coding assistants.
Design patterns for building autonomous coding agents. Covers tool integration, permission systems, browser automation, and human-in-the-loop workflows. Use when building AI agents, designing tool APIs, implementing permission systems, or creating autonomous coding assistants.
🕹️ Autonomous Agent Patterns
Design patterns for building autonomous coding agents, inspired by Cline and OpenAI Codex.
classMultiModelAgent:
"""
Use different models for different purposes:
- Fast model for planning
- Powerful model for complex reasoning
- Specialized model for code generation
"""def__init__(self):
self.models = {
"fast": "gpt-3.5-turbo", # Quick decisions"smart": "gpt-4-turbo", # Complex reasoning"code": "claude-3-sonnet", # Code generation
}
defselect_model(self, task_type: str) -> str:
if task_type == "planning":
returnself.models["fast"]
elif task_type == "analysis":
returnself.models["smart"]
elif task_type == "code":
returnself.models["code"]
returnself.models["smart"]
2. Tool Design Patterns
2.1 Tool Schema
classTool:
"""Base class for agent tools""" @propertydefschema(self) -> dict:
"""JSON Schema for the tool"""return {
"name": self.name,
"description": self.description,
"parameters": {
"type": "object",
"properties": self._get_parameters(),
"required": self._get_required()
}
}
defexecute(self, **kwargs) -> ToolResult:
"""Execute the tool and return result"""raise NotImplementedError
classReadFileTool(Tool):
name = "read_file"
description = "Read the contents of a file from the filesystem"def_get_parameters(self):
return {
"path": {
"type": "string",
"description": "Absolute path to the file"
},
"start_line": {
"type": "integer",
"description": "Line to start reading from (1-indexed)"
},
"end_line": {
"type": "integer",
"description": "Line to stop reading at (inclusive)"
}
}
def_get_required(self):
return ["path"]
defexecute(self, path: str, start_line: int = None, end_line: int = None) -> ToolResult:
try:
withopen(path, 'r') as f:
lines = f.readlines()
if start_line and end_line:
lines = lines[start_line-1:end_line]
return ToolResult(
success=True,
output="".join(lines)
)
except FileNotFoundError:
return ToolResult(
success=False,
error=f"File not found: {path}"
)
2.2 Essential Agent Tools
CODING_AGENT_TOOLS = {
# File operations"read_file": "Read file contents",
"write_file": "Create or overwrite a file",
"edit_file": "Make targeted edits to a file",
"list_directory": "List files and folders",
"search_files": "Search for files by pattern",
# Code understanding"search_code": "Search for code patterns (grep)",
"get_definition": "Find function/class definition",
"get_references": "Find all references to a symbol",
# Terminal"run_command": "Execute a shell command",
"read_output": "Read command output",
"send_input": "Send input to running command",
# Browser (optional)"open_browser": "Open URL in browser",
"click_element": "Click on page element",
"type_text": "Type text into input",
"screenshot": "Capture screenshot",
# Context"ask_user": "Ask the user a question",
"search_web": "Search the web for information"
}
2.3 Edit Tool Design
classEditFileTool(Tool):
"""
Precise file editing with conflict detection.
Uses search/replace pattern for reliable edits.
"""
name = "edit_file"
description = "Edit a file by replacing specific content"defexecute(
self,
path: str,
search: str,
replace: str,
expected_occurrences: int = 1) -> ToolResult:
"""
Args:
path: File to edit
search: Exact text to find (must match exactly, including whitespace)
replace: Text to replace with
expected_occurrences: How many times search should appear (validation)
"""withopen(path, 'r') as f:
content = f.read()
# Validate
actual_occurrences = content.count(search)
if actual_occurrences != expected_occurrences:
return ToolResult(
success=False,
error=f"Expected {expected_occurrences} occurrences, found {actual_occurrences}"
)
if actual_occurrences == 0:
return ToolResult(
success=False,
error="Search text not found in file"
)
# Apply edit
new_content = content.replace(search, replace)
withopen(path, 'w') as f:
f.write(new_content)
return ToolResult(
success=True,
output=f"Replaced {actual_occurrences} occurrence(s)"
)
3. Permission & Safety Patterns
3.1 Permission Levels
classPermissionLevel(Enum):
# Fully automatic - no user approval needed
AUTO = "auto"# Ask once per session
ASK_ONCE = "ask_once"# Ask every time
ASK_EACH = "ask_each"# Never allow
NEVER = "never"
PERMISSION_CONFIG = {
# Low risk - can auto-approve"read_file": PermissionLevel.AUTO,
"list_directory": PermissionLevel.AUTO,
"search_code": PermissionLevel.AUTO,
# Medium risk - ask once"write_file": PermissionLevel.ASK_ONCE,
"edit_file": PermissionLevel.ASK_ONCE,
# High risk - ask each time"run_command": PermissionLevel.ASK_EACH,
"delete_file": PermissionLevel.ASK_EACH,
# Dangerous - never auto-approve"sudo_command": PermissionLevel.NEVER,
"format_disk": PermissionLevel.NEVER
}
3.2 Approval UI Pattern
classApprovalManager:
def__init__(self, ui, config):
self.ui = ui
self.config = config
self.session_approvals = {}
defrequest_approval(self, tool_name: str, args: dict) -> bool:
level = self.config.get(tool_name, PermissionLevel.ASK_EACH)
if level == PermissionLevel.AUTO:
returnTrueif level == PermissionLevel.NEVER:
self.ui.show_error(f"Tool '{tool_name}' is not allowed")
returnFalseif level == PermissionLevel.ASK_ONCE:
if tool_name inself.session_approvals:
returnself.session_approvals[tool_name]
# Show approval dialog
approved = self.ui.show_approval_dialog(
tool=tool_name,
args=args,
risk_level=self._assess_risk(tool_name, args)
)
if level == PermissionLevel.ASK_ONCE:
self.session_approvals[tool_name] = approved
return approved
def_assess_risk(self, tool_name: str, args: dict) -> str:
"""Analyze specific call for risk level"""if tool_name == "run_command":
cmd = args.get("command", "")
ifany(danger in cmd for danger in ["rm -rf", "sudo", "chmod"]):
return"HIGH"return"MEDIUM"
from mcp import Server, Tool
classMCPAgent:
"""
Agent that can dynamically discover and use MCP tools.
'Add a tool that...' pattern from Cline.
"""def__init__(self, llm):
self.llm = llm
self.mcp_servers = {}
self.available_tools = {}
defconnect_server(self, name: str, config: dict) -> None:
"""Connect to an MCP server"""
server = Server(config)
self.mcp_servers[name] = server
# Discover tools
tools = server.list_tools()
for tool in tools:
self.available_tools[tool.name] = {
"server": name,
"schema": tool.schema
}
asyncdefcreate_tool(self, description: str) -> str:
"""
Create a new MCP server based on user description.
'Add a tool that fetches Jira tickets'
"""# Generate MCP server code
code = self.llm.generate(f"""
Create a Python MCP server with a tool that does:
{description}
Use the FastMCP framework. Include proper error handling.
Return only the Python code.
""")
# Save and install
server_name = self._extract_name(description)
path = f"./mcp_servers/{server_name}/server.py"withopen(path, 'w') as f:
f.write(code)
# Hot-reloadself.connect_server(server_name, {"path": path})
returnf"Created tool: {server_name}"