| name | building-ai-agents |
| description | Guide for building AI agents from scratch with tool use, agentic loops, and LLM APIs. Includes nanocode as a complete working example showing how to build a minimal Claude Code alternative in 270 lines. |
Building AI Agents
Complete guide for creating AI agents with tool-use capabilities from scratch.
When to Use
- Building new AI agents or coding assistants
- Implementing agentic loops with tool calling
- Learning agent architecture patterns
- Creating Claude API integrations
Core Architecture
Every AI agent needs four layers:
┌─────────────────────────────────────────┐
│ 1. Tool Definition Layer │ Define what tools exist
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 2. Tool Implementation Layer │ Implement tool logic
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 3. Schema Generation Layer │ Convert to API format
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 4. Agentic Loop Layer │ Execute until complete
└─────────────────────────────────────────┘
Quick Start
Minimal Template (30 lines)
import json, urllib.request
TOOLS = {
"echo": (
"Repeat input back",
{"text": "string"},
lambda args: args["text"],
),
}
def run_tool(name, args):
return TOOLS[name][2](args)
def make_schema():
return [{
"name": name,
"description": desc,
"input_schema": {
"type": "object",
"properties": {
k: {"type": v.rstrip("?").replace("number", "integer")}
for k, v in params.items()
},
"required": [k for k, v in params.items() if not v.endswith("?")],
}
} for name, (desc, params, _) in TOOLS.items()]
while True:
user_input = input("❯ ")
response = urllib.request.urlopen(
"https://api.anthropic.com/v1/messages",
data=json.dumps({
"model": "claude-3-5-sonnet-20241022",
"max_tokens": 1024,
"messages": [{"role": "user", "content": user_input}],
"tools": make_schema()
}).encode()
)
print(response.read())
30 lines. Working agent.
Key Principles
1. Dictionary-Based Parameters
API returns tool_use.input as JSON object → pass as dict:
args = block["input"]
result = tool(args)
2. Schema as Source of Truth
Generate from definition, never write twice:
TOOLS = {"tool": (desc, {"param": "type?"}, fn)}
3. Agentic Loop
Keep calling until no more tool uses:
while True:
response = call_api(messages)
tool_results = [execute(t) for t in response["tool_uses"]]
if not tool_results:
break
messages.append({"role": "user", "content": tool_results})
Complete Working Example
nanocode - Full Claude Code alternative in 270 lines:
- Location:
examples/nanocode.py
- Tools: read, write, edit, glob, grep, bash
- Zero dependencies (Python stdlib only)
- Supports Anthropic API + OpenRouter
See: examples/nanocode.md
Common Patterns
Add New Tool
def my_tool(args):
return process(args["input"])
TOOLS["my_tool"] = (
"Description of what tool does",
{"input": "string", "optional": "number?"},
my_tool,
)
Optional Parameters
def read(args):
offset = args.get("offset", 0)
Mark with ?: {"offset": "number?"}
Error Handling
def run_tool(name, args):
try:
return TOOLS[name][2](args)
except Exception as err:
return f"error: {err}"
Advanced Topics
Multi-step reasoning: references/multi-step-reasoning.md
Tool composition: references/tool-composition.md
State management: references/state-management.md
Troubleshooting: references/troubleshooting.md