Class: SwarmSDK::Hooks::Executor
- Inherits:
-
Object
- Object
- SwarmSDK::Hooks::Executor
- Defined in:
- lib/swarm_sdk/hooks/executor.rb
Overview
Executes hooks with proper chaining, error handling, and logging
The executor:
- Chains multiple hooks for the same event
- Handles errors and blocking (via Error or Result.halt)
- Respects matcher patterns (only runs matching hooks)
- Logs execution for debugging
- Returns Result indicating action to take
Instance Method Summary collapse
-
#execute(event:, context:, callbacks: []) ⇒ Result
Execute all hooks for an event.
-
#execute_safe(event:, context:, callbacks: []) ⇒ Result
Execute hooks and return result safely (without raising).
-
#execute_single(hook_def, context) ⇒ Result?
Execute a single hook.
-
#initialize(registry, logger: nil) ⇒ Executor
constructor
A new instance of Executor.
Constructor Details
#initialize(registry, logger: nil) ⇒ Executor
26 27 28 29 |
# File 'lib/swarm_sdk/hooks/executor.rb', line 26 def initialize(registry, logger: nil) @registry = registry @logger = logger || Logger.new(nil) # Null logger if not provided end |
Instance Method Details
#execute(event:, context:, callbacks: []) ⇒ Result
Execute all hooks for an event
Execution order:
- Swarm-level defaults (from registry)
- Agent-specific hooks
- Within each group, by priority (highest first)
Hooks must return:
- Result - to control execution flow (halt, replace, reprompt, continue)
- nil - treated as continue with unmodified context
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/swarm_sdk/hooks/executor.rb', line 47 def execute(event:, context:, callbacks: []) # Combine swarm defaults and agent hooks all_hooks = @registry.get_defaults(event) + callbacks # Filter by matcher (for tool events) if context.tool_event? && context.tool_name all_hooks = all_hooks.select { |hook| hook.matches?(context.tool_name) } end # Execute hooks in order all_hooks.each do |hook_def| result = execute_single(hook_def, context) # Only Result controls flow - nil means continue next unless result.is_a?(Result) # Early return for control flow actions return result if result.halt? || result.replace? || result.reprompt? || result.finish_agent? || result.finish_swarm? # Update context if continue with modified context context = result.value if result.continue? && result.value.is_a?(Context) end # All hooks executed successfully - continue with final context Result.continue(context) rescue Error => e # Re-raise with context for better error messages @logger.error("Hook blocked execution: #{e.message}") raise rescue StandardError => e # Wrap unexpected errors @logger.error("Hook failed unexpectedly: #{e.class} - #{e.message}") @logger.error(e.backtrace.join("\n")) raise Error.new( "Hook failed: #{e.message}", context: context, ) end |
#execute_safe(event:, context:, callbacks: []) ⇒ Result
Execute hooks and return result safely (without raising)
This is a convenience method that catches Error and converts it to a halt result, making it easier to use in control flow.
138 139 140 141 142 143 |
# File 'lib/swarm_sdk/hooks/executor.rb', line 138 def execute_safe(event:, context:, callbacks: []) execute(event: event, context: context, callbacks: callbacks) rescue Error => e @logger.warn("Execution blocked by hook: #{e.message}") Result.halt(e.) end |
#execute_single(hook_def, context) ⇒ Result?
Execute a single hook
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/swarm_sdk/hooks/executor.rb', line 92 def execute_single(hook_def, context) proc = hook_def.resolve_proc(@registry) @logger.debug("Executing hook for #{context.event} (agent: #{context.agent_name})") # Execute hook with context as parameter # Users can access convenience methods via context parameter: # hook(:event) { |ctx| ctx.halt("msg") } # This preserves lexical scope and access to surrounding instance variables proc.call(context) rescue Error # Pass through blocking errors raise rescue StandardError => e # Wrap other errors with context and detailed debugging hook_name = hook_def.named_hook? ? hook_def.proc : "anonymous" # Log detailed error info for debugging @logger.error("=" * 80) @logger.error("HOOK EXECUTION ERROR") @logger.error(" Hook: #{hook_name}") @logger.error(" Event: #{context.event}") @logger.error(" Agent: #{context.agent_name}") @logger.error(" Proc class: #{proc.class}") @logger.error(" Proc arity: #{proc.arity} (expected: 1 for |context|)") @logger.error(" Error: #{e.class.name}: #{e.message}") @logger.error(" Backtrace:") e.backtrace.first(15).each { |line| @logger.error(" #{line}") } @logger.error("=" * 80) raise Error.new( "Hook #{hook_name} failed: #{e.message}", hook_name: hook_name, context: context, ) end |