analysis_claude_code/v0_bash_agent.py
CrazyBoyM 85f44c358a Complete rewrite: original educational content only
- Remove all reverse-engineered Claude Code source code
- Replace with 100% original educational content from mini-claude-code
- Add clear disclaimer: independent project, not affiliated with Anthropic
- 5 progressive agent implementations (v0-v4, ~1100 lines total)
- Include agent-builder skill for teaching agent construction
- Bilingual documentation (EN + ZH)

This repository now focuses purely on teaching how modern AI agents work
through original, from-scratch implementations.

Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-31 07:01:42 +08:00

197 lines
6.6 KiB
Python

#!/usr/bin/env python
"""
v0_bash_agent.py - Mini Claude Code: Bash is All You Need (~50 lines core)
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.
Why Bash is Enough:
------------------
Unix philosophy says everything is a file, everything can be piped.
Bash is the gateway to this world:
| You need | Bash command |
|---------------|----------------------------------------|
| Read files | cat, head, tail, grep |
| Write files | echo '...' > file, cat << 'EOF' > file |
| Search | find, grep, rg, ls |
| Execute | python, npm, make, any command |
| **Subagent** | python v0_bash_agent.py "task" |
The last line is the KEY INSIGHT: calling itself via bash implements subagents!
No Task tool, no Agent Registry - just recursion through process spawning.
How Subagents Work:
------------------
Main Agent
|-- bash: python v0_bash_agent.py "analyze architecture"
|-- Subagent (isolated process, fresh history)
|-- bash: find . -name "*.py"
|-- bash: cat src/main.py
|-- Returns summary via stdout
Process isolation = Context isolation:
- Child process has its own history=[]
- Parent captures stdout as tool result
- Recursive calls enable unlimited nesting
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 environment variables from .env file
load_dotenv()
# Initialize API client with credentials from environment
client = Anthropic(
api_key=os.getenv("ANTHROPIC_API_KEY"),
base_url=os.getenv("ANTHROPIC_BASE_URL")
)
MODEL = os.getenv("MODEL_NAME", "claude-sonnet-4-20250514")
# 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))