DEV Community

Cover image for Your First Agent: From Zero to Working in 30 Minutes
Gantz AI for Gantz

Posted on

Your First Agent: From Zero to Working in 30 Minutes

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-..."
Enter fullscreen mode Exit fullscreen mode

Install dependencies

pip install openai
# or
pip install anthropic
Enter fullscreen mode Exit fullscreen mode

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}")
Enter fullscreen mode Exit fullscreen mode

50 lines. That's your agent.

Minute 15-20: Test it

python agent.py
Enter fullscreen mode Exit fullscreen mode

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
...
Enter fullscreen mode Exit fullscreen mode
> 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)
...
Enter fullscreen mode Exit fullscreen mode
> 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
...
Enter fullscreen mode Exit fullscreen mode

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"]
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
# 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}"
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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}}"
Enter fullscreen mode Exit fullscreen mode
gantz
Enter fullscreen mode Exit fullscreen mode

Same result. Zero boilerplate.

What to do next

Now that you have a working agent:

  1. Add more tools - Database queries, API calls, git operations
  2. Improve the prompt - Be more specific about behavior
  3. Add guardrails - Confirm before destructive operations
  4. 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
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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:

  1. API key (2 minutes)
  2. Core loop: LLM β†’ Tool β†’ Result β†’ LLM (10 minutes)
  3. Basic tools: read, write, search, run (10 minutes)
  4. Conversation context (5 minutes)
  5. 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)