Skip to content

[Critical] Implement Enhanced Hooks System #18

@marklicata

Description

@marklicata

Summary

Expand the hooks system from basic approval hooks to a comprehensive event-driven extensibility system with 10+ event types, external command execution, and LLM-based hooks.

Current State

Amplifier CLI has only approval-related hooks:

  • on_approval_request - fires when tool needs approval
  • Limited to approval decisions

Missing:

  • Pre/post tool execution hooks
  • Session lifecycle hooks
  • Notification hooks
  • External command hooks
  • LLM-powered hooks for dynamic decisions

Proposed Implementation

1. Event Types to Add

Event When It Fires Use Case
PreToolUse Before any tool executes Validation, logging, modification
PostToolUse After tool completes Logging, cleanup, chaining
SessionStart When session begins Environment setup, announcements
SessionStop When session ends Cleanup, reporting
PromptSubmit When user submits prompt Transform input, logging
ResponseComplete When AI finishes response Post-processing
Error When error occurs Custom error handling
Checkpoint When checkpoint created Backup, sync
ModelSwitch When model changes Logging, quota tracking
MemoryUpdate When memory file changes Sync, backup

2. Hook Configuration in YAML

hooks:
  PreToolUse:
    # External command hook
    - type: command
      command: "python ~/.amplifier/hooks/pre_tool.py"
      timeout_ms: 5000
      on_failure: "warn"  # warn | block | ignore
      
    # Inline matcher hook  
    - type: matcher
      match:
        tool: "Bash"
        args:
          command: "rm *"
      action: "block"
      message: "Destructive rm commands require confirmation"

  PostToolUse:
    - type: command
      command: "~/.amplifier/hooks/log_tool.sh"
      async: true  # Fire and forget

  SessionStart:
    - type: command
      command: "echo 'Session started' >> ~/.amplifier/session.log"
      
  SessionStop:
    - type: command  
      command: "python ~/.amplifier/hooks/cleanup.py"

3. Hook Types

Command Hooks (External)

- type: command
  command: "path/to/script"    # Receives JSON on stdin
  timeout_ms: 5000             # Kill after timeout
  on_failure: "warn"           # warn | block | ignore
  async: false                 # Wait for completion?

Script receives on stdin:

{
  "event": "PreToolUse",
  "tool": "Bash",
  "args": {"command": "git status"},
  "session_id": "abc123",
  "timestamp": "2024-01-15T10:30:00Z"
}

Script returns on stdout:

{
  "action": "allow",           // allow | block | modify
  "modified_args": null,       // If action=modify
  "message": null              // Optional message to display
}

Matcher Hooks (Inline Rules)

- type: matcher
  match:
    tool: "Write"              # Tool name (glob supported)
    args:
      path: "**/.env*"         # Glob pattern matching
  action: "block"
  message: "Cannot modify environment files"

LLM Hooks (AI-Powered Decisions)

- type: llm
  model: "claude-3-haiku"      # Fast, cheap model for hooks
  prompt: |
    Evaluate if this tool use is safe:
    Tool: {{tool}}
    Args: {{args}}
    
    Respond with JSON: {"safe": true/false, "reason": "..."}
  max_tokens: 100
  cache: true                  # Cache identical requests

4. New Module Structure

src/amplifier_app_cli/hooks/
├── __init__.py
├── events.py          # Event type definitions
├── registry.py        # Hook registration and dispatch
├── runners/
│   ├── __init__.py
│   ├── command.py     # External command execution
│   ├── matcher.py     # Pattern matching hooks
│   └── llm.py         # LLM-based hooks
├── context.py         # Hook execution context
└── config.py          # Hook configuration loading

5. Core Interfaces

from enum import Enum
from typing import Protocol, Any
from dataclasses import dataclass

class HookEvent(Enum):
    PRE_TOOL_USE = "PreToolUse"
    POST_TOOL_USE = "PostToolUse"
    SESSION_START = "SessionStart"
    SESSION_STOP = "SessionStop"
    PROMPT_SUBMIT = "PromptSubmit"
    RESPONSE_COMPLETE = "ResponseComplete"
    ERROR = "Error"
    CHECKPOINT = "Checkpoint"
    MODEL_SWITCH = "ModelSwitch"
    MEMORY_UPDATE = "MemoryUpdate"

@dataclass
class HookContext:
    event: HookEvent
    session_id: str
    tool: str | None = None
    args: dict[str, Any] | None = None
    result: Any = None
    error: Exception | None = None
    metadata: dict[str, Any] = field(default_factory=dict)

@dataclass
class HookResult:
    action: Literal["allow", "block", "modify", "continue"]
    modified_args: dict[str, Any] | None = None
    message: str | None = None
    metadata: dict[str, Any] = field(default_factory=dict)

class HookRunner(Protocol):
    async def run(self, context: HookContext) -> HookResult: ...

class HookRegistry:
    def register(self, event: HookEvent, hook: HookRunner) -> None: ...
    async def dispatch(self, context: HookContext) -> HookResult: ...

6. Integration Points

  1. Tool execution: Wrap all tool calls with pre/post hooks
  2. Session lifecycle: Fire start/stop hooks from session manager
  3. Config loading: Parse hooks from profile YAML
  4. Error handling: Fire error hooks on exceptions

Acceptance Criteria

  • All 10 event types implemented
  • Command hooks execute external scripts with JSON I/O
  • Matcher hooks support glob patterns
  • LLM hooks can call fast models for decisions
  • Hooks can block, allow, or modify tool calls
  • Async hooks fire without blocking
  • Timeout handling for slow hooks
  • Hook failures don't crash the session
  • Hooks receive full context (session, tool, args)
  • Unit tests for each hook type
  • Integration tests for hook dispatch

Related

  • Depends on: Permission Rules System (for consistent blocking behavior)
  • Blocks: None (but enables many advanced features)

Estimated Effort

High - 2-3 weeks

Files to Create/Modify

  • src/amplifier_app_cli/hooks/ (new module)
  • src/amplifier_app_cli/lib/session.py (lifecycle hooks)
  • src/amplifier_app_cli/ui/approval.py (pre-tool hooks)
  • Profile schema updates

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions