swarm_sdk 2.7.14 → 3.0.0.alpha2

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