# Multi-Agent System for Claude Code A lean, pragmatic multi-agent system for software and data engineering tasks. Designed for small teams (1-2 people) building data products. --- ## Philosophy **Simple, Direct, Procedural Code** - Functions over classes - Data-oriented design - Explicit over clever - Solve actual problems, not general cases Inspired by Casey Muratori and Jonathan Blow's approach to software development. --- ## System Structure ``` agent_system/ ├── README.md # This file ├── coding_philosophy.md # Core principles (reference for all agents) ├── orchestrator.md # Lead Engineer Agent ├── code_analysis_agent.md # Code exploration agent ├── implementation_agent.md # Code writing agent └── testing_agent.md # Testing agent ``` --- ## Agents ### 1. Orchestrator (Lead Engineer Agent) **File:** `orchestrator.md` **Role:** Coordinates all work, decides when to use workers **Responsibilities:** - Analyze task complexity - Decide: handle directly or spawn workers - Create worker specifications - Synthesize worker outputs - Make architectural decisions **Use:** This is your main agent for Claude Code ### 2. Code Analysis Agent **File:** `code_analysis_agent.md` **Role:** Explore and understand code (read-only) **Responsibilities:** - Map code structure - Trace data flow - Identify patterns and issues - Answer specific questions about codebase **Use:** When you need to understand existing code before making changes ### 3. Implementation Agent **File:** `implementation_agent.md` **Role:** Write simple, direct code **Responsibilities:** - Implement features - Modify existing code - Write SQLMesh models - Create Robyn routes - Build evidence.dev dashboards **Use:** For building and modifying code ### 4. Testing Agent **File:** `testing_agent.md` **Role:** Verify code works correctly **Responsibilities:** - Write pytest tests - Create SQL test queries - Test data transformations - Validate edge cases **Use:** For creating test suites --- ## How It Works ### Decision Tree ``` Task received ↓ Can I do this directly in <30 tool calls? ↓ YES → Handle directly (90% of tasks) NO → ↓ ↓ Is this truly parallelizable? ↓ YES → Spawn 2-3 workers (10% of tasks) NO → Handle directly anyway ``` **Golden Rule:** Most tasks should be handled directly by the orchestrator. Only use multiple agents when parallelization provides clear benefit. ### Example: Simple Task (Direct) ``` User: "Add an API endpoint to get user activity" Orchestrator: This is straightforward, <20 tool calls ↓ Handles directly: - Creates route in src/routes/activity.py - Queries data lake - Returns JSON - Tests manually - Done ``` **No workers needed.** Fast and simple. ### Example: Complex Task (Multi-Agent) ``` User: "Migrate ETL pipeline to SQLMesh" Orchestrator: This is complex, will benefit from parallel work ↓ Phase 1 - Analysis: Spawns Code Analysis Agent - Maps existing pipeline - Identifies transformations - Documents dependencies → Writes to .agent_work/analysis/ ↓ Phase 2 - Implementation: Spawns 2 Implementation Agents in parallel - Agent A: Extract models - Agent B: Transform models → Both write to .agent_work/implementation/ ↓ Phase 3 - Testing: Spawns Testing Agent - Validates output correctness → Writes to .agent_work/testing/ ↓ Orchestrator synthesizes: - Reviews all outputs - Resolves conflicts - Creates migration plan - Done ``` **Parallelization saves time** on truly complex work. --- ## Tech Stack ### Data Engineering - **SQLMesh** - Data transformation framework (SQL models) - **DuckDB** - Analytics database (OLAP queries) - **Iceberg** - Data lake table format (on R2 storage) - **ELT** - Extract → Load → Transform (in warehouse) ### SaaS Application - **Robyn** - Python web framework - Hosts landing page, auth, payment - Serves evidence.dev build at `/dashboard/` - **evidence.dev** - BI dashboards (SQL + Markdown → static site) ### Architecture ``` User → Robyn ├── / (landing, auth, payment) ├── /api/* (API endpoints) └── /dashboard/* (evidence.dev build) ``` --- ## Working Directory All agent work goes into `.agent_work/` with feature-specific subdirectories: ``` project_root/ ├── README.md # Architecture, setup, tech stack ├── CLAUDE.md # Memory: decisions, patterns, conventions ├── .agent_work/ # Agent work (add to .gitignore) │ ├── feature-user-dashboard/ # Feature-specific directory │ │ ├── project_state.md # Track this feature's progress │ │ ├── analysis/ │ │ │ └── findings.md │ │ ├── implementation/ │ │ │ ├── feature.py │ │ │ └── notes.md │ │ └── testing/ │ │ ├── test_feature.py │ │ └── results.md │ └── feature-payment-integration/ # Another feature │ ├── project_state.md │ ├── analysis/ │ ├── implementation/ │ └── testing/ ├── models/ # SQLMesh models ├── src/ # Application code └── tests/ # Final test suite ``` **Workflow:** 1. New feature → Create branch: `git checkout -b feature-name` 2. Create `.agent_work/feature-name/` directory 3. Track progress in `.agent_work/feature-name/project_state.md` 4. Update global context in `README.md` and `CLAUDE.md` as needed **Global vs Feature Context:** - **README.md**: Current architecture, tech stack, how to run - **CLAUDE.md**: Memory file - decisions, patterns, conventions to follow - **project_state.md**: Feature-specific progress (in `.agent_work/feature-name/`) **Why `.agent_work/` instead of `/tmp/`:** - Persists across sessions - Easy to review agent work - Can reference with normal paths - Keep or discard as needed - Feature-scoped organization **Add to `.gitignore`:** ``` .agent_work/ ``` --- ## Usage in Claude Code ### Setting Up 1. Copy agent system files to your project: ``` mkdir -p .claude/agents/ cp agent_system/* .claude/agents/ ``` 2. Add to `.gitignore`: ``` .agent_work/ ``` 3. Create `.agent_work/` directory: ``` mkdir -p .agent_work/{analysis,implementation,testing} ``` ### Using the Orchestrator In Claude Code, load the orchestrator: ``` @orchestrator.md [Your request here] ``` The orchestrator will: 1. Analyze the task 2. Decide if workers are needed 3. Spawn workers if beneficial 4. Handle directly if simple 5. Synthesize results 6. Deliver solution ### When Workers Are Spawned The orchestrator automatically reads the appropriate agent file when spawning: ``` Orchestrator reads: code_analysis_agent.md ↓ Creates specific task spec ↓ Spawns Code Analysis Agent with: - Agent instructions (from file) - Task specification - Output location ↓ Worker executes independently ↓ Writes to .agent_work/analysis/ ``` --- ## Coding Philosophy All agents follow these principles (from `coding_philosophy.md`): ### Core Principles 1. **Functions over classes** - Use functions unless you truly need classes 2. **Data is data** - Simple structures (dicts, lists), not objects hiding behavior 3. **Explicit over implicit** - No magic, no hiding 4. **Simple control flow** - Straightforward if/else, early returns 5. **Build minimum that works** - Solve actual problem, not general case ### What to Avoid ❌ Classes wrapping single functions ❌ Inheritance hierarchies ❌ Framework magic ❌ Over-abstraction "for future flexibility" ❌ Configuration as code pyramids ### What to Do ✅ Write simple, direct functions ✅ Make data transformations obvious ✅ Handle errors explicitly ✅ Keep business logic in SQL when possible ✅ Think about performance --- ## Examples ### Example 1: Build Dashboard **Request:** "Create dashboard showing user activity trends" **Orchestrator Decision:** Moderate complexity, 2 independent tasks **Execution:** 1. Setup: - Create branch: `git checkout -b feature-user-dashboard` - Create `.agent_work/feature-user-dashboard/` - Read `README.md` and `CLAUDE.md` for context 2. Spawns Implementation Agent A - Creates SQLMesh model (user_activity_daily.sql) - Writes to `.agent_work/feature-user-dashboard/implementation-data/` 3. Spawns Implementation Agent B (parallel) - Creates evidence.dev dashboard - Writes to `.agent_work/feature-user-dashboard/implementation-viz/` 4. Orchestrator synthesizes - Reviews both outputs - Tests evidence build - Deploys together - Updates `.agent_work/feature-user-dashboard/project_state.md` **Result:** Working dashboard with data model ### Example 2: Fix Bug **Request:** "This query is timing out, fix it" **Orchestrator Decision:** Simple, direct handling **Execution:** 1. Setup: - Create branch: `git checkout -b fix-query-timeout` - Create `.agent_work/fix-query-timeout/` 2. Orchestrator handles directly - Runs EXPLAIN ANALYZE - Identifies missing index - Creates index - Tests performance - Documents in `.agent_work/fix-query-timeout/project_state.md` **Result:** Query now fast, documented ### Example 3: Large Refactor **Request:** "Migrate 50 Python files from sync to async" **Orchestrator Decision:** Complex, phased approach **Execution:** 1. Phase 1: Analysis - Code Analysis Agent maps dependencies - Identifies blocking calls - Writes to `.agent_work/analysis/` 2. Phase 2: Implementation (parallel) - Implementation Agent A: Core modules (20 files) - Implementation Agent B: API routes (15 files) - Implementation Agent C: Utils (15 files) - All write to `.agent_work/implementation/` 3. Phase 3: Testing - Testing Agent validates async behavior - Writes to `.agent_work/testing/` 4. Orchestrator synthesizes - Resolves conflicts - Integration testing - Migration plan **Result:** Migrated codebase with tests --- ## Best Practices ### For Orchestrator - Default to handling directly - Spawn workers only for truly parallel work - Give workers focused, non-overlapping tasks - Use extended thinking for planning - Document decisions in `project_state.md` ### For Worker Specs **Good:** ``` AGENT: implementation OBJECTIVE: Create SQLMesh model for user_activity_daily SCOPE: Create models/user_activity_daily.sql CONSTRAINTS: DuckDB SQL, incremental by date, partition by event_date OUTPUT: .agent_work/implementation/models/ BUDGET: 20 tool calls ``` **Bad:** ``` AGENT: implementation OBJECTIVE: Help with the data stuff ``` ### For Long Tasks - Maintain `.agent_work/project_state.md` - Update after each major phase - Use compaction if approaching context limits - Load files just-in-time (not entire codebase) --- ## Context Management ### Just-in-Time Loading Don't load entire codebases: ```bash # Good: Survey, then target find models/ -name "*.sql" | head -10 rg "SELECT.*FROM" models/ cat models/specific_model.sql # Bad: Load everything cat models/*.sql ``` ### Project State Tracking For long tasks (>50 turns), maintain state: ```markdown ## Project: [Name] ## Phase: [Current] ### Completed - [x] Task 1 - Agent - Outcome ### Current - [ ] Task 2 - Agent - Status ### Decisions 1. Decision - Rationale ### Next Steps 1. Step 1 2. Step 2 ``` --- ## Troubleshooting ### "Workers are duplicating work" **Cause:** Vague task boundaries **Fix:** Be more specific, assign non-overlapping files ### "Coordination overhead too high" **Cause:** Task not parallelizable **Fix:** Handle directly, don't use workers ### "Context window exceeded" **Cause:** Loading too much data **Fix:** Use JIT loading, summarize outputs ### "Workers stepping on each other" **Cause:** Overlapping responsibilities **Fix:** Separate by file/module, clear boundaries --- ## Summary **System:** - 4 agents: Orchestrator + 3 workers - Orchestrator handles most tasks directly (90%) - Workers used for truly complex, parallelizable work (10%) **Philosophy:** - Simple, direct, procedural code - Data-oriented design - Functions over classes - Build minimum that works **Tech Stack:** - Data: SQLMesh, DuckDB, Iceberg, ELT - SaaS: Robyn, evidence.dev - Testing: pytest, DuckDB SQL tests **Working Directory:** - `.agent_work/` for all agent outputs - Add to `.gitignore` - Review, then move to final locations **Golden Rule:** When in doubt, go simpler. Most tasks don't need multiple agents. --- ## Getting Started 1. Read `coding_philosophy.md` to understand principles 2. Use `orchestrator.md` as your main agent in Claude Code 3. Let orchestrator decide when to spawn workers 4. Review outputs in `.agent_work/` 5. Iterate based on results Start simple. Add complexity only when needed.