swarm_memory 2.1.3 → 2.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/lib/claude_swarm/claude_mcp_server.rb +1 -0
  3. data/lib/claude_swarm/cli.rb +5 -18
  4. data/lib/claude_swarm/configuration.rb +2 -15
  5. data/lib/claude_swarm/mcp_generator.rb +1 -0
  6. data/lib/claude_swarm/openai/chat_completion.rb +4 -12
  7. data/lib/claude_swarm/openai/executor.rb +3 -1
  8. data/lib/claude_swarm/openai/responses.rb +13 -32
  9. data/lib/claude_swarm/version.rb +1 -1
  10. data/lib/swarm_cli/commands/run.rb +2 -2
  11. data/lib/swarm_cli/config_loader.rb +11 -11
  12. data/lib/swarm_cli/formatters/human_formatter.rb +70 -0
  13. data/lib/swarm_cli/interactive_repl.rb +11 -5
  14. data/lib/swarm_cli/ui/icons.rb +0 -23
  15. data/lib/swarm_cli/version.rb +1 -1
  16. data/lib/swarm_memory/adapters/filesystem_adapter.rb +11 -34
  17. data/lib/swarm_memory/integration/sdk_plugin.rb +87 -7
  18. data/lib/swarm_memory/version.rb +1 -1
  19. data/lib/swarm_memory.rb +1 -1
  20. data/lib/swarm_sdk/agent/builder.rb +58 -0
  21. data/lib/swarm_sdk/agent/chat.rb +527 -1059
  22. data/lib/swarm_sdk/agent/{chat → chat_helpers}/context_tracker.rb +9 -88
  23. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +204 -0
  24. data/lib/swarm_sdk/agent/{chat → chat_helpers}/hook_integration.rb +111 -44
  25. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +78 -0
  26. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +233 -0
  27. data/lib/swarm_sdk/agent/{chat → chat_helpers}/logging_helpers.rb +1 -1
  28. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +83 -0
  29. data/lib/swarm_sdk/agent/{chat → chat_helpers}/system_reminder_injector.rb +12 -12
  30. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +79 -0
  31. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +98 -0
  32. data/lib/swarm_sdk/agent/context.rb +2 -2
  33. data/lib/swarm_sdk/agent/definition.rb +66 -154
  34. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +4 -2
  35. data/lib/swarm_sdk/agent/system_prompt_builder.rb +161 -0
  36. data/lib/swarm_sdk/builders/base_builder.rb +409 -0
  37. data/lib/swarm_sdk/concerns/cleanupable.rb +39 -0
  38. data/lib/swarm_sdk/concerns/snapshotable.rb +67 -0
  39. data/lib/swarm_sdk/concerns/validatable.rb +55 -0
  40. data/lib/swarm_sdk/configuration/parser.rb +353 -0
  41. data/lib/swarm_sdk/configuration/translator.rb +255 -0
  42. data/lib/swarm_sdk/configuration.rb +65 -543
  43. data/lib/swarm_sdk/context_compactor/token_counter.rb +3 -3
  44. data/lib/swarm_sdk/context_compactor.rb +6 -11
  45. data/lib/swarm_sdk/context_management/builder.rb +128 -0
  46. data/lib/swarm_sdk/context_management/context.rb +328 -0
  47. data/lib/swarm_sdk/defaults.rb +196 -0
  48. data/lib/swarm_sdk/events_to_messages.rb +18 -0
  49. data/lib/swarm_sdk/hooks/shell_executor.rb +2 -1
  50. data/lib/swarm_sdk/log_collector.rb +179 -29
  51. data/lib/swarm_sdk/log_stream.rb +29 -0
  52. data/lib/swarm_sdk/node_context.rb +1 -1
  53. data/lib/swarm_sdk/observer/builder.rb +81 -0
  54. data/lib/swarm_sdk/observer/config.rb +45 -0
  55. data/lib/swarm_sdk/observer/manager.rb +236 -0
  56. data/lib/swarm_sdk/patterns/agent_observer.rb +160 -0
  57. data/lib/swarm_sdk/plugin.rb +93 -3
  58. data/lib/swarm_sdk/snapshot.rb +6 -6
  59. data/lib/swarm_sdk/snapshot_from_events.rb +13 -2
  60. data/lib/swarm_sdk/state_restorer.rb +136 -151
  61. data/lib/swarm_sdk/state_snapshot.rb +65 -100
  62. data/lib/swarm_sdk/swarm/agent_initializer.rb +180 -136
  63. data/lib/swarm_sdk/swarm/builder.rb +44 -578
  64. data/lib/swarm_sdk/swarm/executor.rb +213 -0
  65. data/lib/swarm_sdk/swarm/hook_triggers.rb +150 -0
  66. data/lib/swarm_sdk/swarm/logging_callbacks.rb +340 -0
  67. data/lib/swarm_sdk/swarm/mcp_configurator.rb +7 -4
  68. data/lib/swarm_sdk/swarm/tool_configurator.rb +42 -138
  69. data/lib/swarm_sdk/swarm.rb +137 -679
  70. data/lib/swarm_sdk/tools/bash.rb +11 -3
  71. data/lib/swarm_sdk/tools/delegate.rb +61 -43
  72. data/lib/swarm_sdk/tools/edit.rb +8 -13
  73. data/lib/swarm_sdk/tools/glob.rb +9 -1
  74. data/lib/swarm_sdk/tools/grep.rb +7 -0
  75. data/lib/swarm_sdk/tools/multi_edit.rb +15 -11
  76. data/lib/swarm_sdk/tools/path_resolver.rb +51 -2
  77. data/lib/swarm_sdk/tools/read.rb +11 -13
  78. data/lib/swarm_sdk/tools/registry.rb +122 -10
  79. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +8 -5
  80. data/lib/swarm_sdk/tools/stores/storage.rb +0 -6
  81. data/lib/swarm_sdk/tools/todo_write.rb +7 -0
  82. data/lib/swarm_sdk/tools/web_fetch.rb +3 -2
  83. data/lib/swarm_sdk/tools/write.rb +8 -13
  84. data/lib/swarm_sdk/version.rb +1 -1
  85. data/lib/swarm_sdk/{node → workflow}/agent_config.rb +1 -1
  86. data/lib/swarm_sdk/workflow/builder.rb +143 -0
  87. data/lib/swarm_sdk/workflow/executor.rb +497 -0
  88. data/lib/swarm_sdk/{node/builder.rb → workflow/node_builder.rb} +3 -3
  89. data/lib/swarm_sdk/{node → workflow}/transformer_executor.rb +3 -2
  90. data/lib/swarm_sdk/{node_orchestrator.rb → workflow.rb} +152 -456
  91. data/lib/swarm_sdk.rb +33 -3
  92. metadata +37 -14
  93. data/lib/swarm_memory/chat_extension.rb +0 -34
  94. data/lib/swarm_sdk/providers/openai_with_responses.rb +0 -589
@@ -44,6 +44,7 @@ module SwarmSDK
44
44
  # - `agent_step`: Reconstructs assistant message with tool calls
45
45
  # - `agent_stop`: Reconstructs final assistant message
46
46
  # - `tool_result`: Reconstructs tool result message
47
+ # - `delegation_result`: Reconstructs tool result message from delegation
47
48
  class EventsToMessages
48
49
  class << self
49
50
  # Reconstruct messages for an agent from event stream
@@ -90,6 +91,8 @@ module SwarmSDK
90
91
  reconstruct_assistant_message(event)
91
92
  when "tool_result"
92
93
  reconstruct_tool_result_message(event)
94
+ when "delegation_result"
95
+ reconstruct_delegation_result_message(event)
93
96
  end
94
97
 
95
98
  messages << message if message
@@ -158,6 +161,21 @@ module SwarmSDK
158
161
  )
159
162
  end
160
163
 
164
+ # Reconstruct tool result message from delegation_result event
165
+ #
166
+ # delegation_result events are emitted when a delegation completes,
167
+ # and they should be converted to tool result messages in the conversation.
168
+ #
169
+ # @param event [Hash] delegation_result event
170
+ # @return [RubyLLM::Message] Tool result message
171
+ def reconstruct_delegation_result_message(event)
172
+ RubyLLM::Message.new(
173
+ role: :tool,
174
+ content: event[:result].to_s,
175
+ tool_call_id: event[:tool_call_id],
176
+ )
177
+ end
178
+
161
179
  # Parse timestamp string to Time object
162
180
  #
163
181
  # @param timestamp [String, nil] ISO 8601 timestamp
@@ -47,7 +47,8 @@ module SwarmSDK
47
47
  # )
48
48
  # # => Result (continue or halt based on exit code)
49
49
  class ShellExecutor
50
- DEFAULT_TIMEOUT = 60
50
+ # Backward compatibility alias - use Defaults module for new code
51
+ DEFAULT_TIMEOUT = Defaults::Timeouts::HOOK_SHELL_SECONDS
51
52
 
52
53
  class << self
53
54
  # Execute a shell command hook
@@ -1,76 +1,226 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SwarmSDK
4
- # LogCollector manages subscriber callbacks for log events.
4
+ # LogCollector manages subscriber callbacks for log events with filtering support.
5
5
  #
6
6
  # This module acts as an emitter implementation that forwards events
7
7
  # to user-registered callbacks. It's designed to be set as the LogStream
8
8
  # emitter during swarm execution.
9
9
  #
10
+ # ## Features
11
+ #
12
+ # - **Filtered Subscriptions**: Subscribe to specific agents, event types, or swarm IDs
13
+ # - **Unsubscribe Support**: Remove subscriptions by ID to prevent memory leaks
14
+ # - **Error Isolation**: One subscriber's error doesn't break others
15
+ # - **Thread Safety**: Fiber-local storage for multi-threaded environments
16
+ #
10
17
  # ## Thread Safety for Multi-Threaded Environments (Puma, Sidekiq)
11
18
  #
12
- # Callbacks are stored in Fiber-local storage (Fiber[:log_callbacks]) instead
19
+ # Subscriptions are stored in Fiber-local storage (Fiber[:log_subscriptions]) instead
13
20
  # of class instance variables. This ensures callbacks registered in the parent
14
21
  # thread/fiber are accessible to child fibers created by Async reactor.
15
22
  #
16
- # Why: In Puma/Sidekiq, class instance variables (@callbacks) are thread-isolated
23
+ # Why: In Puma/Sidekiq, class instance variables (@subscriptions) are thread-isolated
17
24
  # and don't properly propagate to child fibers. Using Fiber-local storage ensures
18
- # events emitted from within Async blocks can reach registered callbacks.
25
+ # events emitted from within Async blocks can reach registered subscriptions.
19
26
  #
20
27
  # Child fibers inherit parent fiber-local storage automatically, so events
21
28
  # emitted from agent callbacks (on_tool_call, on_end_message, etc.) executing
22
- # in child fibers can still reach the parent's registered callbacks.
29
+ # in child fibers can still reach the parent's registered subscriptions.
23
30
  #
24
31
  # ## Usage
25
32
  #
26
- # # Register a callback (before execution starts)
27
- # LogCollector.on_log do |event|
28
- # puts JSON.generate(event)
29
- # end
33
+ # # Subscribe to all events
34
+ # sub_id = LogCollector.subscribe { |event| puts event }
35
+ #
36
+ # # Subscribe to specific agent
37
+ # sub_id = LogCollector.subscribe(filter: { agent: :backend }) { |event|
38
+ # puts "Backend: #{event}"
39
+ # }
30
40
  #
31
- # # During execution, LogStream calls emit
32
- # LogCollector.emit(type: "user_prompt", agent: :backend)
41
+ # # Subscribe to specific event types
42
+ # sub_id = LogCollector.subscribe(filter: { type: ["tool_call", "tool_result"] }) { |event|
43
+ # log_tool_activity(event)
44
+ # }
45
+ #
46
+ # # Subscribe with regex matching
47
+ # sub_id = LogCollector.subscribe(filter: { type: /^tool_/ }) { |event|
48
+ # track_tool_usage(event)
49
+ # }
50
+ #
51
+ # # Unsubscribe when done
52
+ # LogCollector.unsubscribe(sub_id)
33
53
  #
34
54
  # # After execution, reset for next use
35
55
  # LogCollector.reset!
36
56
  #
37
57
  module LogCollector
58
+ # Subscription object with filtering capabilities
59
+ #
60
+ # Encapsulates a callback with optional filters. Filters can match
61
+ # against agent name, event type, swarm_id, or any event field.
62
+ class Subscription
63
+ attr_reader :id, :filter, :callback
64
+
65
+ # Initialize a subscription
66
+ #
67
+ # @param filter [Hash] Filter criteria
68
+ # @param callback [Proc] Block to call when event matches
69
+ def initialize(filter: {}, &callback)
70
+ @id = SecureRandom.uuid
71
+ @filter = normalize_filter(filter)
72
+ @callback = callback
73
+ end
74
+
75
+ # Check if event matches filter criteria
76
+ #
77
+ # Empty filter matches all events. Multiple filter keys are AND'd together.
78
+ #
79
+ # @param event [Hash] Event entry with :type, :agent, etc.
80
+ # @return [Boolean] True if event matches filter
81
+ def matches?(event)
82
+ return true if @filter.empty?
83
+
84
+ @filter.all? do |key, matcher|
85
+ value = event[key]
86
+ case matcher
87
+ when Array
88
+ # Match if value is in array (handles symbols/strings)
89
+ matcher.include?(value) || matcher.map(&:to_s).include?(value.to_s)
90
+ when Regexp
91
+ # Regex matching
92
+ value.to_s.match?(matcher)
93
+ when Proc
94
+ # Custom matcher
95
+ matcher.call(value)
96
+ else
97
+ # Exact match (handles symbols/strings)
98
+ matcher == value || matcher.to_s == value.to_s
99
+ end
100
+ end
101
+ end
102
+
103
+ private
104
+
105
+ # Normalize filter keys to symbols for consistent matching
106
+ #
107
+ # @param filter [Hash] Raw filter
108
+ # @return [Hash] Normalized filter with symbol keys
109
+ def normalize_filter(filter)
110
+ filter.transform_keys(&:to_sym)
111
+ end
112
+ end
113
+
38
114
  class << self
39
- # Register a callback to receive log events
115
+ # Subscribe to log events with optional filtering
40
116
  #
41
- # Stores callback in Fiber-local storage to ensure accessibility
42
- # from child fibers in multi-threaded environments.
117
+ # Registers a callback that will receive events matching the filter criteria.
118
+ # Returns a subscription ID that can be used to unsubscribe later.
43
119
  #
120
+ # @param filter [Hash] Filter criteria (empty = all events)
121
+ # - :agent [Symbol, String, Array, Regexp] Agent name(s) to observe
122
+ # - :type [String, Array, Regexp] Event type(s) to receive
123
+ # - :swarm_id [String] Specific swarm instance
124
+ # - Any other key matches against event fields
44
125
  # @yield [Hash] Log event entry
45
- def on_log(&block)
46
- Fiber[:log_callbacks] ||= []
47
- Fiber[:log_callbacks] << block
126
+ # @return [String] Subscription ID for unsubscribe
127
+ #
128
+ # @example Subscribe to all events
129
+ # LogCollector.subscribe { |event| puts event }
130
+ #
131
+ # @example Subscribe to specific agent's tool calls
132
+ # sub_id = LogCollector.subscribe(
133
+ # filter: { agent: :backend, type: /^tool_/ }
134
+ # ) do |event|
135
+ # puts "Backend used tool: #{event[:tool]}"
136
+ # end
137
+ #
138
+ # @example Subscribe to multiple agents
139
+ # LogCollector.subscribe(
140
+ # filter: { agent: [:backend, :frontend], type: "agent_stop" }
141
+ # ) { |e| record_completion(e) }
142
+ def subscribe(filter: {}, &block)
143
+ subscription = Subscription.new(filter: filter, &block)
144
+ subscriptions << subscription
145
+ subscription.id
48
146
  end
49
147
 
50
- # Emit an event to all registered callbacks
148
+ # Unsubscribe by ID
149
+ #
150
+ # Removes a subscription to prevent memory leaks and stop receiving events.
151
+ #
152
+ # @param subscription_id [String] ID returned from subscribe
153
+ # @return [Subscription, nil] Removed subscription or nil if not found
154
+ def unsubscribe(subscription_id)
155
+ index = subscriptions.find_index { |s| s.id == subscription_id }
156
+ return unless index
157
+
158
+ subscriptions.delete_at(index)
159
+ end
160
+
161
+ # Clear all subscriptions
162
+ #
163
+ # Removes all subscriptions. Useful for testing or execution cleanup.
164
+ #
165
+ # @return [void]
166
+ def clear_subscriptions
167
+ subscriptions.clear
168
+ end
169
+
170
+ # Get current subscription count
171
+ #
172
+ # @return [Integer] Number of active subscriptions
173
+ def subscription_count
174
+ subscriptions.size
175
+ end
176
+
177
+ # Emit an event to all matching subscribers
51
178
  #
52
179
  # Automatically adds a timestamp if one doesn't exist.
53
- # Reads callbacks from Fiber-local storage to support multi-threaded execution.
180
+ # Errors in individual subscribers are isolated - one bad subscriber
181
+ # won't prevent others from receiving events.
54
182
  #
55
183
  # @param entry [Hash] Log event entry
56
184
  # @return [void]
57
185
  def emit(entry)
58
- # Ensure timestamp exists (LogStream adds it, but direct calls might not)
59
- # Use microsecond precision (6 digits) for proper event ordering
60
- entry_with_timestamp = entry.key?(:timestamp) ? entry : entry.merge(timestamp: Time.now.utc.iso8601(6))
61
-
62
- # Read callbacks from Fiber-local storage (set by on_log in parent fiber)
63
- callbacks = Fiber[:log_callbacks] || []
64
- callbacks.each do |callback|
65
- callback.call(entry_with_timestamp)
186
+ entry_with_timestamp = ensure_timestamp(entry)
187
+
188
+ subscriptions.each do |subscription|
189
+ next unless subscription.matches?(entry_with_timestamp)
190
+
191
+ begin
192
+ subscription.callback.call(entry_with_timestamp)
193
+ rescue StandardError => e
194
+ # Error isolation - don't let one subscriber break others
195
+ RubyLLM.logger.error("SwarmSDK: Subscription #{subscription.id} error: #{e.message}")
196
+ end
66
197
  end
67
198
  end
68
199
 
69
- # Reset the collector (clears callbacks for next execution)
200
+ # Reset the collector (clears subscriptions for next execution)
70
201
  #
71
202
  # @return [void]
72
203
  def reset!
73
- Fiber[:log_callbacks] = []
204
+ Fiber[:log_subscriptions] = []
205
+ end
206
+
207
+ private
208
+
209
+ # Get subscriptions from Fiber-local storage
210
+ #
211
+ # @return [Array<Subscription>] Current subscriptions
212
+ def subscriptions
213
+ Fiber[:log_subscriptions] ||= []
214
+ end
215
+
216
+ # Ensure event has a timestamp
217
+ #
218
+ # @param entry [Hash] Event entry
219
+ # @return [Hash] Entry with timestamp
220
+ def ensure_timestamp(entry)
221
+ return entry if entry.key?(:timestamp)
222
+
223
+ entry.merge(timestamp: Time.now.utc.iso8601(6))
74
224
  end
75
225
  end
76
226
  end
@@ -93,6 +93,35 @@ module SwarmSDK
93
93
  def enabled?
94
94
  !Fiber[:log_stream_emitter].nil?
95
95
  end
96
+
97
+ # Emit an internal error event
98
+ #
99
+ # Provides consistent error event emission for all internal errors.
100
+ # These are errors that occur during execution but are handled gracefully
101
+ # (with fallback behavior) rather than causing failures.
102
+ #
103
+ # @param error [Exception] The caught exception
104
+ # @param source [String] Source module/class (e.g., "hook_triggers", "context_compactor")
105
+ # @param context [String] Specific operation context (e.g., "swarm_stop", "summarization")
106
+ # @param agent [Symbol, String, nil] Agent name if applicable
107
+ # @param metadata [Hash] Additional context data
108
+ # @return [void]
109
+ def emit_error(error, source:, context:, agent: nil, **metadata)
110
+ emit(
111
+ type: "internal_error",
112
+ source: source,
113
+ context: context,
114
+ agent: agent,
115
+ error_class: error.class.name,
116
+ error_message: error.message,
117
+ backtrace: error.backtrace&.first(5),
118
+ **metadata,
119
+ )
120
+ rescue StandardError
121
+ # Absolute fallback - if emit_error itself fails, don't break execution
122
+ # This should never happen, but we must be defensive
123
+ nil
124
+ end
96
125
  end
97
126
  end
98
127
  end
@@ -168,7 +168,7 @@ module SwarmSDK
168
168
  end
169
169
 
170
170
  # Control flow methods for transformers
171
- # These return special hashes that NodeOrchestrator recognizes
171
+ # These return special hashes that Workflow recognizes
172
172
 
173
173
  # Skip current node's LLM execution and return content immediately
174
174
  #
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ module Observer
5
+ # DSL for configuring observer agents
6
+ #
7
+ # Used by Swarm::Builder#observer to provide a clean DSL for defining
8
+ # event handlers and observer configuration options.
9
+ #
10
+ # @example Basic usage
11
+ # observer :profiler do
12
+ # on :swarm_start do |event|
13
+ # "Analyze this prompt: #{event[:prompt]}"
14
+ # end
15
+ #
16
+ # timeout 120
17
+ # max_concurrent 2
18
+ # end
19
+ class Builder
20
+ # Initialize builder with agent name and config
21
+ #
22
+ # @param agent_name [Symbol] Name of the observer agent
23
+ # @param config [Observer::Config] Configuration object to populate
24
+ def initialize(agent_name, config)
25
+ @agent_name = agent_name
26
+ @config = config
27
+ end
28
+
29
+ # Register an event handler
30
+ #
31
+ # The block receives the event hash and should return:
32
+ # - A prompt string to trigger the observer agent
33
+ # - nil to skip execution for this event
34
+ #
35
+ # @param event_type [Symbol] Type of event to handle (e.g., :swarm_start, :tool_call)
36
+ # @yield [Hash] Event hash
37
+ # @yieldreturn [String, nil] Prompt or nil to skip
38
+ # @return [void]
39
+ #
40
+ # @example
41
+ # on :tool_call do |event|
42
+ # next unless event[:tool_name] == "Bash"
43
+ # "Check this command: #{event[:arguments][:command]}"
44
+ # end
45
+ def on(event_type, &block)
46
+ @config.add_handler(event_type, &block)
47
+ end
48
+
49
+ # Set maximum concurrent executions for this observer
50
+ #
51
+ # Limits how many instances of this observer agent can run simultaneously.
52
+ # Useful for resource-intensive observers.
53
+ #
54
+ # @param n [Integer] Maximum concurrent executions
55
+ # @return [void]
56
+ def max_concurrent(n)
57
+ @config.options[:max_concurrent] = n
58
+ end
59
+
60
+ # Set timeout for observer execution
61
+ #
62
+ # Observer tasks will be cancelled after this duration.
63
+ #
64
+ # @param seconds [Integer] Timeout in seconds (default: 60)
65
+ # @return [void]
66
+ def timeout(seconds)
67
+ @config.options[:timeout] = seconds
68
+ end
69
+
70
+ # Wait for observer to complete before swarm execution ends
71
+ #
72
+ # By default, observers are fire-and-forget. This option causes
73
+ # the main execution to wait for this observer to complete.
74
+ #
75
+ # @return [void]
76
+ def wait_for_completion!
77
+ @config.options[:fire_and_forget] = false
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ module Observer
5
+ # Configuration for an observer agent
6
+ #
7
+ # Holds the agent name, event handlers (blocks that return prompts or nil),
8
+ # and execution options.
9
+ #
10
+ # @example
11
+ # config = Observer::Config.new(:profiler)
12
+ # config.add_handler(:swarm_start) { |event| "Analyze: #{event[:prompt]}" }
13
+ # config.options[:timeout] = 120
14
+ class Config
15
+ attr_reader :agent_name, :event_handlers, :options
16
+
17
+ # Initialize a new observer configuration
18
+ #
19
+ # @param agent_name [Symbol] Name of the agent to use as observer
20
+ def initialize(agent_name)
21
+ @agent_name = agent_name
22
+ @event_handlers = {} # { event_type => block }
23
+ @options = {
24
+ max_concurrent: nil,
25
+ timeout: 60,
26
+ fire_and_forget: true,
27
+ }
28
+ end
29
+
30
+ # Add an event handler for a specific event type
31
+ #
32
+ # The block receives the event hash and should return:
33
+ # - A prompt string to trigger the observer agent
34
+ # - nil to skip execution for this event
35
+ #
36
+ # @param event_type [Symbol] Type of event to handle (e.g., :swarm_start, :tool_call)
37
+ # @yield [Hash] Event hash with type, agent, and other data
38
+ # @yieldreturn [String, nil] Prompt to execute or nil to skip
39
+ # @return [void]
40
+ def add_handler(event_type, &block)
41
+ @event_handlers[event_type] = block
42
+ end
43
+ end
44
+ end
45
+ end