Skip to content

v0: Bash is Everything

After building v1, v2, and v3, a question emerged: What is the essence of an Agent?

v0 answers this through reverse thinking—strip everything away until only the core remains.

Core Insight

Unix philosophy: Everything is a file, everything can be piped. Bash is the gateway to this world:

You needBash command
Read filescat, head, grep
Write filesecho '...' > file
Searchfind, grep, rg
Executepython, npm, make
Subagentpython v0_bash_agent.py "task"

The last line is the key insight: calling itself via bash implements subagents. No Task tool needed, no Agent Registry—just recursion.

Complete Code

#!/usr/bin/env python3
"""
v0_bash_agent.py - Mini Claude Code: Bash is All You Need (~50 lines)

Core Philosophy: "Bash is All You Need"
======================================
This is the ULTIMATE simplification of a coding agent. After building v1-v3,
we ask: what is the ESSENCE of an agent?

The answer: ONE tool (bash) + ONE loop = FULL agent capability.

Usage:
    # Interactive mode
    python v0_bash_agent.py

    # Subagent mode (called by parent agent or directly)
    python v0_bash_agent.py "explore src/ and summarize"
"""

from anthropic import Anthropic
from dotenv import load_dotenv
import subprocess
import sys
import os

load_dotenv(override=True)

# Initialize Anthropic client (uses ANTHROPIC_API_KEY and ANTHROPIC_BASE_URL env vars)
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
MODEL = os.getenv("MODEL_ID", "claude-sonnet-4-5-20250929")

# The ONE tool that does everything
# Notice how the description teaches the model common patterns AND how to spawn subagents
TOOL = [{
    "name": "bash",
    "description": """Execute shell command. Common patterns:
- Read: cat/head/tail, grep/find/rg/ls, wc -l
- Write: echo 'content' > file, sed -i 's/old/new/g' file
- Subagent: python v0_bash_agent.py 'task description' (spawns isolated agent, returns summary)""",
    "input_schema": {
        "type": "object",
        "properties": {"command": {"type": "string"}},
        "required": ["command"]
    }
}]

# System prompt teaches the model HOW to use bash effectively
# Notice the subagent guidance - this is how we get hierarchical task decomposition
SYSTEM = f"""You are a CLI agent at {os.getcwd()}. Solve problems using bash commands.

Rules:
- Prefer tools over prose. Act first, explain briefly after.
- Read files: cat, grep, find, rg, ls, head, tail
- Write files: echo '...' > file, sed -i, or cat << 'EOF' > file
- Subagent: For complex subtasks, spawn a subagent to keep context clean:
  python v0_bash_agent.py "explore src/ and summarize the architecture"

When to use subagent:
- Task requires reading many files (isolate the exploration)
- Task is independent and self-contained
- You want to avoid polluting current conversation with intermediate details

The subagent runs in isolation and returns only its final summary."""


def chat(prompt, history=None):
    """
    The complete agent loop in ONE function.

    This is the core pattern that ALL coding agents share:
        while not done:
            response = model(messages, tools)
            if no tool calls: return
            execute tools, append results

    Args:
        prompt: User's request
        history: Conversation history (mutable, shared across calls in interactive mode)

    Returns:
        Final text response from the model
    """
    if history is None:
        history = []

    history.append({"role": "user", "content": prompt})

    while True:
        # 1. Call the model with tools
        response = client.messages.create(
            model=MODEL,
            system=SYSTEM,
            messages=history,
            tools=TOOL,
            max_tokens=8000
        )

        # 2. Build assistant message content (preserve both text and tool_use blocks)
        content = []
        for block in response.content:
            if hasattr(block, "text"):
                content.append({"type": "text", "text": block.text})
            elif block.type == "tool_use":
                content.append({
                    "type": "tool_use",
                    "id": block.id,
                    "name": block.name,
                    "input": block.input
                })
        history.append({"role": "assistant", "content": content})

        # 3. If model didn't call tools, we're done
        if response.stop_reason != "tool_use":
            return "".join(b.text for b in response.content if hasattr(b, "text"))

        # 4. Execute each tool call and collect results
        results = []
        for block in response.content:
            if block.type == "tool_use":
                cmd = block.input["command"]
                print(f"\033[33m$ {cmd}\033[0m")  # Yellow color for commands

                try:
                    out = subprocess.run(
                        cmd,
                        shell=True,
                        capture_output=True,
                        text=True,
                        timeout=300,
                        cwd=os.getcwd()
                    )
                    output = out.stdout + out.stderr
                except subprocess.TimeoutExpired:
                    output = "(timeout after 300s)"

                print(output or "(empty)")
                results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": output[:50000]  # Truncate very long outputs
                })

        # 5. Append results and continue the loop
        history.append({"role": "user", "content": results})


if __name__ == "__main__":
    if len(sys.argv) > 1:
        # Subagent mode: execute task and print result
        # This is how parent agents spawn children via bash
        print(chat(sys.argv[1]))
    else:
        # Interactive REPL mode
        history = []
        while True:
            try:
                query = input("\033[36m>> \033[0m")  # Cyan prompt
            except (EOFError, KeyboardInterrupt):
                break
            if query in ("q", "exit", ""):
                break
            print(chat(query, history))

That’s the entire agent. ~50 lines.

How Subagents Work

Main Agent
  └─ bash: python v0_bash_agent.py "analyze architecture"
       └─ Subagent (separate process, fresh history)
            ├─ bash: find . -name "*.py"
            ├─ bash: cat src/main.py
            └─ Returns summary via stdout

Process isolation = context isolation

  • Subprocess has its own history=[]
  • Parent process captures stdout as tool result
  • Recursive calls enable infinite nesting

What v0 Sacrifices

Featurev0v3
Agent typesNoneexplore/code/plan
Tool filteringNoneWhitelist
Progress displayPlain stdoutInline updates
Code complexity~50 lines~450 lines

What v0 Proves

Complex capabilities emerge from simple rules:

  1. One tool suffices — Bash is the gateway to everything
  2. Recursion = hierarchy — Self-calls implement subagents
  3. Process = isolation — OS provides context separation
  4. Prompt = constraints — Instructions shape behavior

The core pattern never changes:

while True:
    response = model(messages, tools)
    if response.stop_reason != "tool_use":
        return response.text
    results = execute(response.tool_calls)
    messages.append(results)

Everything else—todos, subagents, permissions—is refinement around this loop.

Running Examples

# Install dependencies
pip install anthropic python-dotenv

# Configure environment variables (create .env file)
echo "ANTHROPIC_API_KEY=sk-ant-xxx" > .env
# Optional: Configure custom endpoint (uses official API by default)

# Interactive mode
python v0_bash_agent.py

# Subagent mode
python v0_bash_agent.py "Analyze current directory structure and summarize"

Common Questions

Q: Why not just use v1 directly? A: v0 is good for building intuition. Understanding that “one tool can work too” makes the tool separation in v1 more appreciable.

Q: Won’t subagents waste tokens? A: No. Subagents run independently and only return summaries to the parent. This actually reduces noise in the main context.

Q: Can I use v0 in production? A: Not recommended. Lacks safety guardrails, permission systems, progress display, etc. v0 is a minimal implementation for teaching.


Bash is everything.