# Hook System Audit

**Analysis Date:** 2025-01-14
**Purpose:** Verify hook data structure for OpenAI SDK integration (v2.1 Addendum)
**Agent:** Phase0AnalyzeHooks

---

## CRITICAL FINDING: plugin_name vs plugin

### VERIFIED: Hook uses `plugin_name: str` field

The Hook dataclass definitively uses a **string field** called `plugin_name`, NOT an object reference.

**Location:** `/Users/malmazan/dev/kollabor-cli/core/events/models.py` (lines 127-158)

```python
@dataclass
class Hook:
    """Hook definition for the event system.

    Attributes:
        name: Unique name for the hook.
        plugin_name: Name of the plugin that owns this hook.  # <-- STRING FIELD
        event_type: Type of event this hook responds to.
        priority: Execution priority (higher numbers execute first).
        callback: Async function to call when event occurs.
        enabled: Whether the hook is currently enabled.
        timeout: Maximum execution time in seconds (None = use config default, then 30).
        retry_attempts: Number of retry attempts on failure (None = use config default, then 3).
        error_action: Action to take on error (None = use config default, then "continue").
        status: Current execution status.
        status_area: Status area identifier.
        icon_set: Icons for different states.
    """
    name: str
    plugin_name: str        # <-- CONFIRMED: String field, not object
    event_type: EventType
    priority: int
    callback: Callable
    enabled: bool = True
    timeout: Optional[int] = None
    retry_attempts: Optional[int] = None
    error_action: Optional[str] = None
    status: HookStatus = HookStatus.PENDING
    status_area: str = "A"
    icon_set: Dict[str, str] = field(default_factory=lambda: {
        "thinking": "[think]", "processing": "[proc]", "complete": "[ok]", "error": "[err]"
    })
```

**Compatibility Risk:** NONE ✅
The current design is ALREADY compatible with the v2.1 addendum requirements. The Hook dataclass stores plugin ownership as a string identifier, not an object reference, which means:

1. No changes needed to Hook dataclass
2. No changes needed to HookRegistry
3. No changes needed to HookExecutor
4. Plugin name field can be used directly for v2.1 provider identification

---

## Hook Dataclass Structure

### All Fields (13 total)

| Field | Type | Required | Default | Purpose |
|-------|------|----------|---------|---------|
| `name` | str | ✅ | - | Unique hook name (within plugin) |
| `plugin_name` | str | ✅ | - | **Plugin identifier (string)** |
| `event_type` | EventType | ✅ | - | Event to listen for |
| `priority` | int | ✅ | - | Execution order (higher = first) |
| `callback` | Callable | ✅ | - | Async handler function |
| `enabled` | bool | - | True | Whether hook is active |
| `timeout` | Optional[int] | - | None | Max execution time (seconds) |
| `retry_attempts` | Optional[int] | - | None | Retry count on failure |
| `error_action` | Optional[str] | - | None | "continue" or "stop" |
| `status` | HookStatus | - | PENDING | Current execution state |
| `status_area` | str | - | "A" | Status display location |
| `icon_set` | Dict[str, str] | - | {...} | Status icons |

### Hook Priority Levels (HookPriority enum)

```python
class HookPriority(Enum):
    SYSTEM = 1000           # System-level hooks (startup/shutdown)
    SECURITY = 900          # Security/validation hooks
    PREPROCESSING = 500     # Before main processing
    LLM = 100              # LLM-specific operations
    POSTPROCESSING = 50     # After main processing
    DISPLAY = 10            # UI/display hooks
```

**Execution Order:** Hooks execute from highest priority (1000) to lowest (10) within each event type.

### Event Types (43 total)

The system supports 43 distinct event types across 7 categories:

1. **User Input Events (3)**
   - USER_INPUT_PRE, USER_INPUT, USER_INPUT_POST

2. **Key Press Events (3)**
   - KEY_PRESS_PRE, KEY_PRESS, KEY_PRESS_POST

3. **Paste Events (1)**
   - PASTE_DETECTED

4. **LLM Events (8)**
   - LLM_REQUEST_PRE, LLM_REQUEST, LLM_REQUEST_POST
   - LLM_RESPONSE_PRE, LLM_RESPONSE, LLM_RESPONSE_POST
   - LLM_THINKING, CANCEL_REQUEST

5. **Tool Events (3)**
   - TOOL_CALL_PRE, TOOL_CALL, TOOL_CALL_POST

6. **System Events (3)**
   - SYSTEM_STARTUP, SYSTEM_SHUTDOWN, RENDER_FRAME

7. **Input Rendering Events (3)**
   - INPUT_RENDER_PRE, INPUT_RENDER, INPUT_RENDER_POST

8. **Command Menu Events (6)**
   - COMMAND_MENU_SHOW, COMMAND_MENU_NAVIGATE, COMMAND_MENU_SELECT
   - COMMAND_MENU_HIDE, COMMAND_MENU_RENDER, COMMAND_MENU_FILTER

9. **Status Display Events (3)**
   - STATUS_VIEW_CHANGED, STATUS_CONTENT_UPDATE, STATUS_BLOCK_RESIZE

10. **Slash Command Events (4)**
    - SLASH_COMMAND_DETECTED, SLASH_COMMAND_EXECUTE
    - SLASH_COMMAND_COMPLETE, SLASH_COMMAND_ERROR

11. **Command Output Events (1)**
    - COMMAND_OUTPUT_DISPLAY

12. **Message Injection Events (3)**
    - ADD_MESSAGE, PRE_MESSAGE_INJECT, POST_MESSAGE_INJECT

13. **LLM Continuation Control (1)**
    - TRIGGER_LLM_CONTINUE

14. **Modal Events (8)**
    - MODAL_TRIGGER, STATUS_MODAL_TRIGGER, STATUS_MODAL_RENDER
    - LIVE_MODAL_TRIGGER, MODAL_COMMAND_SELECTED
    - PAUSE_RENDERING, RESUME_RENDERING, MODAL_SHOW
    - MODAL_HIDE, MODAL_SAVE, FULLSCREEN_INPUT
    - COMMAND_MENU_ACTIVATE

15. **Status Takeover Events (4)**
    - STATUS_TAKEOVER_START, STATUS_TAKEOVER_NAVIGATE
    - STATUS_TAKEOVER_ACTION, STATUS_TAKEOVER_END

---

## Hook Registration Flow

### How Plugins Register Hooks

**Step 1: Create Hook Objects**
```python
# Plugin creates hooks during __init__
self.hooks = [
    Hook(
        name="render_fancy_input",
        plugin_name=self.name,      # String identifier
        event_type=EventType.INPUT_RENDER,
        priority=HookPriority.DISPLAY.value,
        callback=self._render_fancy_input
    )
]
```

**Step 2: Register via Event Bus**
```python
async def register_hooks(self) -> None:
    for hook in self.hooks:
        await self.event_bus.register_hook(hook)
```

**Step 3: Registry Processing**
```python
# In HookRegistry.register_hook()
hook_key = f"{hook.plugin_name}.{hook.name}"  # e.g., "enhanced_input.render_fancy_input"

# Store hooks by event type
self.hooks[hook.event_type].append(hook)

# Sort by priority (highest first)
self.hooks[hook.event_type].sort(key=lambda h: h.priority, reverse=True)

# Track metadata
self.hook_metadata[hook_key] = {
    "event_type": hook.event_type.value,
    "priority": hook.priority,
    "plugin_name": hook.plugin_name,  # String field
    "enabled": hook.enabled,
    ...
}
```

**Key Points:**
- Hooks are organized by EventType in a dictionary
- Within each event type, hooks are sorted by priority (descending)
- Each hook gets a unique key: `"plugin_name.hook_name"`
- Plugin name is stored as string throughout

---

## Hook Execution Flow

### How Hooks Are Called

**Step 1: Event Emission**
```python
await event_bus.emit_with_hooks(
    EventType.USER_INPUT,
    {"message": "hello"},
    "system"
)
```

**Step 2: Retrieve Hooks**
```python
# In HookRegistry.get_hooks_for_event()
hooks = self.hooks.get(event_type, [])

# Filter enabled hooks only
enabled_hooks = [hook for hook in hooks if hook.enabled]

# Return sorted by priority
return enabled_hooks  # Already sorted from registration
```

**Step 3: Execute Sequentially**
```python
# In HookExecutor.execute_hook()
for hook in hooks:
    hook_key = f"{hook.plugin_name}.{hook.name}"

    # Execute with timeout and retry logic
    result = await asyncio.wait_for(
        hook.callback(event.data, event),  # Async callback
        timeout=timeout
    )

    # Handle data transformation
    if isinstance(result, dict) and "data" in result:
        event.data.update(result["data"])
```

**Step 4: Context Passing**
- All hooks receive the same Event object
- Event.data is a mutable dict that hooks can modify
- Changes persist through the hook chain
- Event.cancelled flag stops execution

**Error Handling:**
- Each hook wrapped in try/except
- Failed hooks logged but don't stop chain (unless error_action="stop")
- Retry logic with exponential backoff (up to 3 retries by default)
- Timeout enforcement per-hook

---

## Plugin Inventory

### Complete List: 9 Active Plugins

| # | Plugin Name | File | Hook Usage | Event Types Used |
|---|-------------|------|------------|------------------|
| 1 | **enhanced_input** | enhanced_input_plugin.py | ✅ Yes | INPUT_RENDER |
| 2 | **hook_monitoring** | hook_monitoring_plugin.py | ✅ Yes | ALL (30+ events) |
| 3 | **query_enhancer** | query_enhancer_plugin.py | ✅ Yes | USER_INPUT_PRE |
| 4 | **workflow_enforcement** | workflow_enforcement_plugin.py | ✅ Yes | LLM_RESPONSE_POST, USER_INPUT_PRE, LLM_REQUEST_PRE |
| 5 | **system_commands** | system_commands_plugin.py | ❌ No | None |
| 6 | **tmux** | tmux_plugin.py | ❌ No | None |
| 7 | **save_conversation** | save_conversation_plugin.py | ❌ No | None (commands only) |
| 8 | **resume_conversation** | resume_conversation_plugin.py | ✅ Yes | MODAL_COMMAND_SELECTED |
| 9 | **agent_orchestrator** | agent_orchestrator/plugin.py | ✅ Yes | LLM_RESPONSE_POST, USER_INPUT_PRE |
| 10 | **background_tasks** | background_tasks/plugin.py | ✅ Yes | LLM_RESPONSE_POST, USER_INPUT_PRE |

### Hook Registration Patterns

**Pattern 1: Direct Hook Creation (Most Common)**
```python
# Used by: enhanced_input, query_enhancer, resume_conversation
Hook(
    name="hook_name",
    plugin_name=self.name,
    event_type=EventType.EVENT_TYPE,
    priority=HookPriority.PRIORITY.value,
    callback=self._handler_method
)
```

**Pattern 2: Bulk Hook Creation**
```python
# Used by: hook_monitoring (creates 30+ hooks)
def _create_all_hooks(self) -> List[Hook]:
    hooks = []
    for event_type in monitored_events:
        hooks.append(Hook(
            name=f"monitor_{event_type.value}",
            plugin_name=self.name,
            event_type=event_type,
            priority=HookPriority.POSTPROCESSING.value,
            callback=self._log_hook_execution
        ))
    return hooks
```

**Pattern 3: No Hooks (Command-Only Plugins)**
```python
# Used by: system_commands, tmux, save_conversation
# These plugins only register slash commands, no hooks
async def register_hooks(self) -> None:
    pass  # No hooks registered
```

### Most Used Event Types

**Top 5 Event Types by Usage:**

1. **LLM_RESPONSE_POST** (4 plugins)
   - hook_monitoring: Monitor response processing
   - workflow_enforcement: Detect todo lists
   - agent_orchestrator: Parse XML commands
   - background_tasks: Parse XML commands

2. **USER_INPUT_PRE** (4 plugins)
   - hook_monitoring: Track input events
   - query_enhancer: Enhance queries
   - workflow_enforcement: Handle workflow commands
   - agent_orchestrator: Trigger instruction injection
   - background_tasks: Trigger instruction injection

3. **INPUT_RENDER** (1 plugin)
   - enhanced_input: Render bordered input box

4. **MODAL_COMMAND_SELECTED** (1 plugin)
   - resume_conversation: Handle modal selections

5. **LLM_REQUEST_PRE** (1 plugin)
   - workflow_enforcement: Inject workflow context

---

## Custom Hook Patterns

### Pattern 1: Display Filtering
```python
# Used by: agent_orchestrator, background_tasks
# These plugins register display filters to strip XML from output
DisplayFilterRegistry.register(
    name="agent_orchestrator",
    filter_fn=self._strip_orchestrator_xml,
    message_types=[MessageType.ASSISTANT],
    priority=100
)
```

### Pattern 2: Keyword-Based Instruction Injection
```python
# Used by: agent_orchestrator, background_tasks
# Triggers when keywords detected in user input
async def _on_user_input(self, data: dict, event) -> dict:
    user_input = data.get("message", "").lower()
    if any(kw in user_input for kw in TRIGGER_KEYWORDS):
        # Inject instructions as hidden system message
        data["message"] = f"<sys_msg>\n{INSTRUCTIONS}\n</sys_msg>\n\n{original}"
    return data
```

### Pattern 3: Result Injection
```python
# Used by: agent_orchestrator, background_tasks
# Injects tool results back into conversation
await self.message_injector.inject(
    source="agent_orchestrator",
    content=result_text,
    trigger_llm=False  # Don't auto-continue
)
```

### Pattern 4: Event Emission
```python
# Used by: agent_orchestrator, background_tasks
# Plugins emit custom events for tracking
await self.event_bus.emit_with_hooks(
    "agent_spawned",  # Custom event (not in EventType enum)
    {"name": agent.name},
    "agent_orchestrator"
)
```

---

## Integration Points for v2.1

### What Changes for OpenAI SDK Integration

**Current State (v2.0):**
```python
Hook(
    name="my_hook",
    plugin_name="my_plugin",      # String identifier
    event_type=EventType.LLM_REQUEST,
    priority=HookPriority.LLM.value,
    callback=self._handle_request
)
```

**Required v2.1 Changes:**
```python
# 1. Add provider field to Hook dataclass
@dataclass
class Hook:
    name: str
    plugin_name: str
    provider: str = "anthropic"    # NEW FIELD - default to "anthropic"
    event_type: EventType
    ...

# 2. Update hook creation
Hook(
    name="my_hook",
    plugin_name="my_plugin",
    provider="openai",             # Specify provider
    event_type=EventType.LLM_REQUEST,
    priority=HookPriority.LLM.value,
    callback=self._handle_request
)

# 3. Filter hooks by provider during routing
# In EventBus.emit_with_hooks()
hooks = [h for h in registry.get_hooks_for_event(event_type)
         if h.provider == target_provider]
```

### Files That Need Modification

**Core Changes:**
1. `/Users/malmazan/dev/kollabor-cli/core/events/models.py` (line 127-158)
   - Add `provider: str = "anthropic"` field to Hook dataclass

2. `/Users/malmazan/dev/kollabor-cli/core/events/registry.py` (line 26-66)
   - Update `register_hook()` to store provider metadata
   - No changes needed (already stores plugin_name as string)

3. `/Users/malmazan/dev/kollabor-cli/core/events/bus.py` (approximate)
   - Update `emit_with_hooks()` to accept provider parameter
   - Filter hooks by provider before execution

4. `/Users/malmazan/dev/kollabor-cli/core/llm/llm_service.py`
   - Pass provider name when emitting events
   - Example: `await self.event_bus.emit_with_hooks(..., provider="openai")`

**Plugin Changes (Minimal):**
- Most plugins don't need changes (default to "anthropic")
- Only plugins that work with both providers need explicit provider field
- Example: query_enhancer might need both:
  ```python
  Hook(
      name="enhance_anthropic",
      plugin_name="query_enhancer",
      provider="anthropic",
      event_type=EventType.USER_INPUT_PRE,
      ...
  )
  Hook(
      name="enhance_openai",
      plugin_name="query_enhancer",
      provider="openai",
      event_type=EventType.USER_INPUT_PRE,
      ...
  )
  ```

### Backward Compatibility Strategy

**Phase 1: Add Field with Default**
```python
provider: str = "anthropic"  # Default maintains backward compatibility
```

**Phase 2: Gradual Migration**
- All existing hooks default to "anthropic" provider
- New OpenAI hooks explicitly set provider="openai"
- Event bus accepts provider parameter but defaults to "anthropic"

**Phase 3: Full Multi-Provider Support**
- Event filtering by provider becomes standard
- Plugins can register provider-specific hooks
- LLM service routes events based on active provider

---

## Data Flow Analysis

### Event Data Structure

```python
Event(
    type=EventType.USER_INPUT,        # What happened
    data={                              # Event payload
        "message": "hello world",
        "enhanced_message": "..."
    },
    source="system",                    # Who emitted it
    timestamp=datetime.now(),          # When it happened
    processed=False,                    # Status flags
    cancelled=False,
    result={}                           # Hook results
)
```

### Context Mutation Through Hooks

```python
# Initial event data
data = {"message": "hello"}

# Hook 1: Query Enhancer (priority=500)
data["enhanced_message"] = "hello, please help me with..."

# Hook 2: Workflow Enforcement (priority=500)
data["workflow_context"] = {...}

# Final data available to all subsequent hooks
```

### Hook Result Aggregation

```python
# Each hook can return results
result = await hook.callback(event.data, event)
# Returns: {"status": "modified", "count": 5}

# Results tracked in Event.result
event.result["hook_name"] = result
```

---

## Performance Characteristics

### Hook Execution Overhead

**Per-Event Cost:**
1. Hook lookup: O(1) dictionary access
2. Priority sorting: O(n log n) but cached from registration
3. Filter enabled: O(n) where n = hooks per event type
4. Sequential execution: O(n × avg_hook_time)

**Typical Numbers:**
- Hooks per event: 2-5 (average)
- Hook execution time: 1-50ms (typical)
- Total overhead: 10-250ms per event

**Bottlenecks:**
- Hook monitoring plugin (registers 30+ hooks)
- Synchronous execution (no parallelism)
- Retry logic on failures

### Optimization Opportunities

1. **Lazy Filtering**: Filter enabled hooks during registration, not execution
2. **Hook Batching**: Group hooks by priority to reduce sorting
3. **Async Parallel**: Execute independent hooks concurrently
4. **Hook Monitoring**: Make it opt-in (currently always on)

---

## Security Considerations

### Hook Priority Risks

**High Priority Hooks (SYSTEM=1000):**
- Can block all other hooks
- Can cancel events
- Must be carefully validated

**Mitigation:**
- SYSTEM priority only for core system plugins
- Security priority (900) for validation hooks
- Preprocessing hooks (500) for safe transformations

### Error Action Behavior

**"continue" (default):**
- Failed hooks don't stop chain
- Logged but execution continues
- Suitable for non-critical hooks

**"stop":**
- Failed hooks cancel event
- Subsequent hooks don't execute
- Suitable for security/validation hooks

### Timeout Protection

**Default Timeout:** 30 seconds per hook
- Prevents runaway hooks
- Configurable per-hook
- Maximum: 5 minutes (300 seconds)

---

## Testing Recommendations

### Unit Test Coverage Needed

1. **Hook Dataclass**
   - Verify plugin_name field is string
   - Test serialization/deserialization
   - Validate default values

2. **Hook Registry**
   - Test hook registration with duplicates
   - Verify priority ordering
   - Test enable/disable functionality

3. **Hook Executor**
   - Test timeout enforcement
   - Verify retry logic
   - Test error action behavior

4. **Event Bus**
   - Test event emission with hooks
   - Verify context mutation
   - Test cancellation behavior

### Integration Test Scenarios

1. **Multi-Provider Routing**
   - Register hooks for different providers
   - Emit events with provider parameter
   - Verify correct hooks execute

2. **Hook Priority Ordering**
   - Register multiple hooks for same event
   - Verify execution order
   - Test priority conflicts

3. **Error Handling**
   - Hook raises exception
   - Hook times out
   - Event cancellation

---

## Conclusion

### Summary of Findings

1. ✅ **Hook uses `plugin_name: str` field** (NOT object reference)
2. ✅ **Design is already compatible with v2.1 requirements**
3. ✅ **Only minor changes needed:**
   - Add `provider: str` field to Hook dataclass
   - Update event bus to filter by provider
   - Update LLM service to pass provider parameter

4. ✅ **No breaking changes required**
   - Backward compatible with default provider="anthropic"
   - Existing hooks continue to work
   - Migration can be gradual

### Risk Assessment

**Compatibility Risk:** LOW ✅
- Current design uses string identifiers
- No object references to break
- Simple field addition

**Performance Risk:** LOW ✅
- Provider filtering is O(n) operation
- Already filtering by enabled status
- No significant overhead

**Migration Risk:** LOW ✅
- Default value maintains compatibility
- Plugins opt-in to provider specification
- No forced migration path

### Next Steps

1. Add `provider` field to Hook dataclass
2. Update event bus to accept provider parameter
3. Implement provider-based filtering
4. Update LLM service to pass provider
5. Test multi-provider hook routing
6. Document provider hook patterns

---

**END OF AUDIT**

Generated by: Phase0AnalyzeHooks
Date: 2025-01-14
Status: ✅ COMPLETE
