Module: SwarmSDK::Agent::ChatHelpers::HookIntegration

Included in:
SwarmSDK::Agent::Chat
Defined in:
lib/swarm_sdk/agent/chat_helpers/hook_integration.rb

Overview

Integrates SwarmSDK's hook system with Agent::Chat

Responsibilities:

  • Setup hook system (registry, executor, agent hooks)
  • Provide trigger methods for all hook events
  • Wrap ask() to inject user_prompt hooks
  • Handle hook results (halt, replace, continue, reprompt)

This module is included in Agent::Chat and provides methods for triggering hooks. It overrides ask() to inject user_prompt hooks, but does NOT override handle_tool_calls (that's handled in Agent::Chat with explicit hook calls).

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#hook_agent_hooksObject (readonly)

Expose hook system components for ContextTracker



19
20
21
# File 'lib/swarm_sdk/agent/chat_helpers/hook_integration.rb', line 19

def hook_agent_hooks
  @hook_agent_hooks
end

#hook_executorObject (readonly)

Expose hook system components for ContextTracker



19
20
21
# File 'lib/swarm_sdk/agent/chat_helpers/hook_integration.rb', line 19

def hook_executor
  @hook_executor
end

#hook_swarmObject (readonly)

Expose hook system components for ContextTracker



19
20
21
# File 'lib/swarm_sdk/agent/chat_helpers/hook_integration.rb', line 19

def hook_swarm
  @hook_swarm
end

Instance Method Details

#add_hook(event, matcher: nil, priority: 0, &block) ⇒ Object

Add a hook programmatically at runtime

This allows agents to add hooks dynamically, which is useful for implementing adaptive behavior or runtime monitoring.

Parameters:

  • event (Symbol)

    Event type (e.g., :pre_tool_use)

  • matcher (String, Regexp, nil) (defaults to: nil)

    Optional regex pattern for tool names

  • priority (Integer) (defaults to: 0)

    Execution priority (higher = earlier)

  • block (Proc)

    Hook implementation

Raises:

  • (ArgumentError)


59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/swarm_sdk/agent/chat_helpers/hook_integration.rb', line 59

def add_hook(event, matcher: nil, priority: 0, &block)
  raise ArgumentError, "Hooks not set up. Call setup_hooks first." unless @hook_executor

  definition = Hooks::Definition.new(
    event: event,
    matcher: matcher,
    priority: priority,
    proc: block,
  )

  @hook_agent_hooks[event] ||= []
  @hook_agent_hooks[event] << definition
  @hook_agent_hooks[event].sort_by! { |cb| -cb.priority }
end

#check_context_warningsObject

Override check_context_warnings to trigger our hook system

This wraps the default context warning behavior to also trigger hooks. Unified implementation that:

  1. Emits context_threshold_hit events (for snapshot reconstruction)
  2. Optionally triggers automatic compression at 60% (if no custom handler)
  3. Emits context_limit_warning events (backward compatibility)
  4. Triggers user-defined context_warning hooks


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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/swarm_sdk/agent/chat_helpers/hook_integration.rb', line 93

def check_context_warnings
  return unless respond_to?(:context_usage_percentage)

  current_percentage = context_usage_percentage

  Context::CONTEXT_WARNING_THRESHOLDS.each do |threshold|
    # Only warn once per threshold
    next if @agent_context.warning_threshold_hit?(threshold)
    next if current_percentage < threshold

    # Mark threshold as hit
    @agent_context.hit_warning_threshold?(threshold)

    # Emit context_threshold_hit event (for snapshot reconstruction) - CRITICAL
    LogStream.emit(
      type: "context_threshold_hit",
      agent: @agent_context.name,
      threshold: threshold,
      current_usage_percentage: current_percentage.round(2),
    )

    # Check if user has defined custom handler for context_warning
    # Custom handlers take responsibility for managing context at this threshold
    has_custom_handler = (@hook_agent_hooks[:context_warning] || []).any?

    # Trigger automatic compression at 60% ONLY if no custom handler
    compression_triggered = false
    if threshold == SwarmSDK.config.context_compression_threshold && !has_custom_handler
      compressed_count = apply_automatic_compression
      compression_triggered = compressed_count > 0
    end

    # Emit legacy context_limit_warning for backwards compatibility
    LogStream.emit(
      type: "context_limit_warning",
      agent: @agent_context.name,
      model: model_id,
      threshold: "#{threshold}%",
      current_usage: "#{current_percentage}%",
      tokens_used: cumulative_total_tokens,
      tokens_remaining: tokens_remaining,
      context_limit: context_limit,
      metadata: @agent_context.,
      compression_triggered: compression_triggered,
    )

    # Trigger hook system (user-defined handlers)
    trigger_context_warning(threshold, current_percentage) if @hook_executor
  end
end

#setup_hooks(registry:, agent_definition:, swarm: nil) ⇒ void

This method returns an undefined value.

Setup the hook system for this agent chat

This must be called after setup_context and before the first ask/complete. It wires up the hook system to trigger at the right times.

Parameters:

  • registry (Hooks::Registry)

    Shared registry for named hooks and swarm defaults

  • agent_definition (Agent::Definition)

    Agent configuration with hooks

  • swarm (Swarm, nil) (defaults to: nil)

    Reference to swarm for context



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/swarm_sdk/agent/chat_helpers/hook_integration.rb', line 30

def setup_hooks(registry:, agent_definition:, swarm: nil)
  @hook_registry = registry
  @hook_swarm = swarm
  @hook_executor = Hooks::Executor.new(registry, logger: RubyLLM.logger)

  # Extract agent hooks based on format
  hooks = agent_definition.hooks || {}

  # Check if hooks are pre-parsed HookDefinition objects (from DSL)
  # or raw YAML hash (to be processed by Hooks::Adapter in pass_5)
  @hook_agent_hooks = if hooks.is_a?(Hash) && hooks.values.all? { |v| v.is_a?(Array) && v.all? { |item| item.is_a?(Hooks::Definition) } }
    # DSL hooks - already parsed, use them
    hooks
  else
    # YAML hooks - raw hash, will be processed in pass_5 by Hooks::Adapter
    # For now, use empty hash (pass_5 will add them later)
    {}
  end
end

#trigger_post_tool_use(result, tool_call:) ⇒ Object, Hash

Trigger post_tool_use hooks

Should be called by Agent::Chat after tool execution. Returns modified result if hook replaces it, or a special marker for finish actions.

Parameters:

  • result (String, Object)

    Tool execution result

  • tool_call (RubyLLM::ToolCall)

    Tool call object with full context

Returns:

  • (Object, Hash)

    Modified result if hook replaces it, hash with :finish_agent or :finish_swarm if finishing, otherwise original result



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/swarm_sdk/agent/chat_helpers/hook_integration.rb', line 191

def trigger_post_tool_use(result, tool_call:)
  return result unless @hook_executor

  # Extract tracking digest for Read/MemoryRead tools
   = extract_tool_tracking_digest(tool_call, result)

  context = build_hook_context(
    event: :post_tool_use,
    tool_result: wrap_tool_result(tool_call.id, tool_call.name, result),
    metadata: ,
  )

  agent_hooks = @hook_agent_hooks[:post_tool_use] || []

  hook_result = @hook_executor.execute_safe(
    event: :post_tool_use,
    context: context,
    callbacks: agent_hooks,
  )

  # Return modified result or finish markers
  if hook_result.replace?
    hook_result.value
  elsif hook_result.finish_agent?
    { __finish_agent__: true, message: hook_result.value }
  elsif hook_result.finish_swarm?
    { __finish_swarm__: true, message: hook_result.value }
  else
    result
  end
end

#trigger_pre_tool_use(tool_call) ⇒ Hash

Trigger pre_tool_use hooks

Should be called by Agent::Chat before tool execution. Returns a hash indicating whether to proceed and any custom result.

Parameters:

  • tool_call (RubyLLM::ToolCall)

    Tool call from LLM

Returns:

  • (Hash)

    { proceed: true/false, custom_result: result (if any) }



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/swarm_sdk/agent/chat_helpers/hook_integration.rb', line 151

def trigger_pre_tool_use(tool_call)
  return { proceed: true } unless @hook_executor

  context = build_hook_context(
    event: :pre_tool_use,
    tool_call: wrap_tool_call_to_hooks(tool_call),
  )

  agent_hooks = @hook_agent_hooks[:pre_tool_use] || []

  result = @hook_executor.execute_safe(
    event: :pre_tool_use,
    context: context,
    callbacks: agent_hooks,
  )

  # Return custom result if hook provides one
  if result.replace?
    { proceed: false, custom_result: result.value }
  elsif result.halt?
    { proceed: false, custom_result: result.value || "Tool execution blocked by hook" }
  elsif result.finish_agent?
    # Finish agent execution immediately with this message
    { proceed: false, finish_agent: true, custom_result: result.value }
  elsif result.finish_swarm?
    # Finish entire swarm execution immediately with this message
    { proceed: false, finish_swarm: true, custom_result: result.value }
  else
    { proceed: true }
  end
end