AI Agents & Automation

Building Autonomous Agents

Build autonomous AI agents that plan, execute, and adapt — from research assistants to code generators.

Building Autonomous Agents

Autonomous agents go beyond single tool calls. They plan multi-step tasks, execute them, handle failures, and adapt their approach — all without human intervention.

Agent Architecture

┌────────────────────────────────────────────┐
│              Autonomous Agent              │
│                                            │
│  ┌──────────────┐  ┌──────────────────┐   │
│  │   Planner    │  │   Executor       │   │
│  │ (Break task  │→ │ (Run tools,      │   │
│  │  into steps) │  │  handle results) │   │
│  └──────────────┘  └───────┬──────────┘   │
│         ↑                   │              │
│         │          ┌────────▼─────────┐   │
│         │          │   Evaluator      │   │
│         └──────────│ (Check results,  │   │
│                    │  adjust plan)    │   │
│                    └──────────────────┘   │
│                                            │
│  ┌──────────────────────────────────────┐ │
│  │          Working Memory              │ │
│  │  - Current plan & progress           │ │
│  │  - Tool results & observations       │ │
│  │  - Error history & recovery attempts │ │
│  └──────────────────────────────────────┘ │
└────────────────────────────────────────────┘

Building a Research Agent

A practical agent that researches topics using web search:

python
from openai import OpenAI
import json

client = OpenAI()

class ResearchAgent:
    def __init__(self):
        self.tools = [
            {
                "type": "function",
                "function": {
                    "name": "web_search",
                    "description": "Search the web for information on a topic",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "query": {"type": "string", "description": "Search query"}
                        },
                        "required": ["query"]
                    }
                }
            },
            {
                "type": "function",
                "function": {
                    "name": "take_notes",
                    "description": "Save important findings for the final report",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "note": {"type": "string", "description": "The finding to save"},
                            "source": {"type": "string", "description": "Where this info came from"}
                        },
                        "required": ["note"]
                    }
                }
            },
            {
                "type": "function",
                "function": {
                    "name": "write_report",
                    "description": "Write the final research report using collected notes",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "title": {"type": "string"},
                            "sections": {
                                "type": "array",
                                "items": {"type": "string"},
                                "description": "Section headings for the report"
                            }
                        },
                        "required": ["title"]
                    }
                }
            }
        ]
        self.notes = []
        self.messages = []

    def web_search(self, query):
        """Simulate web search (use real API in production)."""
        return f"Search results for '{query}': [Relevant information found]"

    def take_notes(self, note, source=""):
        self.notes.append({"note": note, "source": source})
        return f"Note saved. Total notes: {len(self.notes)}"

    def write_report(self, title, sections=None):
        notes_text = "\n".join(
            f"- {n['note']} (Source: {n['source']})" for n in self.notes
        )
        return f"Report '{title}' drafted with {len(self.notes)} findings."

    def execute_tool(self, name, args):
        tool_map = {
            "web_search": self.web_search,
            "take_notes": self.take_notes,
            "write_report": self.write_report
        }
        return tool_map[name](**args)

    def run(self, task, max_steps=10):
        self.messages = [
            {
                "role": "system",
                "content": """You are a thorough research agent. When given a topic:
                1. Plan your research approach
                2. Search for information from multiple angles
                3. Take notes on key findings
                4. Write a comprehensive report

                Be systematic and thorough. Use at least 3 searches."""
            },
            {"role": "user", "content": task}
        ]

        for step in range(max_steps):
            response = client.chat.completions.create(
                model="gpt-4o",
                messages=self.messages,
                tools=self.tools,
                tool_choice="auto"
            )

            message = response.choices[0].message

            if not message.tool_calls:
                return message.content  # Final answer

            self.messages.append(message)

            for tool_call in message.tool_calls:
                name = tool_call.function.name
                args = json.loads(tool_call.function.arguments)
                print(f"  Step {step + 1}: {name}({args})")

                result = self.execute_tool(name, args)

                self.messages.append({
                    "role": "tool",
                    "tool_call_id": tool_call.id,
                    "content": str(result)
                })

        return "Research incomplete — reached maximum steps."

# Usage
agent = ResearchAgent()
report = agent.run("Research the current state of quantum computing")
print(report)

Building a Code Agent

An agent that writes, tests, and fixes code:

python
class CodeAgent:
    def __init__(self):
        self.tools = [
            {
                "type": "function",
                "function": {
                    "name": "write_file",
                    "description": "Write content to a file",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "path": {"type": "string"},
                            "content": {"type": "string"}
                        },
                        "required": ["path", "content"]
                    }
                }
            },
            {
                "type": "function",
                "function": {
                    "name": "read_file",
                    "description": "Read the contents of a file",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "path": {"type": "string"}
                        },
                        "required": ["path"]
                    }
                }
            },
            {
                "type": "function",
                "function": {
                    "name": "run_code",
                    "description": "Execute a Python script and return output",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "path": {"type": "string"}
                        },
                        "required": ["path"]
                    }
                }
            },
            {
                "type": "function",
                "function": {
                    "name": "run_tests",
                    "description": "Run pytest on a test file",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "path": {"type": "string"}
                        },
                        "required": ["path"]
                    }
                }
            }
        ]

    def run(self, task):
        messages = [
            {
                "role": "system",
                "content": """You are a code agent. When given a coding task:
                1. Write the implementation
                2. Write tests
                3. Run the tests
                4. If tests fail, fix the code and retry
                5. Report the final result

                Always write tests. Always run them. Fix failures."""
            },
            {"role": "user", "content": task}
        ]

        for step in range(15):
            response = client.chat.completions.create(
                model="gpt-4o",
                messages=messages,
                tools=self.tools
            )

            message = response.choices[0].message
            if not message.tool_calls:
                return message.content

            messages.append(message)
            for tool_call in message.tool_calls:
                result = self.execute_tool(
                    tool_call.function.name,
                    json.loads(tool_call.function.arguments)
                )
                messages.append({
                    "role": "tool",
                    "tool_call_id": tool_call.id,
                    "content": str(result)
                })

        return "Task incomplete."

Error Recovery

Robust agents handle failures gracefully:

python
def execute_with_retry(self, tool_name, args, max_retries=3):
    for attempt in range(max_retries):
        try:
            result = self.execute_tool(tool_name, args)
            return result
        except Exception as e:
            if attempt == max_retries - 1:
                return {
                    "error": str(e),
                    "attempts": max_retries,
                    "suggestion": "Try a different approach"
                }
            # Wait before retry
            import time
            time.sleep(2 ** attempt)

Agent Guardrails

Iteration Limits

python
MAX_ITERATIONS = 15
MAX_TOOL_CALLS = 50

Budget Controls

python
class BudgetTracker:
    def __init__(self, max_tokens=100000, max_cost=1.0):
        self.total_tokens = 0
        self.max_tokens = max_tokens
        self.max_cost = max_cost

    def check_budget(self, usage):
        self.total_tokens += usage.total_tokens
        if self.total_tokens > self.max_tokens:
            raise BudgetExceededError("Token budget exceeded")

Action Approval

python
SAFE_TOOLS = {"web_search", "read_file", "calculate"}
DANGEROUS_TOOLS = {"write_file", "send_email", "delete_file", "run_code"}

def should_approve(tool_name, args):
    if tool_name in SAFE_TOOLS:
        return True
    print(f"Agent wants to: {tool_name}({args})")
    return input("Allow? [y/n]: ").lower() == "y"

Agent Patterns

1. Plan-Then-Execute

python
# Step 1: Generate a plan
plan = generate_plan(task)
# ["Search for X", "Analyze results", "Write summary"]

# Step 2: Execute each step
for step in plan:
    result = execute_step(step)
    if not result.success:
        plan = replan(task, completed_steps, error=result.error)

2. Reflection Pattern

python
# After completing the task, evaluate quality
reflection = llm_call(
    f"You completed this task: {task}\n"
    f"Your output: {output}\n"
    f"Rate your output 1-5 and identify improvements."
)
if reflection.score < 4:
    # Try again with feedback
    improved = retry_with_feedback(task, output, reflection.feedback)

3. Scratchpad Pattern

Maintain a running scratchpad of thoughts and observations:

python
scratchpad = []
scratchpad.append(f"Task: {task}")
scratchpad.append(f"Step 1: Searched for X, found Y")
scratchpad.append(f"Step 2: Analyzed Y, key insight: Z")
# Include scratchpad in every LLM call for full context

Key Takeaways

  1. Start with simple agents — 2-3 tools, clear task scope
  2. Always set limits — max iterations, budget, timeout
  3. Error recovery is essential — agents will encounter failures
  4. Human-in-the-loop for dangerous actions — require approval
  5. Evaluate agent performance — track success rate, efficiency, cost
  6. Use existing frameworks when possible — LangChain, CrewAI, etc.

Next, we'll explore multi-agent systems where specialized agents collaborate on complex tasks.