-
Notifications
You must be signed in to change notification settings - Fork 11
Open
Description
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 requests4. 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
- Tool execution: Wrap all tool calls with pre/post hooks
- Session lifecycle: Fire start/stop hooks from session manager
- Config loading: Parse hooks from profile YAML
- 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
Labels
No labels