Build Your First AI Agent in 60 Lines of Python — No Framework Needed

Most AI agent tutorials drop you straight into LangChain, AutoGPT, or CrewAI. You copy-paste boilerplate, configure YAML files, and when something breaks, you're debugging framework internals instead of understanding how agents actually work.

Let's fix that. In this tutorial, you'll build a working AI agent in under 60 lines of Python using just the Anthropic SDK and the standard library. By the end, you'll understand the core loop that powers every agent framework — and you'll know exactly when you do need one.

What Makes Something an "Agent"?

An AI agent isn't magic. It's a loop with three parts:

  1. Think — The LLM decides what to do next
  2. Act — Execute tools (search, run code, call APIs)
  3. Observe — Feed results back to the LLM

Repeat until the task is done. That's it.

The key difference from a chatbot? Tool use. A chatbot generates text. An agent generates text and takes actions in the world.

Building the Core Loop

First, install the Anthropic SDK:

pip install anthropic

Here's the skeleton:

import anthropic
import json

client = anthropic.Anthropic(api_key="your-api-key")

def agent_loop(user_task):
    messages = [{"role": "user", "content": user_task}]
    
    while True:
        response = client.messages.create(
            model="claude-sonnet-4-5",
            max_tokens=1024,
            tools=TOOLS,
            messages=messages
        )
        
        # Check if Claude wants to use a tool
        if response.stop_reason == "tool_use":
            # Execute the tool
            tool_result = execute_tool(response.content)
            # Feed result back to Claude
            messages.append({"role": "assistant", "content": response.content})
            messages.append({"role": "user", "content": tool_result})
        else:
            # Task complete
            return response.content[0].text

This 20-line loop is the heart of every agent. Claude decides whether to use a tool or return an answer. If it uses a tool, we execute it and continue. If not, we're done.

Adding Tools: Search and Calculate

Let's give our agent two capabilities:

import requests
from datetime import datetime

TOOLS = [
    {
        "name": "search",
        "description": "Search the web for current information",
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "Search query"}
            },
            "required": ["query"]
        }
    },
    {
        "name": "calculate",
        "description": "Evaluate a Python math expression",
        "input_schema": {
            "type": "object",
            "properties": {
                "expression": {"type": "string", "description": "Math expression"}
            },
            "required": ["expression"]
        }
    }
]

def execute_tool(content):
    for block in content:
        if block.type == "tool_use":
            if block.name == "search":
                # Use DuckDuckGo API (no auth needed)
                query = block.input["query"]
                resp = requests.get(f"https://api.duckduckgo.com/?q={query}&format=json")
                results = resp.json().get("AbstractText", "No results")
                return [{"type": "tool_result", "tool_use_id": block.id, "content": results}]
            
            elif block.name == "calculate":
                try:
                    result = eval(block.input["expression"], {"__builtins__": {}})
                    return [{"type": "tool_result", "tool_use_id": block.id, "content": str(result)}]
                except Exception as e:
                    return [{"type": "tool_result", "tool_use_id": block.id, "content": f"Error: {e}"}]

Security note: Using eval() is unsafe for production. Use ast.literal_eval() or a proper math parser like simpleeval.

Now try it:

result = agent_loop("What's 15% of 2,340, and is that enough to buy a Raspberry Pi 5?")
print(result)

The agent will:

  1. Calculate 15% of 2,340 (351)
  2. Search for Raspberry Pi 5 pricing
  3. Compare and answer

All in one execution.

Making It Production-Ready

This minimal agent works, but here's what you'd add for real use:

Rate limiting and retries:

from tenacity import retry, stop_after_attempt, wait_exponential

@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10))
def call_claude(messages):
    return client.messages.create(
        model="claude-sonnet-4-5",
        max_tokens=1024,
        tools=TOOLS,
        messages=messages
    )

Loop limits (prevent infinite tool calls):

MAX_ITERATIONS = 10
for i in range(MAX_ITERATIONS):
    response = call_claude(messages)
    if response.stop_reason != "tool_use":
        break

Streaming (show progress in real-time):

with client.messages.stream(
    model="claude-sonnet-4-5",
    max_tokens=1024,
    tools=TOOLS,
    messages=messages
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)

When to Use a Framework

You've just built an agent in 60 lines. So when should you reach for LangChain or CrewAI?

Use a framework when you need:

  • Multi-agent orchestration (agents collaborating)
  • Built-in integrations (100+ tools pre-configured)
  • Complex memory systems (vector stores, graph databases)
  • Production observability (tracing, logging, debugging)

Stay minimal when:

  • You're prototyping or learning
  • Your use case is simple (1-3 tools)
  • You need full control over the loop
  • You want minimal dependencies

Takeaway

AI agents aren't black boxes. They're loops: think → act → observe. By building one from scratch, you've learned:

  • How tool use actually works (JSON schemas, result passing)
  • Why frameworks abstract the loop (retries, streaming, limits)
  • When to add complexity vs. keep it simple

The full code is 58 lines. Fork it, add your own tools (file I/O, database queries, API calls), and experiment. Once you hit the limits of this approach, then explore frameworks — but you'll understand exactly what they're doing under the hood.

Next steps: Try adding a web scraper tool, a SQLite query tool, or a file writer. The pattern is always the same: define the schema, implement the function, let Claude decide when to use it.