swarm_memory 2.1.2 → 2.1.3

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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/lib/claude_swarm/configuration.rb +28 -4
  3. data/lib/claude_swarm/mcp_generator.rb +4 -10
  4. data/lib/claude_swarm/version.rb +1 -1
  5. data/lib/swarm_cli/commands/mcp_serve.rb +2 -2
  6. data/lib/swarm_cli/config_loader.rb +3 -3
  7. data/lib/swarm_cli/version.rb +1 -1
  8. data/lib/swarm_memory/adapters/base.rb +4 -4
  9. data/lib/swarm_memory/core/storage_read_tracker.rb +51 -14
  10. data/lib/swarm_memory/integration/cli_registration.rb +3 -2
  11. data/lib/swarm_memory/integration/sdk_plugin.rb +11 -5
  12. data/lib/swarm_memory/tools/memory_edit.rb +2 -2
  13. data/lib/swarm_memory/tools/memory_multi_edit.rb +2 -2
  14. data/lib/swarm_memory/tools/memory_read.rb +3 -3
  15. data/lib/swarm_memory/version.rb +1 -1
  16. data/lib/swarm_memory.rb +5 -0
  17. data/lib/swarm_sdk/agent/builder.rb +33 -0
  18. data/lib/swarm_sdk/agent/chat/context_tracker.rb +33 -0
  19. data/lib/swarm_sdk/agent/chat/hook_integration.rb +41 -0
  20. data/lib/swarm_sdk/agent/chat/system_reminder_injector.rb +11 -27
  21. data/lib/swarm_sdk/agent/chat.rb +198 -51
  22. data/lib/swarm_sdk/agent/context.rb +6 -2
  23. data/lib/swarm_sdk/agent/context_manager.rb +6 -0
  24. data/lib/swarm_sdk/agent/definition.rb +15 -22
  25. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +180 -0
  26. data/lib/swarm_sdk/configuration.rb +420 -103
  27. data/lib/swarm_sdk/events_to_messages.rb +181 -0
  28. data/lib/swarm_sdk/log_collector.rb +31 -5
  29. data/lib/swarm_sdk/log_stream.rb +37 -8
  30. data/lib/swarm_sdk/model_aliases.json +4 -1
  31. data/lib/swarm_sdk/node/agent_config.rb +33 -8
  32. data/lib/swarm_sdk/node/builder.rb +39 -18
  33. data/lib/swarm_sdk/node_orchestrator.rb +293 -26
  34. data/lib/swarm_sdk/proc_helpers.rb +53 -0
  35. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -126
  36. data/lib/swarm_sdk/providers/openai_with_responses.rb +22 -15
  37. data/lib/swarm_sdk/restore_result.rb +65 -0
  38. data/lib/swarm_sdk/snapshot.rb +156 -0
  39. data/lib/swarm_sdk/snapshot_from_events.rb +386 -0
  40. data/lib/swarm_sdk/state_restorer.rb +491 -0
  41. data/lib/swarm_sdk/state_snapshot.rb +369 -0
  42. data/lib/swarm_sdk/swarm/agent_initializer.rb +360 -55
  43. data/lib/swarm_sdk/swarm/all_agents_builder.rb +28 -1
  44. data/lib/swarm_sdk/swarm/builder.rb +208 -12
  45. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +67 -0
  46. data/lib/swarm_sdk/swarm/tool_configurator.rb +46 -11
  47. data/lib/swarm_sdk/swarm.rb +367 -90
  48. data/lib/swarm_sdk/swarm_loader.rb +145 -0
  49. data/lib/swarm_sdk/swarm_registry.rb +136 -0
  50. data/lib/swarm_sdk/tools/delegate.rb +92 -7
  51. data/lib/swarm_sdk/tools/read.rb +17 -5
  52. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +23 -2
  53. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +23 -2
  54. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +21 -4
  55. data/lib/swarm_sdk/tools/stores/read_tracker.rb +47 -12
  56. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +45 -0
  57. data/lib/swarm_sdk/tools/stores/storage.rb +4 -4
  58. data/lib/swarm_sdk/tools/think.rb +4 -1
  59. data/lib/swarm_sdk/tools/todo_write.rb +20 -8
  60. data/lib/swarm_sdk/utils.rb +18 -0
  61. data/lib/swarm_sdk/validation_result.rb +33 -0
  62. data/lib/swarm_sdk/version.rb +1 -1
  63. data/lib/swarm_sdk.rb +362 -21
  64. metadata +17 -5
@@ -69,16 +69,23 @@ module SwarmSDK
69
69
  agent_name: agent_name,
70
70
  swarm: @swarm,
71
71
  hook_registry: @hook_registry,
72
+ call_stack: @swarm.delegation_call_stack,
73
+ swarm_registry: @swarm.swarm_registry,
72
74
  delegating_chat: delegating_chat,
73
75
  )
74
76
  end
75
77
 
76
78
  private
77
79
 
78
- # Pass 1: Create all agent chat instances
80
+ # Pass 1: Create primary agent chat instances
79
81
  #
80
- # This creates the Agent::Chat instances but doesn't wire them together yet.
81
- # Each agent gets its own chat instance with configured tools.
82
+ # Only creates agents that will actually be used as primaries:
83
+ # - The lead agent
84
+ # - Agents with shared_across_delegations: true (shared delegates)
85
+ # - Agents not used as delegates (standalone agents)
86
+ #
87
+ # Agents that are ONLY delegates with shared_across_delegations: false
88
+ # are NOT created here - they'll be created as delegation instances in pass 2a.
82
89
  def pass_1_create_agents
83
90
  # Create plugin storages for agents
84
91
  create_plugin_storages
@@ -86,6 +93,9 @@ module SwarmSDK
86
93
  tool_configurator = ToolConfigurator.new(@swarm, @scratchpad_storage, @plugin_storages)
87
94
 
88
95
  @agent_definitions.each do |name, agent_definition|
96
+ # Skip if this agent will only exist as delegation instances
97
+ next if should_skip_primary_creation?(name, agent_definition)
98
+
89
99
  chat = create_agent_chat(name, agent_definition, tool_configurator)
90
100
  @agents[name] = chat
91
101
 
@@ -94,13 +104,159 @@ module SwarmSDK
94
104
  end
95
105
  end
96
106
 
97
- # Pass 2: Register agent delegation tools
107
+ # Pass 2: Create delegation instances and wire delegation tools
98
108
  #
99
- # Now that all agents exist, we can create delegation tools
100
- # that allow agents to call each other.
109
+ # This pass has three sub-steps that must happen in order:
110
+ # 2a. Create delegation instances (ONLY for agents with shared_across_delegations: false)
111
+ # 2b. Wire primary agents to delegation instances OR shared primaries
112
+ # 2c. Wire delegation instances to their delegates (nested delegation support)
101
113
  def pass_2_register_delegation_tools
102
- @agent_definitions.each do |name, agent_definition|
103
- register_delegation_tools(@agents[name], agent_definition.delegates_to, agent_name: name)
114
+ tool_configurator = ToolConfigurator.new(@swarm, @scratchpad_storage, @plugin_storages)
115
+
116
+ # Sub-pass 2a: Create delegation instances for isolated agents
117
+ @agent_definitions.each do |delegator_name, delegator_def|
118
+ delegator_def.delegates_to.each do |delegate_base_name|
119
+ delegate_base_name = delegate_base_name.to_sym
120
+
121
+ unless @agent_definitions.key?(delegate_base_name)
122
+ raise ConfigurationError,
123
+ "Agent '#{delegator_name}' delegates to unknown agent '#{delegate_base_name}'"
124
+ end
125
+
126
+ delegate_definition = @agent_definitions[delegate_base_name]
127
+
128
+ # Check isolation mode of the DELEGATE agent
129
+ # If delegate wants to be shared, skip instance creation (use primary)
130
+ next if delegate_definition.shared_across_delegations
131
+
132
+ # Create unique delegation instance (isolated mode)
133
+ instance_name = "#{delegate_base_name}@#{delegator_name}"
134
+
135
+ # V7.0: Use existing register_all_tools (no new method needed!)
136
+ delegation_chat = create_agent_chat_for_delegation(
137
+ instance_name: instance_name,
138
+ base_name: delegate_base_name,
139
+ agent_definition: delegate_definition,
140
+ tool_configurator: tool_configurator,
141
+ )
142
+
143
+ # Store in delegation_instances hash
144
+ @swarm.delegation_instances[instance_name] = delegation_chat
145
+ end
146
+ end
147
+
148
+ # Sub-pass 2b: Wire primary agents to delegation instances OR shared primaries OR registered swarms
149
+ @agent_definitions.each do |delegator_name, delegator_def|
150
+ delegator_chat = @agents[delegator_name]
151
+
152
+ # Skip if delegator doesn't exist as primary (wasn't created in pass_1)
153
+ next unless delegator_chat
154
+
155
+ delegator_def.delegates_to.each do |delegate_base_name|
156
+ delegate_base_name_str = delegate_base_name.to_s
157
+
158
+ # Check if target is a registered swarm
159
+ if @swarm.swarm_registry&.registered?(delegate_base_name_str)
160
+ # Create delegation tool for swarm
161
+ tool = create_delegation_tool(
162
+ name: delegate_base_name_str,
163
+ description: "External swarm: #{delegate_base_name_str}",
164
+ delegate_chat: nil, # Swarm delegation - no direct chat
165
+ agent_name: delegator_name,
166
+ delegating_chat: delegator_chat,
167
+ )
168
+
169
+ delegator_chat.with_tool(tool)
170
+ elsif @agent_definitions.key?(delegate_base_name)
171
+ # Delegate to local agent
172
+ delegate_definition = @agent_definitions[delegate_base_name]
173
+
174
+ # Determine which chat instance to use
175
+ target_chat = if delegate_definition.shared_across_delegations
176
+ # Shared mode: use primary agent (old behavior)
177
+ @agents[delegate_base_name]
178
+ else
179
+ # Isolated mode: use delegation instance
180
+ instance_name = "#{delegate_base_name}@#{delegator_name}"
181
+ @swarm.delegation_instances[instance_name]
182
+ end
183
+
184
+ # Create delegation tool pointing to chosen instance
185
+ tool = create_delegation_tool(
186
+ name: delegate_base_name.to_s,
187
+ description: delegate_definition.description,
188
+ delegate_chat: target_chat, # ← Isolated instance OR shared primary
189
+ agent_name: delegator_name,
190
+ delegating_chat: delegator_chat,
191
+ )
192
+
193
+ delegator_chat.with_tool(tool)
194
+ else
195
+ raise ConfigurationError, "Agent '#{delegator_name}' delegates to unknown target '#{delegate_base_name_str}' (not a local agent or registered swarm)"
196
+ end
197
+ end
198
+ end
199
+
200
+ # Sub-pass 2c: Wire delegation instances to their delegates (nested delegation)
201
+ # Convert to array first to avoid "can't add key during iteration" error
202
+ @swarm.delegation_instances.to_a.each do |instance_name, delegation_chat|
203
+ base_name = extract_base_name(instance_name)
204
+ delegate_definition = @agent_definitions[base_name]
205
+
206
+ # Register delegation tools for THIS instance's delegates_to
207
+ delegate_definition.delegates_to.each do |nested_delegate_name|
208
+ nested_delegate_name_sym = nested_delegate_name.to_sym
209
+ nested_delegate_name_str = nested_delegate_name.to_s
210
+
211
+ # Check if target is a registered swarm
212
+ if @swarm.swarm_registry&.registered?(nested_delegate_name_str)
213
+ # Create delegation tool for swarm
214
+ nested_tool = create_delegation_tool(
215
+ name: nested_delegate_name_str,
216
+ description: "External swarm: #{nested_delegate_name_str}",
217
+ delegate_chat: nil, # Swarm delegation - no direct chat
218
+ agent_name: instance_name.to_sym,
219
+ delegating_chat: delegation_chat,
220
+ )
221
+
222
+ delegation_chat.with_tool(nested_tool)
223
+ elsif @agent_definitions.key?(nested_delegate_name_sym)
224
+ # Delegate to local agent
225
+ nested_definition = @agent_definitions[nested_delegate_name_sym]
226
+
227
+ # Determine target: shared primary OR isolated instance
228
+ target_chat = if nested_definition.shared_across_delegations
229
+ # Shared mode: point to primary (semaphore-protected)
230
+ @agents[nested_delegate_name_sym]
231
+ else
232
+ # Isolated mode: delegation instances also get isolated nested delegates
233
+ # Create unique instance for this delegation chain
234
+ nested_instance_name = "#{nested_delegate_name}@#{instance_name}"
235
+
236
+ # Check if already created in 2a (if delegator also delegates to this agent)
237
+ @swarm.delegation_instances[nested_instance_name] ||= create_agent_chat_for_delegation(
238
+ instance_name: nested_instance_name,
239
+ base_name: nested_delegate_name_sym,
240
+ agent_definition: nested_definition,
241
+ tool_configurator: tool_configurator,
242
+ )
243
+ end
244
+
245
+ # Create delegation tool
246
+ nested_tool = create_delegation_tool(
247
+ name: nested_delegate_name.to_s,
248
+ description: nested_definition.description,
249
+ delegate_chat: target_chat, # ← Isolated OR shared
250
+ agent_name: instance_name.to_sym,
251
+ delegating_chat: delegation_chat,
252
+ )
253
+
254
+ delegation_chat.with_tool(nested_tool)
255
+ else
256
+ raise ConfigurationError,
257
+ "Delegation instance '#{instance_name}' delegates to unknown target '#{nested_delegate_name_str}' (not a local agent or registered swarm)"
258
+ end
259
+ end
104
260
  end
105
261
  end
106
262
 
@@ -109,31 +265,46 @@ module SwarmSDK
109
265
  # Create Agent::Context for each agent to track delegations and metadata.
110
266
  # This is needed regardless of whether logging is enabled.
111
267
  def pass_3_setup_contexts
268
+ # Setup contexts for PRIMARY agents
112
269
  @agents.each do |agent_name, chat|
113
- agent_definition = @agent_definitions[agent_name]
114
- delegate_tool_names = agent_definition.delegates_to.map do |delegate_name|
115
- "DelegateTaskTo#{delegate_name.to_s.capitalize}"
116
- end
270
+ setup_agent_context(agent_name, @agent_definitions[agent_name], chat, is_delegation: false)
271
+ end
117
272
 
118
- # Create agent context
119
- context = Agent::Context.new(
120
- name: agent_name,
121
- delegation_tools: delegate_tool_names,
122
- metadata: {},
123
- )
124
- @agent_contexts[agent_name] = context
273
+ # Setup contexts for DELEGATION instances
274
+ @swarm.delegation_instances.each do |instance_name, chat|
275
+ base_name = extract_base_name(instance_name)
276
+ agent_definition = @agent_definitions[base_name]
277
+ setup_agent_context(instance_name.to_sym, agent_definition, chat, is_delegation: true)
278
+ end
279
+ end
280
+
281
+ # Setup context for an agent (primary or delegation instance)
282
+ def setup_agent_context(agent_name, agent_definition, chat, is_delegation: false)
283
+ delegate_tool_names = agent_definition.delegates_to.map do |delegate_name|
284
+ "DelegateTaskTo#{delegate_name.to_s.capitalize}"
285
+ end
125
286
 
126
- # Always set agent context (needed for delegation tracking)
127
- chat.setup_context(context) if chat.respond_to?(:setup_context)
287
+ context = Agent::Context.new(
288
+ name: agent_name,
289
+ swarm_id: @swarm.swarm_id,
290
+ parent_swarm_id: @swarm.parent_swarm_id,
291
+ delegation_tools: delegate_tool_names,
292
+ metadata: { is_delegation_instance: is_delegation },
293
+ )
128
294
 
129
- # Configure logging callbacks if logging is enabled
130
- next unless LogStream.emitter
295
+ # Store context (only for primaries)
296
+ @agent_contexts[agent_name] = context unless is_delegation
131
297
 
132
- chat.setup_logging if chat.respond_to?(:setup_logging)
298
+ # Always set agent context on chat
299
+ chat.setup_context(context) if chat.respond_to?(:setup_context)
133
300
 
134
- # Emit validation warnings for this agent
135
- emit_validation_warnings(agent_name, agent_definition)
136
- end
301
+ # Configure logging if enabled
302
+ return unless LogStream.emitter
303
+
304
+ chat.setup_logging if chat.respond_to?(:setup_logging)
305
+
306
+ # Emit validation warnings (only for primaries, not each delegation instance)
307
+ emit_validation_warnings(agent_name, agent_definition) unless is_delegation
137
308
  end
138
309
 
139
310
  # Emit validation warnings as log events
@@ -166,18 +337,29 @@ module SwarmSDK
166
337
  #
167
338
  # Setup the callback system for each agent, integrating with RubyLLM callbacks.
168
339
  def pass_4_configure_hooks
340
+ # Configure hooks for PRIMARY agents
169
341
  @agents.each do |agent_name, chat|
170
- agent_definition = @agent_definitions[agent_name]
342
+ configure_hooks_for_agent(agent_name, chat)
343
+ end
171
344
 
172
- # Configure callback system (integrates with RubyLLM callbacks)
173
- chat.setup_hooks(
174
- registry: @hook_registry,
175
- agent_definition: agent_definition,
176
- swarm: @swarm,
177
- ) if chat.respond_to?(:setup_hooks)
345
+ # Configure hooks for DELEGATION instances
346
+ @swarm.delegation_instances.each do |instance_name, chat|
347
+ configure_hooks_for_agent(instance_name.to_sym, chat)
178
348
  end
179
349
  end
180
350
 
351
+ # Configure hooks for an agent (primary or delegation instance)
352
+ def configure_hooks_for_agent(agent_name, chat)
353
+ base_name = extract_base_name(agent_name)
354
+ agent_definition = @agent_definitions[base_name]
355
+
356
+ chat.setup_hooks(
357
+ registry: @hook_registry,
358
+ agent_definition: agent_definition,
359
+ swarm: @swarm,
360
+ ) if chat.respond_to?(:setup_hooks)
361
+ end
362
+
181
363
  # Pass 5: Apply YAML hooks
182
364
  #
183
365
  # If the swarm was loaded from YAML with agent-specific hooks,
@@ -185,15 +367,30 @@ module SwarmSDK
185
367
  def pass_5_apply_yaml_hooks
186
368
  return unless @config_for_hooks
187
369
 
370
+ # Apply YAML hooks to PRIMARY agents
188
371
  @agents.each do |agent_name, chat|
189
- agent_def = @config_for_hooks.agents[agent_name]
190
- next unless agent_def&.hooks
372
+ apply_yaml_hooks_for_agent(agent_name, chat)
373
+ end
191
374
 
192
- # Apply agent-specific hooks via Hooks::Adapter
193
- Hooks::Adapter.apply_agent_hooks(chat, agent_name, agent_def.hooks, @swarm.name)
375
+ # Apply YAML hooks to DELEGATION instances
376
+ @swarm.delegation_instances.each do |instance_name, chat|
377
+ apply_yaml_hooks_for_agent(instance_name.to_sym, chat)
194
378
  end
195
379
  end
196
380
 
381
+ # Apply YAML hooks for an agent (primary or delegation instance)
382
+ def apply_yaml_hooks_for_agent(agent_name, chat)
383
+ base_name = extract_base_name(agent_name)
384
+ agent_config = @config_for_hooks.agents[base_name]
385
+ return unless agent_config
386
+
387
+ # Configuration.agents now returns hashes, not Definitions
388
+ hooks = agent_config.is_a?(Hash) ? agent_config[:hooks] : agent_config.hooks
389
+ return unless hooks&.any?
390
+
391
+ Hooks::Adapter.apply_agent_hooks(chat, agent_name, hooks, @swarm.name)
392
+ end
393
+
197
394
  # Create Agent::Chat instance with rate limiting
198
395
  #
199
396
  # @param agent_name [Symbol] Agent name
@@ -226,6 +423,50 @@ module SwarmSDK
226
423
  chat
227
424
  end
228
425
 
426
+ # Create a delegation-specific instance of an agent
427
+ #
428
+ # V7.0: Simplified - just calls register_all_tools with instance_name
429
+ #
430
+ # @param instance_name [String] Unique instance name ("base@delegator")
431
+ # @param base_name [Symbol] Base agent name (for definition lookup)
432
+ # @param agent_definition [Agent::Definition] Base agent definition
433
+ # @param tool_configurator [ToolConfigurator] Shared tool configurator
434
+ # @return [Agent::Chat] Delegation-specific chat instance
435
+ def create_agent_chat_for_delegation(instance_name:, base_name:, agent_definition:, tool_configurator:)
436
+ # Create chat with instance_name for isolated conversation + tool state
437
+ chat = Agent::Chat.new(
438
+ definition: agent_definition.to_h,
439
+ agent_name: instance_name.to_sym, # Full instance name for isolation
440
+ global_semaphore: @global_semaphore,
441
+ )
442
+
443
+ # Set provider agent name for logging
444
+ chat.provider.agent_name = instance_name if chat.provider.respond_to?(:agent_name=)
445
+
446
+ # V7.0 SIMPLIFIED: Just call register_all_tools with instance_name!
447
+ # Base name extraction happens automatically in create_plugin_tool
448
+ tool_configurator.register_all_tools(
449
+ chat: chat,
450
+ agent_name: instance_name.to_sym,
451
+ agent_definition: agent_definition,
452
+ )
453
+
454
+ # Register MCP servers (tracked by instance_name automatically)
455
+ if agent_definition.mcp_servers.any?
456
+ mcp_configurator = McpConfigurator.new(@swarm)
457
+ mcp_configurator.register_mcp_servers(
458
+ chat,
459
+ agent_definition.mcp_servers,
460
+ agent_name: instance_name,
461
+ )
462
+ end
463
+
464
+ # Notify plugins (use instance_name, plugins extract base_name if needed)
465
+ notify_plugins_agent_initialized(instance_name.to_sym, chat, agent_definition, tool_configurator)
466
+
467
+ chat
468
+ end
469
+
229
470
  # Register agent delegation tools
230
471
  #
231
472
  # Creates delegation tools that allow one agent to call another.
@@ -237,25 +478,38 @@ module SwarmSDK
237
478
  return if delegate_names.empty?
238
479
 
239
480
  delegate_names.each do |delegate_name|
240
- delegate_name = delegate_name.to_sym
241
-
242
- unless @agents.key?(delegate_name)
243
- raise ConfigurationError, "Agent delegates to unknown agent '#{delegate_name}'"
244
- end
245
-
246
- # Create a tool that delegates to the specified agent
247
- delegate_agent = @agents[delegate_name]
248
- delegate_definition = @agent_definitions[delegate_name]
481
+ delegate_name_sym = delegate_name.to_sym
482
+ delegate_name_str = delegate_name.to_s
483
+
484
+ # Check if target is a local agent
485
+ if @agents.key?(delegate_name_sym)
486
+ # Delegate to local agent
487
+ delegate_agent = @agents[delegate_name_sym]
488
+ delegate_definition = @agent_definitions[delegate_name_sym]
489
+
490
+ tool = create_delegation_tool(
491
+ name: delegate_name_str,
492
+ description: delegate_definition.description,
493
+ delegate_chat: delegate_agent,
494
+ agent_name: agent_name,
495
+ delegating_chat: chat,
496
+ )
249
497
 
250
- tool = create_delegation_tool(
251
- name: delegate_name.to_s,
252
- description: delegate_definition.description,
253
- delegate_chat: delegate_agent,
254
- agent_name: agent_name,
255
- delegating_chat: chat,
256
- )
498
+ chat.with_tool(tool)
499
+ elsif @swarm.swarm_registry&.registered?(delegate_name_str)
500
+ # Delegate to registered swarm
501
+ tool = create_delegation_tool(
502
+ name: delegate_name_str,
503
+ description: "External swarm: #{delegate_name_str}",
504
+ delegate_chat: nil, # Swarm delegation - no direct chat
505
+ agent_name: agent_name,
506
+ delegating_chat: chat,
507
+ )
257
508
 
258
- chat.with_tool(tool)
509
+ chat.with_tool(tool)
510
+ else
511
+ raise ConfigurationError, "Agent '#{agent_name}' delegates to unknown target '#{delegate_name_str}' (not a local agent or registered swarm)"
512
+ end
259
513
  end
260
514
  end
261
515
 
@@ -329,6 +583,57 @@ module SwarmSDK
329
583
  plugin.on_agent_initialized(agent_name: agent_name, agent: chat, context: context)
330
584
  end
331
585
  end
586
+
587
+ # Determine if we should skip creating a primary agent
588
+ #
589
+ # Skip if:
590
+ # - NOT the lead agent, AND
591
+ # - Has shared_across_delegations: false (isolated mode), AND
592
+ # - Is only referenced as a delegate (not used standalone)
593
+ #
594
+ # @param name [Symbol] Agent name
595
+ # @param agent_definition [Agent::Definition] Agent definition
596
+ # @return [Boolean] True if should skip primary creation
597
+ def should_skip_primary_creation?(name, agent_definition)
598
+ # Always create lead agent
599
+ return false if name == @swarm.lead_agent
600
+
601
+ # If shared mode, create primary (delegates will use it)
602
+ return false if agent_definition.shared_across_delegations
603
+
604
+ # Skip if only used as a delegate
605
+ only_referenced_as_delegate?(name)
606
+ end
607
+
608
+ # Check if an agent is only referenced as a delegate
609
+ #
610
+ # @param name [Symbol] Agent name
611
+ # @return [Boolean] True if only referenced as delegate
612
+ def only_referenced_as_delegate?(name)
613
+ # Check if any agent delegates to this one
614
+ referenced_as_delegate = @agent_definitions.any? do |_agent_name, definition|
615
+ definition.delegates_to.include?(name)
616
+ end
617
+
618
+ # Skip if referenced as delegate (and not lead, already checked above)
619
+ referenced_as_delegate
620
+ end
621
+
622
+ # Extract base agent name from instance name
623
+ #
624
+ # @param instance_name [Symbol, String] Instance name (may be delegation instance)
625
+ # @return [Symbol] Base agent name
626
+ def extract_base_name(instance_name)
627
+ instance_name.to_s.split("@").first.to_sym
628
+ end
629
+
630
+ # Check if instance name is a delegation instance
631
+ #
632
+ # @param instance_name [Symbol, String] Instance name
633
+ # @return [Boolean] True if delegation instance (contains '@')
634
+ def delegation_instance?(instance_name)
635
+ instance_name.to_s.include?("@")
636
+ end
332
637
  end
333
638
  end
334
639
  end
@@ -33,6 +33,7 @@ module SwarmSDK
33
33
  @parameters = nil
34
34
  @headers = nil
35
35
  @coding_agent = nil
36
+ @disable_default_tools = nil
36
37
  end
37
38
 
38
39
  # Set model for all agents
@@ -75,6 +76,15 @@ module SwarmSDK
75
76
  @coding_agent = enabled
76
77
  end
77
78
 
79
+ # Disable default tools for all agents
80
+ #
81
+ # @param value [Boolean, Array<Symbol>]
82
+ # - true: Disable ALL default tools
83
+ # - Array of symbols: Disable specific tools (e.g., [:Think, :TodoWrite])
84
+ def disable_default_tools(value)
85
+ @disable_default_tools = value
86
+ end
87
+
78
88
  # Add tools that all agents will have
79
89
  def tools(*tool_names)
80
90
  @tools_list.concat(tool_names)
@@ -92,6 +102,7 @@ module SwarmSDK
92
102
  :pre_tool_use,
93
103
  :post_tool_use,
94
104
  :user_prompt,
105
+ :agent_step,
95
106
  :agent_stop,
96
107
  :first_message,
97
108
  :pre_delegation,
@@ -108,7 +119,11 @@ module SwarmSDK
108
119
 
109
120
  # Configure permissions for all agents
110
121
  #
111
- # @example
122
+ # Supports two forms:
123
+ # 1. Block form (DSL): permissions do ... end
124
+ # 2. Direct hash (internal/YAML): set_permissions_hash(hash)
125
+ #
126
+ # @example Block form
112
127
  # permissions do
113
128
  # Write.allow_paths "tmp/**/*"
114
129
  # Write.deny_paths "tmp/secrets/**"
@@ -118,6 +133,17 @@ module SwarmSDK
118
133
  @permissions_config = PermissionsBuilder.build(&block)
119
134
  end
120
135
 
136
+ # Set permissions directly from hash (for YAML translation)
137
+ #
138
+ # This is intentionally separate from permissions() to keep the DSL clean.
139
+ # Called by Configuration when translating YAML permissions.
140
+ #
141
+ # @param hash [Hash] Permissions configuration hash
142
+ # @return [void]
143
+ def permissions_hash=(hash)
144
+ @permissions_config = hash || {}
145
+ end
146
+
121
147
  # Convert to hash for merging with agent configs
122
148
  #
123
149
  # @return [Hash] Configuration hash
@@ -131,6 +157,7 @@ module SwarmSDK
131
157
  parameters: @parameters,
132
158
  headers: @headers,
133
159
  coding_agent: @coding_agent,
160
+ disable_default_tools: @disable_default_tools,
134
161
  tools: @tools_list,
135
162
  permissions: @permissions_config,
136
163
  }.compact