You don't need a PhD to build an AI agent.
You need an API key, 50 lines of code, and 30 minutes.
Let's go.
What we're building
An agent that can:
- Read files
- Search code
- Run commands
- Actually be useful
Not a toy. A real, working agent.
Minute 0-5: Setup
Get an API key
Pick one:
Export it:
export OPENAI_API_KEY="sk-..."
# or
export ANTHROPIC_API_KEY="sk-ant-..."
Install dependencies
pip install openai
# or
pip install anthropic
That's it. No frameworks. No LangChain. No vector databases.
Minute 5-15: The core loop
Here's the entire agent:
# agent.py
import openai
import json
import subprocess
client = openai.OpenAI()
# Define what tools the agent can use
tools = [
{
"type": "function",
"function": {
"name": "read_file",
"description": "Read the contents of a file",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "Path to the file"}
},
"required": ["path"]
}
}
},
{
"type": "function",
"function": {
"name": "run_command",
"description": "Run a shell command",
"parameters": {
"type": "object",
"properties": {
"command": {"type": "string", "description": "Command to run"}
},
"required": ["command"]
}
}
}
]
# Execute a tool
def execute_tool(name, args):
if name == "read_file":
try:
with open(args["path"]) as f:
return f.read()
except Exception as e:
return f"Error: {e}"
elif name == "run_command":
try:
result = subprocess.run(
args["command"],
shell=True,
capture_output=True,
text=True,
timeout=30
)
return result.stdout + result.stderr
except Exception as e:
return f"Error: {e}"
return f"Unknown tool: {name}"
# The agent loop
def run_agent(user_message):
messages = [
{"role": "system", "content": "You are a helpful coding assistant."},
{"role": "user", "content": user_message}
]
while True:
# Get response from LLM
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools
)
message = response.choices[0].message
messages.append(message)
# If no tool calls, we're done
if not message.tool_calls:
return message.content
# Execute each tool call
for tool_call in message.tool_calls:
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
print(f"π§ {name}({args})")
result = execute_tool(name, args)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": str(result)
})
# Run it
if __name__ == "__main__":
while True:
user_input = input("\n> ")
if user_input.lower() in ["quit", "exit"]:
break
response = run_agent(user_input)
print(f"\n{response}")
50 lines. That's your agent.
Minute 15-20: Test it
python agent.py
Try these:
> Read my package.json and tell me what dependencies I have
π§ read_file({'path': 'package.json'})
You have the following dependencies:
- react: ^18.2.0
- typescript: ^5.0.0
...
> What files are in the current directory?
π§ run_command({'command': 'ls -la'})
Here are the files in your directory:
- agent.py (the script you're running)
- package.json
- src/ (directory)
...
> Find all TODO comments in my code
π§ run_command({'command': 'grep -r "TODO" . --include="*.py"'})
I found 3 TODO comments:
1. ./agent.py:42 - TODO: add error handling
...
It works. You have a working agent.
Minute 20-25: Add more tools
Let's add search:
# Add to tools list
{
"type": "function",
"function": {
"name": "search",
"description": "Search for text in files",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Text to search for"},
"path": {"type": "string", "description": "Directory to search in", "default": "."}
},
"required": ["query"]
}
}
},
{
"type": "function",
"function": {
"name": "write_file",
"description": "Write content to a file",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "Path to the file"},
"content": {"type": "string", "description": "Content to write"}
},
"required": ["path", "content"]
}
}
}
# Add to execute_tool function
elif name == "search":
try:
result = subprocess.run(
f'grep -r "{args["query"]}" {args.get("path", ".")} --include="*.py" --include="*.js" --include="*.ts" | head -20',
shell=True,
capture_output=True,
text=True
)
return result.stdout or "No matches found"
except Exception as e:
return f"Error: {e}"
elif name == "write_file":
try:
with open(args["path"], "w") as f:
f.write(args["content"])
return f"Wrote {len(args['content'])} bytes to {args['path']}"
except Exception as e:
return f"Error: {e}"
Now your agent can read, write, search, and run commands. That covers most coding tasks.
Minute 25-30: Make it conversational
The agent forgets after each message. Let's fix that:
def run_agent_conversation():
messages = [
{"role": "system", "content": "You are a helpful coding assistant."}
]
while True:
user_input = input("\n> ")
if user_input.lower() in ["quit", "exit"]:
break
messages.append({"role": "user", "content": user_input})
while True:
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools
)
message = response.choices[0].message
messages.append(message)
if not message.tool_calls:
print(f"\n{message.content}")
break
for tool_call in message.tool_calls:
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
print(f"π§ {name}({args})")
result = execute_tool(name, args)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": str(result)[:10000] # Truncate large results
})
if __name__ == "__main__":
run_agent_conversation()
Now it remembers the conversation:
> Read my config.py file
π§ read_file({'path': 'config.py'})
Your config.py contains database settings and API keys...
> What database is it using?
Based on the config.py I just read, you're using PostgreSQL
with the connection string: postgresql://localhost:5432/myapp
You're done
In 30 minutes, you built an agent that:
- β Reads files
- β Writes files
- β Searches code
- β Runs commands
- β Maintains conversation context
The complete code
# agent.py - Complete working agent
import openai
import json
import subprocess
client = openai.OpenAI()
tools = [
{
"type": "function",
"function": {
"name": "read_file",
"description": "Read the contents of a file",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "Path to the file"}
},
"required": ["path"]
}
}
},
{
"type": "function",
"function": {
"name": "write_file",
"description": "Write content to a file",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "Path to the file"},
"content": {"type": "string", "description": "Content to write"}
},
"required": ["path", "content"]
}
}
},
{
"type": "function",
"function": {
"name": "search",
"description": "Search for text in files",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Text to search for"}
},
"required": ["query"]
}
}
},
{
"type": "function",
"function": {
"name": "run_command",
"description": "Run a shell command",
"parameters": {
"type": "object",
"properties": {
"command": {"type": "string", "description": "Command to run"}
},
"required": ["command"]
}
}
}
]
def execute_tool(name, args):
if name == "read_file":
try:
with open(args["path"]) as f:
return f.read()
except Exception as e:
return f"Error: {e}"
elif name == "write_file":
try:
with open(args["path"], "w") as f:
f.write(args["content"])
return f"Wrote {len(args['content'])} bytes to {args['path']}"
except Exception as e:
return f"Error: {e}"
elif name == "search":
try:
result = subprocess.run(
f'grep -r "{args["query"]}" . --include="*.py" --include="*.js" | head -20',
shell=True,
capture_output=True,
text=True
)
return result.stdout or "No matches found"
except Exception as e:
return f"Error: {e}"
elif name == "run_command":
try:
result = subprocess.run(
args["command"],
shell=True,
capture_output=True,
text=True,
timeout=30
)
return result.stdout + result.stderr
except Exception as e:
return f"Error: {e}"
return f"Unknown tool: {name}"
def main():
messages = [
{"role": "system", "content": "You are a helpful coding assistant."}
]
print("Agent ready. Type 'quit' to exit.\n")
while True:
user_input = input("> ")
if user_input.lower() in ["quit", "exit"]:
break
messages.append({"role": "user", "content": user_input})
while True:
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools
)
message = response.choices[0].message
messages.append(message)
if not message.tool_calls:
print(f"\n{message.content}\n")
break
for tool_call in message.tool_calls:
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
print(f"π§ {name}({args})")
result = execute_tool(name, args)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": str(result)[:10000]
})
if __name__ == "__main__":
main()
Skip the boilerplate
If you want to skip writing tool implementations, use Gantz Run:
# gantz.yaml
tools:
- name: read
description: Read a file
parameters:
- name: path
type: string
required: true
script:
shell: cat "{{path}}"
- name: write
description: Write to a file
parameters:
- name: path
type: string
required: true
- name: content
type: string
required: true
script:
shell: echo "{{content}}" > "{{path}}"
- name: search
description: Search for text in files
parameters:
- name: query
type: string
required: true
script:
shell: grep -r "{{query}}" . | head -20
- name: run
description: Run a shell command
parameters:
- name: command
type: string
required: true
script:
shell: "{{command}}"
gantz
Same result. Zero boilerplate.
What to do next
Now that you have a working agent:
- Add more tools - Database queries, API calls, git operations
- Improve the prompt - Be more specific about behavior
- Add guardrails - Confirm before destructive operations
- Handle errors better - Retry logic, better error messages
But first: use it. Build something. See where it fails.
You learn more from 10 minutes of using a broken agent than 10 hours of reading about perfect architectures.
Common mistakes to avoid
Mistake 1: Too many tools
# Don't do this
tools:
- read_file
- read_file_lines
- read_file_head
- read_file_tail
- read_json
- read_yaml
# ... 30 more tools
Start with 4-5 tools. Add more only when you need them.
Mistake 2: Over-engineering
# Don't do this for your first agent
class Agent:
def __init__(self):
self.memory = VectorDatabase()
self.planner = HierarchicalPlanner()
self.reflector = SelfReflectionModule()
self.evaluator = QualityEvaluator()
The simple loop is enough. Add complexity when you hit real limits.
Mistake 3: Not testing early
Build β Test β Fix β Repeat
Don't build for a week then test. Build for 5 minutes then test.
Summary
Building an agent isn't complicated:
- API key (2 minutes)
- Core loop: LLM β Tool β Result β LLM (10 minutes)
- Basic tools: read, write, search, run (10 minutes)
- Conversation context (5 minutes)
- Test and iterate (3 minutes)
Total: 30 minutes to a working agent.
Stop reading tutorials. Start building.
What was the hardest part of building your first agent?
Top comments (0)