swarm_sdk 2.7.13 → 3.0.0.alpha1

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 (183) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_sdk/ruby_llm_patches/chat_callbacks_patch.rb +43 -22
  3. data/lib/swarm_sdk/ruby_llm_patches/init.rb +6 -0
  4. data/lib/swarm_sdk/ruby_llm_patches/mcp_ssl_patch.rb +144 -0
  5. data/lib/swarm_sdk/ruby_llm_patches/tool_concurrency_patch.rb +3 -4
  6. data/lib/swarm_sdk/v3/agent.rb +1165 -0
  7. data/lib/swarm_sdk/v3/agent_builder.rb +533 -0
  8. data/lib/swarm_sdk/v3/agent_definition.rb +330 -0
  9. data/lib/swarm_sdk/v3/configuration.rb +490 -0
  10. data/lib/swarm_sdk/v3/debug_log.rb +86 -0
  11. data/lib/swarm_sdk/v3/event_stream.rb +130 -0
  12. data/lib/swarm_sdk/v3/hooks/context.rb +112 -0
  13. data/lib/swarm_sdk/v3/hooks/result.rb +115 -0
  14. data/lib/swarm_sdk/v3/hooks/runner.rb +128 -0
  15. data/lib/swarm_sdk/v3/mcp/connector.rb +183 -0
  16. data/lib/swarm_sdk/v3/mcp/mcp_error.rb +15 -0
  17. data/lib/swarm_sdk/v3/mcp/server_definition.rb +125 -0
  18. data/lib/swarm_sdk/v3/mcp/ssl_http_transport.rb +103 -0
  19. data/lib/swarm_sdk/v3/mcp/stdio_transport.rb +135 -0
  20. data/lib/swarm_sdk/v3/mcp/tool_proxy.rb +53 -0
  21. data/lib/swarm_sdk/v3/memory/adapters/base.rb +297 -0
  22. data/lib/swarm_sdk/v3/memory/adapters/faiss_support.rb +194 -0
  23. data/lib/swarm_sdk/v3/memory/adapters/filesystem_adapter.rb +212 -0
  24. data/lib/swarm_sdk/v3/memory/adapters/sqlite_adapter.rb +507 -0
  25. data/lib/swarm_sdk/v3/memory/adapters/vector_utils.rb +88 -0
  26. data/lib/swarm_sdk/v3/memory/card.rb +206 -0
  27. data/lib/swarm_sdk/v3/memory/cluster.rb +146 -0
  28. data/lib/swarm_sdk/v3/memory/compressor.rb +496 -0
  29. data/lib/swarm_sdk/v3/memory/consolidator.rb +427 -0
  30. data/lib/swarm_sdk/v3/memory/context_builder.rb +339 -0
  31. data/lib/swarm_sdk/v3/memory/edge.rb +105 -0
  32. data/lib/swarm_sdk/v3/memory/embedder.rb +185 -0
  33. data/lib/swarm_sdk/v3/memory/exposure_tracker.rb +104 -0
  34. data/lib/swarm_sdk/v3/memory/ingestion_pipeline.rb +394 -0
  35. data/lib/swarm_sdk/v3/memory/retriever.rb +289 -0
  36. data/lib/swarm_sdk/v3/memory/store.rb +489 -0
  37. data/lib/swarm_sdk/v3/skills/loader.rb +147 -0
  38. data/lib/swarm_sdk/v3/skills/manifest.rb +45 -0
  39. data/lib/swarm_sdk/v3/sub_task_agent.rb +248 -0
  40. data/lib/swarm_sdk/v3/tools/base.rb +80 -0
  41. data/lib/swarm_sdk/v3/tools/bash.rb +174 -0
  42. data/lib/swarm_sdk/v3/tools/clock.rb +32 -0
  43. data/lib/swarm_sdk/v3/tools/edit.rb +111 -0
  44. data/lib/swarm_sdk/v3/tools/glob.rb +96 -0
  45. data/lib/swarm_sdk/v3/tools/grep.rb +200 -0
  46. data/lib/swarm_sdk/v3/tools/message_teammate.rb +15 -0
  47. data/lib/swarm_sdk/v3/tools/message_user.rb +15 -0
  48. data/lib/swarm_sdk/v3/tools/read.rb +181 -0
  49. data/lib/swarm_sdk/v3/tools/read_tracker.rb +40 -0
  50. data/lib/swarm_sdk/v3/tools/registry.rb +208 -0
  51. data/lib/swarm_sdk/v3/tools/sub_task.rb +183 -0
  52. data/lib/swarm_sdk/v3/tools/think.rb +88 -0
  53. data/lib/swarm_sdk/v3/tools/write.rb +87 -0
  54. data/lib/swarm_sdk/v3.rb +145 -0
  55. metadata +84 -148
  56. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -175
  57. data/lib/swarm_sdk/agent/builder.rb +0 -680
  58. data/lib/swarm_sdk/agent/chat.rb +0 -1432
  59. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -375
  60. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  61. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  62. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -85
  63. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -290
  64. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  65. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  66. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -134
  67. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  68. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -146
  69. data/lib/swarm_sdk/agent/context.rb +0 -115
  70. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  71. data/lib/swarm_sdk/agent/definition.rb +0 -581
  72. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -226
  73. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -161
  74. data/lib/swarm_sdk/agent/tool_registry.rb +0 -189
  75. data/lib/swarm_sdk/agent_registry.rb +0 -146
  76. data/lib/swarm_sdk/builders/base_builder.rb +0 -553
  77. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  78. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -39
  79. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  80. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  81. data/lib/swarm_sdk/config.rb +0 -367
  82. data/lib/swarm_sdk/configuration/parser.rb +0 -397
  83. data/lib/swarm_sdk/configuration/translator.rb +0 -283
  84. data/lib/swarm_sdk/configuration.rb +0 -165
  85. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  86. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -102
  87. data/lib/swarm_sdk/context_compactor.rb +0 -335
  88. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  89. data/lib/swarm_sdk/context_management/context.rb +0 -328
  90. data/lib/swarm_sdk/custom_tool_registry.rb +0 -226
  91. data/lib/swarm_sdk/defaults.rb +0 -251
  92. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  93. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  94. data/lib/swarm_sdk/hooks/context.rb +0 -197
  95. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  96. data/lib/swarm_sdk/hooks/error.rb +0 -29
  97. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  98. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  99. data/lib/swarm_sdk/hooks/result.rb +0 -150
  100. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -256
  101. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  102. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  103. data/lib/swarm_sdk/log_collector.rb +0 -227
  104. data/lib/swarm_sdk/log_stream.rb +0 -127
  105. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  106. data/lib/swarm_sdk/model_aliases.json +0 -8
  107. data/lib/swarm_sdk/models.json +0 -44002
  108. data/lib/swarm_sdk/models.rb +0 -161
  109. data/lib/swarm_sdk/node_context.rb +0 -245
  110. data/lib/swarm_sdk/observer/builder.rb +0 -81
  111. data/lib/swarm_sdk/observer/config.rb +0 -45
  112. data/lib/swarm_sdk/observer/manager.rb +0 -236
  113. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  114. data/lib/swarm_sdk/permissions/config.rb +0 -239
  115. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  116. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  117. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  118. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  119. data/lib/swarm_sdk/plugin.rb +0 -309
  120. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  121. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  122. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -117
  123. data/lib/swarm_sdk/restore_result.rb +0 -65
  124. data/lib/swarm_sdk/result.rb +0 -212
  125. data/lib/swarm_sdk/snapshot.rb +0 -156
  126. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  127. data/lib/swarm_sdk/state_restorer.rb +0 -476
  128. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  129. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -648
  130. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -195
  131. data/lib/swarm_sdk/swarm/builder.rb +0 -256
  132. data/lib/swarm_sdk/swarm/executor.rb +0 -290
  133. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -151
  134. data/lib/swarm_sdk/swarm/lazy_delegate_chat.rb +0 -372
  135. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -360
  136. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -270
  137. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  138. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -392
  139. data/lib/swarm_sdk/swarm.rb +0 -843
  140. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  141. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  142. data/lib/swarm_sdk/tools/base.rb +0 -63
  143. data/lib/swarm_sdk/tools/bash.rb +0 -280
  144. data/lib/swarm_sdk/tools/clock.rb +0 -46
  145. data/lib/swarm_sdk/tools/delegate.rb +0 -389
  146. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  147. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  148. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  149. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  150. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  151. data/lib/swarm_sdk/tools/edit.rb +0 -145
  152. data/lib/swarm_sdk/tools/glob.rb +0 -166
  153. data/lib/swarm_sdk/tools/grep.rb +0 -235
  154. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  155. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -167
  156. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  157. data/lib/swarm_sdk/tools/mcp_tool_stub.rb +0 -198
  158. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  159. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  160. data/lib/swarm_sdk/tools/read.rb +0 -261
  161. data/lib/swarm_sdk/tools/registry.rb +0 -205
  162. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  163. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  164. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  165. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  166. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -273
  167. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  168. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  169. data/lib/swarm_sdk/tools/think.rb +0 -100
  170. data/lib/swarm_sdk/tools/todo_write.rb +0 -237
  171. data/lib/swarm_sdk/tools/web_fetch.rb +0 -264
  172. data/lib/swarm_sdk/tools/write.rb +0 -112
  173. data/lib/swarm_sdk/transcript_builder.rb +0 -278
  174. data/lib/swarm_sdk/utils.rb +0 -68
  175. data/lib/swarm_sdk/validation_result.rb +0 -33
  176. data/lib/swarm_sdk/version.rb +0 -5
  177. data/lib/swarm_sdk/workflow/agent_config.rb +0 -95
  178. data/lib/swarm_sdk/workflow/builder.rb +0 -227
  179. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  180. data/lib/swarm_sdk/workflow/node_builder.rb +0 -593
  181. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -250
  182. data/lib/swarm_sdk/workflow.rb +0 -589
  183. data/lib/swarm_sdk.rb +0 -718
data/lib/swarm_sdk.rb DELETED
@@ -1,718 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler"
4
- require "digest"
5
- require "English"
6
- require "erb"
7
- require "fileutils"
8
- require "json"
9
- require "logger"
10
- require "pathname"
11
- require "securerandom"
12
- require "set"
13
- require "yaml"
14
-
15
- require "async"
16
- require "async/barrier"
17
- require "async/semaphore"
18
- require "ruby_llm"
19
- require "ruby_llm/mcp"
20
-
21
- # Load ruby_llm compatibility patches
22
- # These patches extend upstream ruby_llm to match fork functionality used by SwarmSDK
23
- require_relative "swarm_sdk/ruby_llm_patches/init"
24
-
25
- # Patch Zeitwerk loaders to ignore Rails-dependent files when Rails is not present
26
- # This prevents NameError when eager loading outside of Rails applications
27
- unless defined?(Rails)
28
- require "zeitwerk"
29
-
30
- # Ignore ruby_llm's ActiveRecord integration (requires ActiveSupport)
31
- ruby_llm_loader = nil
32
- Zeitwerk::Registry.loaders.each { |l| ruby_llm_loader = l if l.tag == "ruby_llm" }
33
- if ruby_llm_loader
34
- ruby_llm_gem_dir = Gem.loaded_specs["ruby_llm"]&.gem_dir
35
- if ruby_llm_gem_dir
36
- active_record_dir = File.join(ruby_llm_gem_dir, "lib", "ruby_llm", "active_record")
37
- ruby_llm_loader.ignore(active_record_dir)
38
- end
39
- end
40
-
41
- # Ignore ruby_llm-mcp's railtie
42
- mcp_loader = nil
43
- Zeitwerk::Registry.loaders.each { |l| mcp_loader = l if l.tag == "RubyLLM-mcp" }
44
- if mcp_loader
45
- mcp_gem_dir = Gem.loaded_specs["ruby_llm-mcp"]&.gem_dir ||
46
- Gem.loaded_specs["ruby_llm_swarm-mcp"]&.gem_dir
47
- if mcp_gem_dir
48
- railtie_path = File.join(mcp_gem_dir, "lib", "ruby_llm", "mcp", "railtie.rb")
49
- mcp_loader.ignore(railtie_path)
50
- end
51
- end
52
- end
53
-
54
- # Configure Faraday to use async-http adapter by default
55
- # This ensures HTTP requests are fiber-aware and don't block the Async scheduler
56
- # when SwarmSDK executes LLM requests within Async/Sync blocks
57
- require "async/http/faraday/default"
58
-
59
- require_relative "swarm_sdk/version"
60
-
61
- require "zeitwerk"
62
- loader = Zeitwerk::Loader.new
63
- loader.tag = File.basename(__FILE__, ".rb")
64
- loader.push_dir("#{__dir__}/swarm_sdk", namespace: SwarmSDK)
65
- loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
66
- loader.inflector.inflect(
67
- "cli" => "CLI",
68
- "llm_instrumentation_middleware" => "LLMInstrumentationMiddleware",
69
- "mcp" => "MCP",
70
- "openai_with_responses" => "OpenAIWithResponses",
71
- )
72
- # Ignore ruby_llm_patches - these are manually required monkey patches, not autoloaded modules
73
- loader.ignore("#{__dir__}/swarm_sdk/ruby_llm_patches")
74
- loader.setup
75
-
76
- module SwarmSDK
77
- class Error < StandardError; end
78
- class ConfigurationError < Error; end
79
- class AgentNotFoundError < Error; end
80
- class CircularDependencyError < Error; end
81
- class ToolExecutionError < Error; end
82
- class LLMError < Error; end
83
- class StateError < Error; end
84
-
85
- # Base class for SwarmSDK timeout errors
86
- class TimeoutError < Error; end
87
-
88
- # Raised when swarm execution exceeds execution_timeout
89
- class ExecutionTimeoutError < TimeoutError; end
90
-
91
- # Raised when agent turn exceeds turn_timeout
92
- class TurnTimeoutError < TimeoutError; end
93
-
94
- # Base class for MCP-related errors (provides context about server/tool)
95
- class MCPError < Error; end
96
-
97
- # Raised when MCP request times out
98
- class MCPTimeoutError < MCPError; end
99
-
100
- # Raised when MCP transport fails (connection, HTTP errors)
101
- class MCPTransportError < MCPError; end
102
-
103
- class << self
104
- # Get the global configuration instance
105
- #
106
- # @return [Config] The singleton Config instance
107
- def config
108
- Config.instance
109
- end
110
-
111
- # Configure SwarmSDK global settings
112
- #
113
- # @yield [Config] The configuration instance
114
- # @return [Config] The configuration instance
115
- #
116
- # @example
117
- # SwarmSDK.configure do |config|
118
- # config.openai_api_key = "sk-..."
119
- # config.default_model = "claude-sonnet-4"
120
- # end
121
- def configure
122
- yield(config) if block_given?
123
- config
124
- end
125
-
126
- # Reset configuration to defaults
127
- #
128
- # Clears all configuration including explicit values and cached ENV values.
129
- # Use in tests to ensure clean state.
130
- #
131
- # @return [void]
132
- def reset_config!
133
- Config.reset!
134
- end
135
-
136
- # Register a global agent definition
137
- #
138
- # Declares an agent configuration that can be referenced by name in any
139
- # swarm definition. This allows defining agents in separate files and
140
- # composing them into swarms without duplication.
141
- #
142
- # The registered block uses the Agent::Builder DSL and is executed when
143
- # the agent is referenced in a swarm definition.
144
- #
145
- # @param name [Symbol, String] Agent name (will be symbolized)
146
- # @yield Agent configuration block using Agent::Builder DSL
147
- # @return [void]
148
- # @raise [ArgumentError] If no block is provided
149
- #
150
- # @example Register agent in separate file
151
- # # agents/backend.rb
152
- # SwarmSDK.agent :backend do
153
- # model "claude-sonnet-4"
154
- # description "Backend API developer"
155
- # system_prompt "You build REST APIs"
156
- # tools :Read, :Edit, :Bash
157
- # delegates_to :database
158
- # end
159
- #
160
- # @example Reference in swarm definition
161
- # # swarm.rb
162
- # require_relative "agents/backend"
163
- #
164
- # SwarmSDK.build do
165
- # name "Dev Team"
166
- # lead :backend
167
- #
168
- # agent :backend # Pulls from registry
169
- # end
170
- #
171
- # @example Extend registered agent with overrides
172
- # SwarmSDK.build do
173
- # name "Extended Team"
174
- # lead :backend
175
- #
176
- # agent :backend do
177
- # # Registry config applied first, then this block
178
- # tools :CustomTool # Adds to existing tools
179
- # delegates_to :cache # Adds delegation target
180
- # end
181
- # end
182
- #
183
- # @see AgentRegistry
184
- def agent(name, &block)
185
- AgentRegistry.register(name, &block)
186
- end
187
-
188
- # Clear the global agent registry
189
- #
190
- # Removes all registered agent definitions. Primarily useful for testing
191
- # to ensure clean state between tests.
192
- #
193
- # @return [void]
194
- #
195
- # @example In test teardown
196
- # def teardown
197
- # SwarmSDK.clear_agent_registry!
198
- # end
199
- def clear_agent_registry!
200
- AgentRegistry.clear
201
- end
202
-
203
- # Register a custom tool for use in swarms
204
- #
205
- # Provides a simple way to add tools without creating a full plugin.
206
- # Tools can be registered with an explicit name or the name can be
207
- # inferred from the class name.
208
- #
209
- # Custom tools are available to any agent that includes them in their
210
- # tools configuration, just like built-in tools.
211
- #
212
- # @overload register_tool(tool_class)
213
- # Register a tool with name inferred from class name
214
- # @param tool_class [Class] Tool class (must inherit from RubyLLM::Tool)
215
- # @return [Symbol] The registered tool name
216
- #
217
- # @overload register_tool(name, tool_class)
218
- # Register a tool with explicit name
219
- # @param name [Symbol, String] Tool name
220
- # @param tool_class [Class] Tool class (must inherit from RubyLLM::Tool)
221
- # @return [Symbol] The registered tool name
222
- #
223
- # @raise [ArgumentError] If tool_class doesn't inherit from RubyLLM::Tool
224
- # @raise [ArgumentError] If a tool with the same name is already registered
225
- # @raise [ArgumentError] If the name conflicts with a built-in or plugin tool
226
- #
227
- # @example Register with inferred name
228
- # class WeatherTool < RubyLLM::Tool
229
- # description "Get weather for a city"
230
- # param :city, type: "string", required: true
231
- #
232
- # def execute(city:)
233
- # "Weather in #{city}: Sunny, 72°F"
234
- # end
235
- # end
236
- #
237
- # SwarmSDK.register_tool(WeatherTool) # Registers as :Weather
238
- #
239
- # @example Register with explicit name
240
- # SwarmSDK.register_tool(:GetWeather, WeatherTool)
241
- #
242
- # @example Tool with agent context
243
- # class ContextAwareTool < RubyLLM::Tool
244
- # # Declare what context the tool needs
245
- # def self.creation_requirements
246
- # [:agent_name, :directory]
247
- # end
248
- #
249
- # def initialize(agent_name:, directory:)
250
- # super()
251
- # @agent_name = agent_name
252
- # @directory = directory
253
- # end
254
- #
255
- # description "Shows agent context"
256
- # def execute
257
- # "Agent: #{@agent_name} in #{@directory}"
258
- # end
259
- # end
260
- #
261
- # SwarmSDK.register_tool(ContextAwareTool)
262
- #
263
- # @example Use registered tool in a swarm
264
- # SwarmSDK.register_tool(WeatherTool)
265
- #
266
- # swarm = SwarmSDK.build do
267
- # name "Weather Assistant"
268
- # lead :assistant
269
- #
270
- # agent :assistant do
271
- # model "claude-sonnet-4"
272
- # description "Weather helper"
273
- # tools :Weather, :Read # Custom + built-in tools
274
- # end
275
- # end
276
- #
277
- # @see CustomToolRegistry For the underlying registry
278
- # @see Plugin For complex tool systems requiring storage or lifecycle hooks
279
- def register_tool(name_or_class, tool_class = nil)
280
- if tool_class.nil?
281
- # Single argument: infer name from class
282
- tool_class = name_or_class
283
- name = CustomToolRegistry.infer_name(tool_class)
284
- else
285
- # Two arguments: explicit name
286
- name = name_or_class.to_sym
287
- end
288
-
289
- CustomToolRegistry.register(name, tool_class)
290
- name
291
- end
292
-
293
- # Check if a custom tool is registered
294
- #
295
- # @param name [Symbol, String] Tool name
296
- # @return [Boolean] true if the tool is registered
297
- #
298
- # @example
299
- # SwarmSDK.register_tool(WeatherTool)
300
- # SwarmSDK.custom_tool_registered?(:Weather) #=> true
301
- # SwarmSDK.custom_tool_registered?(:Unknown) #=> false
302
- def custom_tool_registered?(name)
303
- CustomToolRegistry.registered?(name)
304
- end
305
-
306
- # Get all registered custom tool names
307
- #
308
- # @return [Array<Symbol>] List of registered custom tool names
309
- #
310
- # @example
311
- # SwarmSDK.register_tool(WeatherTool)
312
- # SwarmSDK.register_tool(StockTool)
313
- # SwarmSDK.custom_tools #=> [:Weather, :Stock]
314
- def custom_tools
315
- CustomToolRegistry.tool_names
316
- end
317
-
318
- # Unregister a custom tool
319
- #
320
- # @param name [Symbol, String] Tool name to unregister
321
- # @return [Class, nil] The unregistered tool class, or nil if not found
322
- #
323
- # @example
324
- # SwarmSDK.register_tool(WeatherTool)
325
- # SwarmSDK.unregister_tool(:Weather)
326
- # SwarmSDK.custom_tool_registered?(:Weather) #=> false
327
- def unregister_tool(name)
328
- CustomToolRegistry.unregister(name)
329
- end
330
-
331
- # Clear all registered custom tools
332
- #
333
- # Removes all custom tool registrations. Primarily useful for testing
334
- # to ensure clean state between tests.
335
- #
336
- # @return [void]
337
- #
338
- # @example In test teardown
339
- # def teardown
340
- # SwarmSDK.clear_custom_tools!
341
- # end
342
- def clear_custom_tools!
343
- CustomToolRegistry.clear
344
- end
345
-
346
- # Main entry point for DSL - builds simple multi-agent swarms
347
- #
348
- # @return [Swarm] Always returns a Swarm instance
349
- def build(allow_filesystem_tools: nil, &block)
350
- Swarm::Builder.build(allow_filesystem_tools: allow_filesystem_tools, &block)
351
- end
352
-
353
- # Entry point for building multi-stage workflows
354
- #
355
- # @return [Workflow] Always returns a Workflow instance
356
- def workflow(allow_filesystem_tools: nil, &block)
357
- Workflow::Builder.build(allow_filesystem_tools: allow_filesystem_tools, &block)
358
- end
359
-
360
- # Validate YAML configuration without creating a swarm
361
- #
362
- # Performs comprehensive validation of YAML configuration including:
363
- # - YAML syntax
364
- # - Required fields (version, swarm name, lead, agents)
365
- # - Agent configurations (description, directory existence)
366
- # - Circular dependencies
367
- # - File references (agent_file paths)
368
- # - Hook configurations
369
- #
370
- # @param yaml_content [String] YAML configuration content
371
- # @param base_dir [String, Pathname] Base directory for resolving agent file paths (default: Dir.pwd)
372
- # @return [Array<Hash>] Array of error hashes (empty if valid)
373
- #
374
- # @example Validate YAML string
375
- # errors = SwarmSDK.validate(yaml_content)
376
- # if errors.empty?
377
- # puts "Configuration is valid!"
378
- # else
379
- # errors.each do |error|
380
- # puts "#{error[:field]}: #{error[:message]}"
381
- # end
382
- # end
383
- #
384
- # @example Error hash structure
385
- # {
386
- # type: :missing_field, # Error type
387
- # field: "swarm.agents.backend.description", # JSON-style path to field
388
- # message: "Agent 'backend' missing required 'description' field",
389
- # agent: "backend" # Optional, present if error is agent-specific
390
- # }
391
- def validate(yaml_content, base_dir: Dir.pwd)
392
- errors = []
393
-
394
- begin
395
- config = Configuration.new(yaml_content, base_dir: base_dir)
396
- config.load_and_validate
397
-
398
- # Build swarm to trigger DSL validation
399
- # This catches errors from Agent::Definition, Builder, etc.
400
- config.to_swarm
401
- rescue ConfigurationError, CircularDependencyError => e
402
- errors << parse_configuration_error(e)
403
- rescue StandardError => e
404
- errors << {
405
- type: :unknown_error,
406
- field: nil,
407
- message: e.message,
408
- }
409
- end
410
-
411
- errors
412
- end
413
-
414
- # Validate YAML configuration file
415
- #
416
- # Convenience method that reads the file and validates the content.
417
- #
418
- # @param path [String, Pathname] Path to YAML configuration file
419
- # @return [Array<Hash>] Array of error hashes (empty if valid)
420
- #
421
- # @example
422
- # errors = SwarmSDK.validate_file("config.yml")
423
- # if errors.empty?
424
- # puts "Valid configuration!"
425
- # swarm = SwarmSDK.load_file("config.yml")
426
- # else
427
- # errors.each { |e| puts "Error: #{e[:message]}" }
428
- # end
429
- def validate_file(path)
430
- path = Pathname.new(path).expand_path
431
-
432
- unless path.exist?
433
- return [{
434
- type: :file_not_found,
435
- field: nil,
436
- message: "Configuration file not found: #{path}",
437
- }]
438
- end
439
-
440
- yaml_content = File.read(path)
441
- base_dir = path.dirname
442
-
443
- validate(yaml_content, base_dir: base_dir)
444
- rescue StandardError => e
445
- [{
446
- type: :file_read_error,
447
- field: nil,
448
- message: "Error reading file: #{e.message}",
449
- }]
450
- end
451
-
452
- # Load swarm from YAML string
453
- #
454
- # This is the primary programmatic API for loading YAML configurations.
455
- # For file-based loading, use SwarmSDK.load_file for convenience.
456
- #
457
- # @param yaml_content [String] YAML configuration content
458
- # @param base_dir [String, Pathname] Base directory for resolving agent file paths (default: Dir.pwd)
459
- # @param allow_filesystem_tools [Boolean, nil] Whether to allow filesystem tools (nil uses global setting)
460
- # @param env_interpolation [Boolean, nil] Whether to interpolate environment variables.
461
- # When nil, uses the global SwarmSDK.config.env_interpolation setting.
462
- # When true, interpolates ${VAR} and ${VAR:=default} patterns.
463
- # When false, skips interpolation entirely.
464
- # @return [Swarm, Workflow] Configured swarm or workflow instance
465
- # @raise [ConfigurationError] If YAML is invalid or configuration is incorrect
466
- #
467
- # @example Load from YAML string
468
- # yaml = <<~YAML
469
- # version: 2
470
- # swarm:
471
- # name: "Dev Team"
472
- # lead: backend
473
- # agents:
474
- # backend:
475
- # description: "Backend developer"
476
- # model: "gpt-4"
477
- # agent_file: "agents/backend.md" # Resolved relative to base_dir
478
- # YAML
479
- #
480
- # swarm = SwarmSDK.load(yaml, base_dir: "/path/to/project")
481
- # result = swarm.execute("Build authentication")
482
- #
483
- # @example Load with default base_dir (Dir.pwd)
484
- # yaml = File.read("config.yml")
485
- # swarm = SwarmSDK.load(yaml) # base_dir defaults to Dir.pwd
486
- #
487
- # @example Load without environment variable interpolation
488
- # swarm = SwarmSDK.load(yaml, env_interpolation: false)
489
- def load(yaml_content, base_dir: Dir.pwd, allow_filesystem_tools: nil, env_interpolation: nil)
490
- config = Configuration.new(yaml_content, base_dir: base_dir, env_interpolation: env_interpolation)
491
- config.load_and_validate
492
- swarm = config.to_swarm(allow_filesystem_tools: allow_filesystem_tools)
493
-
494
- # Apply hooks if any are configured (YAML-only feature)
495
- if hooks_configured?(config)
496
- Hooks::Adapter.apply_hooks(swarm, config)
497
- end
498
-
499
- # Store config reference for agent hooks (applied during initialize_agents)
500
- swarm.config_for_hooks = config
501
-
502
- swarm
503
- end
504
-
505
- # Load swarm from YAML file (convenience method)
506
- #
507
- # Reads the YAML file and uses the file's directory as the base directory
508
- # for resolving agent file paths. This is the recommended method for
509
- # loading swarms from configuration files.
510
- #
511
- # @param path [String, Pathname] Path to YAML configuration file
512
- # @param allow_filesystem_tools [Boolean, nil] Whether to allow filesystem tools (nil uses global setting)
513
- # @param env_interpolation [Boolean, nil] Whether to interpolate environment variables.
514
- # When nil, uses the global SwarmSDK.config.env_interpolation setting.
515
- # When true, interpolates ${VAR} and ${VAR:=default} patterns.
516
- # When false, skips interpolation entirely.
517
- # @return [Swarm, Workflow] Configured swarm or workflow instance
518
- # @raise [ConfigurationError] If file not found or configuration invalid
519
- #
520
- # @example
521
- # swarm = SwarmSDK.load_file("config.yml")
522
- # result = swarm.execute("Build authentication")
523
- #
524
- # @example With absolute path
525
- # swarm = SwarmSDK.load_file("/absolute/path/config.yml")
526
- #
527
- # @example Load without environment variable interpolation
528
- # swarm = SwarmSDK.load_file("config.yml", env_interpolation: false)
529
- def load_file(path, allow_filesystem_tools: nil, env_interpolation: nil)
530
- config = Configuration.load_file(path, env_interpolation: env_interpolation)
531
- swarm = config.to_swarm(allow_filesystem_tools: allow_filesystem_tools)
532
-
533
- # Apply hooks if any are configured (YAML-only feature)
534
- if hooks_configured?(config)
535
- Hooks::Adapter.apply_hooks(swarm, config)
536
- end
537
-
538
- # Store config reference for agent hooks (applied during initialize_agents)
539
- swarm.config_for_hooks = config
540
-
541
- swarm
542
- end
543
-
544
- private
545
-
546
- # Check if hooks are configured in the configuration
547
- #
548
- # @param config [Configuration] Configuration instance
549
- # @return [Boolean] true if any hooks are configured
550
- def hooks_configured?(config)
551
- config.swarm_hooks.any? ||
552
- config.all_agents_hooks.any? ||
553
- config.agents.any? { |_, agent_config| agent_config[:hooks]&.any? }
554
- end
555
-
556
- # Parse configuration error and extract structured information
557
- #
558
- # Attempts to extract field path and agent name from error messages.
559
- # Returns a structured error hash with type, field, message, and optional agent.
560
- #
561
- # @param error [StandardError] The caught error
562
- # @return [Hash] Structured error hash
563
- def parse_configuration_error(error)
564
- message = error.message
565
- error_hash = { message: message }
566
-
567
- # Detect error type and extract field information
568
- case message
569
- # YAML syntax errors
570
- when /Invalid YAML syntax/i
571
- error_hash.merge!(
572
- type: :syntax_error,
573
- field: nil,
574
- )
575
-
576
- # Missing version field
577
- when /Missing 'version' field/i
578
- error_hash.merge!(
579
- type: :missing_field,
580
- field: "version",
581
- )
582
-
583
- # Invalid version
584
- when /SwarmSDK requires version: (\d+)/i
585
- error_hash.merge!(
586
- type: :invalid_value,
587
- field: "version",
588
- )
589
-
590
- # Missing swarm fields
591
- when /Missing '(\w+)' field in swarm configuration/i
592
- field_name = Regexp.last_match(1)
593
- error_hash.merge!(
594
- type: :missing_field,
595
- field: "swarm.#{field_name}",
596
- )
597
-
598
- # Agent missing required field
599
- when /Agent '([^']+)' missing required '([^']+)' field/i
600
- agent_name = Regexp.last_match(1)
601
- field_name = Regexp.last_match(2)
602
- error_hash.merge!(
603
- type: :missing_field,
604
- field: "swarm.agents.#{agent_name}.#{field_name}",
605
- agent: agent_name,
606
- )
607
-
608
- # Directory does not exist
609
- when /Directory '([^']+)' for agent '([^']+)' does not exist/i
610
- agent_name = Regexp.last_match(2)
611
- error_hash.merge!(
612
- type: :directory_not_found,
613
- field: "swarm.agents.#{agent_name}.directory",
614
- agent: agent_name,
615
- )
616
-
617
- # Error loading agent from file (must come before "Agent file not found")
618
- when /Error loading agent '([^']+)' from file/i
619
- agent_name = Regexp.last_match(1)
620
- error_hash.merge!(
621
- type: :file_load_error,
622
- field: "swarm.agents.#{agent_name}.agent_file",
623
- agent: agent_name,
624
- )
625
-
626
- # Agent file not found
627
- when /Agent file not found: (.+)/i
628
- # Try to extract agent name from the error context if available
629
- error_hash.merge!(
630
- type: :file_not_found,
631
- field: nil, # We don't know which agent without more context
632
- )
633
-
634
- # Lead agent not found
635
- when /Lead agent '([^']+)' not found in agents/i
636
- error_hash.merge!(
637
- type: :invalid_reference,
638
- field: "swarm.lead",
639
- )
640
-
641
- # Unknown agent in connections (old format)
642
- when /Agent '([^']+)' has connection to unknown agent '([^']+)'/i
643
- agent_name = Regexp.last_match(1)
644
- error_hash.merge!(
645
- type: :invalid_reference,
646
- field: "swarm.agents.#{agent_name}.delegates_to",
647
- agent: agent_name,
648
- )
649
-
650
- # Unknown agent in connections (new format with composable swarms)
651
- when /Agent '([^']+)' delegates to unknown target '([^']+)'/i
652
- agent_name = Regexp.last_match(1)
653
- error_hash.merge!(
654
- type: :invalid_reference,
655
- field: "swarm.agents.#{agent_name}.delegates_to",
656
- agent: agent_name,
657
- )
658
-
659
- # Circular dependency
660
- when /Circular dependency detected/i
661
- error_hash.merge!(
662
- type: :circular_dependency,
663
- field: nil,
664
- )
665
-
666
- # Configuration file not found
667
- when /Configuration file not found/i
668
- error_hash.merge!(
669
- type: :file_not_found,
670
- field: nil,
671
- )
672
-
673
- # Invalid hook event
674
- when /Invalid hook event '([^']+)' for agent '([^']+)'/i
675
- agent_name = Regexp.last_match(2)
676
- error_hash.merge!(
677
- type: :invalid_value,
678
- field: "swarm.agents.#{agent_name}.hooks",
679
- agent: agent_name,
680
- )
681
-
682
- # api_version validation error
683
- when /Agent '([^']+)' has api_version set, but provider is/i
684
- agent_name = Regexp.last_match(1)
685
- error_hash.merge!(
686
- type: :invalid_value,
687
- field: "swarm.agents.#{agent_name}.api_version",
688
- agent: agent_name,
689
- )
690
-
691
- # api_version invalid value
692
- when /Agent '([^']+)' has invalid api_version/i
693
- agent_name = Regexp.last_match(1)
694
- error_hash.merge!(
695
- type: :invalid_value,
696
- field: "swarm.agents.#{agent_name}.api_version",
697
- agent: agent_name,
698
- )
699
-
700
- # No agents defined
701
- when /No agents defined/i
702
- error_hash.merge!(
703
- type: :missing_field,
704
- field: "swarm.agents",
705
- )
706
-
707
- # Default: unknown error
708
- else
709
- error_hash.merge!(
710
- type: :validation_error,
711
- field: nil,
712
- )
713
- end
714
-
715
- error_hash.compact
716
- end
717
- end
718
- end