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.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/lib/claude_swarm/claude_mcp_server.rb +1 -0
  3. data/lib/claude_swarm/cli.rb +5 -18
  4. data/lib/claude_swarm/configuration.rb +30 -19
  5. data/lib/claude_swarm/mcp_generator.rb +5 -10
  6. data/lib/claude_swarm/openai/chat_completion.rb +4 -12
  7. data/lib/claude_swarm/openai/executor.rb +3 -1
  8. data/lib/claude_swarm/openai/responses.rb +13 -32
  9. data/lib/claude_swarm/version.rb +1 -1
  10. data/lib/swarm_cli/commands/mcp_serve.rb +2 -2
  11. data/lib/swarm_cli/commands/run.rb +2 -2
  12. data/lib/swarm_cli/config_loader.rb +14 -14
  13. data/lib/swarm_cli/formatters/human_formatter.rb +70 -0
  14. data/lib/swarm_cli/interactive_repl.rb +11 -5
  15. data/lib/swarm_cli/ui/icons.rb +0 -23
  16. data/lib/swarm_cli/version.rb +1 -1
  17. data/lib/swarm_memory/adapters/base.rb +4 -4
  18. data/lib/swarm_memory/adapters/filesystem_adapter.rb +11 -34
  19. data/lib/swarm_memory/core/storage_read_tracker.rb +51 -14
  20. data/lib/swarm_memory/integration/cli_registration.rb +3 -2
  21. data/lib/swarm_memory/integration/sdk_plugin.rb +98 -12
  22. data/lib/swarm_memory/tools/memory_edit.rb +2 -2
  23. data/lib/swarm_memory/tools/memory_multi_edit.rb +2 -2
  24. data/lib/swarm_memory/tools/memory_read.rb +3 -3
  25. data/lib/swarm_memory/version.rb +1 -1
  26. data/lib/swarm_memory.rb +6 -1
  27. data/lib/swarm_sdk/agent/builder.rb +91 -0
  28. data/lib/swarm_sdk/agent/chat.rb +540 -925
  29. data/lib/swarm_sdk/agent/{chat → chat_helpers}/context_tracker.rb +33 -79
  30. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +204 -0
  31. data/lib/swarm_sdk/agent/{chat → chat_helpers}/hook_integration.rb +147 -39
  32. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +78 -0
  33. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +233 -0
  34. data/lib/swarm_sdk/agent/{chat → chat_helpers}/logging_helpers.rb +1 -1
  35. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +83 -0
  36. data/lib/swarm_sdk/agent/{chat → chat_helpers}/system_reminder_injector.rb +22 -38
  37. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +79 -0
  38. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +98 -0
  39. data/lib/swarm_sdk/agent/context.rb +8 -4
  40. data/lib/swarm_sdk/agent/context_manager.rb +6 -0
  41. data/lib/swarm_sdk/agent/definition.rb +79 -174
  42. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +182 -0
  43. data/lib/swarm_sdk/agent/system_prompt_builder.rb +161 -0
  44. data/lib/swarm_sdk/builders/base_builder.rb +409 -0
  45. data/lib/swarm_sdk/concerns/cleanupable.rb +39 -0
  46. data/lib/swarm_sdk/concerns/snapshotable.rb +67 -0
  47. data/lib/swarm_sdk/concerns/validatable.rb +55 -0
  48. data/lib/swarm_sdk/configuration/parser.rb +353 -0
  49. data/lib/swarm_sdk/configuration/translator.rb +255 -0
  50. data/lib/swarm_sdk/configuration.rb +100 -261
  51. data/lib/swarm_sdk/context_compactor/token_counter.rb +3 -3
  52. data/lib/swarm_sdk/context_compactor.rb +6 -11
  53. data/lib/swarm_sdk/context_management/builder.rb +128 -0
  54. data/lib/swarm_sdk/context_management/context.rb +328 -0
  55. data/lib/swarm_sdk/defaults.rb +196 -0
  56. data/lib/swarm_sdk/events_to_messages.rb +199 -0
  57. data/lib/swarm_sdk/hooks/shell_executor.rb +2 -1
  58. data/lib/swarm_sdk/log_collector.rb +192 -16
  59. data/lib/swarm_sdk/log_stream.rb +66 -8
  60. data/lib/swarm_sdk/model_aliases.json +4 -1
  61. data/lib/swarm_sdk/node_context.rb +1 -1
  62. data/lib/swarm_sdk/observer/builder.rb +81 -0
  63. data/lib/swarm_sdk/observer/config.rb +45 -0
  64. data/lib/swarm_sdk/observer/manager.rb +236 -0
  65. data/lib/swarm_sdk/patterns/agent_observer.rb +160 -0
  66. data/lib/swarm_sdk/plugin.rb +93 -3
  67. data/lib/swarm_sdk/proc_helpers.rb +53 -0
  68. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -126
  69. data/lib/swarm_sdk/restore_result.rb +65 -0
  70. data/lib/swarm_sdk/snapshot.rb +156 -0
  71. data/lib/swarm_sdk/snapshot_from_events.rb +397 -0
  72. data/lib/swarm_sdk/state_restorer.rb +476 -0
  73. data/lib/swarm_sdk/state_snapshot.rb +334 -0
  74. data/lib/swarm_sdk/swarm/agent_initializer.rb +428 -79
  75. data/lib/swarm_sdk/swarm/all_agents_builder.rb +28 -1
  76. data/lib/swarm_sdk/swarm/builder.rb +69 -407
  77. data/lib/swarm_sdk/swarm/executor.rb +213 -0
  78. data/lib/swarm_sdk/swarm/hook_triggers.rb +150 -0
  79. data/lib/swarm_sdk/swarm/logging_callbacks.rb +340 -0
  80. data/lib/swarm_sdk/swarm/mcp_configurator.rb +7 -4
  81. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +67 -0
  82. data/lib/swarm_sdk/swarm/tool_configurator.rb +88 -149
  83. data/lib/swarm_sdk/swarm.rb +366 -631
  84. data/lib/swarm_sdk/swarm_loader.rb +145 -0
  85. data/lib/swarm_sdk/swarm_registry.rb +136 -0
  86. data/lib/swarm_sdk/tools/bash.rb +11 -3
  87. data/lib/swarm_sdk/tools/delegate.rb +127 -24
  88. data/lib/swarm_sdk/tools/edit.rb +8 -13
  89. data/lib/swarm_sdk/tools/glob.rb +9 -1
  90. data/lib/swarm_sdk/tools/grep.rb +7 -0
  91. data/lib/swarm_sdk/tools/multi_edit.rb +15 -11
  92. data/lib/swarm_sdk/tools/path_resolver.rb +51 -2
  93. data/lib/swarm_sdk/tools/read.rb +28 -18
  94. data/lib/swarm_sdk/tools/registry.rb +122 -10
  95. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +23 -2
  96. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +23 -2
  97. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +21 -4
  98. data/lib/swarm_sdk/tools/stores/read_tracker.rb +47 -12
  99. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +53 -5
  100. data/lib/swarm_sdk/tools/stores/storage.rb +0 -6
  101. data/lib/swarm_sdk/tools/think.rb +4 -1
  102. data/lib/swarm_sdk/tools/todo_write.rb +27 -8
  103. data/lib/swarm_sdk/tools/web_fetch.rb +3 -2
  104. data/lib/swarm_sdk/tools/write.rb +8 -13
  105. data/lib/swarm_sdk/utils.rb +18 -0
  106. data/lib/swarm_sdk/validation_result.rb +33 -0
  107. data/lib/swarm_sdk/version.rb +1 -1
  108. data/lib/swarm_sdk/{node → workflow}/agent_config.rb +34 -9
  109. data/lib/swarm_sdk/workflow/builder.rb +143 -0
  110. data/lib/swarm_sdk/workflow/executor.rb +497 -0
  111. data/lib/swarm_sdk/{node/builder.rb → workflow/node_builder.rb} +42 -21
  112. data/lib/swarm_sdk/{node → workflow}/transformer_executor.rb +3 -2
  113. data/lib/swarm_sdk/workflow.rb +554 -0
  114. data/lib/swarm_sdk.rb +393 -22
  115. metadata +51 -16
  116. data/lib/swarm_memory/chat_extension.rb +0 -34
  117. data/lib/swarm_sdk/node_orchestrator.rb +0 -591
  118. data/lib/swarm_sdk/providers/openai_with_responses.rb +0 -582
@@ -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
@@ -28,7 +28,7 @@ module SwarmSDK
28
28
  # end
29
29
  #
30
30
  # swarm.execute("Build auth API")
31
- class Builder
31
+ class Builder < Builders::BaseBuilder
32
32
  # Main entry point for DSL
33
33
  #
34
34
  # @example
@@ -37,27 +37,18 @@ module SwarmSDK
37
37
  # agent :backend { ... }
38
38
  # end
39
39
  class << self
40
- def build(&block)
41
- builder = new
40
+ def build(allow_filesystem_tools: nil, &block)
41
+ builder = new(allow_filesystem_tools: allow_filesystem_tools)
42
42
  builder.instance_eval(&block)
43
43
  builder.build_swarm
44
44
  end
45
45
  end
46
46
 
47
- def initialize
48
- @swarm_name = nil
47
+ def initialize(allow_filesystem_tools: nil)
48
+ super
49
49
  @lead_agent = nil
50
- @agents = {}
51
- @all_agents_config = nil
52
50
  @swarm_hooks = []
53
- @nodes = {}
54
- @start_node = nil
55
- @scratchpad_enabled = true # Default: enabled
56
- end
57
-
58
- # Set swarm name
59
- def name(swarm_name)
60
- @swarm_name = swarm_name
51
+ @observer_configs = []
61
52
  end
62
53
 
63
54
  # Set lead agent
@@ -65,57 +56,42 @@ module SwarmSDK
65
56
  @lead_agent = agent_name
66
57
  end
67
58
 
68
- # Enable or disable shared scratchpad
69
- #
70
- # @param enabled [Boolean] Whether to enable scratchpad tools
71
- def use_scratchpad(enabled)
72
- @scratchpad_enabled = enabled
73
- end
74
-
75
- # Define an agent with fluent API or load from markdown content
76
- #
77
- # Supports two forms:
78
- # 1. Inline DSL: agent :name do ... end
79
- # 2. Markdown content: agent :name, <<~MD ... MD
59
+ # Define observer agent behavior
80
60
  #
81
- # The name parameter is always required. If the markdown has a name field
82
- # in frontmatter, it will be replaced by the name parameter.
61
+ # Configures an agent to run in parallel with main execution,
62
+ # triggered by specific events. The block defines event handlers.
83
63
  #
84
- # @example Inline DSL
85
- # agent :backend do
86
- # model "gpt-5"
87
- # system_prompt "You build APIs"
88
- # tools :Read, :Write
64
+ # @param agent_name [Symbol] Name of agent to use as observer (must be defined)
65
+ # @param options [Hash] Optional observer settings (timeout, max_concurrent, etc.)
66
+ # @yield Observer configuration block
89
67
  #
90
- # hook :pre_tool_use, matcher: "Bash" do |ctx|
91
- # # Inline validation logic!
68
+ # @example Basic observer
69
+ # observer :profiler do
70
+ # on :swarm_start do |event|
71
+ # "Analyze this prompt: #{event[:prompt]}"
92
72
  # end
93
73
  # end
94
74
  #
95
- # @example Markdown content
96
- # agent :backend, <<~MD
97
- # ---
98
- # description: "Backend developer"
99
- # model: "gpt-4"
100
- # ---
101
- #
102
- # You build APIs.
103
- # MD
104
- def agent(name, content = nil, &block)
105
- # Case 1: agent :name, <<~MD do ... end (markdown + overrides)
106
- if content.is_a?(String) && block_given? && markdown_content?(content)
107
- load_agent_from_markdown_with_overrides(content, name, &block)
108
- # Case 2: agent :name, <<~MD (markdown only)
109
- elsif content.is_a?(String) && !block_given? && markdown_content?(content)
110
- load_agent_from_markdown(content, name)
111
- # Case 3: agent :name do ... end (inline DSL)
112
- elsif block_given?
113
- builder = Agent::Builder.new(name)
114
- builder.instance_eval(&block)
115
- @agents[name] = builder
116
- else
117
- raise ArgumentError, "Invalid agent definition. Use: agent :name { ... } OR agent :name, <<~MD ... MD OR agent :name, <<~MD do ... end"
75
+ # @example Observer with options
76
+ # observer :monitor, timeout: 120 do
77
+ # on :tool_call do |event|
78
+ # next unless event[:tool_name] == "Bash"
79
+ # "Check command: #{event[:arguments][:command]}"
80
+ # end
81
+ # end
82
+ def observer(agent_name, **options, &block)
83
+ unless @agents.key?(agent_name)
84
+ raise ConfigurationError,
85
+ "Observer agent '#{agent_name}' not defined. " \
86
+ "Define the agent first with `agent :#{agent_name} do ... end`"
118
87
  end
88
+
89
+ config = Observer::Config.new(agent_name)
90
+ config.options.merge!(options) if options.any?
91
+ builder = Observer::Builder.new(agent_name, config)
92
+ builder.instance_eval(&block)
93
+
94
+ @observer_configs << config
119
95
  end
120
96
 
121
97
  # Add swarm-level hook (swarm_start, swarm_stop only)
@@ -136,197 +112,50 @@ module SwarmSDK
136
112
  @swarm_hooks << { event: event, command: command, timeout: timeout, block: block }
137
113
  end
138
114
 
139
- # Configure all agents with a block
140
- #
141
- # @example
142
- # all_agents do
143
- # tools :Read, :Write
144
- #
145
- # hook :pre_tool_use, matcher: "Write" do |ctx|
146
- # # Validation for all agents
147
- # end
148
- # end
149
- def all_agents(&block)
150
- builder = AllAgentsBuilder.new
151
- builder.instance_eval(&block)
152
- @all_agents_config = builder
153
- end
154
-
155
- # Define a node (mini-swarm execution stage)
156
- #
157
- # Nodes enable multi-stage workflows where different agent teams
158
- # collaborate in sequence. Each node is an independent swarm execution.
159
- #
160
- # @param name [Symbol] Node name
161
- # @yield Block for node configuration
162
- # @return [void]
163
- #
164
- # @example Solo agent node
165
- # node :planning do
166
- # agent(:architect)
167
- # end
168
- #
169
- # @example Multi-agent node with delegation
170
- # node :implementation do
171
- # agent(:backend).delegates_to(:tester, :database)
172
- # agent(:tester).delegates_to(:database)
173
- # agent(:database)
174
- # after :planning
175
- # end
176
- def node(name, &block)
177
- builder = Node::Builder.new(name)
178
- builder.instance_eval(&block)
179
- @nodes[name] = builder
180
- end
181
-
182
- # Set the starting node for workflow execution
183
- #
184
- # Required when nodes are defined. Specifies which node to execute first.
185
- #
186
- # @param name [Symbol] Name of starting node
187
- # @return [void]
188
- #
189
- # @example
190
- # start_node :planning
191
- def start_node(name)
192
- @start_node = name.to_sym
193
- end
194
-
195
- # Build the actual Swarm instance or NodeOrchestrator
115
+ # Build the actual Swarm instance
196
116
  def build_swarm
197
117
  raise ConfigurationError, "Swarm name not set. Use: name 'My Swarm'" unless @swarm_name
118
+ raise ConfigurationError, "No agents defined. Use: agent :name { ... }" if @agents.empty?
119
+ raise ConfigurationError, "Lead agent not set. Use: lead :agent_name" unless @lead_agent
198
120
 
199
- # Check if nodes are defined
200
- if @nodes.any?
201
- # Node-based workflow (agents optional for agent-less workflows)
202
- build_node_orchestrator
203
- else
204
- # Traditional single-swarm execution (requires agents and lead)
205
- raise ConfigurationError, "No agents defined. Use: agent :name { ... }" if @agents.empty?
206
- raise ConfigurationError, "Lead agent not set. Use: lead :agent_name" unless @lead_agent
121
+ # Validate filesystem tools BEFORE building
122
+ validate_all_agents_filesystem_tools if @all_agents_config
123
+ validate_agent_filesystem_tools
207
124
 
208
- build_single_swarm
209
- end
125
+ build_single_swarm
210
126
  end
211
127
 
212
128
  private
213
129
 
214
- # Check if a string is markdown content (has frontmatter)
215
- #
216
- # @param str [String] String to check
217
- # @return [Boolean] true if string contains markdown frontmatter
218
- def markdown_content?(str)
219
- str.start_with?("---") || str.include?("\n---\n")
220
- end
221
-
222
- # Load an agent from markdown content
223
- #
224
- # Returns a hash of the agent config (not a Definition yet) so that
225
- # all_agents config can be applied later in the build process.
226
- #
227
- # @param content [String] Markdown content with frontmatter
228
- # @param name_override [Symbol, nil] Optional name to override frontmatter name
229
- # @return [void]
230
- def load_agent_from_markdown(content, name_override = nil)
231
- # Parse markdown content - will extract name from frontmatter if not overridden
232
- definition = MarkdownParser.parse(content, name_override)
233
-
234
- # Store the config hash (not Definition) so all_agents can be applied
235
- # We'll wrap this in a special marker so we know it came from markdown
236
- @agents[definition.name] = { __file_config__: definition.to_h }
237
- end
238
-
239
- # Load an agent from markdown content with DSL overrides
240
- #
241
- # This allows loading from a file and then overriding specific settings:
242
- # agent :reviewer, File.read("reviewer.md") do
243
- # provider :openai
244
- # model "gpt-4o"
245
- # end
246
- #
247
- # @param content [String] Markdown content with frontmatter
248
- # @param name_override [Symbol, nil] Optional name to override frontmatter name
249
- # @yield Block with DSL overrides
250
- # @return [void]
251
- def load_agent_from_markdown_with_overrides(content, name_override = nil, &block)
252
- # Parse markdown content first
253
- definition = MarkdownParser.parse(content, name_override)
254
-
255
- # Create a builder with the markdown config
256
- builder = Agent::Builder.new(definition.name)
257
-
258
- # Apply markdown settings to builder (these become the base)
259
- apply_definition_to_builder(builder, definition.to_h)
260
-
261
- # Apply DSL overrides (these override the markdown settings)
262
- builder.instance_eval(&block)
263
-
264
- # Store the builder (not file config) so overrides are preserved
265
- @agents[definition.name] = builder
266
- end
267
-
268
- # Apply agent definition hash to a builder
269
- #
270
- # @param builder [Agent::Builder] Builder to configure
271
- # @param config [Hash] Configuration hash from definition
272
- # @return [void]
273
- def apply_definition_to_builder(builder, config)
274
- builder.description(config[:description]) if config[:description]
275
- builder.model(config[:model]) if config[:model]
276
- builder.provider(config[:provider]) if config[:provider]
277
- builder.base_url(config[:base_url]) if config[:base_url]
278
- builder.api_version(config[:api_version]) if config[:api_version]
279
- builder.context_window(config[:context_window]) if config[:context_window]
280
- builder.system_prompt(config[:system_prompt]) if config[:system_prompt]
281
- builder.directory(config[:directory]) if config[:directory]
282
- builder.timeout(config[:timeout]) if config[:timeout]
283
- builder.parameters(config[:parameters]) if config[:parameters]
284
- builder.headers(config[:headers]) if config[:headers]
285
- builder.coding_agent(config[:coding_agent]) unless config[:coding_agent].nil?
286
- # Don't apply assume_model_exists from markdown - let DSL overrides or auto-enable handle it
287
- # builder.assume_model_exists(config[:assume_model_exists]) unless config[:assume_model_exists].nil?
288
- builder.bypass_permissions(config[:bypass_permissions]) if config[:bypass_permissions]
289
- builder.disable_default_tools(config[:disable_default_tools]) unless config[:disable_default_tools].nil?
290
-
291
- # Add tools from markdown
292
- if config[:tools]&.any?
293
- # Extract tool names from the tools array (which may be hashes with permissions)
294
- tool_names = config[:tools].map do |tool|
295
- tool.is_a?(Hash) ? tool[:name] : tool
296
- end
297
- builder.tools(*tool_names)
298
- end
299
-
300
- # Add delegates_to
301
- builder.delegates_to(*config[:delegates_to]) if config[:delegates_to]&.any?
302
-
303
- # Add MCP servers
304
- config[:mcp_servers]&.each do |server|
305
- builder.mcp_server(server[:name], **server.except(:name))
306
- end
307
- end
308
-
309
130
  # Build a traditional single-swarm execution
310
131
  #
311
132
  # @return [Swarm] Configured swarm instance
312
133
  def build_single_swarm
313
- # Create swarm using SDK
314
- swarm = Swarm.new(name: @swarm_name, scratchpad_enabled: @scratchpad_enabled)
134
+ # Validate swarm_id is set if external swarms are registered (required for composable swarms)
135
+ if @swarm_registry_config.any? && @swarm_id.nil?
136
+ raise ConfigurationError, "Swarm id must be set using id(...) when using composable swarms"
137
+ end
315
138
 
316
- # Merge all_agents config into each agent (including file-loaded ones)
317
- merge_all_agents_config_into_agents if @all_agents_config
139
+ # Create swarm using SDK (swarm_id auto-generates if nil)
140
+ swarm = Swarm.new(
141
+ name: @swarm_name,
142
+ swarm_id: @swarm_id,
143
+ scratchpad_mode: @scratchpad,
144
+ allow_filesystem_tools: @allow_filesystem_tools,
145
+ )
318
146
 
319
- # Build definitions and add to swarm
320
- # Handle both Agent::Builder (inline DSL) and file configs (from files)
321
- @agents.each do |agent_name, agent_builder_or_config|
322
- definition = if agent_builder_or_config.is_a?(Hash) && agent_builder_or_config.key?(:__file_config__)
323
- # File-loaded agent config (with all_agents merged)
324
- Agent::Definition.new(agent_name, agent_builder_or_config[:__file_config__])
325
- else
326
- # Builder object (from inline DSL) - convert to definition
327
- agent_builder_or_config.to_definition
147
+ # Setup swarm registry if external swarms are registered
148
+ if @swarm_registry_config.any?
149
+ registry = SwarmRegistry.new(parent_swarm_id: @swarm_id)
150
+ @swarm_registry_config.each do |reg|
151
+ registry.register(reg[:name], source: reg[:source], keep_context: reg[:keep_context])
328
152
  end
153
+ swarm.swarm_registry = registry
154
+ end
329
155
 
156
+ # Build agent definitions and add to swarm
157
+ agent_definitions = build_agent_definitions
158
+ agent_definitions.each_value do |definition|
330
159
  swarm.add_agent(definition)
331
160
  end
332
161
 
@@ -334,191 +163,21 @@ module SwarmSDK
334
163
  swarm.lead = @lead_agent
335
164
 
336
165
  # Apply swarm hooks (Ruby blocks)
337
- # These are swarm-level hooks (swarm_start, swarm_stop)
338
166
  @swarm_hooks.each do |hook_config|
339
167
  apply_swarm_hook(swarm, hook_config)
340
168
  end
341
169
 
342
170
  # Apply all_agents hooks (Ruby blocks)
343
- # These become swarm-level default callbacks that apply to all agents
344
171
  @all_agents_config&.hooks&.each do |hook_config|
345
172
  apply_all_agents_hook(swarm, hook_config)
346
173
  end
347
174
 
348
- # NOTE: Agent-specific hooks are already stored in Agent::Definition.callbacks
349
- # They'll be applied automatically during agent initialization (pass_4_configure_hooks)
350
- # This ensures they're applied at the right time, after LogStream is set up
175
+ # Add observer configurations to swarm
176
+ @observer_configs.each { |c| swarm.add_observer_config(c) }
351
177
 
352
178
  swarm
353
179
  end
354
180
 
355
- # Build a node-based workflow orchestrator
356
- #
357
- # @return [NodeOrchestrator] Configured orchestrator
358
- def build_node_orchestrator
359
- raise ConfigurationError, "start_node required when nodes are defined. Use: start_node :name" unless @start_node
360
-
361
- # Merge all_agents config into each agent (applies to all nodes)
362
- merge_all_agents_config_into_agents if @all_agents_config
363
-
364
- # Build agent definitions
365
- # Handle both Agent::Builder (inline DSL) and file configs (from files)
366
- agent_definitions = {}
367
- @agents.each do |agent_name, agent_builder_or_config|
368
- agent_definitions[agent_name] = if agent_builder_or_config.is_a?(Hash) && agent_builder_or_config.key?(:__file_config__)
369
- # File-loaded agent config (with all_agents merged)
370
- Agent::Definition.new(agent_name, agent_builder_or_config[:__file_config__])
371
- else
372
- # Builder object (from inline DSL) - convert to definition
373
- agent_builder_or_config.to_definition
374
- end
375
- end
376
-
377
- # Create node orchestrator
378
- NodeOrchestrator.new(
379
- swarm_name: @swarm_name,
380
- agent_definitions: agent_definitions,
381
- nodes: @nodes,
382
- start_node: @start_node,
383
- scratchpad_enabled: @scratchpad_enabled,
384
- )
385
- end
386
-
387
- # Merge all_agents configuration into each agent
388
- #
389
- # All_agents values are used as defaults - agent-specific values override.
390
- # This applies to both inline DSL agents (Builder) and file-loaded agents (config hash).
391
- #
392
- # @return [void]
393
- def merge_all_agents_config_into_agents
394
- return unless @all_agents_config
395
-
396
- all_agents_hash = @all_agents_config.to_h
397
-
398
- @agents.each_value do |agent_builder_or_config|
399
- if agent_builder_or_config.is_a?(Hash) && agent_builder_or_config.key?(:__file_config__)
400
- # File-loaded agent - merge into the config hash
401
- file_config = agent_builder_or_config[:__file_config__]
402
-
403
- # Merge all_agents into file config (file config overrides)
404
- # Use same merge strategy as Configuration class
405
- merged_config = merge_all_agents_into_config(all_agents_hash, file_config)
406
-
407
- # Update the stored config
408
- agent_builder_or_config[:__file_config__] = merged_config
409
- else
410
- # Builder object (inline DSL agent)
411
- agent_builder = agent_builder_or_config
412
-
413
- # Apply all_agents defaults that haven't been set at agent level
414
- # Agent values override all_agents values
415
- apply_all_agents_defaults(agent_builder, all_agents_hash)
416
-
417
- # Merge tools (prepend all_agents tools)
418
- all_agents_tools = @all_agents_config.tools_list
419
- agent_builder.prepend_tools(*all_agents_tools) if all_agents_tools.any?
420
-
421
- # Pass all_agents permissions as default_permissions
422
- if @all_agents_config.permissions_config.any?
423
- agent_builder.default_permissions = @all_agents_config.permissions_config
424
- end
425
- end
426
- end
427
- end
428
-
429
- # Merge all_agents config into file-loaded agent config
430
- #
431
- # Follows same merge strategy as Configuration class:
432
- # - Arrays (tools, delegates_to): Concatenate (all_agents + file)
433
- # - Hashes (parameters, headers): Merge (file values override)
434
- # - Scalars (model, provider, etc.): File overrides
435
- #
436
- # @param all_agents_hash [Hash] All_agents configuration
437
- # @param file_config [Hash] File-loaded agent configuration
438
- # @return [Hash] Merged configuration
439
- def merge_all_agents_into_config(all_agents_hash, file_config)
440
- merged = all_agents_hash.dup
441
-
442
- file_config.each do |key, value|
443
- case key
444
- when :tools
445
- # Concatenate tools: all_agents.tools + file.tools
446
- merged[:tools] = Array(merged[:tools]) + Array(value)
447
- when :delegates_to
448
- # Concatenate delegates_to
449
- merged[:delegates_to] = Array(merged[:delegates_to]) + Array(value)
450
- when :parameters
451
- # Merge parameters: file values override all_agents
452
- merged[:parameters] = (merged[:parameters] || {}).merge(value || {})
453
- when :headers
454
- # Merge headers: file values override all_agents
455
- merged[:headers] = (merged[:headers] || {}).merge(value || {})
456
- else
457
- # For everything else, file value overrides all_agents value
458
- merged[key] = value
459
- end
460
- end
461
-
462
- # Pass all_agents permissions as default_permissions
463
- if all_agents_hash[:permissions] && !merged[:default_permissions]
464
- merged[:default_permissions] = all_agents_hash[:permissions]
465
- end
466
-
467
- merged
468
- end
469
-
470
- # Apply all_agents defaults to an agent builder
471
- #
472
- # Only sets values that haven't been explicitly set at the agent level.
473
- # This implements the override semantics: agent values take precedence.
474
- #
475
- # @param agent_builder [Agent::Builder] The agent builder to configure
476
- # @param all_agents_hash [Hash] All_agents configuration
477
- # @return [void]
478
- def apply_all_agents_defaults(agent_builder, all_agents_hash)
479
- # Model: only set if agent hasn't explicitly set it
480
- if all_agents_hash[:model] && !agent_builder.model_set?
481
- agent_builder.model(all_agents_hash[:model])
482
- end
483
-
484
- # Provider: only set if agent hasn't set it
485
- if all_agents_hash[:provider] && !agent_builder.provider_set?
486
- agent_builder.provider(all_agents_hash[:provider])
487
- end
488
-
489
- # Base URL: only set if agent hasn't set it
490
- if all_agents_hash[:base_url] && !agent_builder.base_url_set?
491
- agent_builder.base_url(all_agents_hash[:base_url])
492
- end
493
-
494
- # API Version: only set if agent hasn't set it
495
- if all_agents_hash[:api_version] && !agent_builder.api_version_set?
496
- agent_builder.api_version(all_agents_hash[:api_version])
497
- end
498
-
499
- # Timeout: only set if agent hasn't set it
500
- if all_agents_hash[:timeout] && !agent_builder.timeout_set?
501
- agent_builder.timeout(all_agents_hash[:timeout])
502
- end
503
-
504
- # Parameters: merge (all_agents + agent, agent values override)
505
- if all_agents_hash[:parameters]
506
- merged_params = all_agents_hash[:parameters].merge(agent_builder.parameters)
507
- agent_builder.parameters(merged_params)
508
- end
509
-
510
- # Headers: merge (all_agents + agent, agent values override)
511
- if all_agents_hash[:headers]
512
- merged_headers = all_agents_hash[:headers].merge(agent_builder.headers)
513
- agent_builder.headers(merged_headers)
514
- end
515
-
516
- # Coding_agent: only set if agent hasn't set it
517
- if !all_agents_hash[:coding_agent].nil? && !agent_builder.coding_agent_set?
518
- agent_builder.coding_agent(all_agents_hash[:coding_agent])
519
- end
520
- end
521
-
522
181
  def apply_swarm_hook(swarm, config)
523
182
  event = config[:event]
524
183
 
@@ -564,7 +223,7 @@ module SwarmSDK
564
223
  end
565
224
 
566
225
  def build_hook_input(context, event)
567
- # Build JSON input for shell hooks (similar to HooksAdapter)
226
+ # Build JSON input for shell hooks
568
227
  base = { event: event.to_s }
569
228
 
570
229
  case event
@@ -583,5 +242,8 @@ module SwarmSDK
583
242
  end
584
243
  end
585
244
  end
245
+
246
+ # Helper class for swarms block in DSL (kept in this file for reference)
247
+ # Actual implementation is in swarm_registry_builder.rb for Zeitwerk
586
248
  end
587
249
  end