Tutorial How To Write 80 20 Tools - 80-20-Human-In-The-Loop/Community GitHub Wiki

How To Write Tools That Embody the 80-20 Human in The Loop Philosophy

Build tools that make humans smarter, not obsolete. Automate the mundane, preserve the meaningful.

Table of Contents

  1. Core Philosophy
  2. Three Audiences Pattern
  3. Design Principles
  4. Implementation Patterns
  5. Real-World Examples
  6. Technical Implementation
  7. Testing Your Tool
  8. Common Pitfalls
  9. Community Integration

Core Philosophy

The 80-20 Human in The Loop philosophy recognizes that effective tools must balance automation with human learning and decision-making.

The 80-20 Balance

80% Automation: Let tools handle:

  • Repetitive analysis
  • Pattern detection
  • Data collection
  • Formatting and structure
  • Known problem identification

20% Human Intelligence: Preserve human involvement for:

  • Understanding context
  • Making architectural decisions
  • Learning from patterns
  • Ethical considerations
  • Creative problem-solving

Why This Matters

When we over-automate, we create developers who cannot debug their own systems. When we under-automate, we waste human potential on repetitive tasks. The 80-20 balance creates tools that enhance human capability without replacing human understanding.


Three Audiences Pattern

When building tools within this ecosystem, you must design for three distinct audiences:

1. Beginners/Students (--edu flag)

Characteristics:

  • Need additional guidance and explanation
  • Learn through tool usage
  • Start with slower, more educational workflow
  • Build understanding progressively

Implementation:

# Example: Educational mode with detailed explanations
$ your-tool analyze --edu
šŸ“š Educational Mode Active
→ Analyzing your code for performance issues...
→ Found 3 issues. Let's understand each one:

Issue 1: N+1 Query Problem
šŸ“– What this means: Your code makes 1 query to get a list,
   then 1 query for each item. With 100 items = 101 queries!
šŸ’” Why it matters: Each query takes time. More queries = slower app.
šŸ”§ How to fix: Use select_related() or prefetch_related()
šŸ“ Learn more: https://docs.djangoproject.com/en/stable/n+1-queries

2. Experts/Professionals (Default)

Characteristics:

  • Expect tools to work fast and efficiently
  • Already understand the concepts
  • Need concise, actionable output
  • Use advanced features regularly

Implementation:

# Example: Professional mode - direct and efficient
$ your-tool analyze
āœ“ Analysis complete (0.3s)
  - N+1 queries: views.py:45, views.py:78
  - Missing indexes: models.py:23 (User.email)
  - Slow query: queries.py:112 (avg 342ms)
Run with --details for more information

3. AI Agents/LLMs (--agent flag, MCP servers)

Characteristics:

  • Need structured, parseable output
  • Can process large amounts of data
  • Require clear action items
  • Must preserve human decision points

Implementation:

# Example: Agent mode - structured JSON output
$ your-tool analyze --agent
{
  "status": "complete",
  "issues": [
    {
      "type": "n_plus_one",
      "severity": "high",
      "location": "views.py:45",
      "suggestion": "Add select_related('author')",
      "requires_human_review": true,
      "reason": "Architectural decision needed"
    }
  ],
  "metrics": {
    "total_queries": 156,
    "slow_queries": 3,
    "optimization_potential": "68%"
  }
}

Design Principles

1. Progressive Disclosure

Start simple, reveal complexity as needed:

class PerformanceTool:
    def analyze(self, edu_mode=False, detail_level=1):
        issues = self.detect_issues()
        
        if edu_mode:
            # Educational: Full explanation
            return self.format_educational(issues)
        elif detail_level > 2:
            # Expert: Detailed technical output
            return self.format_detailed(issues)
        else:
            # Default: Concise summary
            return self.format_summary(issues)

2. Educational Opportunities

Embed learning in normal usage:

def format_issue(self, issue, edu_mode=False):
    output = f"Issue: {issue.type} at {issue.location}"
    
    if edu_mode:
        output += f"\nšŸ“– Explanation: {issue.get_explanation()}"
        output += f"\nšŸ’” Why this matters: {issue.get_impact()}"
        output += f"\nšŸ”§ How to fix: {issue.get_solution()}"
        output += f"\nšŸ“š Learn more: {issue.get_resources()}"
    
    return output

3. Preserve Human Decision Points

Never fully automate critical decisions:

def suggest_optimization(self, issue):
    suggestion = self.generate_suggestion(issue)
    
    # Always require human review for architectural changes
    if issue.impacts_architecture:
        return {
            "suggestion": suggestion,
            "auto_fix": False,
            "reason": "Architectural decision requires human review",
            "considerations": [
                "Will this affect other components?",
                "Is this aligned with system design?",
                "Are there performance trade-offs?"
            ]
        }
    
    return {
        "suggestion": suggestion,
        "auto_fix": issue.is_trivial,
        "confidence": self.calculate_confidence(issue)
    }

4. Clear Feedback Loops

Provide immediate, understandable feedback:

def run_with_feedback(self, task):
    # Visual progress for long operations
    with self.progress_bar() as progress:
        progress.update("Analyzing code structure...")
        structure = self.analyze_structure()
        
        progress.update("Detecting patterns...")
        patterns = self.detect_patterns()
        
        progress.update("Generating recommendations...")
        recommendations = self.generate_recommendations()
    
    # Clear result presentation
    self.display_results(
        structure, 
        patterns, 
        recommendations,
        verbosity=self.get_verbosity_level()
    )

Implementation Patterns

Command-Line Interface Pattern

Design flexible CLI that serves all audiences:

import argparse

def create_parser():
    parser = argparse.ArgumentParser()
    
    # Audience-specific flags
    parser.add_argument('--edu', action='store_true',
                       help='Educational mode with detailed explanations')
    parser.add_argument('--agent', action='store_true',
                       help='Agent mode with structured JSON output')
    
    # Progressive complexity
    parser.add_argument('--detail', type=int, default=1,
                       help='Detail level (1-5, default: 1)')
    
    # Learning features
    parser.add_argument('--explain', action='store_true',
                       help='Explain the analysis process')
    parser.add_argument('--tutorial', action='store_true',
                       help='Run in tutorial mode')
    
    return parser

Configuration Pattern

Support different workflows through configuration:

class ToolConfig:
    PROFILES = {
        'student': {
            'explanations': True,
            'pace': 'slow',
            'hints': True,
            'auto_fix': False,
            'teach_concepts': True
        },
        'professional': {
            'explanations': False,
            'pace': 'fast',
            'hints': False,
            'auto_fix': True,
            'teach_concepts': False
        },
        'agent': {
            'format': 'json',
            'explanations': False,
            'decisions': 'defer_to_human',
            'batch_mode': True
        }
    }
    
    @classmethod
    def load_profile(cls, profile_name):
        return cls.PROFILES.get(profile_name, cls.PROFILES['professional'])

Output Formatting Pattern

Adapt output to audience needs:

class OutputFormatter:
    def format(self, data, mode='default'):
        if mode == 'educational':
            return self._format_educational(data)
        elif mode == 'agent':
            return self._format_json(data)
        else:
            return self._format_concise(data)
    
    def _format_educational(self, data):
        output = []
        output.append("="*50)
        output.append("šŸ“š LEARNING MOMENT")
        output.append("="*50)
        
        for item in data:
            output.append(f"\nšŸŽÆ {item.title}")
            output.append(f"šŸ“– What: {item.description}")
            output.append(f"šŸ’” Why: {item.importance}")
            output.append(f"šŸ”§ How: {item.solution}")
            output.append(f"šŸ“š Learn: {item.resources}")
        
        return "\n".join(output)
    
    def _format_concise(self, data):
        return "\n".join([
            f"• {item.title}: {item.location}"
            for item in data
        ])
    
    def _format_json(self, data):
        return json.dumps({
            'issues': [item.to_dict() for item in data],
            'metadata': {
                'tool_version': self.version,
                'timestamp': datetime.now().isoformat(),
                'requires_human_review': any(
                    item.requires_human for item in data
                )
            }
        }, indent=2)

Real-World Examples

Example 1: Storm Checker Pattern

Storm Checker demonstrates the three-audience pattern for type checking:

class StormChecker:
    def check_types(self, path, edu=False, agent=False):
        """Check Python type annotations with audience-appropriate output."""
        
        issues = self.run_mypy(path)
        categorized = self.categorize_by_complexity(issues)
        
        if edu:
            # Educational mode: Teach type concepts
            self.display_educational(categorized)
            self.offer_interactive_fixing(categorized)
            self.show_type_tutorial_progress()
            
        elif agent:
            # Agent mode: Structured data for automation
            return {
                'fixable_automatically': categorized['simple'],
                'requires_human': categorized['complex'],
                'learning_opportunities': categorized['educational']
            }
            
        else:
            # Professional mode: Efficient summary
            self.display_summary(categorized)
            if categorized['simple']:
                self.offer_quick_fix(categorized['simple'])

Example 2: Django Mercury Pattern

Django Mercury shows progressive enhancement in performance testing:

class DjangoMercuryTestCase:
    def run_performance_test(self, test_func):
        """Run test with performance monitoring."""
        
        # Collect metrics
        metrics = self.collect_metrics(test_func)
        
        # Adapt output to user level
        if self.educational_mode:
            self.explain_metrics(metrics)
            self.teach_optimization(metrics)
            self.suggest_learning_path(metrics)
            
        elif self.agent_mode:
            return self.format_for_agent(metrics)
            
        else:
            self.display_grade(metrics)
            if metrics.grade < 'B':
                self.suggest_improvements(metrics)

Example 3: MCP Server Pattern

MCP servers enable AI agent integration while preserving human control:

class ToolMCPServer:
    @tool()
    def analyze_code(self, path: str, auto_fix: bool = False):
        """Analyze code with human-in-the-loop safeguards."""
        
        issues = self.detect_issues(path)
        
        results = []
        for issue in issues:
            result = {
                'issue': issue.description,
                'severity': issue.severity,
                'location': issue.location
            }
            
            if issue.can_auto_fix and auto_fix:
                if issue.requires_human_review:
                    result['action'] = 'requires_human_review'
                    result['reason'] = issue.human_review_reason
                else:
                    result['action'] = 'auto_fixed'
                    result['fix'] = self.apply_fix(issue)
            else:
                result['action'] = 'suggestion'
                result['suggestion'] = issue.suggested_fix
            
            results.append(result)
        
        return results

Technical Implementation

Error Messages That Teach

Transform errors into learning opportunities:

class EducationalError(Exception):
    def __init__(self, message, explanation=None, suggestion=None, resources=None):
        self.message = message
        self.explanation = explanation
        self.suggestion = suggestion
        self.resources = resources
    
    def display(self, edu_mode=False):
        output = f"āŒ Error: {self.message}"
        
        if edu_mode and self.explanation:
            output += f"\n\nšŸ“– What happened: {self.explanation}"
            output += f"\nšŸ’” Why: This usually occurs when {self.get_common_cause()}"
            output += f"\nšŸ”§ Fix: {self.suggestion}"
            output += f"\nšŸ“š Learn more: {', '.join(self.resources)}"
        
        return output

Progress Indicators That Educate

Make waiting time valuable:

class EducationalProgress:
    def __init__(self, edu_mode=False):
        self.edu_mode = edu_mode
        self.tips = [
            "Did you know? Indexing can improve query speed by 100x",
            "Tip: Use prefetch_related() for many-to-many relationships",
            "Fun fact: The first database query is often the slowest (cold cache)"
        ]
    
    def update(self, task, percentage):
        if self.edu_mode and percentage % 20 == 0:
            # Show educational tips during processing
            tip = random.choice(self.tips)
            print(f"šŸ’” {tip}")
        
        # Standard progress update
        self.display_bar(task, percentage)

Metrics Collection Pattern

Gather data to support all three audiences:

class MetricsCollector:
    def collect(self, operation):
        metrics = {
            'duration': self.measure_duration(operation),
            'memory': self.measure_memory(operation),
            'queries': self.count_queries(operation),
            'complexity': self.calculate_complexity(operation)
        }
        
        # Add educational context
        metrics['educational'] = {
            'is_optimal': metrics['queries'] < 5,
            'bottleneck': self.identify_bottleneck(metrics),
            'improvement_potential': self.calculate_potential(metrics)
        }
        
        # Add agent context
        metrics['agent'] = {
            'auto_optimizable': self.can_auto_optimize(metrics),
            'confidence': self.optimization_confidence(metrics),
            'human_review_needed': metrics['complexity'] > 7
        }
        
        return metrics

Testing Your Tool

Validate Educational Value

Test that your tool teaches effectively:

def test_educational_mode():
    """Ensure educational mode provides learning value."""
    
    tool = YourTool(edu_mode=True)
    output = tool.analyze(sample_code)
    
    # Check for educational elements
    assert "What this means" in output
    assert "Why it matters" in output
    assert "How to fix" in output
    assert "Learn more" in output
    
    # Verify progressive learning
    assert tool.tracks_user_progress()
    assert tool.adjusts_to_skill_level()

Ensure Expert Efficiency

Test that professional mode is fast and focused:

def test_professional_mode():
    """Ensure professional mode is efficient."""
    
    tool = YourTool(edu_mode=False)
    
    start_time = time.time()
    output = tool.analyze(large_codebase)
    duration = time.time() - start_time
    
    # Should be fast
    assert duration < 5.0  # seconds
    
    # Should be concise
    assert len(output.split('\n')) < 50
    
    # Should be actionable
    assert tool.provides_quick_fixes()

Test AI Agent Integration

Verify agent mode provides appropriate automation:

def test_agent_mode():
    """Ensure agent mode preserves human decision points."""
    
    tool = YourTool(agent_mode=True)
    result = tool.analyze(complex_code)
    
    # Should return structured data
    assert isinstance(result, dict)
    assert 'requires_human_review' in result
    
    # Should not auto-fix critical issues
    critical_issues = [i for i in result['issues'] if i['severity'] == 'critical']
    for issue in critical_issues:
        assert not issue['auto_fixed']
        assert issue['requires_human_review']

Common Pitfalls

Pitfall 1: Over-Automation

Problem: Tool does everything automatically, users learn nothing.

Solution: Always require human understanding for important decisions:

# Bad: Full automation
def fix_all_issues(code):
    for issue in detect_issues(code):
        apply_fix(issue)  # User learns nothing

# Good: Human in the loop
def fix_issues_with_review(code, auto_fix_trivial=True):
    for issue in detect_issues(code):
        if issue.is_trivial and auto_fix_trivial:
            apply_fix(issue)
            explain_what_was_fixed(issue)
        else:
            present_issue_to_human(issue)
            teach_fix_strategy(issue)
            if user_approves():
                apply_fix(issue)

Pitfall 2: Information Overload

Problem: Educational mode drowns users in information.

Solution: Progressive disclosure based on user engagement:

# Bad: Dump everything
def explain_issue(issue):
    print(issue.full_technical_explanation)  # 500 lines of text

# Good: Progressive depth
def explain_issue(issue, depth=1):
    print(issue.summary)  # One line
    
    if user_wants_more():
        print(issue.explanation)  # Paragraph
        
        if user_wants_even_more():
            print(issue.technical_details)  # Full details
            offer_interactive_tutorial()

Pitfall 3: Unclear Audience Separation

Problem: Mixing output styles confuses users.

Solution: Clear mode separation with consistent behavior:

# Bad: Mixed signals
def display_results(results, flags):
    print("Issue found!")  # Casual
    print(json.dumps(results))  # Technical
    print("šŸ’” Did you know...")  # Educational
    
# Good: Clear separation
def display_results(results, mode):
    formatter = get_formatter(mode)
    print(formatter.format(results))
    # Each formatter is consistent within itself

Pitfall 4: Neglecting Human Growth

Problem: Tool doesn't help users improve over time.

Solution: Track progress and adapt:

class GrowthTracker:
    def track_user_progress(self, user_id, issue_fixed):
        # Record what user learned
        self.record_learning(user_id, issue_fixed.concept)
        
        # Adjust future guidance
        if self.user_has_mastered(user_id, issue_fixed.concept):
            self.reduce_explanation_detail(user_id, issue_fixed.concept)
            self.introduce_advanced_concepts(user_id)

Community Integration

Documentation Standards

Follow the community's writing principles:

  1. Write for Translation: Simple, clear language
  2. Progressive Complexity: Start simple, add depth
  3. Global Accessibility: Consider bandwidth and hardware limitations
  4. Educational Focus: Every tool should teach something

Creating Tutorials

Build tutorials that serve all audiences:

# Tutorial: Using YourTool

## Quick Start (Everyone)
```bash
$ yourtool analyze mycode.py

For Students

Want to understand what's happening? Use educational mode:

$ yourtool analyze mycode.py --edu

This will explain each issue and teach you how to fix it.

For Professionals

Need fast results? YourTool works efficiently by default:

$ yourtool analyze . --fix

For AI Integration

Building an AI workflow? Use agent mode:

$ yourtool analyze --agent --output json

### Building MCP Servers

Create MCP servers that preserve human control:

```python
from mcp import tool, server

@server(name="yourtool-mcp")
class YourToolMCP:
    @tool()
    def analyze(self, path: str, education_level: str = "normal"):
        """Analyze code while preserving human learning."""
        
        # Never fully automate critical decisions
        results = self.tool.analyze(path)
        
        return {
            "automated_fixes": self.get_safe_fixes(results),
            "requires_human": self.get_critical_issues(results),
            "learning_opportunities": self.get_educational_items(results)
        }

Contributing to the Ecosystem

When contributing tools:

  1. Provide Multiple Interfaces: CLI, API, and MCP
  2. Document Educational Value: What will users learn?
  3. Include Examples: Show all three modes in action
  4. Test Accessibility: Ensure tools work on limited hardware
  5. Share Knowledge: Write about your design decisions

Conclusion

Building 80-20 tools requires thoughtful balance. Your tool should:

  • Empower beginners to learn while doing
  • Enable experts to work efficiently
  • Allow AI agents to automate safely
  • Preserve human wisdom in critical decisions
  • Create opportunities for growth and understanding

Remember: The goal is not to replace human intelligence but to augment it. Build tools that make developers smarter, not more dependent.


Resources


Building tools that preserve human wisdom while embracing AI efficiency - together, we create a better future for development.