swarm_sdk 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_sdk/agent/builder.rb +58 -0
  3. data/lib/swarm_sdk/agent/chat.rb +527 -1059
  4. data/lib/swarm_sdk/agent/{chat → chat_helpers}/context_tracker.rb +9 -88
  5. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +204 -0
  6. data/lib/swarm_sdk/agent/{chat → chat_helpers}/hook_integration.rb +111 -44
  7. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +78 -0
  8. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +233 -0
  9. data/lib/swarm_sdk/agent/{chat → chat_helpers}/logging_helpers.rb +1 -1
  10. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +83 -0
  11. data/lib/swarm_sdk/agent/{chat → chat_helpers}/system_reminder_injector.rb +12 -12
  12. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +79 -0
  13. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +98 -0
  14. data/lib/swarm_sdk/agent/context.rb +2 -2
  15. data/lib/swarm_sdk/agent/definition.rb +66 -154
  16. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +4 -2
  17. data/lib/swarm_sdk/agent/system_prompt_builder.rb +161 -0
  18. data/lib/swarm_sdk/builders/base_builder.rb +409 -0
  19. data/lib/swarm_sdk/concerns/cleanupable.rb +39 -0
  20. data/lib/swarm_sdk/concerns/snapshotable.rb +67 -0
  21. data/lib/swarm_sdk/concerns/validatable.rb +55 -0
  22. data/lib/swarm_sdk/configuration/parser.rb +353 -0
  23. data/lib/swarm_sdk/configuration/translator.rb +255 -0
  24. data/lib/swarm_sdk/configuration.rb +65 -543
  25. data/lib/swarm_sdk/context_compactor/token_counter.rb +3 -3
  26. data/lib/swarm_sdk/context_compactor.rb +6 -11
  27. data/lib/swarm_sdk/context_management/builder.rb +128 -0
  28. data/lib/swarm_sdk/context_management/context.rb +328 -0
  29. data/lib/swarm_sdk/defaults.rb +196 -0
  30. data/lib/swarm_sdk/events_to_messages.rb +18 -0
  31. data/lib/swarm_sdk/hooks/shell_executor.rb +2 -1
  32. data/lib/swarm_sdk/log_collector.rb +179 -29
  33. data/lib/swarm_sdk/log_stream.rb +29 -0
  34. data/lib/swarm_sdk/node_context.rb +1 -1
  35. data/lib/swarm_sdk/observer/builder.rb +81 -0
  36. data/lib/swarm_sdk/observer/config.rb +45 -0
  37. data/lib/swarm_sdk/observer/manager.rb +236 -0
  38. data/lib/swarm_sdk/patterns/agent_observer.rb +160 -0
  39. data/lib/swarm_sdk/plugin.rb +93 -3
  40. data/lib/swarm_sdk/snapshot.rb +6 -6
  41. data/lib/swarm_sdk/snapshot_from_events.rb +13 -2
  42. data/lib/swarm_sdk/state_restorer.rb +136 -151
  43. data/lib/swarm_sdk/state_snapshot.rb +65 -100
  44. data/lib/swarm_sdk/swarm/agent_initializer.rb +180 -136
  45. data/lib/swarm_sdk/swarm/builder.rb +44 -578
  46. data/lib/swarm_sdk/swarm/executor.rb +213 -0
  47. data/lib/swarm_sdk/swarm/hook_triggers.rb +150 -0
  48. data/lib/swarm_sdk/swarm/logging_callbacks.rb +340 -0
  49. data/lib/swarm_sdk/swarm/mcp_configurator.rb +7 -4
  50. data/lib/swarm_sdk/swarm/tool_configurator.rb +42 -138
  51. data/lib/swarm_sdk/swarm.rb +137 -679
  52. data/lib/swarm_sdk/tools/bash.rb +11 -3
  53. data/lib/swarm_sdk/tools/delegate.rb +61 -43
  54. data/lib/swarm_sdk/tools/edit.rb +8 -13
  55. data/lib/swarm_sdk/tools/glob.rb +9 -1
  56. data/lib/swarm_sdk/tools/grep.rb +7 -0
  57. data/lib/swarm_sdk/tools/multi_edit.rb +15 -11
  58. data/lib/swarm_sdk/tools/path_resolver.rb +51 -2
  59. data/lib/swarm_sdk/tools/read.rb +11 -13
  60. data/lib/swarm_sdk/tools/registry.rb +122 -10
  61. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +8 -5
  62. data/lib/swarm_sdk/tools/stores/storage.rb +0 -6
  63. data/lib/swarm_sdk/tools/todo_write.rb +7 -0
  64. data/lib/swarm_sdk/tools/web_fetch.rb +3 -2
  65. data/lib/swarm_sdk/tools/write.rb +8 -13
  66. data/lib/swarm_sdk/version.rb +1 -1
  67. data/lib/swarm_sdk/{node → workflow}/agent_config.rb +1 -1
  68. data/lib/swarm_sdk/workflow/builder.rb +143 -0
  69. data/lib/swarm_sdk/workflow/executor.rb +497 -0
  70. data/lib/swarm_sdk/{node/builder.rb → workflow/node_builder.rb} +3 -3
  71. data/lib/swarm_sdk/{node → workflow}/transformer_executor.rb +3 -2
  72. data/lib/swarm_sdk/{node_orchestrator.rb → workflow.rb} +152 -456
  73. data/lib/swarm_sdk.rb +33 -3
  74. metadata +67 -15
  75. data/lib/swarm_sdk/providers/openai_with_responses.rb +0 -589
@@ -40,7 +40,7 @@ module SwarmSDK
40
40
  # Fetch tools from MCP server and register with chat
41
41
  # Tools are already in RubyLLM::Tool format
42
42
  tools = client.tools
43
- tools.each { |tool| chat.with_tool(tool) }
43
+ tools.each { |tool| chat.add_tool(tool) }
44
44
 
45
45
  RubyLLM.logger.debug("SwarmSDK: Registered #{tools.size} tools from MCP server '#{server_config[:name]}' for agent #{agent_name}")
46
46
  rescue StandardError => e
@@ -138,13 +138,16 @@ module SwarmSDK
138
138
  # @param config [Hash] MCP server configuration
139
139
  # @return [Hash] Streamable configuration
140
140
  def build_streamable_config(config)
141
- {
141
+ streamable_config = {
142
142
  url: config[:url],
143
143
  headers: config[:headers] || {},
144
144
  version: config[:version]&.to_sym || :http2,
145
- oauth: config[:oauth],
146
- rate_limit: config[:rate_limit],
147
145
  }
146
+
147
+ # Only include rate_limit if present
148
+ streamable_config[:rate_limit] = config[:rate_limit] if config[:rate_limit]
149
+
150
+ streamable_config
148
151
  end
149
152
  end
150
153
  end
@@ -57,6 +57,9 @@ module SwarmSDK
57
57
 
58
58
  # Create a tool instance by name
59
59
  #
60
+ # Uses the Registry factory pattern to instantiate tools based on their
61
+ # declared requirements. This eliminates the need for a giant case statement.
62
+ #
60
63
  # File tools and TodoWrite require agent context for tracking state.
61
64
  # Scratchpad tools require shared scratchpad instance.
62
65
  # Plugin tools are delegated to their respective plugins.
@@ -77,47 +80,14 @@ module SwarmSDK
77
80
  return create_plugin_tool(tool_name_sym, agent_name, directory, chat, agent_definition)
78
81
  end
79
82
 
80
- case tool_name_sym
81
- when :Read
82
- Tools::Read.new(agent_name: agent_name, directory: directory)
83
- when :Write
84
- Tools::Write.new(agent_name: agent_name, directory: directory)
85
- when :Edit
86
- Tools::Edit.new(agent_name: agent_name, directory: directory)
87
- when :MultiEdit
88
- Tools::MultiEdit.new(agent_name: agent_name, directory: directory)
89
- when :Bash
90
- Tools::Bash.new(directory: directory)
91
- when :Glob
92
- Tools::Glob.new(directory: directory)
93
- when :Grep
94
- Tools::Grep.new(directory: directory)
95
- when :TodoWrite
96
- Tools::TodoWrite.new(agent_name: agent_name) # TodoWrite doesn't need directory
97
- when :ScratchpadWrite
98
- Tools::Scratchpad::ScratchpadWrite.create_for_scratchpad(@scratchpad_storage)
99
- when :ScratchpadRead
100
- Tools::Scratchpad::ScratchpadRead.create_for_scratchpad(@scratchpad_storage)
101
- when :ScratchpadList
102
- Tools::Scratchpad::ScratchpadList.create_for_scratchpad(@scratchpad_storage)
103
- when :Think
104
- Tools::Think.new
105
- when :Clock
106
- Tools::Clock.new
107
- else
108
- # Regular tools - get class from registry and instantiate
109
- tool_class = Tools::Registry.get(tool_name_sym)
110
- raise ConfigurationError, "Unknown tool: #{tool_name}" unless tool_class
111
-
112
- # Check if tool is marked as :special but not handled in case statement
113
- if tool_class == :special
114
- raise ConfigurationError,
115
- "Tool '#{tool_name}' requires special initialization but is not handled in create_tool_instance. " \
116
- "This is a bug - #{tool_name} should be added to the case statement above."
117
- end
83
+ # Use Registry factory pattern - tools declare their own requirements
84
+ context = {
85
+ agent_name: agent_name,
86
+ directory: directory,
87
+ scratchpad_storage: @scratchpad_storage,
88
+ }
118
89
 
119
- tool_class.new
120
- end
90
+ Tools::Registry.create(tool_name_sym, context)
121
91
  end
122
92
 
123
93
  # Wrap a tool instance with permissions validator if configured
@@ -171,14 +141,8 @@ module SwarmSDK
171
141
  # Create tool instance
172
142
  tool_instance = create_tool_instance(tool_name, agent_name, agent_definition.directory)
173
143
 
174
- # Wrap with permissions validator if configured
175
- tool_instance = wrap_tool_with_permissions(
176
- tool_instance,
177
- permissions_config,
178
- agent_definition,
179
- )
180
-
181
- chat.with_tool(tool_instance)
144
+ # Wrap with permissions and add to chat
145
+ wrap_and_add_tool(chat, tool_instance, permissions_config, agent_definition)
182
146
  end
183
147
  end
184
148
 
@@ -225,19 +189,36 @@ module SwarmSDK
225
189
  return if tool_disabled?(tool_name, agent_definition.disable_default_tools)
226
190
 
227
191
  tool_instance = create_tool_instance(tool_name, agent_name, agent_definition.directory)
192
+ permissions_config = resolve_default_permissions(tool_name, agent_definition)
228
193
 
229
- # Resolve permissions for default tool
230
- permissions_config = agent_definition.agent_permissions[tool_name] ||
231
- agent_definition.default_permissions[tool_name]
194
+ wrap_and_add_tool(chat, tool_instance, permissions_config, agent_definition)
195
+ end
232
196
 
233
- # Wrap with permissions validator if configured
234
- tool_instance = wrap_tool_with_permissions(
235
- tool_instance,
236
- permissions_config,
237
- agent_definition,
238
- )
197
+ # Wrap tool with permissions and add to chat
198
+ #
199
+ # This is the common pattern for registering tools:
200
+ # 1. Wrap with permissions validator (if configured)
201
+ # 2. Add to chat
202
+ #
203
+ # @param chat [Agent::Chat] The chat instance
204
+ # @param tool_instance [RubyLLM::Tool] Tool instance
205
+ # @param permissions_config [Hash, nil] Permissions configuration
206
+ # @param agent_definition [Agent::Definition] Agent definition
207
+ # @return [void]
208
+ def wrap_and_add_tool(chat, tool_instance, permissions_config, agent_definition)
209
+ tool_instance = wrap_tool_with_permissions(tool_instance, permissions_config, agent_definition)
210
+ chat.add_tool(tool_instance)
211
+ end
239
212
 
240
- chat.with_tool(tool_instance)
213
+ # Resolve permissions for a default/plugin tool
214
+ #
215
+ # Looks up permissions in agent-specific config first, falls back to global defaults.
216
+ #
217
+ # @param tool_name [Symbol] Tool name
218
+ # @param agent_definition [Agent::Definition] Agent definition
219
+ # @return [Hash, nil] Permissions configuration or nil
220
+ def resolve_default_permissions(tool_name, agent_definition)
221
+ agent_definition.agent_permissions[tool_name] || agent_definition.default_permissions[tool_name]
241
222
  end
242
223
 
243
224
  # Create a tool instance via plugin
@@ -288,10 +269,6 @@ module SwarmSDK
288
269
  # Check if plugin has storage enabled for this agent
289
270
  next unless plugin.storage_enabled?(agent_definition)
290
271
 
291
- # Get plugin storage for this agent
292
- plugin_storages = @plugin_storages[plugin.name] || {}
293
- plugin_storages[agent_name]
294
-
295
272
  # Register each tool provided by the plugin
296
273
  plugin.tools.each do |tool_name|
297
274
  # Skip if already registered explicitly
@@ -308,18 +285,9 @@ module SwarmSDK
308
285
  agent_definition: agent_definition,
309
286
  )
310
287
 
311
- # Resolve permissions for plugin tool
312
- permissions_config = agent_definition.agent_permissions[tool_name] ||
313
- agent_definition.default_permissions[tool_name]
288
+ permissions_config = resolve_default_permissions(tool_name, agent_definition)
314
289
 
315
- # Wrap with permissions validator if configured
316
- tool_instance = wrap_tool_with_permissions(
317
- tool_instance,
318
- permissions_config,
319
- agent_definition,
320
- )
321
-
322
- chat.with_tool(tool_instance)
290
+ wrap_and_add_tool(chat, tool_instance, permissions_config, agent_definition)
323
291
  end
324
292
  end
325
293
  end
@@ -379,76 +347,12 @@ module SwarmSDK
379
347
  delegate_chat: delegate_agent,
380
348
  agent_name: agent_name,
381
349
  swarm: @swarm,
382
- hook_registry: @hook_registry,
383
350
  delegating_chat: chat,
384
351
  )
385
352
 
386
- chat.with_tool(tool)
353
+ chat.add_tool(tool)
387
354
  end
388
355
  end
389
-
390
- # Pass 4: Configure hook system
391
- #
392
- # Setup the callback system for each agent.
393
- def pass_4_configure_hooks
394
- @agents.each do |agent_name, chat|
395
- agent_definition = @agent_definitions[agent_name]
396
-
397
- chat.setup_hooks(
398
- registry: @hook_registry,
399
- agent_definition: agent_definition,
400
- swarm: @swarm,
401
- ) if chat.respond_to?(:setup_hooks)
402
- end
403
- end
404
-
405
- # Pass 5: Apply YAML hooks if present
406
- #
407
- # If loaded from YAML, apply agent-specific hooks.
408
- def pass_5_apply_yaml_hooks
409
- return unless @config_for_hooks
410
-
411
- @agents.each do |agent_name, chat|
412
- agent_def = @config_for_hooks.agents[agent_name]
413
- next unless agent_def&.hooks
414
-
415
- HooksAdapter.apply_agent_hooks(chat, agent_name, agent_def.hooks, @swarm.name)
416
- end
417
- end
418
-
419
- # Create an AgentChat instance
420
- #
421
- # NOTE: This is dead code, left over from refactoring. AgentInitializer
422
- # now handles agent creation. This should be removed in a cleanup pass.
423
- #
424
- # @param agent_name [Symbol] Agent name
425
- # @param agent_definition [AgentDefinition] Agent definition
426
- # @param tool_configurator [ToolConfigurator] Tool configurator
427
- # @return [AgentChat] Configured chat instance
428
- def create_agent_chat(agent_name, agent_definition, tool_configurator)
429
- chat = AgentChat.new(
430
- definition: agent_definition.to_h,
431
- global_semaphore: @global_semaphore,
432
- )
433
-
434
- # Set agent name on provider for logging (if provider supports it)
435
- chat.provider.agent_name = agent_name if chat.provider.respond_to?(:agent_name=)
436
-
437
- # Register tools
438
- tool_configurator.register_all_tools(
439
- chat: chat,
440
- agent_name: agent_name,
441
- agent_definition: agent_definition,
442
- )
443
-
444
- # Register MCP servers if any
445
- if agent_definition.mcp_servers.any?
446
- mcp_configurator = McpConfigurator.new(@swarm)
447
- mcp_configurator.register_mcp_servers(chat, agent_definition.mcp_servers, agent_name: agent_name)
448
- end
449
-
450
- chat
451
- end
452
356
  end
453
357
  end
454
358
  end