swarm_sdk 2.7.9 → 2.7.10
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/swarm/agent_initializer.rb +47 -274
- data/lib/swarm_sdk/swarm/lazy_delegate_chat.rb +372 -0
- data/lib/swarm_sdk/swarm.rb +55 -0
- data/lib/swarm_sdk/tools/delegate.rb +49 -3
- data/lib/swarm_sdk/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7863f2b6c3b7c74c13ca47957713e5588167a09a2c95acfebb3c53bab571af7f
|
|
4
|
+
data.tar.gz: edf4cc3e12b13327909b8c21a5b0b40dafde5707ff82883762d980b267330e8b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: df8234592d637315378d078c211b98b61bf3411d11ac49b2c2d82954314cf985686deb927d9d8f41855a341675fb216814415a7be0b7e5e0297fd593b83fc95f
|
|
7
|
+
data.tar.gz: 360d76065cbc15d6968d13616cb5e2c29d5a6b3771aa58a7490fca5cf360fc6b0a2bd3b3c7c90b98d89388205ad927ea998b2804fb1076bed5d91aff0d982596
|
|
@@ -189,119 +189,18 @@ module SwarmSDK
|
|
|
189
189
|
results
|
|
190
190
|
end
|
|
191
191
|
|
|
192
|
-
#
|
|
192
|
+
# Pass 2: Wire delegation tools (lazy loading for isolated delegates)
|
|
193
193
|
#
|
|
194
|
-
#
|
|
195
|
-
#
|
|
194
|
+
# This pass wires delegation tools for primary agents:
|
|
195
|
+
# - Shared delegates use the primary agent instance
|
|
196
|
+
# - Isolated delegates use LazyDelegateChat (created on first use)
|
|
196
197
|
#
|
|
197
|
-
#
|
|
198
|
-
|
|
199
|
-
instances = []
|
|
200
|
-
|
|
201
|
-
@swarm.agent_definitions.each do |delegator_name, delegator_def|
|
|
202
|
-
delegator_def.delegation_configs.each do |delegation_config|
|
|
203
|
-
delegate_base_name = delegation_config[:agent]
|
|
204
|
-
|
|
205
|
-
# Validate delegate exists
|
|
206
|
-
unless @swarm.agent_definitions.key?(delegate_base_name)
|
|
207
|
-
raise ConfigurationError,
|
|
208
|
-
"Agent '#{delegator_name}' delegates to unknown agent '#{delegate_base_name}'"
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
delegate_definition = @swarm.agent_definitions[delegate_base_name]
|
|
212
|
-
|
|
213
|
-
# Skip if delegate wants to be shared (use primary instead)
|
|
214
|
-
next if delegate_definition.shared_across_delegations
|
|
215
|
-
|
|
216
|
-
instance_name = "#{delegate_base_name}@#{delegator_name}"
|
|
217
|
-
|
|
218
|
-
instances << {
|
|
219
|
-
instance_name: instance_name,
|
|
220
|
-
base_name: delegate_base_name,
|
|
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
|
|
261
|
-
|
|
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
|
-
)
|
|
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.
|
|
198
|
+
# Sub-pass 2a (eager creation) is REMOVED - delegation instances are now lazy.
|
|
199
|
+
# Sub-pass 2c (nested delegation) is handled by LazyDelegateChat when initialized.
|
|
291
200
|
def pass_2_register_delegation_tools
|
|
292
201
|
tool_configurator = ToolConfigurator.new(@swarm, @swarm.scratchpad_storage, @swarm.plugin_storages)
|
|
293
202
|
|
|
294
|
-
#
|
|
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
|
|
302
|
-
end
|
|
303
|
-
|
|
304
|
-
# Sub-pass 2b: Wire primary agents to delegation instances OR shared primaries OR registered swarms
|
|
203
|
+
# Wire primary agents to delegates (shared primaries or lazy loaders)
|
|
305
204
|
@swarm.agent_definitions.each do |delegator_name, delegator_def|
|
|
306
205
|
delegator_chat = @agents[delegator_name]
|
|
307
206
|
|
|
@@ -314,43 +213,25 @@ module SwarmSDK
|
|
|
314
213
|
delegator_chat: delegator_chat,
|
|
315
214
|
delegation_config: delegation_config,
|
|
316
215
|
tool_configurator: tool_configurator,
|
|
317
|
-
create_nested_instances: false,
|
|
318
216
|
)
|
|
319
217
|
end
|
|
320
218
|
end
|
|
321
219
|
|
|
322
|
-
#
|
|
323
|
-
#
|
|
324
|
-
@swarm.delegation_instances.to_a.each do |instance_name, delegation_chat|
|
|
325
|
-
base_name = extract_base_name(instance_name)
|
|
326
|
-
delegate_definition = @swarm.agent_definitions[base_name]
|
|
327
|
-
|
|
328
|
-
# Register delegation tools for THIS instance's delegates_to
|
|
329
|
-
delegate_definition.delegation_configs.each do |delegation_config|
|
|
330
|
-
wire_delegation(
|
|
331
|
-
delegator_name: instance_name.to_sym,
|
|
332
|
-
delegator_chat: delegation_chat,
|
|
333
|
-
delegation_config: delegation_config,
|
|
334
|
-
tool_configurator: tool_configurator,
|
|
335
|
-
create_nested_instances: true,
|
|
336
|
-
)
|
|
337
|
-
end
|
|
338
|
-
end
|
|
220
|
+
# NOTE: Nested delegation wiring is now handled by LazyDelegateChat#wire_delegation_tools
|
|
221
|
+
# when the lazy delegate is first accessed.
|
|
339
222
|
end
|
|
340
223
|
|
|
341
|
-
# Wire a single delegation from one agent
|
|
224
|
+
# Wire a single delegation from one agent to a delegate
|
|
342
225
|
#
|
|
343
|
-
#
|
|
344
|
-
#
|
|
345
|
-
# - Sub-pass 2c: Delegation instances → nested delegates
|
|
226
|
+
# For isolated delegates, creates a LazyDelegateChat wrapper instead of
|
|
227
|
+
# eagerly creating the chat instance.
|
|
346
228
|
#
|
|
347
229
|
# @param delegator_name [Symbol, String] Name of the agent doing the delegating
|
|
348
230
|
# @param delegator_chat [Agent::Chat] Chat instance of the delegator
|
|
349
231
|
# @param delegation_config [Hash] Delegation configuration with :agent, :tool_name, and :preserve_context keys
|
|
350
232
|
# @param tool_configurator [ToolConfigurator] Tool configuration helper
|
|
351
|
-
# @param create_nested_instances [Boolean] Whether to create new instances for nested delegation
|
|
352
233
|
# @return [void]
|
|
353
|
-
def wire_delegation(delegator_name:, delegator_chat:, delegation_config:, tool_configurator
|
|
234
|
+
def wire_delegation(delegator_name:, delegator_chat:, delegation_config:, tool_configurator:)
|
|
354
235
|
delegate_name_sym = delegation_config[:agent]
|
|
355
236
|
delegate_name_str = delegate_name_sym.to_s
|
|
356
237
|
custom_tool_name = delegation_config[:tool_name]
|
|
@@ -365,8 +246,6 @@ module SwarmSDK
|
|
|
365
246
|
delegator_chat: delegator_chat,
|
|
366
247
|
delegate_name_sym: delegate_name_sym,
|
|
367
248
|
custom_tool_name: custom_tool_name,
|
|
368
|
-
tool_configurator: tool_configurator,
|
|
369
|
-
create_nested_instances: create_nested_instances,
|
|
370
249
|
preserve_context: preserve_context,
|
|
371
250
|
)
|
|
372
251
|
else
|
|
@@ -404,18 +283,17 @@ module SwarmSDK
|
|
|
404
283
|
|
|
405
284
|
# Wire delegation to a local agent
|
|
406
285
|
#
|
|
407
|
-
#
|
|
408
|
-
#
|
|
286
|
+
# For shared delegates, uses the primary agent instance.
|
|
287
|
+
# For isolated delegates, creates a LazyDelegateChat wrapper that
|
|
288
|
+
# defers creation until first use.
|
|
409
289
|
#
|
|
410
290
|
# @param delegator_name [Symbol, String] Name of the delegating agent
|
|
411
291
|
# @param delegator_chat [Agent::Chat] Chat instance of the delegator
|
|
412
292
|
# @param delegate_name_sym [Symbol] Name of the delegate agent
|
|
413
293
|
# @param custom_tool_name [String, nil] Optional custom tool name
|
|
414
|
-
# @param tool_configurator [ToolConfigurator] Tool configuration helper
|
|
415
|
-
# @param create_nested_instances [Boolean] Whether to create new instances if not found
|
|
416
294
|
# @param preserve_context [Boolean] Whether to preserve context between delegations
|
|
417
295
|
# @return [void]
|
|
418
|
-
def wire_agent_delegation(delegator_name:, delegator_chat:, delegate_name_sym:, custom_tool_name:,
|
|
296
|
+
def wire_agent_delegation(delegator_name:, delegator_chat:, delegate_name_sym:, custom_tool_name:, preserve_context:)
|
|
419
297
|
delegate_definition = @swarm.agent_definitions[delegate_name_sym]
|
|
420
298
|
|
|
421
299
|
# Determine which chat instance to use
|
|
@@ -423,24 +301,18 @@ module SwarmSDK
|
|
|
423
301
|
# Shared mode: use primary agent (semaphore-protected)
|
|
424
302
|
@agents[delegate_name_sym]
|
|
425
303
|
else
|
|
426
|
-
# Isolated mode: use delegation
|
|
304
|
+
# Isolated mode: use lazy loader (created on first delegation)
|
|
427
305
|
instance_name = "#{delegate_name_sym}@#{delegator_name}"
|
|
428
306
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
tool_configurator: tool_configurator,
|
|
436
|
-
)
|
|
437
|
-
else
|
|
438
|
-
# For primary delegation: instance was pre-created in 2a
|
|
439
|
-
@swarm.delegation_instances[instance_name]
|
|
440
|
-
end
|
|
307
|
+
LazyDelegateChat.new(
|
|
308
|
+
instance_name: instance_name,
|
|
309
|
+
base_name: delegate_name_sym,
|
|
310
|
+
agent_definition: delegate_definition,
|
|
311
|
+
swarm: @swarm,
|
|
312
|
+
)
|
|
441
313
|
end
|
|
442
314
|
|
|
443
|
-
# Create delegation tool pointing to chosen instance
|
|
315
|
+
# Create delegation tool pointing to chosen instance (or lazy loader)
|
|
444
316
|
tool = create_delegation_tool(
|
|
445
317
|
name: delegate_name_sym.to_s,
|
|
446
318
|
description: delegate_definition.description,
|
|
@@ -467,18 +339,15 @@ module SwarmSDK
|
|
|
467
339
|
#
|
|
468
340
|
# Create Agent::Context for each agent to track delegations and metadata.
|
|
469
341
|
# This is needed regardless of whether logging is enabled.
|
|
342
|
+
#
|
|
343
|
+
# NOTE: Delegation instances are now lazy-loaded, so their contexts are
|
|
344
|
+
# set up in LazyDelegateChat#setup_context when first accessed.
|
|
470
345
|
def pass_3_setup_contexts
|
|
471
|
-
# Setup contexts for PRIMARY agents
|
|
346
|
+
# Setup contexts for PRIMARY agents only
|
|
347
|
+
# (Delegation instances handle their own context setup via LazyDelegateChat)
|
|
472
348
|
@agents.each do |agent_name, chat|
|
|
473
349
|
setup_agent_context(agent_name, @swarm.agent_definitions[agent_name], chat, is_delegation: false)
|
|
474
350
|
end
|
|
475
|
-
|
|
476
|
-
# Setup contexts for DELEGATION instances
|
|
477
|
-
@swarm.delegation_instances.each do |instance_name, chat|
|
|
478
|
-
base_name = extract_base_name(instance_name)
|
|
479
|
-
agent_definition = @swarm.agent_definitions[base_name]
|
|
480
|
-
setup_agent_context(instance_name.to_sym, agent_definition, chat, is_delegation: true)
|
|
481
|
-
end
|
|
482
351
|
end
|
|
483
352
|
|
|
484
353
|
# Setup context for an agent (primary or delegation instance)
|
|
@@ -541,16 +410,15 @@ module SwarmSDK
|
|
|
541
410
|
# Pass 4: Configure hook system
|
|
542
411
|
#
|
|
543
412
|
# Setup the callback system for each agent, integrating with RubyLLM callbacks.
|
|
413
|
+
#
|
|
414
|
+
# NOTE: Delegation instances are now lazy-loaded, so their hooks are
|
|
415
|
+
# configured in LazyDelegateChat#configure_hooks when first accessed.
|
|
544
416
|
def pass_4_configure_hooks
|
|
545
|
-
# Configure hooks for PRIMARY agents
|
|
417
|
+
# Configure hooks for PRIMARY agents only
|
|
418
|
+
# (Delegation instances handle their own hook setup via LazyDelegateChat)
|
|
546
419
|
@agents.each do |agent_name, chat|
|
|
547
420
|
configure_hooks_for_agent(agent_name, chat)
|
|
548
421
|
end
|
|
549
|
-
|
|
550
|
-
# Configure hooks for DELEGATION instances
|
|
551
|
-
@swarm.delegation_instances.each do |instance_name, chat|
|
|
552
|
-
configure_hooks_for_agent(instance_name.to_sym, chat)
|
|
553
|
-
end
|
|
554
422
|
end
|
|
555
423
|
|
|
556
424
|
# Configure hooks for an agent (primary or delegation instance)
|
|
@@ -569,18 +437,17 @@ module SwarmSDK
|
|
|
569
437
|
#
|
|
570
438
|
# If the swarm was loaded from YAML with agent-specific hooks,
|
|
571
439
|
# apply them now via HooksAdapter.
|
|
440
|
+
#
|
|
441
|
+
# NOTE: Delegation instances are now lazy-loaded, so their YAML hooks are
|
|
442
|
+
# applied in LazyDelegateChat#apply_yaml_hooks when first accessed.
|
|
572
443
|
def pass_5_apply_yaml_hooks
|
|
573
444
|
return unless @swarm.config_for_hooks
|
|
574
445
|
|
|
575
|
-
# Apply YAML hooks to PRIMARY agents
|
|
446
|
+
# Apply YAML hooks to PRIMARY agents only
|
|
447
|
+
# (Delegation instances handle their own YAML hooks via LazyDelegateChat)
|
|
576
448
|
@agents.each do |agent_name, chat|
|
|
577
449
|
apply_yaml_hooks_for_agent(agent_name, chat)
|
|
578
450
|
end
|
|
579
|
-
|
|
580
|
-
# Apply YAML hooks to DELEGATION instances
|
|
581
|
-
@swarm.delegation_instances.each do |instance_name, chat|
|
|
582
|
-
apply_yaml_hooks_for_agent(instance_name.to_sym, chat)
|
|
583
|
-
end
|
|
584
451
|
end
|
|
585
452
|
|
|
586
453
|
# Apply YAML hooks for an agent (primary or delegation instance)
|
|
@@ -603,13 +470,14 @@ module SwarmSDK
|
|
|
603
470
|
# - Tools must be activated AFTER all registration is complete
|
|
604
471
|
# - This populates @llm_chat.tools from the registry
|
|
605
472
|
#
|
|
473
|
+
# NOTE: Delegation instances are now lazy-loaded, so their tools are
|
|
474
|
+
# activated in LazyDelegateChat#initialize_chat when first accessed.
|
|
475
|
+
#
|
|
606
476
|
# @return [void]
|
|
607
477
|
def pass_6_activate_tools
|
|
608
|
-
# Activate tools for PRIMARY agents
|
|
478
|
+
# Activate tools for PRIMARY agents only
|
|
479
|
+
# (Delegation instances handle their own tool activation via LazyDelegateChat)
|
|
609
480
|
@agents.each_value(&:activate_tools_for_prompt)
|
|
610
|
-
|
|
611
|
-
# Activate tools for DELEGATION instances
|
|
612
|
-
@swarm.delegation_instances.each_value(&:activate_tools_for_prompt)
|
|
613
481
|
end
|
|
614
482
|
|
|
615
483
|
# Create Agent::Chat instance with rate limiting
|
|
@@ -653,103 +521,8 @@ module SwarmSDK
|
|
|
653
521
|
chat
|
|
654
522
|
end
|
|
655
523
|
|
|
656
|
-
#
|
|
657
|
-
#
|
|
658
|
-
# V7.0: Simplified - just calls register_all_tools with instance_name
|
|
659
|
-
#
|
|
660
|
-
# @param instance_name [String] Unique instance name ("base@delegator")
|
|
661
|
-
# @param base_name [Symbol] Base agent name (for definition lookup)
|
|
662
|
-
# @param agent_definition [Agent::Definition] Base agent definition
|
|
663
|
-
# @param tool_configurator [ToolConfigurator] Shared tool configurator
|
|
664
|
-
# @return [Agent::Chat] Delegation-specific chat instance
|
|
665
|
-
def create_agent_chat_for_delegation(instance_name:, base_name:, agent_definition:, tool_configurator:)
|
|
666
|
-
# Create chat with instance_name for isolated conversation + tool state
|
|
667
|
-
chat = Agent::Chat.new(
|
|
668
|
-
definition: agent_definition.to_h,
|
|
669
|
-
agent_name: instance_name.to_sym, # Full instance name for isolation
|
|
670
|
-
global_semaphore: @swarm.global_semaphore,
|
|
671
|
-
)
|
|
672
|
-
|
|
673
|
-
# Set provider agent name for logging
|
|
674
|
-
chat.provider.agent_name = instance_name if chat.provider.respond_to?(:agent_name=)
|
|
675
|
-
|
|
676
|
-
# V7.0 SIMPLIFIED: Just call register_all_tools with instance_name!
|
|
677
|
-
# Base name extraction happens automatically in create_plugin_tool
|
|
678
|
-
tool_configurator.register_all_tools(
|
|
679
|
-
chat: chat,
|
|
680
|
-
agent_name: instance_name.to_sym,
|
|
681
|
-
agent_definition: agent_definition,
|
|
682
|
-
)
|
|
683
|
-
|
|
684
|
-
# Register MCP servers (tracked by instance_name automatically)
|
|
685
|
-
if agent_definition.mcp_servers.any?
|
|
686
|
-
mcp_configurator = McpConfigurator.new(@swarm)
|
|
687
|
-
mcp_configurator.register_mcp_servers(
|
|
688
|
-
chat,
|
|
689
|
-
agent_definition.mcp_servers,
|
|
690
|
-
agent_name: instance_name,
|
|
691
|
-
)
|
|
692
|
-
end
|
|
693
|
-
|
|
694
|
-
# Setup tool activation dependencies (Plan 025)
|
|
695
|
-
chat.setup_tool_activation(
|
|
696
|
-
tool_configurator: tool_configurator,
|
|
697
|
-
agent_definition: agent_definition,
|
|
698
|
-
)
|
|
699
|
-
|
|
700
|
-
# Notify plugins (use instance_name, plugins extract base_name if needed)
|
|
701
|
-
notify_plugins_agent_initialized(instance_name.to_sym, chat, agent_definition, tool_configurator)
|
|
702
|
-
|
|
703
|
-
# NOTE: activate_tools_for_prompt is called in Pass 6 after all plugins
|
|
704
|
-
|
|
705
|
-
chat
|
|
706
|
-
end
|
|
707
|
-
|
|
708
|
-
# Register agent delegation tools
|
|
709
|
-
#
|
|
710
|
-
# Creates delegation tools that allow one agent to call another.
|
|
711
|
-
#
|
|
712
|
-
# @param chat [Agent::Chat] The chat instance
|
|
713
|
-
# @param delegate_names [Array<Symbol>] Names of agents to delegate to
|
|
714
|
-
# @param agent_name [Symbol] Name of the agent doing the delegating
|
|
715
|
-
def register_delegation_tools(chat, delegate_names, agent_name:)
|
|
716
|
-
return if delegate_names.empty?
|
|
717
|
-
|
|
718
|
-
delegate_names.each do |delegate_name|
|
|
719
|
-
delegate_name_sym = delegate_name.to_sym
|
|
720
|
-
delegate_name_str = delegate_name.to_s
|
|
721
|
-
|
|
722
|
-
# Check if target is a local agent
|
|
723
|
-
if @agents.key?(delegate_name_sym)
|
|
724
|
-
# Delegate to local agent
|
|
725
|
-
delegate_agent = @agents[delegate_name_sym]
|
|
726
|
-
delegate_definition = @swarm.agent_definitions[delegate_name_sym]
|
|
727
|
-
|
|
728
|
-
tool = create_delegation_tool(
|
|
729
|
-
name: delegate_name_str,
|
|
730
|
-
description: delegate_definition.description,
|
|
731
|
-
delegate_chat: delegate_agent,
|
|
732
|
-
agent_name: agent_name,
|
|
733
|
-
delegating_chat: chat,
|
|
734
|
-
)
|
|
735
|
-
|
|
736
|
-
chat.add_tool(tool)
|
|
737
|
-
elsif @swarm.swarm_registry&.registered?(delegate_name_str)
|
|
738
|
-
# Delegate to registered swarm
|
|
739
|
-
tool = create_delegation_tool(
|
|
740
|
-
name: delegate_name_str,
|
|
741
|
-
description: "External swarm: #{delegate_name_str}",
|
|
742
|
-
delegate_chat: nil, # Swarm delegation - no direct chat
|
|
743
|
-
agent_name: agent_name,
|
|
744
|
-
delegating_chat: chat,
|
|
745
|
-
)
|
|
746
|
-
|
|
747
|
-
chat.add_tool(tool)
|
|
748
|
-
else
|
|
749
|
-
raise ConfigurationError, "Agent '#{agent_name}' delegates to unknown target '#{delegate_name_str}' (not a local agent or registered swarm)"
|
|
750
|
-
end
|
|
751
|
-
end
|
|
752
|
-
end
|
|
524
|
+
# NOTE: create_agent_chat_for_delegation and register_delegation_tools were removed.
|
|
525
|
+
# Delegation instances are now lazy-loaded via LazyDelegateChat.
|
|
753
526
|
|
|
754
527
|
# Create plugin storages for all agents
|
|
755
528
|
#
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SwarmSDK
|
|
4
|
+
class Swarm
|
|
5
|
+
# Lazy loader for delegation agent chats
|
|
6
|
+
#
|
|
7
|
+
# Instead of creating delegation instances eagerly at swarm initialization,
|
|
8
|
+
# this class defers creation until the first delegation call. This improves
|
|
9
|
+
# initialization performance for swarms where not all agents are used.
|
|
10
|
+
#
|
|
11
|
+
# Thread-safe via Mutex - safe for concurrent access.
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# lazy_chat = LazyDelegateChat.new(
|
|
15
|
+
# instance_name: "backend@frontend",
|
|
16
|
+
# base_name: :backend,
|
|
17
|
+
# agent_definition: backend_def,
|
|
18
|
+
# swarm: swarm
|
|
19
|
+
# )
|
|
20
|
+
#
|
|
21
|
+
# # Later, when delegation is first called:
|
|
22
|
+
# chat = lazy_chat.chat # Creates agent on first access
|
|
23
|
+
# chat.ask("Do something")
|
|
24
|
+
class LazyDelegateChat
|
|
25
|
+
attr_reader :instance_name, :base_name, :agent_definition
|
|
26
|
+
|
|
27
|
+
# Initialize a lazy delegate chat wrapper
|
|
28
|
+
#
|
|
29
|
+
# @param instance_name [String] Unique instance name ("base@delegator")
|
|
30
|
+
# @param base_name [Symbol] Base agent name (for definition lookup)
|
|
31
|
+
# @param agent_definition [Agent::Definition] Agent definition
|
|
32
|
+
# @param swarm [Swarm] Parent swarm reference
|
|
33
|
+
def initialize(instance_name:, base_name:, agent_definition:, swarm:)
|
|
34
|
+
@instance_name = instance_name
|
|
35
|
+
@base_name = base_name
|
|
36
|
+
@agent_definition = agent_definition
|
|
37
|
+
@swarm = swarm
|
|
38
|
+
@chat = nil
|
|
39
|
+
@mutex = Mutex.new
|
|
40
|
+
@initialized = false
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Get or create the agent chat instance
|
|
44
|
+
#
|
|
45
|
+
# On first call, creates the agent chat and runs initialization.
|
|
46
|
+
# Subsequent calls return the cached instance.
|
|
47
|
+
#
|
|
48
|
+
# @return [Agent::Chat] The initialized chat instance
|
|
49
|
+
def chat
|
|
50
|
+
return @chat if @initialized
|
|
51
|
+
|
|
52
|
+
@mutex.synchronize do
|
|
53
|
+
return @chat if @initialized
|
|
54
|
+
|
|
55
|
+
@chat = initialize_chat
|
|
56
|
+
@initialized = true
|
|
57
|
+
@chat
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Check if the agent has been initialized
|
|
62
|
+
#
|
|
63
|
+
# @return [Boolean] True if chat has been created
|
|
64
|
+
def initialized?
|
|
65
|
+
@mutex.synchronize { @initialized }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
# Create and initialize the agent chat
|
|
71
|
+
#
|
|
72
|
+
# This mirrors the initialization done in AgentInitializer passes 1-6
|
|
73
|
+
# but for a single lazy delegate.
|
|
74
|
+
#
|
|
75
|
+
# @return [Agent::Chat] Fully initialized chat instance
|
|
76
|
+
def initialize_chat
|
|
77
|
+
emit_lazy_init_start
|
|
78
|
+
|
|
79
|
+
# Create tool configurator
|
|
80
|
+
tool_configurator = ToolConfigurator.new(
|
|
81
|
+
@swarm,
|
|
82
|
+
@swarm.scratchpad_storage,
|
|
83
|
+
@swarm.plugin_storages,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Create the agent chat (mirrors AgentInitializer#create_agent_chat_for_delegation)
|
|
87
|
+
chat = create_delegation_chat(tool_configurator)
|
|
88
|
+
|
|
89
|
+
# Store in delegation_instances (so other code can find it)
|
|
90
|
+
@swarm.delegation_instances[@instance_name] = chat
|
|
91
|
+
|
|
92
|
+
# Wire delegation tools for this instance (nested delegation support)
|
|
93
|
+
wire_delegation_tools(chat, tool_configurator)
|
|
94
|
+
|
|
95
|
+
# Setup context
|
|
96
|
+
setup_context(chat)
|
|
97
|
+
|
|
98
|
+
# Configure hooks
|
|
99
|
+
configure_hooks(chat)
|
|
100
|
+
|
|
101
|
+
# Apply YAML hooks if present
|
|
102
|
+
apply_yaml_hooks(chat)
|
|
103
|
+
|
|
104
|
+
# Activate tools
|
|
105
|
+
chat.activate_tools_for_prompt
|
|
106
|
+
|
|
107
|
+
# Notify plugins
|
|
108
|
+
notify_plugins(chat, tool_configurator)
|
|
109
|
+
|
|
110
|
+
emit_lazy_init_complete
|
|
111
|
+
|
|
112
|
+
chat
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Create the delegation chat instance
|
|
116
|
+
#
|
|
117
|
+
# @param tool_configurator [ToolConfigurator] Tool configuration helper
|
|
118
|
+
# @return [Agent::Chat] Newly created chat instance
|
|
119
|
+
def create_delegation_chat(tool_configurator)
|
|
120
|
+
chat = Agent::Chat.new(
|
|
121
|
+
definition: @agent_definition.to_h,
|
|
122
|
+
agent_name: @instance_name.to_sym,
|
|
123
|
+
global_semaphore: @swarm.global_semaphore,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Set provider agent name for logging
|
|
127
|
+
chat.provider.agent_name = @instance_name if chat.provider.respond_to?(:agent_name=)
|
|
128
|
+
|
|
129
|
+
# Register all tools (built-in, permissions, etc.)
|
|
130
|
+
tool_configurator.register_all_tools(
|
|
131
|
+
chat: chat,
|
|
132
|
+
agent_name: @instance_name.to_sym,
|
|
133
|
+
agent_definition: @agent_definition,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Register MCP servers if configured
|
|
137
|
+
if @agent_definition.mcp_servers.any?
|
|
138
|
+
mcp_configurator = McpConfigurator.new(@swarm)
|
|
139
|
+
mcp_configurator.register_mcp_servers(
|
|
140
|
+
chat,
|
|
141
|
+
@agent_definition.mcp_servers,
|
|
142
|
+
agent_name: @instance_name,
|
|
143
|
+
)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Setup tool activation dependencies
|
|
147
|
+
chat.setup_tool_activation(
|
|
148
|
+
tool_configurator: tool_configurator,
|
|
149
|
+
agent_definition: @agent_definition,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
chat
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Wire delegation tools for this instance (nested delegation support)
|
|
156
|
+
#
|
|
157
|
+
# If this agent delegates to other agents, create those tools.
|
|
158
|
+
# The delegate targets may themselves be lazy.
|
|
159
|
+
#
|
|
160
|
+
# @param chat [Agent::Chat] The chat instance
|
|
161
|
+
# @param tool_configurator [ToolConfigurator] Tool configuration helper
|
|
162
|
+
# @return [void]
|
|
163
|
+
def wire_delegation_tools(chat, tool_configurator)
|
|
164
|
+
@agent_definition.delegation_configs.each do |delegation_config|
|
|
165
|
+
delegate_name_sym = delegation_config[:agent]
|
|
166
|
+
delegate_name_str = delegate_name_sym.to_s
|
|
167
|
+
custom_tool_name = delegation_config[:tool_name]
|
|
168
|
+
preserve_context = delegation_config.fetch(:preserve_context, true)
|
|
169
|
+
|
|
170
|
+
# Check if target is a registered swarm
|
|
171
|
+
if @swarm.swarm_registry&.registered?(delegate_name_str)
|
|
172
|
+
wire_swarm_delegation(chat, delegate_name_str, custom_tool_name, preserve_context)
|
|
173
|
+
elsif @swarm.agent_definitions.key?(delegate_name_sym)
|
|
174
|
+
wire_agent_delegation(chat, delegate_name_sym, custom_tool_name, tool_configurator, preserve_context)
|
|
175
|
+
else
|
|
176
|
+
raise ConfigurationError,
|
|
177
|
+
"Agent '#{@instance_name}' delegates to unknown target '#{delegate_name_str}'"
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Wire delegation to an external swarm
|
|
183
|
+
#
|
|
184
|
+
# @param chat [Agent::Chat] The delegating chat
|
|
185
|
+
# @param swarm_name [String] Name of the registered swarm
|
|
186
|
+
# @param custom_tool_name [String, nil] Optional custom tool name
|
|
187
|
+
# @param preserve_context [Boolean] Whether to preserve context
|
|
188
|
+
# @return [void]
|
|
189
|
+
def wire_swarm_delegation(chat, swarm_name, custom_tool_name, preserve_context)
|
|
190
|
+
tool = Tools::Delegate.new(
|
|
191
|
+
delegate_name: swarm_name,
|
|
192
|
+
delegate_description: "External swarm: #{swarm_name}",
|
|
193
|
+
delegate_chat: nil,
|
|
194
|
+
agent_name: @instance_name,
|
|
195
|
+
swarm: @swarm,
|
|
196
|
+
delegating_chat: chat,
|
|
197
|
+
custom_tool_name: custom_tool_name,
|
|
198
|
+
preserve_context: preserve_context,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
chat.tool_registry.register(
|
|
202
|
+
tool,
|
|
203
|
+
source: :delegation,
|
|
204
|
+
metadata: { delegate_name: swarm_name, delegation_type: :swarm, preserve_context: preserve_context },
|
|
205
|
+
)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Wire delegation to a local agent
|
|
209
|
+
#
|
|
210
|
+
# For lazy-loaded agents, creates a LazyDelegateChat wrapper.
|
|
211
|
+
# For shared agents, uses the primary instance.
|
|
212
|
+
#
|
|
213
|
+
# @param chat [Agent::Chat] The delegating chat
|
|
214
|
+
# @param delegate_name_sym [Symbol] Name of the delegate agent
|
|
215
|
+
# @param custom_tool_name [String, nil] Optional custom tool name
|
|
216
|
+
# @param tool_configurator [ToolConfigurator] Tool configuration helper
|
|
217
|
+
# @param preserve_context [Boolean] Whether to preserve context
|
|
218
|
+
# @return [void]
|
|
219
|
+
def wire_agent_delegation(chat, delegate_name_sym, custom_tool_name, tool_configurator, preserve_context)
|
|
220
|
+
delegate_definition = @swarm.agent_definitions[delegate_name_sym]
|
|
221
|
+
|
|
222
|
+
# Determine which chat instance to use
|
|
223
|
+
target_chat = if delegate_definition.shared_across_delegations
|
|
224
|
+
# Shared mode: use primary agent
|
|
225
|
+
@swarm.agents[delegate_name_sym]
|
|
226
|
+
else
|
|
227
|
+
# Isolated mode: create nested lazy loader or get existing instance
|
|
228
|
+
nested_instance_name = "#{delegate_name_sym}@#{@instance_name}"
|
|
229
|
+
|
|
230
|
+
# Check if already exists (might have been created by another path)
|
|
231
|
+
existing = @swarm.delegation_instances[nested_instance_name]
|
|
232
|
+
if existing
|
|
233
|
+
existing
|
|
234
|
+
else
|
|
235
|
+
# Create lazy loader for nested delegation
|
|
236
|
+
LazyDelegateChat.new(
|
|
237
|
+
instance_name: nested_instance_name,
|
|
238
|
+
base_name: delegate_name_sym,
|
|
239
|
+
agent_definition: delegate_definition,
|
|
240
|
+
swarm: @swarm,
|
|
241
|
+
)
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Create delegation tool pointing to chosen instance
|
|
246
|
+
tool = Tools::Delegate.new(
|
|
247
|
+
delegate_name: delegate_name_sym.to_s,
|
|
248
|
+
delegate_description: delegate_definition.description,
|
|
249
|
+
delegate_chat: target_chat,
|
|
250
|
+
agent_name: @instance_name,
|
|
251
|
+
swarm: @swarm,
|
|
252
|
+
delegating_chat: chat,
|
|
253
|
+
custom_tool_name: custom_tool_name,
|
|
254
|
+
preserve_context: preserve_context,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
chat.tool_registry.register(
|
|
258
|
+
tool,
|
|
259
|
+
source: :delegation,
|
|
260
|
+
metadata: {
|
|
261
|
+
delegate_name: delegate_name_sym,
|
|
262
|
+
delegation_mode: delegate_definition.shared_across_delegations ? :shared : :isolated,
|
|
263
|
+
preserve_context: preserve_context,
|
|
264
|
+
},
|
|
265
|
+
)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Setup agent context
|
|
269
|
+
#
|
|
270
|
+
# @param chat [Agent::Chat] The chat instance
|
|
271
|
+
# @return [void]
|
|
272
|
+
def setup_context(chat)
|
|
273
|
+
delegate_tool_names = @agent_definition.delegation_configs.map do |delegation_config|
|
|
274
|
+
delegation_config[:tool_name] || Tools::Delegate.tool_name_for(delegation_config[:agent])
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
context = Agent::Context.new(
|
|
278
|
+
name: @instance_name.to_sym,
|
|
279
|
+
swarm_id: @swarm.swarm_id,
|
|
280
|
+
parent_swarm_id: @swarm.parent_swarm_id,
|
|
281
|
+
delegation_tools: delegate_tool_names,
|
|
282
|
+
metadata: { is_delegation_instance: true },
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
chat.setup_context(context) if chat.respond_to?(:setup_context)
|
|
286
|
+
|
|
287
|
+
# Configure logging if enabled
|
|
288
|
+
return unless LogStream.emitter
|
|
289
|
+
|
|
290
|
+
chat.setup_logging if chat.respond_to?(:setup_logging)
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Configure hooks for the agent
|
|
294
|
+
#
|
|
295
|
+
# @param chat [Agent::Chat] The chat instance
|
|
296
|
+
# @return [void]
|
|
297
|
+
def configure_hooks(chat)
|
|
298
|
+
return unless chat.respond_to?(:setup_hooks)
|
|
299
|
+
|
|
300
|
+
chat.setup_hooks(
|
|
301
|
+
registry: @swarm.hook_registry,
|
|
302
|
+
agent_definition: @agent_definition,
|
|
303
|
+
swarm: @swarm,
|
|
304
|
+
)
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Apply YAML hooks if present
|
|
308
|
+
#
|
|
309
|
+
# @param chat [Agent::Chat] The chat instance
|
|
310
|
+
# @return [void]
|
|
311
|
+
def apply_yaml_hooks(chat)
|
|
312
|
+
return unless @swarm.config_for_hooks
|
|
313
|
+
|
|
314
|
+
agent_config = @swarm.config_for_hooks.agents[@base_name]
|
|
315
|
+
return unless agent_config
|
|
316
|
+
|
|
317
|
+
hooks = agent_config.is_a?(Hash) ? agent_config[:hooks] : agent_config.hooks
|
|
318
|
+
return unless hooks&.any?
|
|
319
|
+
|
|
320
|
+
Hooks::Adapter.apply_agent_hooks(chat, @instance_name.to_sym, hooks, @swarm.name)
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# Notify plugins that agent was initialized
|
|
324
|
+
#
|
|
325
|
+
# @param chat [Agent::Chat] The chat instance
|
|
326
|
+
# @param tool_configurator [ToolConfigurator] Tool configuration helper
|
|
327
|
+
# @return [void]
|
|
328
|
+
def notify_plugins(chat, tool_configurator)
|
|
329
|
+
PluginRegistry.all.each do |plugin|
|
|
330
|
+
plugin_storages = @swarm.plugin_storages[plugin.name] || {}
|
|
331
|
+
storage = plugin_storages[@base_name]
|
|
332
|
+
|
|
333
|
+
context = {
|
|
334
|
+
storage: storage,
|
|
335
|
+
agent_definition: @agent_definition,
|
|
336
|
+
tool_configurator: tool_configurator,
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
plugin.on_agent_initialized(
|
|
340
|
+
agent_name: @instance_name.to_sym,
|
|
341
|
+
agent: chat,
|
|
342
|
+
context: context,
|
|
343
|
+
)
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# Emit lazy initialization start event
|
|
348
|
+
#
|
|
349
|
+
# @return [void]
|
|
350
|
+
def emit_lazy_init_start
|
|
351
|
+
LogStream.emit(
|
|
352
|
+
type: "agent_lazy_initialization_start",
|
|
353
|
+
instance_name: @instance_name,
|
|
354
|
+
base_name: @base_name,
|
|
355
|
+
timestamp: Time.now.utc.iso8601,
|
|
356
|
+
)
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
# Emit lazy initialization complete event
|
|
360
|
+
#
|
|
361
|
+
# @return [void]
|
|
362
|
+
def emit_lazy_init_complete
|
|
363
|
+
LogStream.emit(
|
|
364
|
+
type: "agent_lazy_initialization_complete",
|
|
365
|
+
instance_name: @instance_name,
|
|
366
|
+
base_name: @base_name,
|
|
367
|
+
timestamp: Time.now.utc.iso8601,
|
|
368
|
+
)
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
end
|
data/lib/swarm_sdk/swarm.rb
CHANGED
|
@@ -581,6 +581,61 @@ module SwarmSDK
|
|
|
581
581
|
# @return [void]
|
|
582
582
|
attr_writer :swarm_registry
|
|
583
583
|
|
|
584
|
+
# Force initialization of all lazy delegation instances
|
|
585
|
+
#
|
|
586
|
+
# By default, delegation instances for isolated delegates are lazy-loaded
|
|
587
|
+
# (created on first delegation call). This method forces immediate initialization
|
|
588
|
+
# of all lazy delegates, which can be useful for:
|
|
589
|
+
# - Testing: Verify delegation instance properties without mocking
|
|
590
|
+
# - Preloading: Initialize all agents upfront for predictable timing
|
|
591
|
+
#
|
|
592
|
+
# This method is recursive - it will initialize nested lazy delegates until
|
|
593
|
+
# all delegation chains are fully initialized.
|
|
594
|
+
#
|
|
595
|
+
# @return [void]
|
|
596
|
+
#
|
|
597
|
+
# @example Force-initialize all delegates for testing
|
|
598
|
+
# swarm.initialize_agents
|
|
599
|
+
# swarm.initialize_lazy_delegates!
|
|
600
|
+
# assert swarm.delegation_instances.key?("backend@lead")
|
|
601
|
+
def initialize_lazy_delegates!
|
|
602
|
+
initialize_agents unless @agents_initialized
|
|
603
|
+
|
|
604
|
+
# Keep initializing until no more lazy delegates are found
|
|
605
|
+
# This handles nested delegation chains (A -> B -> C)
|
|
606
|
+
loop do
|
|
607
|
+
initialized_any = false
|
|
608
|
+
|
|
609
|
+
# Find all delegation tools with lazy loaders in primary agents
|
|
610
|
+
@agents.each_value do |chat|
|
|
611
|
+
chat.tools.each_value do |tool|
|
|
612
|
+
next unless tool.is_a?(Tools::Delegate)
|
|
613
|
+
next unless tool.lazy? && !tool.initialized?
|
|
614
|
+
|
|
615
|
+
tool.initialize_delegate!
|
|
616
|
+
initialized_any = true
|
|
617
|
+
end
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
# Also check delegation instances for their own lazy delegates (nested)
|
|
621
|
+
@delegation_instances.values.each do |chat|
|
|
622
|
+
# Skip if this is still a LazyDelegateChat (shouldn't happen after above loop)
|
|
623
|
+
next if chat.is_a?(Swarm::LazyDelegateChat)
|
|
624
|
+
|
|
625
|
+
chat.tools.each_value do |tool|
|
|
626
|
+
next unless tool.is_a?(Tools::Delegate)
|
|
627
|
+
next unless tool.lazy? && !tool.initialized?
|
|
628
|
+
|
|
629
|
+
tool.initialize_delegate!
|
|
630
|
+
initialized_any = true
|
|
631
|
+
end
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
# Exit loop when no more lazy delegates were initialized
|
|
635
|
+
break unless initialized_any
|
|
636
|
+
end
|
|
637
|
+
end
|
|
638
|
+
|
|
584
639
|
# --- Internal API (for Executor use only) ---
|
|
585
640
|
# Hook triggers for swarm lifecycle events are provided by HookTriggers module
|
|
586
641
|
|
|
@@ -37,7 +37,7 @@ module SwarmSDK
|
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
attr_reader :delegate_name, :delegate_target, :tool_name, :preserve_context
|
|
40
|
+
attr_reader :delegate_name, :delegate_target, :tool_name, :preserve_context, :delegate_chat
|
|
41
41
|
|
|
42
42
|
# Initialize a delegation tool
|
|
43
43
|
#
|
|
@@ -94,6 +94,32 @@ module SwarmSDK
|
|
|
94
94
|
@tool_name
|
|
95
95
|
end
|
|
96
96
|
|
|
97
|
+
# Check if this delegate uses lazy loading
|
|
98
|
+
#
|
|
99
|
+
# @return [Boolean] True if delegate is lazy-loaded
|
|
100
|
+
def lazy?
|
|
101
|
+
@delegate_chat.is_a?(Swarm::LazyDelegateChat)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Check if this delegate has been initialized
|
|
105
|
+
#
|
|
106
|
+
# @return [Boolean] True if delegate chat is ready (either eager or lazy-initialized)
|
|
107
|
+
def initialized?
|
|
108
|
+
return true unless lazy?
|
|
109
|
+
|
|
110
|
+
@delegate_chat.initialized?
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Force initialization of lazy delegate
|
|
114
|
+
#
|
|
115
|
+
# If the delegate is lazy-loaded, this will trigger immediate initialization.
|
|
116
|
+
# For eager delegates, this is a no-op.
|
|
117
|
+
#
|
|
118
|
+
# @return [Agent::Chat] The resolved chat instance
|
|
119
|
+
def initialize_delegate!
|
|
120
|
+
resolve_delegate_chat
|
|
121
|
+
end
|
|
122
|
+
|
|
97
123
|
# Execute delegation with pre/post hooks
|
|
98
124
|
#
|
|
99
125
|
# @param message [String] Message to send to the agent
|
|
@@ -226,6 +252,9 @@ module SwarmSDK
|
|
|
226
252
|
|
|
227
253
|
# Delegate to an agent
|
|
228
254
|
#
|
|
255
|
+
# Handles both eager Agent::Chat instances and lazy-loaded delegates.
|
|
256
|
+
# LazyDelegateChat instances are initialized on first access.
|
|
257
|
+
#
|
|
229
258
|
# @param message [String] Message to send to the agent
|
|
230
259
|
# @param call_stack [Array] Delegation call stack for circular dependency detection
|
|
231
260
|
# @param reset_context [Boolean] Whether to reset the agent's conversation history before delegation
|
|
@@ -234,11 +263,14 @@ module SwarmSDK
|
|
|
234
263
|
# Push delegate target onto call stack to track delegation chain
|
|
235
264
|
call_stack.push(@delegate_target)
|
|
236
265
|
begin
|
|
266
|
+
# Resolve the chat instance (handles lazy loading)
|
|
267
|
+
chat = resolve_delegate_chat
|
|
268
|
+
|
|
237
269
|
# Clear conversation if reset_context is true OR if preserve_context is false
|
|
238
270
|
# reset_context takes precedence as it's an explicit request
|
|
239
|
-
|
|
271
|
+
chat.clear_conversation if reset_context || !@preserve_context
|
|
240
272
|
|
|
241
|
-
response =
|
|
273
|
+
response = chat.ask(message, source: "delegation")
|
|
242
274
|
response.content
|
|
243
275
|
ensure
|
|
244
276
|
# Always pop from stack, even if delegation fails
|
|
@@ -246,6 +278,20 @@ module SwarmSDK
|
|
|
246
278
|
end
|
|
247
279
|
end
|
|
248
280
|
|
|
281
|
+
# Resolve the delegate chat instance
|
|
282
|
+
#
|
|
283
|
+
# If the delegate is a LazyDelegateChat, initializes it on first access.
|
|
284
|
+
# Otherwise, returns the chat directly.
|
|
285
|
+
#
|
|
286
|
+
# @return [Agent::Chat] The resolved chat instance
|
|
287
|
+
def resolve_delegate_chat
|
|
288
|
+
if @delegate_chat.is_a?(Swarm::LazyDelegateChat)
|
|
289
|
+
@delegate_chat.chat
|
|
290
|
+
else
|
|
291
|
+
@delegate_chat
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
249
295
|
# Delegate to a registered swarm
|
|
250
296
|
#
|
|
251
297
|
# @param message [String] Message to send to the swarm
|
data/lib/swarm_sdk/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: swarm_sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.7.
|
|
4
|
+
version: 2.7.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Paulo Arruda
|
|
@@ -200,6 +200,7 @@ files:
|
|
|
200
200
|
- lib/swarm_sdk/swarm/builder.rb
|
|
201
201
|
- lib/swarm_sdk/swarm/executor.rb
|
|
202
202
|
- lib/swarm_sdk/swarm/hook_triggers.rb
|
|
203
|
+
- lib/swarm_sdk/swarm/lazy_delegate_chat.rb
|
|
203
204
|
- lib/swarm_sdk/swarm/logging_callbacks.rb
|
|
204
205
|
- lib/swarm_sdk/swarm/mcp_configurator.rb
|
|
205
206
|
- lib/swarm_sdk/swarm/swarm_registry_builder.rb
|