swarm_memory 2.1.2 → 2.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/claude_swarm/claude_mcp_server.rb +1 -0
- data/lib/claude_swarm/cli.rb +5 -18
- data/lib/claude_swarm/configuration.rb +30 -19
- data/lib/claude_swarm/mcp_generator.rb +5 -10
- data/lib/claude_swarm/openai/chat_completion.rb +4 -12
- data/lib/claude_swarm/openai/executor.rb +3 -1
- data/lib/claude_swarm/openai/responses.rb +13 -32
- data/lib/claude_swarm/version.rb +1 -1
- data/lib/swarm_cli/commands/mcp_serve.rb +2 -2
- data/lib/swarm_cli/commands/run.rb +2 -2
- data/lib/swarm_cli/config_loader.rb +14 -14
- data/lib/swarm_cli/formatters/human_formatter.rb +70 -0
- data/lib/swarm_cli/interactive_repl.rb +11 -5
- data/lib/swarm_cli/ui/icons.rb +0 -23
- data/lib/swarm_cli/version.rb +1 -1
- data/lib/swarm_memory/adapters/base.rb +4 -4
- data/lib/swarm_memory/adapters/filesystem_adapter.rb +11 -34
- data/lib/swarm_memory/core/storage_read_tracker.rb +51 -14
- data/lib/swarm_memory/integration/cli_registration.rb +3 -2
- data/lib/swarm_memory/integration/sdk_plugin.rb +98 -12
- data/lib/swarm_memory/tools/memory_edit.rb +2 -2
- data/lib/swarm_memory/tools/memory_multi_edit.rb +2 -2
- data/lib/swarm_memory/tools/memory_read.rb +3 -3
- data/lib/swarm_memory/version.rb +1 -1
- data/lib/swarm_memory.rb +6 -1
- data/lib/swarm_sdk/agent/builder.rb +91 -0
- data/lib/swarm_sdk/agent/chat.rb +540 -925
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/context_tracker.rb +33 -79
- data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +204 -0
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/hook_integration.rb +147 -39
- data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +78 -0
- data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +233 -0
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/logging_helpers.rb +1 -1
- data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +83 -0
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/system_reminder_injector.rb +22 -38
- data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +79 -0
- data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +98 -0
- data/lib/swarm_sdk/agent/context.rb +8 -4
- data/lib/swarm_sdk/agent/context_manager.rb +6 -0
- data/lib/swarm_sdk/agent/definition.rb +79 -174
- data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +182 -0
- data/lib/swarm_sdk/agent/system_prompt_builder.rb +161 -0
- data/lib/swarm_sdk/builders/base_builder.rb +409 -0
- data/lib/swarm_sdk/concerns/cleanupable.rb +39 -0
- data/lib/swarm_sdk/concerns/snapshotable.rb +67 -0
- data/lib/swarm_sdk/concerns/validatable.rb +55 -0
- data/lib/swarm_sdk/configuration/parser.rb +353 -0
- data/lib/swarm_sdk/configuration/translator.rb +255 -0
- data/lib/swarm_sdk/configuration.rb +100 -261
- data/lib/swarm_sdk/context_compactor/token_counter.rb +3 -3
- data/lib/swarm_sdk/context_compactor.rb +6 -11
- data/lib/swarm_sdk/context_management/builder.rb +128 -0
- data/lib/swarm_sdk/context_management/context.rb +328 -0
- data/lib/swarm_sdk/defaults.rb +196 -0
- data/lib/swarm_sdk/events_to_messages.rb +199 -0
- data/lib/swarm_sdk/hooks/shell_executor.rb +2 -1
- data/lib/swarm_sdk/log_collector.rb +192 -16
- data/lib/swarm_sdk/log_stream.rb +66 -8
- data/lib/swarm_sdk/model_aliases.json +4 -1
- data/lib/swarm_sdk/node_context.rb +1 -1
- data/lib/swarm_sdk/observer/builder.rb +81 -0
- data/lib/swarm_sdk/observer/config.rb +45 -0
- data/lib/swarm_sdk/observer/manager.rb +236 -0
- data/lib/swarm_sdk/patterns/agent_observer.rb +160 -0
- data/lib/swarm_sdk/plugin.rb +93 -3
- data/lib/swarm_sdk/proc_helpers.rb +53 -0
- data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -126
- 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 +397 -0
- data/lib/swarm_sdk/state_restorer.rb +476 -0
- data/lib/swarm_sdk/state_snapshot.rb +334 -0
- data/lib/swarm_sdk/swarm/agent_initializer.rb +428 -79
- data/lib/swarm_sdk/swarm/all_agents_builder.rb +28 -1
- data/lib/swarm_sdk/swarm/builder.rb +69 -407
- data/lib/swarm_sdk/swarm/executor.rb +213 -0
- data/lib/swarm_sdk/swarm/hook_triggers.rb +150 -0
- data/lib/swarm_sdk/swarm/logging_callbacks.rb +340 -0
- data/lib/swarm_sdk/swarm/mcp_configurator.rb +7 -4
- data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +67 -0
- data/lib/swarm_sdk/swarm/tool_configurator.rb +88 -149
- data/lib/swarm_sdk/swarm.rb +366 -631
- data/lib/swarm_sdk/swarm_loader.rb +145 -0
- data/lib/swarm_sdk/swarm_registry.rb +136 -0
- data/lib/swarm_sdk/tools/bash.rb +11 -3
- data/lib/swarm_sdk/tools/delegate.rb +127 -24
- data/lib/swarm_sdk/tools/edit.rb +8 -13
- data/lib/swarm_sdk/tools/glob.rb +9 -1
- data/lib/swarm_sdk/tools/grep.rb +7 -0
- data/lib/swarm_sdk/tools/multi_edit.rb +15 -11
- data/lib/swarm_sdk/tools/path_resolver.rb +51 -2
- data/lib/swarm_sdk/tools/read.rb +28 -18
- data/lib/swarm_sdk/tools/registry.rb +122 -10
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +23 -2
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +23 -2
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +21 -4
- data/lib/swarm_sdk/tools/stores/read_tracker.rb +47 -12
- data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +53 -5
- data/lib/swarm_sdk/tools/stores/storage.rb +0 -6
- data/lib/swarm_sdk/tools/think.rb +4 -1
- data/lib/swarm_sdk/tools/todo_write.rb +27 -8
- data/lib/swarm_sdk/tools/web_fetch.rb +3 -2
- data/lib/swarm_sdk/tools/write.rb +8 -13
- 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/{node → workflow}/agent_config.rb +34 -9
- data/lib/swarm_sdk/workflow/builder.rb +143 -0
- data/lib/swarm_sdk/workflow/executor.rb +497 -0
- data/lib/swarm_sdk/{node/builder.rb → workflow/node_builder.rb} +42 -21
- data/lib/swarm_sdk/{node → workflow}/transformer_executor.rb +3 -2
- data/lib/swarm_sdk/workflow.rb +554 -0
- data/lib/swarm_sdk.rb +393 -22
- metadata +51 -16
- data/lib/swarm_memory/chat_extension.rb +0 -34
- data/lib/swarm_sdk/node_orchestrator.rb +0 -591
- data/lib/swarm_sdk/providers/openai_with_responses.rb +0 -582
|
@@ -14,16 +14,11 @@ module SwarmSDK
|
|
|
14
14
|
# This encapsulates the complex initialization logic that was previously
|
|
15
15
|
# embedded in Swarm#initialize_agents.
|
|
16
16
|
class AgentInitializer
|
|
17
|
-
#
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
# Initialize with swarm reference (all data accessible via swarm)
|
|
18
|
+
#
|
|
19
|
+
# @param swarm [Swarm] The parent swarm instance
|
|
20
|
+
def initialize(swarm)
|
|
20
21
|
@swarm = swarm
|
|
21
|
-
@agent_definitions = agent_definitions
|
|
22
|
-
@global_semaphore = global_semaphore
|
|
23
|
-
@hook_registry = hook_registry
|
|
24
|
-
@scratchpad_storage = scratchpad_storage
|
|
25
|
-
@plugin_storages = plugin_storages
|
|
26
|
-
@config_for_hooks = config_for_hooks
|
|
27
22
|
@agents = {}
|
|
28
23
|
@agent_contexts = {}
|
|
29
24
|
end
|
|
@@ -51,6 +46,39 @@ module SwarmSDK
|
|
|
51
46
|
# Provide access to agent contexts for Swarm
|
|
52
47
|
attr_reader :agent_contexts
|
|
53
48
|
|
|
49
|
+
# Initialize a single agent in isolation (for observer agents)
|
|
50
|
+
#
|
|
51
|
+
# Creates an isolated agent chat without delegation tools,
|
|
52
|
+
# suitable for observer agents that don't need to delegate.
|
|
53
|
+
# Reuses existing create_agent_chat infrastructure.
|
|
54
|
+
#
|
|
55
|
+
# @param agent_name [Symbol] Name of agent to initialize
|
|
56
|
+
# @return [Agent::Chat] Isolated agent chat instance
|
|
57
|
+
# @raise [ConfigurationError] If agent not found
|
|
58
|
+
#
|
|
59
|
+
# @example
|
|
60
|
+
# initializer = AgentInitializer.new(swarm)
|
|
61
|
+
# chat = initializer.initialize_isolated_agent(:profiler)
|
|
62
|
+
# chat.ask("Analyze this prompt")
|
|
63
|
+
def initialize_isolated_agent(agent_name)
|
|
64
|
+
agent_def = @swarm.agent_definitions[agent_name]
|
|
65
|
+
raise ConfigurationError, "Agent '#{agent_name}' not found" unless agent_def
|
|
66
|
+
|
|
67
|
+
# Ensure plugin storages are created (needed by ToolConfigurator)
|
|
68
|
+
create_plugin_storages if @swarm.plugin_storages.empty?
|
|
69
|
+
|
|
70
|
+
# Reuse existing create_agent_chat infrastructure
|
|
71
|
+
tool_configurator = ToolConfigurator.new(
|
|
72
|
+
@swarm,
|
|
73
|
+
@swarm.scratchpad_storage,
|
|
74
|
+
@swarm.plugin_storages,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Create chat using same method as pass_1_create_agents
|
|
78
|
+
# This gives us full tool setup, MCP servers, etc.
|
|
79
|
+
create_agent_chat(agent_name, agent_def, tool_configurator)
|
|
80
|
+
end
|
|
81
|
+
|
|
54
82
|
# Create a tool that delegates work to another agent
|
|
55
83
|
#
|
|
56
84
|
# This method is public for testing delegation from Swarm.
|
|
@@ -68,24 +96,31 @@ module SwarmSDK
|
|
|
68
96
|
delegate_chat: delegate_chat,
|
|
69
97
|
agent_name: agent_name,
|
|
70
98
|
swarm: @swarm,
|
|
71
|
-
hook_registry: @hook_registry,
|
|
72
99
|
delegating_chat: delegating_chat,
|
|
73
100
|
)
|
|
74
101
|
end
|
|
75
102
|
|
|
76
103
|
private
|
|
77
104
|
|
|
78
|
-
# Pass 1: Create
|
|
105
|
+
# Pass 1: Create primary agent chat instances
|
|
79
106
|
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
107
|
+
# Only creates agents that will actually be used as primaries:
|
|
108
|
+
# - The lead agent
|
|
109
|
+
# - Agents with shared_across_delegations: true (shared delegates)
|
|
110
|
+
# - Agents not used as delegates (standalone agents)
|
|
111
|
+
#
|
|
112
|
+
# Agents that are ONLY delegates with shared_across_delegations: false
|
|
113
|
+
# are NOT created here - they'll be created as delegation instances in pass 2a.
|
|
82
114
|
def pass_1_create_agents
|
|
83
115
|
# Create plugin storages for agents
|
|
84
116
|
create_plugin_storages
|
|
85
117
|
|
|
86
|
-
tool_configurator = ToolConfigurator.new(@swarm, @scratchpad_storage, @plugin_storages)
|
|
118
|
+
tool_configurator = ToolConfigurator.new(@swarm, @swarm.scratchpad_storage, @swarm.plugin_storages)
|
|
119
|
+
|
|
120
|
+
@swarm.agent_definitions.each do |name, agent_definition|
|
|
121
|
+
# Skip if this agent will only exist as delegation instances
|
|
122
|
+
next if should_skip_primary_creation?(name, agent_definition)
|
|
87
123
|
|
|
88
|
-
@agent_definitions.each do |name, agent_definition|
|
|
89
124
|
chat = create_agent_chat(name, agent_definition, tool_configurator)
|
|
90
125
|
@agents[name] = chat
|
|
91
126
|
|
|
@@ -94,46 +129,228 @@ module SwarmSDK
|
|
|
94
129
|
end
|
|
95
130
|
end
|
|
96
131
|
|
|
97
|
-
# Pass 2:
|
|
132
|
+
# Pass 2: Create delegation instances and wire delegation tools
|
|
98
133
|
#
|
|
99
|
-
#
|
|
100
|
-
#
|
|
134
|
+
# This pass has three sub-steps that must happen in order:
|
|
135
|
+
# 2a. Create delegation instances (ONLY for agents with shared_across_delegations: false)
|
|
136
|
+
# 2b. Wire primary agents to delegation instances OR shared primaries
|
|
137
|
+
# 2c. Wire delegation instances to their delegates (nested delegation support)
|
|
101
138
|
def pass_2_register_delegation_tools
|
|
102
|
-
|
|
103
|
-
|
|
139
|
+
tool_configurator = ToolConfigurator.new(@swarm, @swarm.scratchpad_storage, @swarm.plugin_storages)
|
|
140
|
+
|
|
141
|
+
# Sub-pass 2a: Create delegation instances for isolated agents
|
|
142
|
+
@swarm.agent_definitions.each do |delegator_name, delegator_def|
|
|
143
|
+
delegator_def.delegates_to.each do |delegate_base_name|
|
|
144
|
+
delegate_base_name = delegate_base_name.to_sym
|
|
145
|
+
|
|
146
|
+
unless @swarm.agent_definitions.key?(delegate_base_name)
|
|
147
|
+
raise ConfigurationError,
|
|
148
|
+
"Agent '#{delegator_name}' delegates to unknown agent '#{delegate_base_name}'"
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
delegate_definition = @swarm.agent_definitions[delegate_base_name]
|
|
152
|
+
|
|
153
|
+
# Check isolation mode of the DELEGATE agent
|
|
154
|
+
# If delegate wants to be shared, skip instance creation (use primary)
|
|
155
|
+
next if delegate_definition.shared_across_delegations
|
|
156
|
+
|
|
157
|
+
# Create unique delegation instance (isolated mode)
|
|
158
|
+
instance_name = "#{delegate_base_name}@#{delegator_name}"
|
|
159
|
+
|
|
160
|
+
# V7.0: Use existing register_all_tools (no new method needed!)
|
|
161
|
+
delegation_chat = create_agent_chat_for_delegation(
|
|
162
|
+
instance_name: instance_name,
|
|
163
|
+
base_name: delegate_base_name,
|
|
164
|
+
agent_definition: delegate_definition,
|
|
165
|
+
tool_configurator: tool_configurator,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# Store in delegation_instances hash
|
|
169
|
+
@swarm.delegation_instances[instance_name] = delegation_chat
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Sub-pass 2b: Wire primary agents to delegation instances OR shared primaries OR registered swarms
|
|
174
|
+
@swarm.agent_definitions.each do |delegator_name, delegator_def|
|
|
175
|
+
delegator_chat = @agents[delegator_name]
|
|
176
|
+
|
|
177
|
+
# Skip if delegator doesn't exist as primary (wasn't created in pass_1)
|
|
178
|
+
next unless delegator_chat
|
|
179
|
+
|
|
180
|
+
delegator_def.delegates_to.each do |delegate_name|
|
|
181
|
+
wire_delegation(
|
|
182
|
+
delegator_name: delegator_name,
|
|
183
|
+
delegator_chat: delegator_chat,
|
|
184
|
+
delegate_name: delegate_name,
|
|
185
|
+
tool_configurator: tool_configurator,
|
|
186
|
+
create_nested_instances: false,
|
|
187
|
+
)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Sub-pass 2c: Wire delegation instances to their delegates (nested delegation)
|
|
192
|
+
# Convert to array first to avoid "can't add key during iteration" error
|
|
193
|
+
@swarm.delegation_instances.to_a.each do |instance_name, delegation_chat|
|
|
194
|
+
base_name = extract_base_name(instance_name)
|
|
195
|
+
delegate_definition = @swarm.agent_definitions[base_name]
|
|
196
|
+
|
|
197
|
+
# Register delegation tools for THIS instance's delegates_to
|
|
198
|
+
delegate_definition.delegates_to.each do |nested_delegate_name|
|
|
199
|
+
wire_delegation(
|
|
200
|
+
delegator_name: instance_name.to_sym,
|
|
201
|
+
delegator_chat: delegation_chat,
|
|
202
|
+
delegate_name: nested_delegate_name,
|
|
203
|
+
tool_configurator: tool_configurator,
|
|
204
|
+
create_nested_instances: true,
|
|
205
|
+
)
|
|
206
|
+
end
|
|
104
207
|
end
|
|
105
208
|
end
|
|
106
209
|
|
|
210
|
+
# Wire a single delegation from one agent/instance to a delegate
|
|
211
|
+
#
|
|
212
|
+
# This is the unified logic for delegation wiring used by both:
|
|
213
|
+
# - Sub-pass 2b: Primary agents → delegates
|
|
214
|
+
# - Sub-pass 2c: Delegation instances → nested delegates
|
|
215
|
+
#
|
|
216
|
+
# @param delegator_name [Symbol, String] Name of the agent doing the delegating
|
|
217
|
+
# @param delegator_chat [Agent::Chat] Chat instance of the delegator
|
|
218
|
+
# @param delegate_name [Symbol, String] Name of the delegate target
|
|
219
|
+
# @param tool_configurator [ToolConfigurator] Tool configuration helper
|
|
220
|
+
# @param create_nested_instances [Boolean] Whether to create new instances for nested delegation
|
|
221
|
+
# @return [void]
|
|
222
|
+
def wire_delegation(delegator_name:, delegator_chat:, delegate_name:, tool_configurator:, create_nested_instances:)
|
|
223
|
+
delegate_name_str = delegate_name.to_s
|
|
224
|
+
delegate_name_sym = delegate_name.to_sym
|
|
225
|
+
|
|
226
|
+
# Check if target is a registered swarm
|
|
227
|
+
if @swarm.swarm_registry&.registered?(delegate_name_str)
|
|
228
|
+
wire_swarm_delegation(delegator_name, delegator_chat, delegate_name_str)
|
|
229
|
+
elsif @swarm.agent_definitions.key?(delegate_name_sym)
|
|
230
|
+
wire_agent_delegation(
|
|
231
|
+
delegator_name: delegator_name,
|
|
232
|
+
delegator_chat: delegator_chat,
|
|
233
|
+
delegate_name_sym: delegate_name_sym,
|
|
234
|
+
tool_configurator: tool_configurator,
|
|
235
|
+
create_nested_instances: create_nested_instances,
|
|
236
|
+
)
|
|
237
|
+
else
|
|
238
|
+
raise ConfigurationError,
|
|
239
|
+
"Agent '#{delegator_name}' delegates to unknown target '#{delegate_name_str}' (not a local agent or registered swarm)"
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Wire delegation to an external swarm
|
|
244
|
+
#
|
|
245
|
+
# @param delegator_name [Symbol, String] Name of the delegating agent
|
|
246
|
+
# @param delegator_chat [Agent::Chat] Chat instance of the delegator
|
|
247
|
+
# @param swarm_name [String] Name of the registered swarm
|
|
248
|
+
# @return [void]
|
|
249
|
+
def wire_swarm_delegation(delegator_name, delegator_chat, swarm_name)
|
|
250
|
+
tool = create_delegation_tool(
|
|
251
|
+
name: swarm_name,
|
|
252
|
+
description: "External swarm: #{swarm_name}",
|
|
253
|
+
delegate_chat: nil, # Swarm delegation - no direct chat
|
|
254
|
+
agent_name: delegator_name,
|
|
255
|
+
delegating_chat: delegator_chat,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
delegator_chat.add_tool(tool)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Wire delegation to a local agent
|
|
262
|
+
#
|
|
263
|
+
# Determines whether to use shared primary or isolated instance based on
|
|
264
|
+
# the delegate's shared_across_delegations setting.
|
|
265
|
+
#
|
|
266
|
+
# @param delegator_name [Symbol, String] Name of the delegating agent
|
|
267
|
+
# @param delegator_chat [Agent::Chat] Chat instance of the delegator
|
|
268
|
+
# @param delegate_name_sym [Symbol] Name of the delegate agent
|
|
269
|
+
# @param tool_configurator [ToolConfigurator] Tool configuration helper
|
|
270
|
+
# @param create_nested_instances [Boolean] Whether to create new instances if not found
|
|
271
|
+
# @return [void]
|
|
272
|
+
def wire_agent_delegation(delegator_name:, delegator_chat:, delegate_name_sym:, tool_configurator:, create_nested_instances:)
|
|
273
|
+
delegate_definition = @swarm.agent_definitions[delegate_name_sym]
|
|
274
|
+
|
|
275
|
+
# Determine which chat instance to use
|
|
276
|
+
target_chat = if delegate_definition.shared_across_delegations
|
|
277
|
+
# Shared mode: use primary agent (semaphore-protected)
|
|
278
|
+
@agents[delegate_name_sym]
|
|
279
|
+
else
|
|
280
|
+
# Isolated mode: use delegation instance
|
|
281
|
+
instance_name = "#{delegate_name_sym}@#{delegator_name}"
|
|
282
|
+
|
|
283
|
+
if create_nested_instances
|
|
284
|
+
# For nested delegation: create if not exists
|
|
285
|
+
@swarm.delegation_instances[instance_name] ||= create_agent_chat_for_delegation(
|
|
286
|
+
instance_name: instance_name,
|
|
287
|
+
base_name: delegate_name_sym,
|
|
288
|
+
agent_definition: delegate_definition,
|
|
289
|
+
tool_configurator: tool_configurator,
|
|
290
|
+
)
|
|
291
|
+
else
|
|
292
|
+
# For primary delegation: instance was pre-created in 2a
|
|
293
|
+
@swarm.delegation_instances[instance_name]
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# Create delegation tool pointing to chosen instance
|
|
298
|
+
tool = create_delegation_tool(
|
|
299
|
+
name: delegate_name_sym.to_s,
|
|
300
|
+
description: delegate_definition.description,
|
|
301
|
+
delegate_chat: target_chat,
|
|
302
|
+
agent_name: delegator_name,
|
|
303
|
+
delegating_chat: delegator_chat,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
delegator_chat.add_tool(tool)
|
|
307
|
+
end
|
|
308
|
+
|
|
107
309
|
# Pass 3: Setup agent contexts
|
|
108
310
|
#
|
|
109
311
|
# Create Agent::Context for each agent to track delegations and metadata.
|
|
110
312
|
# This is needed regardless of whether logging is enabled.
|
|
111
313
|
def pass_3_setup_contexts
|
|
314
|
+
# Setup contexts for PRIMARY agents
|
|
112
315
|
@agents.each do |agent_name, chat|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
"DelegateTaskTo#{delegate_name.to_s.capitalize}"
|
|
116
|
-
end
|
|
316
|
+
setup_agent_context(agent_name, @swarm.agent_definitions[agent_name], chat, is_delegation: false)
|
|
317
|
+
end
|
|
117
318
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
319
|
+
# Setup contexts for DELEGATION instances
|
|
320
|
+
@swarm.delegation_instances.each do |instance_name, chat|
|
|
321
|
+
base_name = extract_base_name(instance_name)
|
|
322
|
+
agent_definition = @swarm.agent_definitions[base_name]
|
|
323
|
+
setup_agent_context(instance_name.to_sym, agent_definition, chat, is_delegation: true)
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# Setup context for an agent (primary or delegation instance)
|
|
328
|
+
def setup_agent_context(agent_name, agent_definition, chat, is_delegation: false)
|
|
329
|
+
delegate_tool_names = agent_definition.delegates_to.map do |delegate_name|
|
|
330
|
+
Tools::Delegate.tool_name_for(delegate_name)
|
|
331
|
+
end
|
|
125
332
|
|
|
126
|
-
|
|
127
|
-
|
|
333
|
+
context = Agent::Context.new(
|
|
334
|
+
name: agent_name,
|
|
335
|
+
swarm_id: @swarm.swarm_id,
|
|
336
|
+
parent_swarm_id: @swarm.parent_swarm_id,
|
|
337
|
+
delegation_tools: delegate_tool_names,
|
|
338
|
+
metadata: { is_delegation_instance: is_delegation },
|
|
339
|
+
)
|
|
128
340
|
|
|
129
|
-
|
|
130
|
-
|
|
341
|
+
# Store context (only for primaries)
|
|
342
|
+
@agent_contexts[agent_name] = context unless is_delegation
|
|
131
343
|
|
|
132
|
-
|
|
344
|
+
# Always set agent context on chat
|
|
345
|
+
chat.setup_context(context) if chat.respond_to?(:setup_context)
|
|
133
346
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
347
|
+
# Configure logging if enabled
|
|
348
|
+
return unless LogStream.emitter
|
|
349
|
+
|
|
350
|
+
chat.setup_logging if chat.respond_to?(:setup_logging)
|
|
351
|
+
|
|
352
|
+
# Emit validation warnings (only for primaries, not each delegation instance)
|
|
353
|
+
emit_validation_warnings(agent_name, agent_definition) unless is_delegation
|
|
137
354
|
end
|
|
138
355
|
|
|
139
356
|
# Emit validation warnings as log events
|
|
@@ -166,34 +383,60 @@ module SwarmSDK
|
|
|
166
383
|
#
|
|
167
384
|
# Setup the callback system for each agent, integrating with RubyLLM callbacks.
|
|
168
385
|
def pass_4_configure_hooks
|
|
386
|
+
# Configure hooks for PRIMARY agents
|
|
169
387
|
@agents.each do |agent_name, chat|
|
|
170
|
-
|
|
388
|
+
configure_hooks_for_agent(agent_name, chat)
|
|
389
|
+
end
|
|
171
390
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
agent_definition: agent_definition,
|
|
176
|
-
swarm: @swarm,
|
|
177
|
-
) if chat.respond_to?(:setup_hooks)
|
|
391
|
+
# Configure hooks for DELEGATION instances
|
|
392
|
+
@swarm.delegation_instances.each do |instance_name, chat|
|
|
393
|
+
configure_hooks_for_agent(instance_name.to_sym, chat)
|
|
178
394
|
end
|
|
179
395
|
end
|
|
180
396
|
|
|
397
|
+
# Configure hooks for an agent (primary or delegation instance)
|
|
398
|
+
def configure_hooks_for_agent(agent_name, chat)
|
|
399
|
+
base_name = extract_base_name(agent_name)
|
|
400
|
+
agent_definition = @swarm.agent_definitions[base_name]
|
|
401
|
+
|
|
402
|
+
chat.setup_hooks(
|
|
403
|
+
registry: @swarm.hook_registry,
|
|
404
|
+
agent_definition: agent_definition,
|
|
405
|
+
swarm: @swarm,
|
|
406
|
+
) if chat.respond_to?(:setup_hooks)
|
|
407
|
+
end
|
|
408
|
+
|
|
181
409
|
# Pass 5: Apply YAML hooks
|
|
182
410
|
#
|
|
183
411
|
# If the swarm was loaded from YAML with agent-specific hooks,
|
|
184
412
|
# apply them now via HooksAdapter.
|
|
185
413
|
def pass_5_apply_yaml_hooks
|
|
186
|
-
return unless @config_for_hooks
|
|
414
|
+
return unless @swarm.config_for_hooks
|
|
187
415
|
|
|
416
|
+
# Apply YAML hooks to PRIMARY agents
|
|
188
417
|
@agents.each do |agent_name, chat|
|
|
189
|
-
|
|
190
|
-
|
|
418
|
+
apply_yaml_hooks_for_agent(agent_name, chat)
|
|
419
|
+
end
|
|
191
420
|
|
|
192
|
-
|
|
193
|
-
|
|
421
|
+
# Apply YAML hooks to DELEGATION instances
|
|
422
|
+
@swarm.delegation_instances.each do |instance_name, chat|
|
|
423
|
+
apply_yaml_hooks_for_agent(instance_name.to_sym, chat)
|
|
194
424
|
end
|
|
195
425
|
end
|
|
196
426
|
|
|
427
|
+
# Apply YAML hooks for an agent (primary or delegation instance)
|
|
428
|
+
def apply_yaml_hooks_for_agent(agent_name, chat)
|
|
429
|
+
base_name = extract_base_name(agent_name)
|
|
430
|
+
agent_config = @swarm.config_for_hooks.agents[base_name]
|
|
431
|
+
return unless agent_config
|
|
432
|
+
|
|
433
|
+
# Configuration.agents now returns hashes, not Definitions
|
|
434
|
+
hooks = agent_config.is_a?(Hash) ? agent_config[:hooks] : agent_config.hooks
|
|
435
|
+
return unless hooks&.any?
|
|
436
|
+
|
|
437
|
+
Hooks::Adapter.apply_agent_hooks(chat, agent_name, hooks, @swarm.name)
|
|
438
|
+
end
|
|
439
|
+
|
|
197
440
|
# Create Agent::Chat instance with rate limiting
|
|
198
441
|
#
|
|
199
442
|
# @param agent_name [Symbol] Agent name
|
|
@@ -204,7 +447,7 @@ module SwarmSDK
|
|
|
204
447
|
chat = Agent::Chat.new(
|
|
205
448
|
definition: agent_definition.to_h,
|
|
206
449
|
agent_name: agent_name,
|
|
207
|
-
global_semaphore: @global_semaphore,
|
|
450
|
+
global_semaphore: @swarm.global_semaphore,
|
|
208
451
|
)
|
|
209
452
|
|
|
210
453
|
# Set agent name on provider for logging (if provider supports it)
|
|
@@ -226,6 +469,50 @@ module SwarmSDK
|
|
|
226
469
|
chat
|
|
227
470
|
end
|
|
228
471
|
|
|
472
|
+
# Create a delegation-specific instance of an agent
|
|
473
|
+
#
|
|
474
|
+
# V7.0: Simplified - just calls register_all_tools with instance_name
|
|
475
|
+
#
|
|
476
|
+
# @param instance_name [String] Unique instance name ("base@delegator")
|
|
477
|
+
# @param base_name [Symbol] Base agent name (for definition lookup)
|
|
478
|
+
# @param agent_definition [Agent::Definition] Base agent definition
|
|
479
|
+
# @param tool_configurator [ToolConfigurator] Shared tool configurator
|
|
480
|
+
# @return [Agent::Chat] Delegation-specific chat instance
|
|
481
|
+
def create_agent_chat_for_delegation(instance_name:, base_name:, agent_definition:, tool_configurator:)
|
|
482
|
+
# Create chat with instance_name for isolated conversation + tool state
|
|
483
|
+
chat = Agent::Chat.new(
|
|
484
|
+
definition: agent_definition.to_h,
|
|
485
|
+
agent_name: instance_name.to_sym, # Full instance name for isolation
|
|
486
|
+
global_semaphore: @swarm.global_semaphore,
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
# Set provider agent name for logging
|
|
490
|
+
chat.provider.agent_name = instance_name if chat.provider.respond_to?(:agent_name=)
|
|
491
|
+
|
|
492
|
+
# V7.0 SIMPLIFIED: Just call register_all_tools with instance_name!
|
|
493
|
+
# Base name extraction happens automatically in create_plugin_tool
|
|
494
|
+
tool_configurator.register_all_tools(
|
|
495
|
+
chat: chat,
|
|
496
|
+
agent_name: instance_name.to_sym,
|
|
497
|
+
agent_definition: agent_definition,
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
# Register MCP servers (tracked by instance_name automatically)
|
|
501
|
+
if agent_definition.mcp_servers.any?
|
|
502
|
+
mcp_configurator = McpConfigurator.new(@swarm)
|
|
503
|
+
mcp_configurator.register_mcp_servers(
|
|
504
|
+
chat,
|
|
505
|
+
agent_definition.mcp_servers,
|
|
506
|
+
agent_name: instance_name,
|
|
507
|
+
)
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
# Notify plugins (use instance_name, plugins extract base_name if needed)
|
|
511
|
+
notify_plugins_agent_initialized(instance_name.to_sym, chat, agent_definition, tool_configurator)
|
|
512
|
+
|
|
513
|
+
chat
|
|
514
|
+
end
|
|
515
|
+
|
|
229
516
|
# Register agent delegation tools
|
|
230
517
|
#
|
|
231
518
|
# Creates delegation tools that allow one agent to call another.
|
|
@@ -237,25 +524,38 @@ module SwarmSDK
|
|
|
237
524
|
return if delegate_names.empty?
|
|
238
525
|
|
|
239
526
|
delegate_names.each do |delegate_name|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
527
|
+
delegate_name_sym = delegate_name.to_sym
|
|
528
|
+
delegate_name_str = delegate_name.to_s
|
|
529
|
+
|
|
530
|
+
# Check if target is a local agent
|
|
531
|
+
if @agents.key?(delegate_name_sym)
|
|
532
|
+
# Delegate to local agent
|
|
533
|
+
delegate_agent = @agents[delegate_name_sym]
|
|
534
|
+
delegate_definition = @swarm.agent_definitions[delegate_name_sym]
|
|
535
|
+
|
|
536
|
+
tool = create_delegation_tool(
|
|
537
|
+
name: delegate_name_str,
|
|
538
|
+
description: delegate_definition.description,
|
|
539
|
+
delegate_chat: delegate_agent,
|
|
540
|
+
agent_name: agent_name,
|
|
541
|
+
delegating_chat: chat,
|
|
542
|
+
)
|
|
249
543
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
544
|
+
chat.add_tool(tool)
|
|
545
|
+
elsif @swarm.swarm_registry&.registered?(delegate_name_str)
|
|
546
|
+
# Delegate to registered swarm
|
|
547
|
+
tool = create_delegation_tool(
|
|
548
|
+
name: delegate_name_str,
|
|
549
|
+
description: "External swarm: #{delegate_name_str}",
|
|
550
|
+
delegate_chat: nil, # Swarm delegation - no direct chat
|
|
551
|
+
agent_name: agent_name,
|
|
552
|
+
delegating_chat: chat,
|
|
553
|
+
)
|
|
257
554
|
|
|
258
|
-
|
|
555
|
+
chat.add_tool(tool)
|
|
556
|
+
else
|
|
557
|
+
raise ConfigurationError, "Agent '#{agent_name}' delegates to unknown target '#{delegate_name_str}' (not a local agent or registered swarm)"
|
|
558
|
+
end
|
|
259
559
|
end
|
|
260
560
|
end
|
|
261
561
|
|
|
@@ -267,7 +567,7 @@ module SwarmSDK
|
|
|
267
567
|
# @return [void]
|
|
268
568
|
def create_plugin_storages
|
|
269
569
|
PluginRegistry.all.each do |plugin|
|
|
270
|
-
@agent_definitions.each do |agent_name, agent_definition|
|
|
570
|
+
@swarm.agent_definitions.each do |agent_name, agent_definition|
|
|
271
571
|
# Check if this plugin needs storage for this agent
|
|
272
572
|
next unless plugin.storage_enabled?(agent_definition)
|
|
273
573
|
|
|
@@ -282,25 +582,23 @@ module SwarmSDK
|
|
|
282
582
|
storage = plugin.create_storage(agent_name: agent_name, config: parsed_config)
|
|
283
583
|
|
|
284
584
|
# Store in plugin_storages: { plugin_name => { agent_name => storage } }
|
|
285
|
-
@plugin_storages[plugin.name] ||= {}
|
|
286
|
-
@plugin_storages[plugin.name][agent_name] = storage
|
|
585
|
+
@swarm.plugin_storages[plugin.name] ||= {}
|
|
586
|
+
@swarm.plugin_storages[plugin.name][agent_name] = storage
|
|
287
587
|
end
|
|
288
588
|
end
|
|
289
589
|
end
|
|
290
590
|
|
|
291
591
|
# Get plugin-specific config from agent definition
|
|
292
592
|
#
|
|
293
|
-
#
|
|
294
|
-
# E.g., memory plugin
|
|
593
|
+
# Uses the generic plugin_configs accessor to retrieve plugin-specific config.
|
|
594
|
+
# E.g., memory plugin config is accessed via `agent_definition.plugin_config(:memory)`
|
|
295
595
|
#
|
|
296
596
|
# @param agent_definition [Agent::Definition] Agent definition
|
|
297
597
|
# @param plugin_name [Symbol] Plugin name
|
|
298
598
|
# @return [Object, nil] Plugin config or nil
|
|
299
599
|
def get_plugin_config(agent_definition, plugin_name)
|
|
300
|
-
#
|
|
301
|
-
|
|
302
|
-
agent_definition.public_send(plugin_name)
|
|
303
|
-
end
|
|
600
|
+
# Use generic plugin config accessor
|
|
601
|
+
agent_definition.plugin_config(plugin_name)
|
|
304
602
|
end
|
|
305
603
|
|
|
306
604
|
# Notify all plugins that an agent was initialized
|
|
@@ -315,7 +613,7 @@ module SwarmSDK
|
|
|
315
613
|
def notify_plugins_agent_initialized(agent_name, chat, agent_definition, tool_configurator)
|
|
316
614
|
PluginRegistry.all.each do |plugin|
|
|
317
615
|
# Get plugin storage for this agent (if any)
|
|
318
|
-
plugin_storages = @plugin_storages[plugin.name] || {}
|
|
616
|
+
plugin_storages = @swarm.plugin_storages[plugin.name] || {}
|
|
319
617
|
storage = plugin_storages[agent_name]
|
|
320
618
|
|
|
321
619
|
# Build context for plugin
|
|
@@ -329,6 +627,57 @@ module SwarmSDK
|
|
|
329
627
|
plugin.on_agent_initialized(agent_name: agent_name, agent: chat, context: context)
|
|
330
628
|
end
|
|
331
629
|
end
|
|
630
|
+
|
|
631
|
+
# Determine if we should skip creating a primary agent
|
|
632
|
+
#
|
|
633
|
+
# Skip if:
|
|
634
|
+
# - NOT the lead agent, AND
|
|
635
|
+
# - Has shared_across_delegations: false (isolated mode), AND
|
|
636
|
+
# - Is only referenced as a delegate (not used standalone)
|
|
637
|
+
#
|
|
638
|
+
# @param name [Symbol] Agent name
|
|
639
|
+
# @param agent_definition [Agent::Definition] Agent definition
|
|
640
|
+
# @return [Boolean] True if should skip primary creation
|
|
641
|
+
def should_skip_primary_creation?(name, agent_definition)
|
|
642
|
+
# Always create lead agent
|
|
643
|
+
return false if name == @swarm.lead_agent
|
|
644
|
+
|
|
645
|
+
# If shared mode, create primary (delegates will use it)
|
|
646
|
+
return false if agent_definition.shared_across_delegations
|
|
647
|
+
|
|
648
|
+
# Skip if only used as a delegate
|
|
649
|
+
only_referenced_as_delegate?(name)
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
# Check if an agent is only referenced as a delegate
|
|
653
|
+
#
|
|
654
|
+
# @param name [Symbol] Agent name
|
|
655
|
+
# @return [Boolean] True if only referenced as delegate
|
|
656
|
+
def only_referenced_as_delegate?(name)
|
|
657
|
+
# Check if any agent delegates to this one
|
|
658
|
+
referenced_as_delegate = @swarm.agent_definitions.any? do |_agent_name, definition|
|
|
659
|
+
definition.delegates_to.include?(name)
|
|
660
|
+
end
|
|
661
|
+
|
|
662
|
+
# Skip if referenced as delegate (and not lead, already checked above)
|
|
663
|
+
referenced_as_delegate
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
# Extract base agent name from instance name
|
|
667
|
+
#
|
|
668
|
+
# @param instance_name [Symbol, String] Instance name (may be delegation instance)
|
|
669
|
+
# @return [Symbol] Base agent name
|
|
670
|
+
def extract_base_name(instance_name)
|
|
671
|
+
instance_name.to_s.split("@").first.to_sym
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
# Check if instance name is a delegation instance
|
|
675
|
+
#
|
|
676
|
+
# @param instance_name [Symbol, String] Instance name
|
|
677
|
+
# @return [Boolean] True if delegation instance (contains '@')
|
|
678
|
+
def delegation_instance?(instance_name)
|
|
679
|
+
instance_name.to_s.include?("@")
|
|
680
|
+
end
|
|
332
681
|
end
|
|
333
682
|
end
|
|
334
683
|
end
|