refactor: organize agent harness courses

This commit is contained in:
Haoran
2026-06-16 00:10:35 +08:00
parent 20e7cbb72c
commit 8af5c24e46
491 changed files with 7961 additions and 564 deletions

View File

@@ -0,0 +1,129 @@
---
name: agent-builder
description: |
Design and build AI agents for any domain. Use when users:
(1) ask to "create an agent", "build an assistant", or "design an AI system"
(2) want to understand agent architecture, agentic patterns, or autonomous AI
(3) need help with capabilities, subagents, planning, or skill mechanisms
(4) ask about Claude Code, Cursor, or similar agent internals
(5) want to build agents for business, research, creative, or operational tasks
Keywords: agent, assistant, autonomous, workflow, tool use, multi-step, orchestration
---
# Agent Builder
Build AI agents for any domain - customer service, research, operations, creative work, or specialized business processes.
## The Core Philosophy
> **The model already knows how to be an agent. Your job is to get out of the way.**
An agent is not complex engineering. It's a simple loop that invites the model to act:
```
LOOP:
Model sees: context + available capabilities
Model decides: act or respond
If act: execute capability, add result, continue
If respond: return to user
```
**That's it.** The magic isn't in the code - it's in the model. Your code just provides the opportunity.
## The Three Elements
### 1. Capabilities (What can it DO?)
Atomic actions the agent can perform: search, read, create, send, query, modify.
**Design principle**: Start with 3-5 capabilities. Add more only when the agent consistently fails because a capability is missing.
### 2. Knowledge (What does it KNOW?)
Domain expertise injected on-demand: policies, workflows, best practices, schemas.
**Design principle**: Make knowledge available, not mandatory. Load it when relevant, not upfront.
### 3. Context (What has happened?)
The conversation history - the thread connecting actions into coherent behavior.
**Design principle**: Context is precious. Isolate noisy subtasks. Truncate verbose outputs. Protect clarity.
## Agent Design Thinking
Before building, understand:
- **Purpose**: What should this agent accomplish?
- **Domain**: What world does it operate in? (customer service, research, operations, creative...)
- **Capabilities**: What 3-5 actions are essential?
- **Knowledge**: What expertise does it need access to?
- **Trust**: What decisions can you delegate to the model?
**CRITICAL**: Trust the model. Don't over-engineer. Don't pre-specify workflows. Give it capabilities and let it reason.
## Progressive Complexity
Start simple. Add complexity only when real usage reveals the need:
| Level | What to add | When to add it |
|-------|-------------|----------------|
| Basic | 3-5 capabilities | Always start here |
| Planning | Progress tracking | Multi-step tasks lose coherence |
| Subagents | Isolated child agents | Exploration pollutes context |
| Skills | On-demand knowledge | Domain expertise needed |
**Most agents never need to go beyond Level 2.**
## Domain Examples
**Business**: CRM queries, email, calendar, approvals
**Research**: Database search, document analysis, citations
**Operations**: Monitoring, tickets, notifications, escalation
**Creative**: Asset generation, editing, collaboration, review
The pattern is universal. Only the capabilities change.
## Key Principles
1. **The model IS the agent** - Code just runs the loop
2. **Capabilities enable** - What it CAN do
3. **Knowledge informs** - What it KNOWS how to do
4. **Constraints focus** - Limits create clarity
5. **Trust liberates** - Let the model reason
6. **Iteration reveals** - Start minimal, evolve from usage
## Anti-Patterns
| Pattern | Problem | Solution |
|---------|---------|----------|
| Over-engineering | Complexity before need | Start simple |
| Too many capabilities | Model confusion | 3-5 to start |
| Rigid workflows | Can't adapt | Let model decide |
| Front-loaded knowledge | Context bloat | Load on-demand |
| Micromanagement | Undercuts intelligence | Trust the model |
## Resources
**Philosophy & Theory**:
- `references/agent-philosophy.md` - Deep dive into why agents work
**Implementation**:
- `references/minimal-agent.py` - Complete working agent (~80 lines)
- `references/tool-templates.py` - Capability definitions
- `references/subagent-pattern.py` - Context isolation
**Scaffolding**:
- `scripts/init_agent.py` - Generate new agent projects
## The Agent Mindset
**From**: "How do I make the system do X?"
**To**: "How do I enable the model to do X?"
**From**: "What's the workflow for this task?"
**To**: "What capabilities would help accomplish this?"
The best agent code is almost boring. Simple loops. Clear capabilities. Clean context. The magic isn't in the code.
**Give the model capabilities and knowledge. Trust it to figure out the rest.**

View File

@@ -0,0 +1,154 @@
# The Philosophy of Agent Harness Engineering
> **The model already knows how to be an agent. Your job is to build it a world worth acting in.**
## The Fundamental Truth
Strip away every framework, every library, every architectural pattern. What remains?
A loop. A model. An invitation to act.
The agent is not the code. The agent is the model itself -- a vast neural network trained on humanity's collective problem-solving, reasoning, and tool use. The code merely provides the opportunity for the model to express its agency.
The code is the harness. The model is the agent. These are not interchangeable. Confuse them, and you will build the wrong thing.
## What an Agent IS
An agent is a neural network -- a Transformer, an RNN, a learned function -- that has been trained, through billions of gradient updates on action-sequence data, to perceive an environment, reason about goals, and take actions to achieve them.
A human is an agent: a biological neural network shaped by evolution. DeepMind's DQN is an agent: a convolutional network that learned to play Atari from raw pixels. OpenAI Five is an agent: five networks that learned Dota 2 teamwork through self-play. Claude is an agent: a language model that learned to reason and act from the breadth of human knowledge.
In every case, the agent is the trained model. Not the game engine. Not the Dota 2 client. Not the terminal. The model.
## What an Agent Is NOT
Prompt plumbing is not agency. Wiring together LLM API calls with if-else branches, node graphs, and hardcoded routing logic does not produce an agent. It produces a brittle pipeline -- a Rube Goldberg machine with an LLM wedged in as a text-completion node.
You cannot engineer your way to agency. Agency is learned, not programmed. No amount of glue code will emergently produce autonomous behavior. Those systems are the modern resurrection of GOFAI -- symbolic rule systems the field abandoned decades ago, now spray-painted with an LLM veneer.
## The Harness: What We Actually Build
If the model is the agent, then what is the code? It is the **harness** -- the environment that gives the agent the ability to perceive and act in a specific domain.
```
Harness = Tools + Knowledge + Observation + Action Interfaces + Permissions
```
### Tools: The Agent's Hands
Tools answer: **What can the agent DO?**
Each tool is an atomic action the agent can take in its environment. File read/write, shell execution, API calls, browser control, database queries. The model needs to understand what each tool does, but not how to sequence them -- it will figure that out.
**Design principle**: Atomic, composable, well-described. Start with 3-5. Add more only when the model consistently fails to accomplish tasks because a tool is missing.
### Knowledge: The Agent's Expertise
Knowledge answers: **What does the agent KNOW?**
Domain expertise that turns a general agent into a domain specialist. Product documentation, architectural decisions, regulatory requirements, style guides. Inject on-demand (via tool_result), not upfront (via system prompt). Progressive disclosure preserves context for what matters.
**Design principle**: Available but not mandatory. The agent should know what knowledge exists and pull what it needs.
### Context: The Agent's Memory
Context is the thread connecting individual actions into coherent behavior. What has been said, tried, learned, and decided.
**Design principle**: Context is precious. Protect it. Isolate subtasks that generate noise (s04). Compress when history grows long (s06). Persist goals beyond single conversations (s07).
### Permissions: The Agent's Boundaries
Permissions answer: **What is the agent ALLOWED to do?**
Sandbox file access. Require approval for destructive operations. Enforce trust boundaries between the agent and external systems. This is where safety engineering meets harness engineering.
**Design principle**: Constraints focus behavior, not limit it. "One task in_progress at a time" forces sequential focus. "Read-only subagent" prevents accidental modifications.
### Task-Process Data: The Agent's Training Signal
Every action sequence the agent executes in your harness is training signal. The perception-reasoning-action traces from real deployments are the raw material for fine-tuning the next generation of agent models. Your harness doesn't just serve the agent -- it can help evolve the agent.
## The Universal Loop
Every effective agent -- regardless of domain -- follows the same pattern:
```
LOOP:
Model sees: conversation history + available tools
Model decides: act or respond
If act: tool executed, result added to context, loop continues
If respond: answer returned, loop ends
```
This is not a simplification. This is the actual architecture. Everything else is harness engineering -- mechanisms layered on top of this loop to make the agent more effective. The loop belongs to the agent. The mechanisms belong to the harness.
## Principles of Harness Engineering
### Trust the Model
The most important principle: **trust the model**.
Don't anticipate every edge case. Don't build elaborate decision trees. Don't pre-specify the workflow.
The model is better at reasoning than any rule system you could write. Your conditional logic will fail on edge cases. The model will reason through them.
**Give the model tools and knowledge. Let it figure out how to use them.**
### Constraints Enable
This seems paradoxical, but constraints don't limit agents -- they focus them.
A todo list with "only one task in progress" forces sequential focus. A subagent with read-only access prevents accidental modifications. A context compression threshold keeps history from overwhelming.
The best constraints prevent the model from getting lost, not micromanage its approach.
### Progressive Complexity
Never build everything upfront.
```
Level 0: Model + one tool (bash) -- s01
Level 1: Model + tool dispatch map -- s02
Level 2: Model + planning -- s03
Level 3: Model + subagents + skills -- s04, s05
Level 4: Model + context management + persistence -- s06, s07, s08
Level 5: Model + teams + autonomy + isolation -- s09-s12
```
Start at the lowest level that might work. Move up only when real usage reveals the need.
## The Mind Shift
Building harnesses requires a fundamental shift in thinking:
**From**: "How do I make the system do X?"
**To**: "How do I enable the model to do X?"
**From**: "What should happen when the user says Y?"
**To**: "What tools would help address Y?"
**From**: "What's the workflow for this task?"
**To**: "What does the model need to figure out the workflow?"
**From**: "I'm building an agent."
**To**: "I'm building a harness for the agent."
The best harness code is almost boring. Simple loops. Clear tool definitions. Clean context management. The magic isn't in the code -- it's in the model.
## The Vehicle Metaphor
The model is the driver. The harness is the vehicle.
A coding agent's vehicle is its IDE, terminal, and filesystem. A farm agent's vehicle is its sensor array, irrigation controls, and weather data. A hotel agent's vehicle is its booking system, guest channels, and facility APIs.
The driver generalizes. The vehicle specializes. Your job as a harness engineer is to build the best vehicle for your domain -- one that gives the driver maximum visibility, precise controls, and clear boundaries.
Build the cockpit. Build the dashboard. Build the controls. The pilot is already trained.
## Conclusion
The model is the agent. The code is the harness. Know which one you're building.
You are not writing intelligence. You are building the world intelligence inhabits. The quality of that world -- how clearly the agent can perceive, how precisely it can act, how rich its knowledge -- directly determines how effectively the intelligence can express itself.
Build great harnesses. The agent will do the rest.

View File

@@ -0,0 +1,149 @@
#!/usr/bin/env python3
"""
Minimal Agent Template - Copy and customize this.
This is the simplest possible working agent (~80 lines).
It has everything you need: 3 tools + loop.
Usage:
1. Set ANTHROPIC_API_KEY environment variable
2. python minimal-agent.py
3. Type commands, 'q' to quit
"""
from anthropic import Anthropic
from pathlib import Path
import subprocess
import os
# Configuration
client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
MODEL = os.getenv("MODEL_NAME", "claude-sonnet-4-20250514")
WORKDIR = Path.cwd()
# System prompt - keep it simple
SYSTEM = f"""You are a coding agent at {WORKDIR}.
Rules:
- Use tools to complete tasks
- Prefer action over explanation
- Summarize what you did when done"""
# Minimal tool set - add more as needed
TOOLS = [
{
"name": "bash",
"description": "Run shell command",
"input_schema": {
"type": "object",
"properties": {"command": {"type": "string"}},
"required": ["command"]
}
},
{
"name": "read_file",
"description": "Read file contents",
"input_schema": {
"type": "object",
"properties": {"path": {"type": "string"}},
"required": ["path"]
}
},
{
"name": "write_file",
"description": "Write content to file",
"input_schema": {
"type": "object",
"properties": {
"path": {"type": "string"},
"content": {"type": "string"}
},
"required": ["path", "content"]
}
},
]
def execute_tool(name: str, args: dict) -> str:
"""Execute a tool and return result."""
if name == "bash":
try:
r = subprocess.run(
args["command"], shell=True, cwd=WORKDIR,
capture_output=True, text=True, timeout=60
)
return (r.stdout + r.stderr).strip() or "(empty)"
except subprocess.TimeoutExpired:
return "Error: Timeout"
if name == "read_file":
try:
return (WORKDIR / args["path"]).read_text()[:50000]
except Exception as e:
return f"Error: {e}"
if name == "write_file":
try:
p = WORKDIR / args["path"]
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(args["content"])
return f"Wrote {len(args['content'])} bytes to {args['path']}"
except Exception as e:
return f"Error: {e}"
return f"Unknown tool: {name}"
def agent(prompt: str, history: list = None) -> str:
"""Run the agent loop."""
if history is None:
history = []
history.append({"role": "user", "content": prompt})
while True:
response = client.messages.create(
model=MODEL,
system=SYSTEM,
messages=history,
tools=TOOLS,
max_tokens=8000,
)
# Build assistant message
history.append({"role": "assistant", "content": response.content})
# If no tool calls, return text
if response.stop_reason != "tool_use":
return "".join(b.text for b in response.content if hasattr(b, "text"))
# Execute tools
results = []
for block in response.content:
if block.type == "tool_use":
print(f"> {block.name}: {block.input}")
output = execute_tool(block.name, block.input)
print(f" {output[:100]}...")
results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": output
})
history.append({"role": "user", "content": results})
if __name__ == "__main__":
print(f"Minimal Agent - {WORKDIR}")
print("Type 'q' to quit.\n")
history = []
while True:
try:
query = input(">> ").strip()
except (EOFError, KeyboardInterrupt):
break
if query in ("q", "quit", "exit", ""):
break
print(agent(query, history))
print()

View File

@@ -0,0 +1,243 @@
"""
Subagent Pattern - How to implement Task tool for context isolation.
The key insight: spawn child agents with ISOLATED context to prevent
"context pollution" where exploration details fill up the main conversation.
"""
import time
import sys
# Assuming client, MODEL, execute_tool are defined elsewhere
# =============================================================================
# AGENT TYPE REGISTRY
# =============================================================================
AGENT_TYPES = {
# Explore: Read-only, for searching and analyzing
"explore": {
"description": "Read-only agent for exploring code, finding files, searching",
"tools": ["bash", "read_file"], # No write access!
"prompt": "You are an exploration agent. Search and analyze, but NEVER modify files. Return a concise summary of what you found.",
},
# Code: Full-powered, for implementation
"code": {
"description": "Full agent for implementing features and fixing bugs",
"tools": "*", # All tools
"prompt": "You are a coding agent. Implement the requested changes efficiently. Return a summary of what you changed.",
},
# Plan: Read-only, for design work
"plan": {
"description": "Planning agent for designing implementation strategies",
"tools": ["bash", "read_file"], # Read-only
"prompt": "You are a planning agent. Analyze the codebase and output a numbered implementation plan. Do NOT make any changes.",
},
# Add your own types here...
# "test": {
# "description": "Testing agent for running and analyzing tests",
# "tools": ["bash", "read_file"],
# "prompt": "Run tests and report results. Don't modify code.",
# },
}
def get_agent_descriptions() -> str:
"""Generate descriptions for Task tool schema."""
return "\n".join(
f"- {name}: {cfg['description']}"
for name, cfg in AGENT_TYPES.items()
)
def get_tools_for_agent(agent_type: str, base_tools: list) -> list:
"""
Filter tools based on agent type.
'*' means all base tools.
Otherwise, whitelist specific tool names.
Note: Subagents don't get Task tool to prevent infinite recursion.
"""
allowed = AGENT_TYPES.get(agent_type, {}).get("tools", "*")
if allowed == "*":
return base_tools # All base tools, but NOT Task
return [t for t in base_tools if t["name"] in allowed]
# =============================================================================
# TASK TOOL DEFINITION
# =============================================================================
TASK_TOOL = {
"name": "Task",
"description": f"""Spawn a subagent for a focused subtask.
Subagents run in ISOLATED context - they don't see parent's history.
Use this to keep the main conversation clean.
Agent types:
{get_agent_descriptions()}
Example uses:
- Task(explore): "Find all files using the auth module"
- Task(plan): "Design a migration strategy for the database"
- Task(code): "Implement the user registration form"
""",
"input_schema": {
"type": "object",
"properties": {
"description": {
"type": "string",
"description": "Short task name (3-5 words) for progress display"
},
"prompt": {
"type": "string",
"description": "Detailed instructions for the subagent"
},
"agent_type": {
"type": "string",
"enum": list(AGENT_TYPES.keys()),
"description": "Type of agent to spawn"
},
},
"required": ["description", "prompt", "agent_type"],
},
}
# =============================================================================
# SUBAGENT EXECUTION
# =============================================================================
def run_task(description: str, prompt: str, agent_type: str,
client, model: str, workdir, base_tools: list, execute_tool) -> str:
"""
Execute a subagent task with isolated context.
Key concepts:
1. ISOLATED HISTORY - subagent starts fresh, no parent context
2. FILTERED TOOLS - based on agent type permissions
3. AGENT-SPECIFIC PROMPT - specialized behavior
4. RETURNS SUMMARY ONLY - parent sees just the final result
Args:
description: Short name for progress display
prompt: Detailed instructions for subagent
agent_type: Key from AGENT_TYPES
client: Anthropic client
model: Model to use
workdir: Working directory
base_tools: List of tool definitions
execute_tool: Function to execute tools
Returns:
Final text output from subagent
"""
if agent_type not in AGENT_TYPES:
return f"Error: Unknown agent type '{agent_type}'"
config = AGENT_TYPES[agent_type]
# Agent-specific system prompt
sub_system = f"""You are a {agent_type} subagent at {workdir}.
{config["prompt"]}
Complete the task and return a clear, concise summary."""
# Filtered tools for this agent type
sub_tools = get_tools_for_agent(agent_type, base_tools)
# KEY: ISOLATED message history!
# The subagent starts fresh, doesn't see parent's conversation
sub_messages = [{"role": "user", "content": prompt}]
# Progress display
print(f" [{agent_type}] {description}")
start = time.time()
tool_count = 0
# Run the same agent loop (but silently)
while True:
response = client.messages.create(
model=model,
system=sub_system,
messages=sub_messages,
tools=sub_tools,
max_tokens=8000,
)
# Check if done
if response.stop_reason != "tool_use":
break
# Execute tools
tool_calls = [b for b in response.content if b.type == "tool_use"]
results = []
for tc in tool_calls:
tool_count += 1
output = execute_tool(tc.name, tc.input)
results.append({
"type": "tool_result",
"tool_use_id": tc.id,
"content": output
})
# Update progress (in-place on same line)
elapsed = time.time() - start
sys.stdout.write(
f"\r [{agent_type}] {description} ... {tool_count} tools, {elapsed:.1f}s"
)
sys.stdout.flush()
sub_messages.append({"role": "assistant", "content": response.content})
sub_messages.append({"role": "user", "content": results})
# Final progress update
elapsed = time.time() - start
sys.stdout.write(
f"\r [{agent_type}] {description} - done ({tool_count} tools, {elapsed:.1f}s)\n"
)
# Extract and return ONLY the final text
# This is what the parent agent sees - a clean summary
for block in response.content:
if hasattr(block, "text"):
return block.text
return "(subagent returned no text)"
# =============================================================================
# USAGE EXAMPLE
# =============================================================================
"""
# In your main agent's execute_tool function:
def execute_tool(name: str, args: dict) -> str:
if name == "Task":
return run_task(
description=args["description"],
prompt=args["prompt"],
agent_type=args["agent_type"],
client=client,
model=MODEL,
workdir=WORKDIR,
base_tools=BASE_TOOLS,
execute_tool=execute_tool # Pass self for recursion
)
# ... other tools ...
# In your TOOLS list:
TOOLS = BASE_TOOLS + [TASK_TOOL]
"""

View File

@@ -0,0 +1,271 @@
"""
Tool Templates - Copy and customize these for your agent.
Each tool needs:
1. Definition (JSON schema for the model)
2. Implementation (Python function)
"""
from pathlib import Path
import subprocess
WORKDIR = Path.cwd()
# =============================================================================
# TOOL DEFINITIONS (for TOOLS list)
# =============================================================================
BASH_TOOL = {
"name": "bash",
"description": "Run a shell command. Use for: ls, find, grep, git, npm, python, etc.",
"input_schema": {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "The shell command to execute"
}
},
"required": ["command"],
},
}
READ_FILE_TOOL = {
"name": "read_file",
"description": "Read file contents. Returns UTF-8 text.",
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Relative path to the file"
},
"limit": {
"type": "integer",
"description": "Max lines to read (default: all)"
},
},
"required": ["path"],
},
}
WRITE_FILE_TOOL = {
"name": "write_file",
"description": "Write content to a file. Creates parent directories if needed.",
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Relative path for the file"
},
"content": {
"type": "string",
"description": "Content to write"
},
},
"required": ["path", "content"],
},
}
EDIT_FILE_TOOL = {
"name": "edit_file",
"description": "Replace exact text in a file. Use for surgical edits.",
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Relative path to the file"
},
"old_text": {
"type": "string",
"description": "Exact text to find (must match precisely)"
},
"new_text": {
"type": "string",
"description": "Replacement text"
},
},
"required": ["path", "old_text", "new_text"],
},
}
TODO_WRITE_TOOL = {
"name": "TodoWrite",
"description": "Update the task list. Use to plan and track progress.",
"input_schema": {
"type": "object",
"properties": {
"items": {
"type": "array",
"description": "Complete list of tasks",
"items": {
"type": "object",
"properties": {
"content": {"type": "string", "description": "Task description"},
"status": {"type": "string", "enum": ["pending", "in_progress", "completed"]},
"activeForm": {"type": "string", "description": "Present tense, e.g. 'Reading files'"},
},
"required": ["content", "status", "activeForm"],
},
}
},
"required": ["items"],
},
}
TASK_TOOL_TEMPLATE = """
# Generate dynamically with agent types
TASK_TOOL = {
"name": "Task",
"description": f"Spawn a subagent for a focused subtask.\\n\\nAgent types:\\n{get_agent_descriptions()}",
"input_schema": {
"type": "object",
"properties": {
"description": {"type": "string", "description": "Short task name (3-5 words)"},
"prompt": {"type": "string", "description": "Detailed instructions"},
"agent_type": {"type": "string", "enum": list(AGENT_TYPES.keys())},
},
"required": ["description", "prompt", "agent_type"],
},
}
"""
# =============================================================================
# TOOL IMPLEMENTATIONS
# =============================================================================
def safe_path(p: str) -> Path:
"""
Security: Ensure path stays within workspace.
Prevents ../../../etc/passwd attacks.
"""
path = (WORKDIR / p).resolve()
if not path.is_relative_to(WORKDIR):
raise ValueError(f"Path escapes workspace: {p}")
return path
def run_bash(command: str) -> str:
"""
Execute shell command with safety checks.
Safety features:
- Blocks obviously dangerous commands
- 60 second timeout
- Output truncated to 50KB
"""
dangerous = ["rm -rf /", "sudo", "shutdown", "reboot", "> /dev/"]
if any(d in command for d in dangerous):
return "Error: Dangerous command blocked"
try:
result = subprocess.run(
command,
shell=True,
cwd=WORKDIR,
capture_output=True,
text=True,
timeout=60
)
output = (result.stdout + result.stderr).strip()
return output[:50000] if output else "(no output)"
except subprocess.TimeoutExpired:
return "Error: Command timed out (60s)"
except Exception as e:
return f"Error: {e}"
def run_read_file(path: str, limit: int = None) -> str:
"""
Read file contents with optional line limit.
Features:
- Safe path resolution
- Optional line limit for large files
- Output truncated to 50KB
"""
try:
text = safe_path(path).read_text()
lines = text.splitlines()
if limit and limit < len(lines):
lines = lines[:limit]
lines.append(f"... ({len(text.splitlines()) - limit} more lines)")
return "\n".join(lines)[:50000]
except Exception as e:
return f"Error: {e}"
def run_write_file(path: str, content: str) -> str:
"""
Write content to file, creating parent directories if needed.
Features:
- Safe path resolution
- Auto-creates parent directories
- Returns byte count for confirmation
"""
try:
fp = safe_path(path)
fp.parent.mkdir(parents=True, exist_ok=True)
fp.write_text(content)
return f"Wrote {len(content)} bytes to {path}"
except Exception as e:
return f"Error: {e}"
def run_edit_file(path: str, old_text: str, new_text: str) -> str:
"""
Replace exact text in a file (surgical edit).
Features:
- Exact string matching (not regex)
- Only replaces first occurrence (safety)
- Clear error if text not found
"""
try:
fp = safe_path(path)
content = fp.read_text()
if old_text not in content:
return f"Error: Text not found in {path}"
new_content = content.replace(old_text, new_text, 1)
fp.write_text(new_content)
return f"Edited {path}"
except Exception as e:
return f"Error: {e}"
# =============================================================================
# DISPATCHER PATTERN
# =============================================================================
def execute_tool(name: str, args: dict) -> str:
"""
Dispatch tool call to implementation.
This pattern makes it easy to add new tools:
1. Add definition to TOOLS list
2. Add implementation function
3. Add case to this dispatcher
"""
if name == "bash":
return run_bash(args["command"])
if name == "read_file":
return run_read_file(args["path"], args.get("limit"))
if name == "write_file":
return run_write_file(args["path"], args["content"])
if name == "edit_file":
return run_edit_file(args["path"], args["old_text"], args["new_text"])
# Add more tools here...
return f"Unknown tool: {name}"

View File

@@ -0,0 +1,279 @@
#!/usr/bin/env python3
"""
Agent Scaffold Script - Create a new agent project with best practices.
Usage:
python init_agent.py <agent-name> [--level 0-4] [--path <output-dir>]
Examples:
python init_agent.py my-agent # Level 1 (4 tools)
python init_agent.py my-agent --level 0 # Minimal (bash only)
python init_agent.py my-agent --level 2 # With TodoWrite
python init_agent.py my-agent --path ./bots # Custom output directory
"""
import argparse
import sys
from pathlib import Path
# Agent templates for each level
TEMPLATES = {
0: '''#!/usr/bin/env python3
"""
Level 0 Agent - Bash is All You Need (~50 lines)
Core insight: One tool (bash) can do everything.
Subagents via self-recursion: python {name}.py "subtask"
"""
from anthropic import Anthropic
from dotenv import load_dotenv
import subprocess
import os
load_dotenv()
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")
SYSTEM = """You are a coding agent. Use bash for everything:
- Read: cat, grep, find, ls
- Write: echo 'content' > file
- Subagent: python {name}.py "subtask"
"""
TOOL = [{{
"name": "bash",
"description": "Execute shell command",
"input_schema": {{"type": "object", "properties": {{"command": {{"type": "string"}}}}, "required": ["command"]}}
}}]
def run(prompt, history=[]):
history.append({{"role": "user", "content": prompt}})
while True:
r = client.messages.create(model=MODEL, system=SYSTEM, messages=history, tools=TOOL, max_tokens=8000)
history.append({{"role": "assistant", "content": r.content}})
if r.stop_reason != "tool_use":
return "".join(b.text for b in r.content if hasattr(b, "text"))
results = []
for b in r.content:
if b.type == "tool_use":
print(f"> {{b.input['command']}}")
try:
out = subprocess.run(b.input["command"], shell=True, capture_output=True, text=True, timeout=60)
output = (out.stdout + out.stderr).strip() or "(empty)"
except Exception as e:
output = f"Error: {{e}}"
results.append({{"type": "tool_result", "tool_use_id": b.id, "content": output[:50000]}})
history.append({{"role": "user", "content": results}})
if __name__ == "__main__":
h = []
print("{name} - Level 0 Agent\\nType 'q' to quit.\\n")
while (q := input(">> ").strip()) not in ("q", "quit", ""):
print(run(q, h), "\\n")
''',
1: '''#!/usr/bin/env python3
"""
Level 1 Agent - Model as Agent (~200 lines)
Core insight: 4 tools cover 90% of coding tasks.
The model IS the agent. Code just runs the loop.
"""
from anthropic import Anthropic
from dotenv import load_dotenv
from pathlib import Path
import subprocess
import os
load_dotenv()
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")
WORKDIR = Path.cwd()
SYSTEM = f"""You are a coding agent at {{WORKDIR}}.
Rules:
- Prefer tools over prose. Act, don't just explain.
- Never invent file paths. Use ls/find first if unsure.
- Make minimal changes. Don't over-engineer.
- After finishing, summarize what changed."""
TOOLS = [
{{"name": "bash", "description": "Run shell command",
"input_schema": {{"type": "object", "properties": {{"command": {{"type": "string"}}}}, "required": ["command"]}}}},
{{"name": "read_file", "description": "Read file contents",
"input_schema": {{"type": "object", "properties": {{"path": {{"type": "string"}}}}, "required": ["path"]}}}},
{{"name": "write_file", "description": "Write content to file",
"input_schema": {{"type": "object", "properties": {{"path": {{"type": "string"}}, "content": {{"type": "string"}}}}, "required": ["path", "content"]}}}},
{{"name": "edit_file", "description": "Replace exact text in file",
"input_schema": {{"type": "object", "properties": {{"path": {{"type": "string"}}, "old_text": {{"type": "string"}}, "new_text": {{"type": "string"}}}}, "required": ["path", "old_text", "new_text"]}}}},
]
def safe_path(p: str) -> Path:
"""Prevent path escape attacks."""
path = (WORKDIR / p).resolve()
if not path.is_relative_to(WORKDIR):
raise ValueError(f"Path escapes workspace: {{p}}")
return path
def execute(name: str, args: dict) -> str:
"""Execute a tool and return result."""
if name == "bash":
dangerous = ["rm -rf /", "sudo", "shutdown", "> /dev/"]
if any(d in args["command"] for d in dangerous):
return "Error: Dangerous command blocked"
try:
r = subprocess.run(args["command"], shell=True, cwd=WORKDIR, capture_output=True, text=True, timeout=60)
return (r.stdout + r.stderr).strip()[:50000] or "(empty)"
except subprocess.TimeoutExpired:
return "Error: Timeout (60s)"
except Exception as e:
return f"Error: {{e}}"
if name == "read_file":
try:
return safe_path(args["path"]).read_text()[:50000]
except Exception as e:
return f"Error: {{e}}"
if name == "write_file":
try:
p = safe_path(args["path"])
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(args["content"])
return f"Wrote {{len(args['content'])}} bytes to {{args['path']}}"
except Exception as e:
return f"Error: {{e}}"
if name == "edit_file":
try:
p = safe_path(args["path"])
content = p.read_text()
if args["old_text"] not in content:
return f"Error: Text not found in {{args['path']}}"
p.write_text(content.replace(args["old_text"], args["new_text"], 1))
return f"Edited {{args['path']}}"
except Exception as e:
return f"Error: {{e}}"
return f"Unknown tool: {{name}}"
def agent(prompt: str, history: list = None) -> str:
"""Run the agent loop."""
if history is None:
history = []
history.append({{"role": "user", "content": prompt}})
while True:
response = client.messages.create(
model=MODEL, system=SYSTEM, messages=history, tools=TOOLS, max_tokens=8000
)
history.append({{"role": "assistant", "content": response.content}})
if response.stop_reason != "tool_use":
return "".join(b.text for b in response.content if hasattr(b, "text"))
results = []
for block in response.content:
if block.type == "tool_use":
print(f"> {{block.name}}: {{str(block.input)[:100]}}")
output = execute(block.name, block.input)
print(f" {{output[:100]}}...")
results.append({{"type": "tool_result", "tool_use_id": block.id, "content": output}})
history.append({{"role": "user", "content": results}})
if __name__ == "__main__":
print(f"{name} - Level 1 Agent at {{WORKDIR}}")
print("Type 'q' to quit.\\n")
h = []
while True:
try:
query = input(">> ").strip()
except (EOFError, KeyboardInterrupt):
break
if query in ("q", "quit", "exit", ""):
break
print(agent(query, h), "\\n")
''',
}
ENV_TEMPLATE = '''# API Configuration
ANTHROPIC_API_KEY=sk-xxx
ANTHROPIC_BASE_URL=https://api.anthropic.com
MODEL_NAME=claude-sonnet-4-20250514
'''
def create_agent(name: str, level: int, output_dir: Path):
"""Create a new agent project."""
# Validate level
if level not in TEMPLATES and level not in (2, 3, 4):
print(f"Error: Level {level} not yet implemented in scaffold.")
print("Available levels: 0 (minimal), 1 (4 tools)")
print("For levels 2-4, copy from mini-claude-code repository.")
sys.exit(1)
# Create output directory
agent_dir = output_dir / name
agent_dir.mkdir(parents=True, exist_ok=True)
# Write agent file
agent_file = agent_dir / f"{name}.py"
template = TEMPLATES.get(level, TEMPLATES[1])
agent_file.write_text(template.format(name=name))
print(f"Created: {agent_file}")
# Write .env.example
env_file = agent_dir / ".env.example"
env_file.write_text(ENV_TEMPLATE)
print(f"Created: {env_file}")
# Write .gitignore
gitignore = agent_dir / ".gitignore"
gitignore.write_text(".env\n__pycache__/\n*.pyc\n")
print(f"Created: {gitignore}")
print(f"\nAgent '{name}' created at {agent_dir}")
print(f"\nNext steps:")
print(f" 1. cd {agent_dir}")
print(f" 2. cp .env.example .env")
print(f" 3. Edit .env with your API key")
print(f" 4. pip install anthropic python-dotenv")
print(f" 5. python {name}.py")
def main():
parser = argparse.ArgumentParser(
description="Scaffold a new AI coding agent project",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Levels:
0 Minimal (~50 lines) - Single bash tool, self-recursion for subagents
1 Basic (~200 lines) - 4 core tools: bash, read, write, edit
2 Todo (~300 lines) - + TodoWrite for structured planning
3 Subagent (~450) - + Task tool for context isolation
4 Skills (~550) - + Skill tool for domain expertise
"""
)
parser.add_argument("name", help="Name of the agent to create")
parser.add_argument("--level", type=int, default=1, choices=[0, 1, 2, 3, 4],
help="Complexity level (default: 1)")
parser.add_argument("--path", type=Path, default=Path.cwd(),
help="Output directory (default: current directory)")
args = parser.parse_args()
create_agent(args.name, args.level, args.path)
if __name__ == "__main__":
main()