mirror of
https://github.com/shareAI-lab/analysis_claude_code.git
synced 2026-02-04 13:16:37 +08:00
190 lines
6.5 KiB
Python
190 lines
6.5 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 provider_utils import get_client, get_model
|
|
import subprocess
|
|
import sys
|
|
import os
|
|
|
|
# Initialize API client and model using provider utilities
|
|
client = get_client()
|
|
MODEL = get_model()
|
|
|
|
# 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))
|