swarm_sdk 2.7.7 → 2.7.9
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.
- checksums.yaml +4 -4
- data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +19 -19
- data/lib/swarm_sdk/config.rb +1 -0
- data/lib/swarm_sdk/defaults.rb +41 -0
- data/lib/swarm_sdk/models.json +41627 -2121
- data/lib/swarm_sdk/swarm/agent_initializer.rb +149 -24
- data/lib/swarm_sdk/swarm/mcp_configurator.rb +29 -2
- data/lib/swarm_sdk/version.rb +1 -1
- metadata +1 -1
|
@@ -117,38 +117,92 @@ module SwarmSDK
|
|
|
117
117
|
#
|
|
118
118
|
# Agents that are ONLY delegates with shared_across_delegations: false
|
|
119
119
|
# are NOT created here - they'll be created as delegation instances in pass 2a.
|
|
120
|
+
#
|
|
121
|
+
# Agent creation is parallelized using Async::Barrier for faster initialization.
|
|
120
122
|
def pass_1_create_agents
|
|
121
123
|
# Create plugin storages for agents
|
|
122
124
|
create_plugin_storages
|
|
123
125
|
|
|
124
126
|
tool_configurator = ToolConfigurator.new(@swarm, @swarm.scratchpad_storage, @swarm.plugin_storages)
|
|
125
127
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
128
|
+
# Filter agents that need primary creation
|
|
129
|
+
agents_to_create = @swarm.agent_definitions.reject do |name, agent_definition|
|
|
130
|
+
should_skip_primary_creation?(name, agent_definition)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Create agents in parallel using Async::Barrier
|
|
134
|
+
results = create_agents_in_parallel(agents_to_create, tool_configurator)
|
|
129
135
|
|
|
130
|
-
|
|
136
|
+
# Store results and notify plugins (sequential for safety)
|
|
137
|
+
results.each do |name, chat, agent_definition|
|
|
131
138
|
@agents[name] = chat
|
|
132
|
-
|
|
133
|
-
# Notify plugins that agent was initialized
|
|
134
139
|
notify_plugins_agent_initialized(name, chat, agent_definition, tool_configurator)
|
|
135
140
|
end
|
|
136
141
|
end
|
|
137
142
|
|
|
138
|
-
#
|
|
143
|
+
# Create multiple agents in parallel using Async fibers
|
|
139
144
|
#
|
|
140
|
-
#
|
|
141
|
-
#
|
|
142
|
-
#
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
145
|
+
# @param agents_to_create [Hash] Hash of { name => agent_definition }
|
|
146
|
+
# @param tool_configurator [ToolConfigurator] Shared tool configurator
|
|
147
|
+
# @return [Array<Array>] Array of [name, chat, agent_definition] tuples
|
|
148
|
+
def create_agents_in_parallel(agents_to_create, tool_configurator)
|
|
149
|
+
return [] if agents_to_create.empty?
|
|
150
|
+
|
|
151
|
+
results = []
|
|
152
|
+
errors = []
|
|
153
|
+
mutex = Mutex.new
|
|
154
|
+
|
|
155
|
+
Sync do
|
|
156
|
+
barrier = Async::Barrier.new
|
|
157
|
+
|
|
158
|
+
agents_to_create.each do |name, agent_definition|
|
|
159
|
+
barrier.async do
|
|
160
|
+
chat = create_agent_chat(name, agent_definition, tool_configurator)
|
|
161
|
+
mutex.synchronize { results << [name, chat, agent_definition] }
|
|
162
|
+
rescue StandardError => e
|
|
163
|
+
# Catch errors to avoid Async warning logs (which fail in tests with StringIO)
|
|
164
|
+
mutex.synchronize { errors << [name, e] }
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
barrier.wait
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Re-raise first error if any occurred
|
|
172
|
+
unless errors.empty?
|
|
173
|
+
# Emit events for all errors (not just the first)
|
|
174
|
+
errors.each do |agent_name, err|
|
|
175
|
+
LogStream.emit(
|
|
176
|
+
type: "agent_initialization_error",
|
|
177
|
+
agent: agent_name,
|
|
178
|
+
error_class: err.class.name,
|
|
179
|
+
error_message: err.message,
|
|
180
|
+
timestamp: Time.now.utc.iso8601,
|
|
181
|
+
)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Re-raise first error with context
|
|
185
|
+
name, error = errors.first
|
|
186
|
+
raise error.class, "Agent '#{name}' initialization failed: #{error.message}", error.backtrace
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
results
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Collect all delegation instances that need to be created
|
|
193
|
+
#
|
|
194
|
+
# Validates delegation configs and returns a list of instances to create.
|
|
195
|
+
# This is done sequentially to fail fast on configuration errors.
|
|
196
|
+
#
|
|
197
|
+
# @return [Array<Hash>] Array of { instance_name:, base_name:, definition: }
|
|
198
|
+
def collect_delegation_instances_to_create
|
|
199
|
+
instances = []
|
|
146
200
|
|
|
147
|
-
# Sub-pass 2a: Create delegation instances for isolated agents
|
|
148
201
|
@swarm.agent_definitions.each do |delegator_name, delegator_def|
|
|
149
202
|
delegator_def.delegation_configs.each do |delegation_config|
|
|
150
203
|
delegate_base_name = delegation_config[:agent]
|
|
151
204
|
|
|
205
|
+
# Validate delegate exists
|
|
152
206
|
unless @swarm.agent_definitions.key?(delegate_base_name)
|
|
153
207
|
raise ConfigurationError,
|
|
154
208
|
"Agent '#{delegator_name}' delegates to unknown agent '#{delegate_base_name}'"
|
|
@@ -156,24 +210,95 @@ module SwarmSDK
|
|
|
156
210
|
|
|
157
211
|
delegate_definition = @swarm.agent_definitions[delegate_base_name]
|
|
158
212
|
|
|
159
|
-
#
|
|
160
|
-
# If delegate wants to be shared, skip instance creation (use primary)
|
|
213
|
+
# Skip if delegate wants to be shared (use primary instead)
|
|
161
214
|
next if delegate_definition.shared_across_delegations
|
|
162
215
|
|
|
163
|
-
# Create unique delegation instance (isolated mode)
|
|
164
216
|
instance_name = "#{delegate_base_name}@#{delegator_name}"
|
|
165
217
|
|
|
166
|
-
|
|
167
|
-
delegation_chat = create_agent_chat_for_delegation(
|
|
218
|
+
instances << {
|
|
168
219
|
instance_name: instance_name,
|
|
169
220
|
base_name: delegate_base_name,
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
221
|
+
definition: delegate_definition,
|
|
222
|
+
}
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
instances
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Create multiple delegation instances in parallel using Async fibers
|
|
230
|
+
#
|
|
231
|
+
# @param instances_to_create [Array<Hash>] Array of instance configs
|
|
232
|
+
# @param tool_configurator [ToolConfigurator] Shared tool configurator
|
|
233
|
+
# @return [Array<Array>] Array of [instance_name, chat] tuples
|
|
234
|
+
def create_delegation_instances_in_parallel(instances_to_create, tool_configurator)
|
|
235
|
+
return [] if instances_to_create.empty?
|
|
236
|
+
|
|
237
|
+
results = []
|
|
238
|
+
errors = []
|
|
239
|
+
mutex = Mutex.new
|
|
240
|
+
|
|
241
|
+
Sync do
|
|
242
|
+
barrier = Async::Barrier.new
|
|
243
|
+
|
|
244
|
+
instances_to_create.each do |config|
|
|
245
|
+
barrier.async do
|
|
246
|
+
delegation_chat = create_agent_chat_for_delegation(
|
|
247
|
+
instance_name: config[:instance_name],
|
|
248
|
+
base_name: config[:base_name],
|
|
249
|
+
agent_definition: config[:definition],
|
|
250
|
+
tool_configurator: tool_configurator,
|
|
251
|
+
)
|
|
252
|
+
mutex.synchronize { results << [config[:instance_name], delegation_chat] }
|
|
253
|
+
rescue StandardError => e
|
|
254
|
+
# Catch errors to avoid Async warning logs (which fail in tests with StringIO)
|
|
255
|
+
mutex.synchronize { errors << [config[:instance_name], e] }
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
barrier.wait
|
|
260
|
+
end
|
|
173
261
|
|
|
174
|
-
|
|
175
|
-
|
|
262
|
+
# Re-raise first error if any occurred
|
|
263
|
+
unless errors.empty?
|
|
264
|
+
# Emit events for all errors (not just the first)
|
|
265
|
+
errors.each do |inst_name, err|
|
|
266
|
+
LogStream.emit(
|
|
267
|
+
type: "delegation_instance_initialization_error",
|
|
268
|
+
instance_name: inst_name,
|
|
269
|
+
error_class: err.class.name,
|
|
270
|
+
error_message: err.message,
|
|
271
|
+
timestamp: Time.now.utc.iso8601,
|
|
272
|
+
)
|
|
176
273
|
end
|
|
274
|
+
|
|
275
|
+
# Re-raise first error with context
|
|
276
|
+
instance_name, error = errors.first
|
|
277
|
+
raise error.class, "Delegation instance '#{instance_name}' initialization failed: #{error.message}", error.backtrace
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
results
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Pass 2: Create delegation instances and wire delegation tools
|
|
284
|
+
#
|
|
285
|
+
# This pass has three sub-steps that must happen in order:
|
|
286
|
+
# 2a. Create delegation instances (ONLY for agents with shared_across_delegations: false)
|
|
287
|
+
# 2b. Wire primary agents to delegation instances OR shared primaries
|
|
288
|
+
# 2c. Wire delegation instances to their delegates (nested delegation support)
|
|
289
|
+
#
|
|
290
|
+
# Sub-pass 2a is parallelized using Async::Barrier for faster initialization.
|
|
291
|
+
def pass_2_register_delegation_tools
|
|
292
|
+
tool_configurator = ToolConfigurator.new(@swarm, @swarm.scratchpad_storage, @swarm.plugin_storages)
|
|
293
|
+
|
|
294
|
+
# Sub-pass 2a: Create delegation instances for isolated agents (parallelized)
|
|
295
|
+
delegation_instances_to_create = collect_delegation_instances_to_create
|
|
296
|
+
|
|
297
|
+
results = create_delegation_instances_in_parallel(delegation_instances_to_create, tool_configurator)
|
|
298
|
+
|
|
299
|
+
# Store results after all parallel creation completes
|
|
300
|
+
results.each do |instance_name, delegation_chat|
|
|
301
|
+
@swarm.delegation_instances[instance_name] = delegation_chat
|
|
177
302
|
end
|
|
178
303
|
|
|
179
304
|
# Sub-pass 2b: Wire primary agents to delegation instances OR shared primaries OR registered swarms
|
|
@@ -130,7 +130,8 @@ module SwarmSDK
|
|
|
130
130
|
# @return [RubyLLM::MCP::Client] Initialized MCP client
|
|
131
131
|
def initialize_mcp_client(config)
|
|
132
132
|
# Convert timeout from seconds to milliseconds
|
|
133
|
-
|
|
133
|
+
# Use explicit config[:timeout] if provided, otherwise use global default
|
|
134
|
+
timeout_seconds = config[:timeout] || SwarmSDK.config.mcp_request_timeout
|
|
134
135
|
timeout_ms = timeout_seconds * 1000
|
|
135
136
|
|
|
136
137
|
# Determine transport type
|
|
@@ -179,11 +180,16 @@ module SwarmSDK
|
|
|
179
180
|
# @param config [Hash] MCP server configuration
|
|
180
181
|
# @return [Hash] SSE configuration
|
|
181
182
|
def build_sse_config(config)
|
|
182
|
-
{
|
|
183
|
+
sse_config = {
|
|
183
184
|
url: config[:url],
|
|
184
185
|
headers: config[:headers] || {},
|
|
185
186
|
version: config[:version]&.to_sym || :http2,
|
|
186
187
|
}
|
|
188
|
+
|
|
189
|
+
# Add reconnection options for resilient SSE connections
|
|
190
|
+
sse_config[:reconnection] = build_reconnection_options(config)
|
|
191
|
+
|
|
192
|
+
sse_config
|
|
187
193
|
end
|
|
188
194
|
|
|
189
195
|
# Build streamable (HTTP) transport configuration
|
|
@@ -200,9 +206,30 @@ module SwarmSDK
|
|
|
200
206
|
# Only include rate_limit if present
|
|
201
207
|
streamable_config[:rate_limit] = config[:rate_limit] if config[:rate_limit]
|
|
202
208
|
|
|
209
|
+
# Add reconnection options for resilient streamable connections
|
|
210
|
+
streamable_config[:reconnection] = build_reconnection_options(config)
|
|
211
|
+
|
|
203
212
|
streamable_config
|
|
204
213
|
end
|
|
205
214
|
|
|
215
|
+
# Build reconnection options from config or defaults
|
|
216
|
+
#
|
|
217
|
+
# Provides exponential backoff reconnection for SSE/streamable transports.
|
|
218
|
+
# Can be customized per-server or uses global defaults.
|
|
219
|
+
#
|
|
220
|
+
# @param config [Hash] MCP server configuration
|
|
221
|
+
# @return [Hash] Reconnection options
|
|
222
|
+
def build_reconnection_options(config)
|
|
223
|
+
reconnection_config = config[:reconnection] || {}
|
|
224
|
+
|
|
225
|
+
{
|
|
226
|
+
max_retries: reconnection_config[:max_retries] || Defaults::McpReconnection::MAX_RETRIES,
|
|
227
|
+
initial_reconnection_delay: reconnection_config[:initial_delay] || Defaults::McpReconnection::INITIAL_DELAY_MS,
|
|
228
|
+
reconnection_delay_grow_factor: reconnection_config[:delay_grow_factor] || Defaults::McpReconnection::DELAY_GROW_FACTOR,
|
|
229
|
+
max_reconnection_delay: reconnection_config[:max_delay] || Defaults::McpReconnection::MAX_DELAY_MS,
|
|
230
|
+
}
|
|
231
|
+
end
|
|
232
|
+
|
|
206
233
|
# Emit MCP server initialization start event
|
|
207
234
|
#
|
|
208
235
|
# @param agent_name [Symbol] Agent name
|
data/lib/swarm_sdk/version.rb
CHANGED