feat: build an AI agent from 0 to 1 -- 11 progressive sessions

- 11 sessions from basic agent loop to autonomous teams
- Python MVP implementations for each session
- Mental-model-first docs in en/zh/ja
- Interactive web platform with step-through visualizations
- Incremental architecture: each session adds one mechanism
This commit is contained in:
CrazyBoyM
2026-02-21 17:02:43 +08:00
committed by CrazyBoyM
commit c6a27ef1d7
156 changed files with 28059 additions and 0 deletions
+129
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.**
@@ -0,0 +1,144 @@
# The Philosophy of Agents
> **The model already knows how to be an agent. Your job is to get out of the way.**
## The Fundamental Insight
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.
## Why This Matters
Most agent implementations fail not from too little engineering, but from too much. They constrain. They prescribe. They second-guess the very intelligence they're trying to leverage.
Consider: The model has been trained on millions of examples of problem-solving. It has seen how experts approach complex tasks, how tools are used, how plans are formed and revised. This knowledge is already there, encoded in billions of parameters.
Your job is not to teach it how to think. Your job is to give it the means to act.
## The Three Elements
### 1. Capabilities (Tools)
Capabilities answer: **What can the agent DO?**
They are the hands of the model - its ability to affect the world. Without capabilities, the model can only speak. With them, it can act.
**The design principle**: Each capability should be atomic, clear, and well-described. The model needs to understand what each capability does, but not how to use them in sequence - it will figure that out.
**Common mistake**: Too many capabilities. The model gets confused, starts using the wrong ones, or paralyzed by choice. Start with 3-5. Add more only when the model consistently fails to accomplish tasks because a capability is missing.
### 2. Knowledge (Skills)
Knowledge answers: **What does the agent KNOW?**
This is domain expertise - the specialized understanding that turns a general assistant into a domain expert. A customer service agent needs to know company policies. A research agent needs to know methodology. A creative agent needs to know style guidelines.
**The design principle**: Inject knowledge on-demand, not upfront. The model doesn't need to know everything at once - only what's relevant to the current task. Progressive disclosure preserves context for what matters.
**Common mistake**: Front-loading all possible knowledge into the system prompt. This wastes context, confuses the model, and makes every interaction expensive. Instead, make knowledge available but not mandatory.
### 3. Context (The Conversation)
Context is the memory of the interaction - what has been said, what has been tried, what has been learned. It's the thread that connects individual actions into coherent behavior.
**The design principle**: Context is precious. Protect it. Isolate subtasks that generate noise. Truncate outputs that exceed usefulness. Summarize when history grows long.
**Common mistake**: Letting context grow unbounded, filling it with exploration details, failed attempts, and verbose tool outputs. Eventually the model can't find the signal in the noise.
## The Universal Pattern
Every effective agent - regardless of domain, framework, or implementation - follows the same pattern:
```
LOOP:
Model sees: conversation history + available capabilities
Model decides: act or respond
If act: capability 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 optimization.
## Designing for Agency
### Trust the Model
The most important principle: **trust the model**.
Don't try to 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 capabilities 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 response with "under 100 words" demands clarity.
The best constraints are those that prevent the model from getting lost, not those that micromanage its approach.
### Progressive Complexity
Never build everything upfront.
```
Level 0: Model + one capability
Level 1: Model + 3-5 capabilities
Level 2: Model + capabilities + planning
Level 3: Model + capabilities + planning + subagents
Level 4: Model + capabilities + planning + subagents + skills
```
Start at the lowest level that might work. Move up only when real usage reveals the need. Most agents never need to go beyond Level 2.
## The Agent Mindset
Building agents requires a 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 capabilities would help address Y?"
**From**: "What's the workflow for this task?"
**To**: "What does the model need to figure out the workflow?"
The best agent code is almost boring. Simple loops. Clear capability definitions. Clean context management. The magic isn't in the code - it's in the model.
## Philosophical Foundations
### The Model as Emergent Agent
Language models trained on human text have learned not just language, but patterns of thought. They've absorbed how humans approach problems, use tools, and accomplish goals. This is emergent agency - not programmed, but learned.
When you give a model capabilities, you're not teaching it to be an agent. You're giving it permission to express the agency it already has.
### The Loop as Liberation
The agent loop is deceptively simple: get response, check for tool use, execute, repeat. But this simplicity is its power.
The loop doesn't constrain the model to particular sequences. It doesn't enforce specific workflows. It simply says: "You have capabilities. Use them as you see fit. I'll execute what you request and show you the results."
This is liberation, not limitation.
### Capabilities as Expression
Each capability you provide is a form of expression for the model. "Read file" lets it see. "Write file" lets it create. "Search" lets it explore. "Send message" lets it communicate.
The art of agent design is choosing which forms of expression to enable. Too few, and the model is mute. Too many, and it speaks in tongues.
## Conclusion
The agent is the model. The code is just the loop. Your job is to get out of the way.
Give the model clear capabilities. Make knowledge available when needed. Protect the context from noise. Trust the model to figure out the rest.
That's it. That's the philosophy.
Everything else is refinement.
@@ -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()
@@ -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]
"""
@@ -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}"
+279
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()
+157
View File
@@ -0,0 +1,157 @@
---
name: code-review
description: Perform thorough code reviews with security, performance, and maintainability analysis. Use when user asks to review code, check for bugs, or audit a codebase.
---
# Code Review Skill
You now have expertise in conducting comprehensive code reviews. Follow this structured approach:
## Review Checklist
### 1. Security (Critical)
Check for:
- [ ] **Injection vulnerabilities**: SQL, command, XSS, template injection
- [ ] **Authentication issues**: Hardcoded credentials, weak auth
- [ ] **Authorization flaws**: Missing access controls, IDOR
- [ ] **Data exposure**: Sensitive data in logs, error messages
- [ ] **Cryptography**: Weak algorithms, improper key management
- [ ] **Dependencies**: Known vulnerabilities (check with `npm audit`, `pip-audit`)
```bash
# Quick security scans
npm audit # Node.js
pip-audit # Python
cargo audit # Rust
grep -r "password\|secret\|api_key" --include="*.py" --include="*.js"
```
### 2. Correctness
Check for:
- [ ] **Logic errors**: Off-by-one, null handling, edge cases
- [ ] **Race conditions**: Concurrent access without synchronization
- [ ] **Resource leaks**: Unclosed files, connections, memory
- [ ] **Error handling**: Swallowed exceptions, missing error paths
- [ ] **Type safety**: Implicit conversions, any types
### 3. Performance
Check for:
- [ ] **N+1 queries**: Database calls in loops
- [ ] **Memory issues**: Large allocations, retained references
- [ ] **Blocking operations**: Sync I/O in async code
- [ ] **Inefficient algorithms**: O(n^2) when O(n) possible
- [ ] **Missing caching**: Repeated expensive computations
### 4. Maintainability
Check for:
- [ ] **Naming**: Clear, consistent, descriptive
- [ ] **Complexity**: Functions > 50 lines, deep nesting > 3 levels
- [ ] **Duplication**: Copy-pasted code blocks
- [ ] **Dead code**: Unused imports, unreachable branches
- [ ] **Comments**: Outdated, redundant, or missing where needed
### 5. Testing
Check for:
- [ ] **Coverage**: Critical paths tested
- [ ] **Edge cases**: Null, empty, boundary values
- [ ] **Mocking**: External dependencies isolated
- [ ] **Assertions**: Meaningful, specific checks
## Review Output Format
```markdown
## Code Review: [file/component name]
### Summary
[1-2 sentence overview]
### Critical Issues
1. **[Issue]** (line X): [Description]
- Impact: [What could go wrong]
- Fix: [Suggested solution]
### Improvements
1. **[Suggestion]** (line X): [Description]
### Positive Notes
- [What was done well]
### Verdict
[ ] Ready to merge
[ ] Needs minor changes
[ ] Needs major revision
```
## Common Patterns to Flag
### Python
```python
# Bad: SQL injection
cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")
# Good:
cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))
# Bad: Command injection
os.system(f"ls {user_input}")
# Good:
subprocess.run(["ls", user_input], check=True)
# Bad: Mutable default argument
def append(item, lst=[]): # Bug: shared mutable default
# Good:
def append(item, lst=None):
lst = lst or []
```
### JavaScript/TypeScript
```javascript
// Bad: Prototype pollution
Object.assign(target, userInput)
// Good:
Object.assign(target, sanitize(userInput))
// Bad: eval usage
eval(userCode)
// Good: Never use eval with user input
// Bad: Callback hell
getData(x => process(x, y => save(y, z => done(z))))
// Good:
const data = await getData();
const processed = await process(data);
await save(processed);
```
## Review Commands
```bash
# Show recent changes
git diff HEAD~5 --stat
git log --oneline -10
# Find potential issues
grep -rn "TODO\|FIXME\|HACK\|XXX" .
grep -rn "password\|secret\|token" . --include="*.py"
# Check complexity (Python)
pip install radon && radon cc . -a
# Check dependencies
npm outdated # Node
pip list --outdated # Python
```
## Review Workflow
1. **Understand context**: Read PR description, linked issues
2. **Run the code**: Build, test, run locally if possible
3. **Read top-down**: Start with main entry points
4. **Check tests**: Are changes tested? Do tests pass?
5. **Security scan**: Run automated tools
6. **Manual review**: Use checklist above
7. **Write feedback**: Be specific, suggest fixes, be kind
+213
View File
@@ -0,0 +1,213 @@
---
name: mcp-builder
description: Build MCP (Model Context Protocol) servers that give Claude new capabilities. Use when user wants to create an MCP server, add tools to Claude, or integrate external services.
---
# MCP Server Building Skill
You now have expertise in building MCP (Model Context Protocol) servers. MCP enables Claude to interact with external services through a standardized protocol.
## What is MCP?
MCP servers expose:
- **Tools**: Functions Claude can call (like API endpoints)
- **Resources**: Data Claude can read (like files or database records)
- **Prompts**: Pre-built prompt templates
## Quick Start: Python MCP Server
### 1. Project Setup
```bash
# Create project
mkdir my-mcp-server && cd my-mcp-server
python3 -m venv venv && source venv/bin/activate
# Install MCP SDK
pip install mcp
```
### 2. Basic Server Template
```python
#!/usr/bin/env python3
"""my_server.py - A simple MCP server"""
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
# Create server instance
server = Server("my-server")
# Define a tool
@server.tool()
async def hello(name: str) -> str:
"""Say hello to someone.
Args:
name: The name to greet
"""
return f"Hello, {name}!"
@server.tool()
async def add_numbers(a: int, b: int) -> str:
"""Add two numbers together.
Args:
a: First number
b: Second number
"""
return str(a + b)
# Run server
async def main():
async with stdio_server() as (read, write):
await server.run(read, write)
if __name__ == "__main__":
import asyncio
asyncio.run(main())
```
### 3. Register with Claude
Add to `~/.claude/mcp.json`:
```json
{
"mcpServers": {
"my-server": {
"command": "python3",
"args": ["/path/to/my_server.py"]
}
}
}
```
## TypeScript MCP Server
### 1. Setup
```bash
mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk
```
### 2. Template
```typescript
// src/index.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new Server({
name: "my-server",
version: "1.0.0",
});
// Define tools
server.setRequestHandler("tools/list", async () => ({
tools: [
{
name: "hello",
description: "Say hello to someone",
inputSchema: {
type: "object",
properties: {
name: { type: "string", description: "Name to greet" },
},
required: ["name"],
},
},
],
}));
server.setRequestHandler("tools/call", async (request) => {
if (request.params.name === "hello") {
const name = request.params.arguments.name;
return { content: [{ type: "text", text: `Hello, ${name}!` }] };
}
throw new Error("Unknown tool");
});
// Start server
const transport = new StdioServerTransport();
server.connect(transport);
```
## Advanced Patterns
### External API Integration
```python
import httpx
from mcp.server import Server
server = Server("weather-server")
@server.tool()
async def get_weather(city: str) -> str:
"""Get current weather for a city."""
async with httpx.AsyncClient() as client:
resp = await client.get(
f"https://api.weatherapi.com/v1/current.json",
params={"key": "YOUR_API_KEY", "q": city}
)
data = resp.json()
return f"{city}: {data['current']['temp_c']}C, {data['current']['condition']['text']}"
```
### Database Access
```python
import sqlite3
from mcp.server import Server
server = Server("db-server")
@server.tool()
async def query_db(sql: str) -> str:
"""Execute a read-only SQL query."""
if not sql.strip().upper().startswith("SELECT"):
return "Error: Only SELECT queries allowed"
conn = sqlite3.connect("data.db")
cursor = conn.execute(sql)
rows = cursor.fetchall()
conn.close()
return str(rows)
```
### Resources (Read-only Data)
```python
@server.resource("config://settings")
async def get_settings() -> str:
"""Application settings."""
return open("settings.json").read()
@server.resource("file://{path}")
async def read_file(path: str) -> str:
"""Read a file from the workspace."""
return open(path).read()
```
## Testing
```bash
# Test with MCP Inspector
npx @anthropics/mcp-inspector python3 my_server.py
# Or send test messages directly
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | python3 my_server.py
```
## Best Practices
1. **Clear tool descriptions**: Claude uses these to decide when to call tools
2. **Input validation**: Always validate and sanitize inputs
3. **Error handling**: Return meaningful error messages
4. **Async by default**: Use async/await for I/O operations
5. **Security**: Never expose sensitive operations without auth
6. **Idempotency**: Tools should be safe to retry
+112
View File
@@ -0,0 +1,112 @@
---
name: pdf
description: Process PDF files - extract text, create PDFs, merge documents. Use when user asks to read PDF, create PDF, or work with PDF files.
---
# PDF Processing Skill
You now have expertise in PDF manipulation. Follow these workflows:
## Reading PDFs
**Option 1: Quick text extraction (preferred)**
```bash
# Using pdftotext (poppler-utils)
pdftotext input.pdf - # Output to stdout
pdftotext input.pdf output.txt # Output to file
# If pdftotext not available, try:
python3 -c "
import fitz # PyMuPDF
doc = fitz.open('input.pdf')
for page in doc:
print(page.get_text())
"
```
**Option 2: Page-by-page with metadata**
```python
import fitz # pip install pymupdf
doc = fitz.open("input.pdf")
print(f"Pages: {len(doc)}")
print(f"Metadata: {doc.metadata}")
for i, page in enumerate(doc):
text = page.get_text()
print(f"--- Page {i+1} ---")
print(text)
```
## Creating PDFs
**Option 1: From Markdown (recommended)**
```bash
# Using pandoc
pandoc input.md -o output.pdf
# With custom styling
pandoc input.md -o output.pdf --pdf-engine=xelatex -V geometry:margin=1in
```
**Option 2: Programmatically**
```python
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
c = canvas.Canvas("output.pdf", pagesize=letter)
c.drawString(100, 750, "Hello, PDF!")
c.save()
```
**Option 3: From HTML**
```bash
# Using wkhtmltopdf
wkhtmltopdf input.html output.pdf
# Or with Python
python3 -c "
import pdfkit
pdfkit.from_file('input.html', 'output.pdf')
"
```
## Merging PDFs
```python
import fitz
result = fitz.open()
for pdf_path in ["file1.pdf", "file2.pdf", "file3.pdf"]:
doc = fitz.open(pdf_path)
result.insert_pdf(doc)
result.save("merged.pdf")
```
## Splitting PDFs
```python
import fitz
doc = fitz.open("input.pdf")
for i in range(len(doc)):
single = fitz.open()
single.insert_pdf(doc, from_page=i, to_page=i)
single.save(f"page_{i+1}.pdf")
```
## Key Libraries
| Task | Library | Install |
|------|---------|---------|
| Read/Write/Merge | PyMuPDF | `pip install pymupdf` |
| Create from scratch | ReportLab | `pip install reportlab` |
| HTML to PDF | pdfkit | `pip install pdfkit` + wkhtmltopdf |
| Text extraction | pdftotext | `brew install poppler` / `apt install poppler-utils` |
## Best Practices
1. **Always check if tools are installed** before using them
2. **Handle encoding issues** - PDFs may contain various character encodings
3. **Large PDFs**: Process page by page to avoid memory issues
4. **OCR for scanned PDFs**: Use `pytesseract` if text extraction returns empty