
---
title: Plugin System Architecture
description: Comprehensive architecture documentation for the plugin system
category: architecture
status: review
last_updated: 2026-01-14
version: 0.1.0
tags:
  - level: intermediate
  - component: plugins
  - status: stable
related:
  - ../plugin-development/plugin-development-tutorial.md
  - ../architecture/event-system.md
  - ../../core/plugins/discovery.py
  - ../../core/plugins/factory.py
  - ../../core/plugins/registry.py
  - ../../core/plugins/collector.py
---

# Plugin System Architecture

## Overview

The Kollabor CLI plugin system provides a modular, extensible architecture for adding custom functionality to the application. Plugins can register slash commands, hook into event flows, display status information, and manage configuration through a well-defined lifecycle.

The system uses a 4-component architecture: PluginDiscovery, PluginFactory, PluginRegistry, and PluginStatusCollector. Together, they handle plugin discovery, instantiation, registration, and information collection.

## Purpose

This document describes the internal architecture of the plugin system, including:

- The 4-component architecture and their responsibilities
- Plugin lifecycle from discovery to shutdown
- Dependency injection mechanisms
- Security considerations
- Integration with the event bus
- Troubleshooting common issues

## Scope

This document covers:

- Core plugin system components (discovery, factory, registry, collector)
- Plugin lifecycle methods and execution order
- Plugin class structure and required methods
- Security validation for plugin loading
- Configuration management for plugins

This document does NOT cover:

- Plugin development tutorial (see plugin-development-tutorial.md)
- Hook system details (see event-system.md)
- Slash command API (see slash-commands-guide.md)
- Fullscreen plugin architecture

## Prerequisites

To understand this document, you should be familiar with:

- Python class-based programming
- Asynchronous programming (async/await)
- Dependency injection patterns
- Event-driven architecture
- The kollabor CLI application structure

## Table of Contents

- [Component Architecture](#component-architecture)
- [Plugin Discovery](#plugin-discovery)
- [Plugin Factory](#plugin-factory)
- [Plugin Registry](#plugin-registry)
- [Plugin Status Collector](#plugin-status-collector)
- [Plugin Lifecycle](#plugin-lifecycle)
- [Plugin Base Class](#plugin-base-class)
- [Security Model](#security-model)
- [Configuration Management](#configuration-management)
- [Integration Points](#integration-points)
- [Troubleshooting](#troubleshooting)

## Component Architecture

The plugin system consists of four specialized components that work together:

```mermaid
graph TB
    subgraph "Plugin System Architecture"
        PR[PluginRegistry]
        PD[PluginDiscovery]
        PF[PluginFactory]
        PSC[PluginStatusCollector]
    end
    
    subgraph "File System"
        PDir[plugins/ Directory]
        PFiles[Plugin Files]
        PDirs[Plugin Packages]
    end
    
    subgraph "Plugin Instances"
        PI1[Plugin Instance 1]
        PI2[Plugin Instance 2]
        PIN[Plugin Instance N]
    end
    
    subgraph "Core Systems"
        EB[Event Bus]
        TR[Terminal Renderer]
        CM[Config Manager]
    end
    
    PDir --> PD
    PFiles --> PD
    PDirs --> PD
    
    PD --> PR
    PF --> PR
    PSC --> PR
    
    PR --> PF
    PF --> PI1
    PF --> PI2
    PF --> PIN
    
    PI1 --> EB
    PI2 --> EB
    PIN --> EB
    
    PI1 --> TR
    PI2 --> TR
    PIN --> TR
    
    PR --> PSC
    PSC --> CM
```

### Component Responsibilities

| Component | Responsibility | Key Methods |
|-----------|---------------|-------------|
| **PluginRegistry** | Coordinates all plugin operations | discover_plugins(), load_all_plugins(), instantiate_plugins() |
| **PluginDiscovery** | File system scanning and module loading | scan_plugin_files(), load_module(), discover_and_load() |
| **PluginFactory** | Plugin instantiation with dependencies | instantiate_plugin(), instantiate_all(), initialize_all_plugins() |
| **PluginStatusCollector** | Startup information collection | get_plugin_startup_info(), collect_all_startup_info() |

## Plugin Discovery

PluginDiscovery handles scanning the plugins directory, loading Python modules, and extracting plugin classes with security validation.

### Directory Structure

Plugins are discovered from two locations:

```
plugins/                          # Development mode
  enhanced_input_plugin.py         # File-based plugin
  hook_monitoring_plugin.py
  resume_conversation_plugin/
    __init__.py                   # Package-based plugin
    components.py
    config.py
  
kollabor_cli/plugins/             # Installed package
  system_commands_plugin.py
  fullscreen/
    matrix_plugin.py
    setup_wizard_plugin.py
```

### Security Validation

PluginDiscovery implements multiple security layers:

```python
# File: core/plugins/discovery.py

class PluginDiscovery:
    def __init__(self, plugins_dir: Path):
        self.valid_plugin_name_pattern = re.compile(r'^[a-zA-Z][a-zA-Z0-9_]*$')
        self.max_plugin_name_length = 50
        self.blocked_names = {
            '__init__', '__pycache__', 'system', 'os', 'sys', 'subprocess',
            'eval', 'exec', 'compile', 'open', 'file', 'input', 'raw_input'
        }
```

**Security Checks**:

1. **Name Validation**: Plugin names must match pattern `[a-zA-Z][a-zA-Z0-9_]*`
2. **Length Limit**: Maximum 50 characters
3. **Blocked Names**: Prevents loading system modules
4. **Path Traversal Prevention**: Blocks `..`, `/`, `\` in names
5. **Shell Metacharacter Blocking**: Blocks `;`, `&`, `|`, `` ` ``, `$`, `"`, `'`
6. **File Location Verification**: Ensures plugins are within plugins directory
7. **Module Verification**: Verifies loaded module matches expected file
8. **Permission Checks** (Unix): Warns on unusual file permissions

### Discovery Process

```python
def scan_plugin_files(self) -> List[str]:
    """Scan plugins directory for plugin files with security validation."""
    
    # 1. Resolve and verify plugins directory
    plugins_dir = self.plugins_dir.resolve()
    
    # 2. Check directory permissions (Unix)
    if not IS_WINDOWS:
        if plugins_dir.stat().st_mode & 0o002:  # world-writable
            logger.error("Plugins directory is world-writable")
            return []
    
    # 3. Scan for *_plugin.py files
    for plugin_file in plugins_dir.glob("*_plugin.py"):
        module_name = plugin_file.stem  # e.g., "enhanced_input_plugin"
        
        # 4. Validate and sanitize name
        safe_name = self._sanitize_plugin_name(module_name)
        if not safe_name:
            continue
        
        # 5. Verify file location
        if self._verify_plugin_location(safe_name):
            discovered.append(safe_name)
    
    # 6. Scan for directory-based plugins
    for plugin_dir in plugins_dir.iterdir():
        if plugin_dir.is_dir() and not plugin_dir.name.startswith(('_', '.')):
            if (plugin_dir / "__init__.py").exists():
                safe_name = self._sanitize_plugin_name(plugin_dir.name)
                if safe_name:
                    discovered.append(safe_name)
    
    return discovered
```

### Module Loading

```python
def load_module(self, module_name: str) -> bool:
    """Load plugin module and extract plugin classes."""
    
    # 1. Validate module name
    safe_name = self._sanitize_plugin_name(module_name)
    if not safe_name:
        return False
    
    # 2. Verify plugin location again
    if not self._verify_plugin_location(safe_name):
        return False
    
    # 3. Import module with security wrapper
    module = importlib.import_module(f"plugins.{safe_name}")
    
    # 4. Verify loaded module
    if not self._verify_loaded_module(module, safe_name):
        return False
    
    # 5. Extract plugin classes (ending with 'Plugin')
    for name, obj in inspect.getmembers(module, inspect.isclass):
        if name.endswith('Plugin') and has_method(obj, 'get_default_config'):
            self.loaded_classes[name] = obj
            self.plugin_configs[name] = get_plugin_config_safely(obj)
    
    return True
```

### Lightweight Discovery for CLI Arguments

For early CLI argument registration before app initialization:

```python
def discover_classes_only(self) -> List[Type]:
    """Lightweight discovery that only loads plugin classes.
    
    Used for CLI arg registration before app initialization.
    Does NOT instantiate plugins.
    """
    self.scan_plugin_files()
    
    plugin_classes = []
    for module_name in self.discovered_modules:
        if self.load_module(module_name):
            for class_name, class_obj in self.loaded_classes.items():
                plugin_classes.append(class_obj)
    
    return plugin_classes
```

## Plugin Factory

PluginFactory handles plugin instantiation with dependency injection and lifecycle management.

### Instantiation with Dependency Injection

```python
def instantiate_plugin(
    self,
    plugin_class: Type,
    plugin_name: str,
    event_bus: Any,
    renderer: Any,
    config: Any
) -> Any:
    """Instantiate a single plugin with dependencies."""
    
    # 1. Check if plugin has __init__ method
    if not has_method(plugin_class, '__init__'):
        return None
    
    # 2. Clean plugin name (remove 'Plugin' suffix)
    clean_name = plugin_name
    if plugin_name.endswith('Plugin'):
        clean_name = plugin_name[:-6].lower()
    
    # 3. Instantiate with dependencies
    instance = instantiate_plugin_safely(
        plugin_class,
        name=clean_name,
        event_bus=event_bus,
        renderer=renderer,
        config=config
    )
    
    # 4. Store instance (both with class name and clean name)
    if instance:
        self.plugin_instances[plugin_name] = instance
        self.plugin_instances[clean_name] = instance
    
    return instance
```

### Instantiation Example

```python
# What gets called
factory.instantiate_plugin(
    plugin_class=EchoPlugin,
    plugin_name="EchoPlugin",
    event_bus=event_bus,
    renderer=renderer,
    config=config_manager
)

# Equivalent to calling
echo_plugin = EchoPlugin(
    name="echo",
    event_bus=event_bus,
    renderer=renderer,
    config=config_manager
)
```

### Lifecycle Management

```python
def initialize_all_plugins(self) -> Dict[str, bool]:
    """Initialize all plugin instances."""
    results = {}
    
    for plugin_name in self.plugin_instances:
        instance = self.plugin_instances[plugin_name]
        
        # Check if plugin has initialize method
        if has_method(instance, 'initialize'):
            result = safe_execute(
                instance.initialize,
                f"initializing plugin {plugin_name}",
                default=False,
                logger_instance=logger
            )
            results[plugin_name] = (result is not False)
    
    return results

def shutdown_all_plugins(self) -> Dict[str, bool]:
    """Shutdown all plugin instances."""
    results = {}
    
    for plugin_name in self.plugin_instances:
        instance = self.plugin_instances[plugin_name]
        
        if has_method(instance, 'shutdown'):
            result = safe_execute(
                instance.shutdown,
                f"shutting down plugin {plugin_name}",
                default=False,
                logger_instance=logger
            )
            results[plugin_name] = (result is not False)
    
    return results
```

## Plugin Registry

PluginRegistry coordinates between discovery, factory, and collector components.

### Registry Architecture

```python
class PluginRegistry:
    def __init__(self, plugins_dir: Path):
        self.plugins_dir = plugins_dir
        self.discovery = PluginDiscovery(plugins_dir)
        self.factory = PluginFactory()
        self.collector = PluginStatusCollector()
```

### Main Registry Methods

```python
def load_all_plugins(self) -> None:
    """Discover and load all available plugins."""
    self.discovery.discover_and_load()
    logger.info(f"Loaded {len(self.discovery.loaded_classes)} plugins")

def get_merged_config(self) -> Dict[str, Any]:
    """Get merged configuration from all plugins."""
    merged_config = {}
    plugin_configs = self.discovery.get_all_configs()
    
    for plugin_name, plugin_config in plugin_configs.items():
        merged_config = deep_merge(merged_config, plugin_config)
    
    return merged_config

def instantiate_plugins(self, event_bus, renderer, config) -> Dict[str, Any]:
    """Create instances of all registered plugins."""
    return self.factory.instantiate_all(
        self.discovery.loaded_classes,
        event_bus,
        renderer,
        config
    )
```

### Registry Statistics

```python
def get_registry_stats(self) -> Dict[str, Any]:
    """Get comprehensive statistics about the registry."""
    return {
        "plugins_directory": str(self.plugins_dir),
        "discovery_stats": self.discovery.get_discovery_stats(),
        "factory_stats": self.factory.get_factory_stats(),
        "collector_stats": self.collector.get_collector_stats()
    }
```

## Plugin Status Collector

PluginStatusCollector gathers plugin information like startup messages.

### Startup Information Collection

```python
def get_plugin_startup_info(self, plugin_name: str, plugin_class: type, config: Any) -> List[str]:
    """Get startup information for a specific plugin."""
    
    def get_startup_info():
        return plugin_class.get_startup_info(config)
    
    result = safe_execute(
        get_startup_info,
        f"getting startup info from plugin {plugin_name}",
        default=[],
        logger_instance=logger
    )
    
    return result if isinstance(result, list) else []
```

### Example Startup Info

```python
# In plugin class
@staticmethod
def get_startup_info(config) -> list[str]:
    """Get startup information for this plugin."""
    enabled = config.get('plugins.echo.enabled', True)
    
    info = [f"Echo Plugin: {'enabled' if enabled else 'disabled'}"]
    
    if enabled:
        stats = config.get('plugins.echo.stats', {})
        if stats:
            info.append(f"  Total echos: {stats.get('total', 0)}")
    
    return info

# Output during startup:
# Echo Plugin: enabled
#   Total echos: 0
```

## Plugin Lifecycle

The complete plugin lifecycle from discovery to shutdown:

```mermaid
sequenceDiagram
    participant App as Application
    participant PR as PluginRegistry
    participant PD as PluginDiscovery
    participant PF as PluginFactory
    participant P as Plugin Instance
    participant EB as Event Bus
    
    Note over App,EB: 1. Discovery Phase
    App->>PR: load_all_plugins()
    PR->>PD: discover_and_load()
    PD->>PD: scan_plugin_files()
    PD->>PD: load_module() for each
    PD-->>PR: loaded_classes, configs
    
    Note over App,EB: 2. Instantiation Phase
    App->>PR: instantiate_plugins(event_bus, renderer, config)
    PR->>PF: instantiate_all(plugin_classes, ...)
    PF->>PF: instantiate_plugin() for each
    PF->>P: __init__(name, event_bus, renderer, config)
    P-->>PF: instance created
    PF-->>PR: plugin_instances
    
    Note over App,EB: 3. Initialization Phase
    App->>PF: initialize_all_plugins()
    PF->>P: initialize()
    P->>P: Set up resources
    P->>P: Register commands
    P->>EB: register_hooks()
    P-->>PF: initialized
    PF-->>App: initialization results
    
    Note over App,EB: 4. Runtime Phase
    EB->>P: Hook callbacks
    EB->>P: Command handlers
    App->>P: get_status_lines()
    
    Note over App,EB: 5. Shutdown Phase
    App->>PF: shutdown_all_plugins()
    PF->>P: shutdown()
    P->>P: Clean up resources
    P->>EB: Unregister hooks
    P-->>PF: shutdown complete
    PF-->>App: shutdown results
```

### Lifecycle Phases

#### Phase 1: Discovery
- Scan plugins directory for `*_plugin.py` files
- Validate plugin names and file locations
- Load Python modules
- Extract plugin classes and configurations
- Security validation throughout

#### Phase 2: Instantiation
- Create plugin instances with dependency injection
- Inject event_bus, renderer, config
- Store instances in factory registry

#### Phase 3: Initialization
- Call `plugin.initialize()` for each plugin
- Plugins register commands and hooks
- Set up resources (connections, background tasks, etc.)

#### Phase 4: Runtime
- Execute hook callbacks on events
- Handle slash commands
- Provide status lines
- Manage plugin state

#### Phase 5: Shutdown
- Call `plugin.shutdown()` for each plugin
- Cancel background tasks
- Close connections
- Clean up resources

## Plugin Base Class

BasePlugin provides default implementations and structure for plugins.

### BasePlugin Structure

```python
class BasePlugin:
    """Base class for all Kollabor CLI plugins."""
    
    @staticmethod
    def register_cli_args(parser: argparse.ArgumentParser) -> None:
        """Register custom CLI arguments before app initialization."""
        pass
    
    @staticmethod
    def handle_early_args(args: argparse.Namespace) -> bool:
        """Handle args that should exit before app starts."""
        return False
    
    def initialize(self, args: argparse.Namespace = None, **kwargs) -> None:
        """Initialize the plugin with dependencies."""
        pass
    
    def register_hooks(self) -> None:
        """Register plugin hooks with the event bus."""
        pass
    
    def shutdown(self) -> None:
        """Shutdown the plugin and clean up resources."""
        pass
    
    @staticmethod
    def get_default_config() -> dict[str, Any]:
        """Get default configuration for this plugin."""
        return {}
    
    @staticmethod
    def get_startup_info(config) -> list[str]:
        """Get startup information for this plugin."""
        return []
    
    @staticmethod
    def get_config_widgets() -> dict[str, Any]:
        """Get configuration widgets for this plugin."""
        return {}
```

### Required Dependencies

Every plugin receives these dependencies via `__init__`:

```python
def __init__(self, name: str, event_bus, renderer, config):
    """Initialize plugin with injected dependencies.
    
    Args:
        name: Plugin identifier (e.g., "echo", "enhanced_input")
        event_bus: EventBus instance for hook registration
        renderer: TerminalRenderer instance for UI operations
        config: ConfigManager instance for configuration access
    """
    self.name = name
    self.event_bus = event_bus
    self.renderer = renderer
    self.config = config
```

### Optional Dependencies

Additional dependencies available through `**kwargs` in `initialize()`:

```python
async def initialize(self, event_bus, config, **kwargs):
    """Initialize with optional dependencies."""
    self.event_bus = event_bus
    self.config = config
    
    # Optional dependencies
    self.command_registry = kwargs.get('command_registry')
    self.input_handler = kwargs.get('input_handler')
    self.llm_service = kwargs.get('llm_service')
    self.conversation_logger = kwargs.get('conversation_logger')
    self.conversation_manager = kwargs.get('conversation_manager')
```

## Security Model

The plugin system implements multiple security layers to prevent malicious code execution.

### Plugin Name Sanitization

```python
def _sanitize_plugin_name(self, plugin_name: str) -> Optional[str]:
    """Sanitize and validate plugin name for security."""
    
    # 1. Check for empty name
    if not plugin_name:
        return None
    
    # 2. Length check
    if len(plugin_name) > self.max_plugin_name_length:
        return None
    
    # 3. Pattern validation
    if not self.valid_plugin_name_pattern.match(plugin_name):
        return None
    
    # 4. Block dangerous names
    if plugin_name.lower() in self.blocked_names:
        return None
    
    # 5. Block path traversal
    if '..' in plugin_name or '/' in plugin_name or '\\' in plugin_name:
        return None
    
    # 6. Block shell metacharacters
    if any(char in plugin_name for char in [';', '&', '|', '`', '$', '"', "'"]):
        return None
    
    return plugin_name
```

### File Location Verification

```python
def _verify_plugin_location(self, plugin_name: str) -> bool:
    """Verify plugin file exists in expected location."""
    
    # Resolve paths to absolute
    plugins_dir = self.plugins_dir.resolve()
    
    # Check file-based plugin
    plugin_file = self.plugins_dir / f"{plugin_name}.py"
    if plugin_file.exists():
        plugin_file = plugin_file.resolve()
        
        # Verify it's within plugins directory
        if not str(plugin_file).startswith(str(plugins_dir)):
            logger.error(f"Plugin file outside plugins directory")
            return False
        
        # Check file permissions (Unix)
        if not IS_WINDOWS:
            if plugin_file.stat().st_mode & 0o777 != 0o644:
                logger.warning(f"Plugin file has unusual permissions")
        
        return True
    
    # Check directory-based plugin
    plugin_dir = self.plugins_dir / plugin_name
    if plugin_dir.exists() and plugin_dir.is_dir():
        plugin_dir = plugin_dir.resolve()
        
        if not str(plugin_dir).startswith(str(plugins_dir)):
            return False
        
        if not (plugin_dir / "__init__.py").exists():
            return False
        
        return True
    
    return False
```

### Module Verification

```python
def _verify_loaded_module(self, module, plugin_name: str) -> bool:
    """Verify the loaded module is actually our plugin."""
    
    # 1. Check module name matches
    expected_name = f"plugins.{plugin_name}"
    if module.__name__ != expected_name:
        return False
    
    # 2. Check module file location
    if hasattr(module, '__file__'):
        module_file = Path(module.__file__).resolve()
        plugins_dir = self.plugins_dir.resolve()
        
        if not str(module_file).startswith(str(plugins_dir)):
            return False
    
    # 3. Verify module structure
    if not hasattr(module, '__dict__'):
        return False
    
    return True
```

### Safe Execution Wrapper

All plugin operations are wrapped in safe execution:

```python
from core.utils.error_utils import safe_execute

result = safe_execute(
    plugin.initialize,
    f"initializing plugin {plugin_name}",
    default=None,
    logger_instance=logger
)
```

## Configuration Management

Plugins can provide default configuration that gets merged into the main config.

### Default Configuration

```python
@staticmethod
def get_default_config() -> dict[str, Any]:
    """Return default configuration for this plugin."""
    return {
        "plugins": {
            "my_plugin": {
                "enabled": True,
                "show_status": True,
                "timeout": 30,
                "advanced": {
                    "debug_mode": False,
                    "log_level": "INFO"
                }
            }
        }
    }
```

### Configuration Merging

```python
def get_merged_config(self) -> Dict[str, Any]:
    """Get merged configuration from all plugins."""
    merged_config = {}
    plugin_configs = self.discovery.get_all_configs()
    
    for plugin_name, plugin_config in plugin_configs.items():
        merged_config = deep_merge(merged_config, plugin_config)
    
    return merged_config
```

### Configuration Access

```python
# Direct access in plugin
enabled = self.config.get('plugins.my_plugin.enabled', True)
timeout = self.config.get('plugins.my_plugin.timeout', 30)
debug_mode = self.config.get('plugins.my_plugin.advanced.debug_mode', False)
```

### Configuration Widgets

```python
@staticmethod
def get_config_widgets() -> dict[str, Any]:
    """Get configuration widgets for the config modal."""
    return {
        "title": "My Plugin Settings",
        "widgets": [
            {
                "type": "checkbox",
                "label": "Enable Plugin",
                "config_path": "plugins.my_plugin.enabled",
                "help": "Toggle the plugin on or off"
            },
            {
                "type": "slider",
                "label": "Timeout",
                "config_path": "plugins.my_plugin.timeout",
                "min_value": 1,
                "max_value": 120,
                "step": 1
            }
        ]
    }
```

## Integration Points

### Event Bus Integration

Plugins register hooks with the event bus:

```python
async def register_hooks(self):
    """Register hooks with the event bus."""
    hook = Hook(
        name="log_user_input",
        plugin_name=self.name,
        event_type=EventType.USER_INPUT_PRE,
        priority=HookPriority.PREPROCESSING.value,
        callback=self._log_user_input,
        timeout=30,
        error_action="continue"
    )
    await self.event_bus.register_hook(hook)
```

### Command Registry Integration

Plugins register slash commands:

```python
async def initialize(self, event_bus, config, **kwargs):
    """Initialize and register commands."""
    command_registry = kwargs.get('command_registry')
    
    command = CommandDefinition(
        name="mycommand",
        description="My custom command",
        handler=self._handle_command,
        plugin_name=self.name,
        category=CommandCategory.CUSTOM,
        subcommands=[
            SubcommandInfo("list", "", "List items"),
            SubcommandInfo("add", "<item>", "Add an item")
        ]
    )
    command_registry.register_command(command)
```

### Status Bar Integration

Plugins provide status lines:

```python
def get_status_lines(self) -> Dict[str, List[str]]:
    """Return status lines for the status bar."""
    enabled = self.config.get(f'plugins.{self.name}.enabled', True)
    
    return {
        "A": [],  # Core system status (left)
        "B": [f"{self.name}: {'On' if enabled else 'Off'}"],  # Middle
        "C": [f"Count: {self.counter}"] if enabled else []  # Right
    }
```

### Plugin-to-Plugin Communication

Plugins can access other plugin instances via the factory:

```python
# Get another plugin instance
if hasattr(self.renderer, 'plugin_factory'):
    factory = self.renderer.plugin_factory
    other_plugin = factory.get_instance("OtherPlugin")
    
    if other_plugin:
        # Call methods on other plugin
        result = other_plugin.some_method()
```

## Troubleshooting

### Plugin Not Loading

**Symptoms**: Plugin doesn't appear in `/help` or logs

**Diagnosis**:

```python
# Check discovery stats
stats = registry.get_registry_stats()
print(stats['discovery_stats'])
# Output: {
#   "discovered_modules": [],
#   "loaded_classes": {}
# }
```

**Common Causes**:

1. **File Naming**: Plugin file must end with `_plugin.py`
   ```bash
   # Wrong: myplugin.py
   # Correct: my_plugin.py
   ```

2. **Invalid Plugin Name**: Must match security pattern
   ```python
   # Invalid: 123_plugin.py, -plugin.py, my-plugin.py
   # Valid: my_plugin.py, MyPlugin.py
   ```

3. **Missing get_default_config()**: Required for plugin detection
   ```python
   @staticmethod
   def get_default_config() -> dict:
       return {}  # Must have this method
   ```

4. **Syntax Errors**: Check file compiles
   ```bash
   python -m py_compile plugins/my_plugin.py
   ```

5. **Import Errors**: Check dependencies
   ```bash
   # Try importing manually
   python -c "import plugins.my_plugin"
   ```

**Solution**: Verify plugin structure:

```python
# File: plugins/my_plugin.py
class MyPlugin:  # Must end with 'Plugin'
    @staticmethod
    def get_default_config() -> dict:  # Required
        return {}
    
    def __init__(self, name: str, event_bus, renderer, config):
        pass
```

### Instantiation Errors

**Symptoms**: Plugin discovered but fails to instantiate

**Diagnosis**:

```python
# Check factory stats
stats = registry.get_registry_stats()
print(stats['factory_stats'])
# Output: {
#   "instantiation_errors": {
#       "MyPlugin": "Failed to instantiate"
#   }
# }
```

**Common Causes**:

1. **Missing __init__ Method**: Plugin must be instantiable
   ```python
   # Wrong
   class MyPlugin:
       pass
   
   # Correct
   class MyPlugin:
       def __init__(self, name: str, event_bus, renderer, config):
           pass
   ```

2. **Wrong __init__ Signature**: Must accept 4 parameters
   ```python
   # Wrong - missing parameters
   def __init__(self, name):
       pass
   
   # Wrong - wrong parameter order
   def __init__(self, name, config, event_bus):
       pass
   
   # Correct
   def __init__(self, name: str, event_bus, renderer, config):
       pass
   ```

3. **Missing Dependencies**: If plugin needs extra dependencies
   ```python
   # Plugin tries to use optional dependency that wasn't passed
   def __init__(self, name, event_bus, renderer, config):
       self.command_registry = kwargs.get('command_registry')  # None!
       self.command_registry.register(...)  # AttributeError
   ```

**Solution**: Check __init__ signature matches expected pattern:

```python
class MyPlugin:
    def __init__(self, name: str, event_bus, renderer, config):
        """Initialize plugin.
        
        Args:
            name: Plugin identifier (snake_case)
            event_bus: EventBus instance
            renderer: TerminalRenderer instance
            config: ConfigManager instance
        """
        self.name = name
        self.event_bus = event_bus
        self.renderer = renderer
        self.config = config
```

### Initialization Failures

**Symptoms**: Plugin loads but initialize() fails

**Diagnosis**:

```python
# Check initialization results
results = factory.initialize_all_plugins()
print(results)
# Output: {"MyPlugin": False, "OtherPlugin": True}
```

**Common Causes**:

1. **Uncaught Exceptions**: Exception in initialize()
   ```python
   async def initialize(self, event_bus, config, **kwargs):
       # Uncaught exception
       value = kwargs['missing_key']  # KeyError
   ```

2. **Blocking I/O**: Using synchronous I/O in async context
   ```python
   async def initialize(self, event_bus, config, **kwargs):
       # Wrong - blocks event loop
       import requests
       data = requests.get(url)  # Don't do this!
       
       # Correct - use async
       import aiohttp
       async with aiohttp.ClientSession() as session:
           data = await session.get(url)
   ```

3. **Missing Dependencies**: Optional dependencies not available
   ```python
   async def initialize(self, event_bus, config, **kwargs):
       command_registry = kwargs.get('command_registry')
       if not command_registry:
           logger.error("Command registry not available")
           return  # Initialize fails
   ```

**Solution**: Use proper error handling:

```python
async def initialize(self, event_bus, config, **kwargs):
    """Initialize plugin with proper error handling."""
    try:
        self.event_bus = event_bus
        self.config = config
        
        # Check optional dependencies
        command_registry = kwargs.get('command_registry')
        if command_registry:
            self._register_commands(command_registry)
        else:
            logger.warning("Command registry not available")
        
        # Register hooks
        await self.register_hooks()
        
    except Exception as e:
        logger.error(f"Initialization failed: {e}", exc_info=True)
        raise
```

### Hooks Not Executing

**Symptoms**: Hooks registered but never called

**Diagnosis**:

```python
# Check event bus hook status
status = event_bus.get_hook_status()
print(status.get('my_plugin', {}))
# Output: {
#   "log_user_input": {
#       "enabled": true,
#       "calls": 0,
#       "errors": []
#   }
# }
```

**Common Causes**:

1. **Wrong Event Type**: Event doesn't fire or different type
   ```python
   # Wrong - event type doesn't exist
   Hook(event_type=EventType.USER_INPUT_POST, ...)
   
   # Maybe you want this instead
   Hook(event_type=EventType.USER_INPUT, ...)
   ```

2. **Event Not Emitted**: Source code doesn't emit the event
   ```bash
   # Search for event emission in codebase
   grep -r "emit_with_hooks(USER_INPUT_POST)" core/
   ```

3. **Plugin Disabled**: Configuration disables plugin
   ```python
   enabled = self.config.get('plugins.my_plugin.enabled', True)
   if not enabled:
       return  # Hooks not registered
   ```

4. **Event Cancelled**: Upstream hook cancels event
   ```python
   async def _validate_input(self, data, event):
       if not data.get('message'):
           event.cancelled = True  # Cancels entire chain
           return {"status": "cancelled"}
   ```

**Solution**: Verify hook registration and event flow:

```python
async def register_hooks(self):
    """Register hooks with proper configuration."""
    
    # 1. Define hooks
    hook = Hook(
        name="log_input",
        plugin_name=self.name,
        event_type=EventType.USER_INPUT,  # Verify event type
        priority=HookPriority.POSTPROCESSING.value,
        callback=self._log_user_input,
        timeout=30,
        error_action="continue"  # Continue on error
    )
    
    # 2. Register and verify
    success = await self.event_bus.register_hook(hook)
    if success:
        logger.info(f"Registered hook: {hook.name}")
    else:
        logger.error(f"Failed to register hook: {hook.name}")
```

### Configuration Issues

**Symptoms**: Plugin reads wrong config values

**Common Causes**:

1. **Wrong Config Path**: Typo in config path
   ```python
   # Wrong
   value = config.get('plugins.my_pluginn.timeout', 30)  # typo
   
   # Correct
   value = config.get('plugins.my_plugin.timeout', 30)
   ```

2. **Missing Default**: No default value for missing config
   ```python
   # Wrong - returns None if config missing
   value = config.get('plugins.my_plugin.timeout')
   
   # Correct - provides default
   value = config.get('plugins.my_plugin.timeout', 30)
   ```

3. **Config Not Merged**: Plugin config not loaded
   ```python
   # Check if config was loaded
   stats = registry.discovery.get_discovery_stats()
   print(stats['plugins_with_config'])  # Should be > 0
   ```

**Solution**: Verify config loading:

```python
@staticmethod
def get_default_config() -> dict[str, Any]:
    """Provide default configuration."""
    return {
        "plugins": {
            "my_plugin": {  # Must match plugin name
                "enabled": True,
                "timeout": 30
            }
        }
    }

# In plugin code
async def initialize(self, event_bus, config, **kwargs):
    # Use full path with default
    enabled = config.get('plugins.my_plugin.enabled', True)
    timeout = config.get('plugins.my_plugin.timeout', 30)
    
    logger.info(f"Plugin enabled: {enabled}, timeout: {timeout}")
```

### Resource Leaks

**Symptoms**: Memory usage grows over time, connections not closed

**Common Causes**:

1. **Background Tasks Not Cancelled**: Tasks continue after shutdown
   ```python
   async def initialize(self, event_bus, config, **kwargs):
       self.task = asyncio.create_task(self._worker())
       # Task not tracked for cleanup
   ```

2. **Connections Not Closed**: Network connections leak
   ```python
   async def initialize(self, event_bus, config, **kwargs):
       self.session = aiohttp.ClientSession()
       # Connection not closed in shutdown
   ```

**Solution**: Track and clean up resources:

```python
class MyPlugin:
    def __init__(self, name: str, event_bus, renderer, config):
        self.name = name
        self.event_bus = event_bus
        self.renderer = renderer
        self.config = config
        
        # Track resources
        self.background_tasks = []
        self.connections = []
    
    async def initialize(self, event_bus, config, **kwargs):
        """Initialize and track resources."""
        
        # Start background tasks
        task = asyncio.create_task(self._worker())
        self.background_tasks.append(task)
        
        # Open connections
        session = aiohttp.ClientSession()
        self.connections.append(session)
    
    async def shutdown(self):
        """Clean up all tracked resources."""
        
        # Cancel background tasks
        for task in self.background_tasks:
            if not task.done():
                task.cancel()
                try:
                    await task
                except asyncio.CancelledError:
                    pass
        
        # Close connections
        for connection in self.connections:
            await connection.close()
        
        logger.info(f"{self.name} shutdown complete")
```

### Debugging Tools

Enable debug logging for plugin system:

```python
import logging

# Enable debug logging
logging.getLogger('core.plugins').setLevel(logging.DEBUG)
logging.getLogger('core.plugins.discovery').setLevel(logging.DEBUG)
logging.getLogger('core.plugins.factory').setLevel(logging.DEBUG)
```

Check registry statistics:

```python
# Get comprehensive stats
stats = registry.get_registry_stats()
import json
print(json.dumps(stats, indent=2))
```

Trace plugin loading:

```python
# Manually trigger discovery
registry.discovery.scan_plugin_files()
print("Discovered:", registry.discovery.discovered_modules)

# Load single plugin
registry.load_plugin("my_plugin")
print("Loaded classes:", list(registry.discovery.loaded_classes.keys()))

# Check config
print("Config:", registry.discovery.get_plugin_config("MyPlugin"))
```

## Related Documents

- [Plugin Development Tutorial](../plugin-development/plugin-development-tutorial.md) - Complete guide to creating plugins
- [Event System Architecture](../architecture/event-system.md) - Event bus and hook system details
- [Hook System SDK](../architecture/hook-system-sdk.md) - Comprehensive hook system reference
- [Slash Commands Guide](../guides/slash-commands-guide.md) - Command registration patterns

## References

### Source Files

- **core/plugins/discovery.py** - Plugin discovery and module loading
- **core/plugins/factory.py** - Plugin instantiation and lifecycle
- **core/plugins/registry.py** - Plugin registry coordination
- **core/plugins/collector.py** - Startup information collection
- **core/plugins/base.py** - BasePlugin class definition

### Key Functions

- `PluginDiscovery.scan_plugin_files()` - Scan directory for plugins
- `PluginDiscovery.load_module()` - Load single plugin module
- `PluginFactory.instantiate_plugin()` - Create plugin instance
- `PluginFactory.initialize_all_plugins()` - Initialize all plugins
- `PluginRegistry.get_merged_config()` - Merge plugin configs

### Plugin Methods

- `__init__(name, event_bus, renderer, config)` - Plugin constructor
- `initialize(event_bus, config, **kwargs)` - Plugin initialization
- `register_hooks()` - Register event hooks
- `shutdown()` - Plugin cleanup
- `get_status_lines()` - Provide status bar info
- `get_default_config()` - Provide default configuration
- `get_startup_info(config)` - Provide startup messages

---

**Document Version**: 0.1.0  
**Last Updated**: 2026-01-14  
**Status**: Review - Ready for technical review
