swarm_sdk 2.1.3 → 2.2.0
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/builder.rb +33 -0
- data/lib/swarm_sdk/agent/chat/context_tracker.rb +33 -0
- data/lib/swarm_sdk/agent/chat/hook_integration.rb +41 -0
- data/lib/swarm_sdk/agent/chat/system_reminder_injector.rb +11 -27
- data/lib/swarm_sdk/agent/chat.rb +198 -51
- data/lib/swarm_sdk/agent/context.rb +6 -2
- data/lib/swarm_sdk/agent/context_manager.rb +6 -0
- data/lib/swarm_sdk/agent/definition.rb +14 -2
- data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +180 -0
- data/lib/swarm_sdk/configuration.rb +387 -94
- data/lib/swarm_sdk/events_to_messages.rb +181 -0
- data/lib/swarm_sdk/log_collector.rb +31 -5
- data/lib/swarm_sdk/log_stream.rb +37 -8
- data/lib/swarm_sdk/model_aliases.json +4 -1
- data/lib/swarm_sdk/node/agent_config.rb +33 -8
- data/lib/swarm_sdk/node/builder.rb +39 -18
- data/lib/swarm_sdk/node_orchestrator.rb +293 -26
- data/lib/swarm_sdk/proc_helpers.rb +53 -0
- data/lib/swarm_sdk/providers/openai_with_responses.rb +22 -15
- data/lib/swarm_sdk/restore_result.rb +65 -0
- data/lib/swarm_sdk/snapshot.rb +156 -0
- data/lib/swarm_sdk/snapshot_from_events.rb +386 -0
- data/lib/swarm_sdk/state_restorer.rb +491 -0
- data/lib/swarm_sdk/state_snapshot.rb +369 -0
- data/lib/swarm_sdk/swarm/agent_initializer.rb +360 -55
- data/lib/swarm_sdk/swarm/all_agents_builder.rb +28 -1
- data/lib/swarm_sdk/swarm/builder.rb +208 -12
- data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +67 -0
- data/lib/swarm_sdk/swarm/tool_configurator.rb +46 -11
- data/lib/swarm_sdk/swarm.rb +337 -42
- data/lib/swarm_sdk/swarm_loader.rb +145 -0
- data/lib/swarm_sdk/swarm_registry.rb +136 -0
- data/lib/swarm_sdk/tools/delegate.rb +92 -7
- data/lib/swarm_sdk/tools/read.rb +17 -5
- data/lib/swarm_sdk/tools/stores/read_tracker.rb +47 -12
- data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +45 -0
- data/lib/swarm_sdk/utils.rb +18 -0
- data/lib/swarm_sdk/validation_result.rb +33 -0
- data/lib/swarm_sdk/version.rb +1 -1
- data/lib/swarm_sdk.rb +40 -8
- metadata +17 -6
- data/lib/swarm_sdk/mcp.rb +0 -16
|
@@ -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
|
|
80
|
+
# Pass 1: Create primary agent chat instances
|
|
79
81
|
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
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:
|
|
107
|
+
# Pass 2: Create delegation instances and wire delegation tools
|
|
98
108
|
#
|
|
99
|
-
#
|
|
100
|
-
#
|
|
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
|
-
|
|
103
|
-
|
|
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
|
-
|
|
114
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
127
|
-
|
|
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
|
-
|
|
130
|
-
|
|
295
|
+
# Store context (only for primaries)
|
|
296
|
+
@agent_contexts[agent_name] = context unless is_delegation
|
|
131
297
|
|
|
132
|
-
|
|
298
|
+
# Always set agent context on chat
|
|
299
|
+
chat.setup_context(context) if chat.respond_to?(:setup_context)
|
|
133
300
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
342
|
+
configure_hooks_for_agent(agent_name, chat)
|
|
343
|
+
end
|
|
171
344
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
190
|
-
|
|
372
|
+
apply_yaml_hooks_for_agent(agent_name, chat)
|
|
373
|
+
end
|
|
191
374
|
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|