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
@@ -1,146 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- # Global registry for reusable agent definitions
5
- #
6
- # AgentRegistry allows declaring agents in separate files that can be
7
- # referenced by name in swarm definitions. This promotes code reuse and
8
- # separation of concerns - agent definitions can live in dedicated files
9
- # while swarm configurations compose them together.
10
- #
11
- # ## Usage
12
- #
13
- # Register agents globally (typically in separate files):
14
- #
15
- # # agents/backend.rb
16
- # SwarmSDK.agent :backend do
17
- # model "claude-sonnet-4"
18
- # description "Backend API developer"
19
- # system_prompt "You build REST APIs"
20
- # tools :Read, :Edit, :Bash
21
- # end
22
- #
23
- # Reference registered agents in swarm definitions:
24
- #
25
- # # swarm.rb
26
- # SwarmSDK.build do
27
- # name "Dev Team"
28
- # lead :backend
29
- #
30
- # agent :backend # Pulls from registry
31
- # end
32
- #
33
- # ## Override Support
34
- #
35
- # Registered agents can be extended with additional configuration:
36
- #
37
- # SwarmSDK.build do
38
- # name "Dev Team"
39
- # lead :backend
40
- #
41
- # agent :backend do
42
- # # Registry config is applied first, then this block
43
- # tools :CustomTool # Adds to tools from registry
44
- # delegates_to :database
45
- # end
46
- # end
47
- #
48
- # @note This registry is not thread-safe. In multi-threaded environments,
49
- # register all agents before spawning threads, or synchronize access
50
- # externally. For typical fiber-based async usage (the default in SwarmSDK),
51
- # this is not a concern.
52
- #
53
- class AgentRegistry
54
- @agents = {}
55
-
56
- class << self
57
- # Register an agent definition block
58
- #
59
- # Stores a configuration block that will be executed when the agent
60
- # is referenced in a swarm definition. The block receives an
61
- # Agent::Builder context and can use all builder DSL methods.
62
- #
63
- # @param name [Symbol, String] Agent name (will be symbolized)
64
- # @yield Agent configuration block using Agent::Builder DSL
65
- # @return [void]
66
- # @raise [ArgumentError] If no block is provided
67
- # @raise [ArgumentError] If agent with same name is already registered
68
- #
69
- # @example Register a backend agent
70
- # SwarmSDK::AgentRegistry.register(:backend) do
71
- # model "claude-sonnet-4"
72
- # description "Backend developer"
73
- # tools :Read, :Edit, :Bash
74
- # end
75
- #
76
- # @example Register with MCP servers
77
- # SwarmSDK::AgentRegistry.register(:filesystem_agent) do
78
- # model "gpt-4"
79
- # description "File manager"
80
- # mcp_server :fs, type: :stdio, command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem"]
81
- # end
82
- def register(name, &block)
83
- raise ArgumentError, "Block required for agent registration" unless block_given?
84
-
85
- sym_name = name.to_sym
86
- if @agents.key?(sym_name)
87
- raise ArgumentError,
88
- "Agent '#{sym_name}' is already registered. " \
89
- "Use SwarmSDK.clear_agent_registry! to reset, or choose a different name."
90
- end
91
-
92
- @agents[sym_name] = block
93
- end
94
-
95
- # Retrieve a registered agent block
96
- #
97
- # @param name [Symbol, String] Agent name
98
- # @return [Proc, nil] The registration block or nil if not found
99
- #
100
- # @example
101
- # block = SwarmSDK::AgentRegistry.get(:backend)
102
- # builder.instance_eval(&block) if block
103
- def get(name)
104
- @agents[name.to_sym]
105
- end
106
-
107
- # Check if an agent is registered
108
- #
109
- # @param name [Symbol, String] Agent name
110
- # @return [Boolean] true if agent is registered
111
- #
112
- # @example
113
- # if SwarmSDK::AgentRegistry.registered?(:backend)
114
- # puts "Backend agent is available"
115
- # end
116
- def registered?(name)
117
- @agents.key?(name.to_sym)
118
- end
119
-
120
- # List all registered agent names
121
- #
122
- # @return [Array<Symbol>] Names of all registered agents
123
- #
124
- # @example
125
- # SwarmSDK::AgentRegistry.names
126
- # # => [:backend, :frontend, :database]
127
- def names
128
- @agents.keys
129
- end
130
-
131
- # Clear all registrations
132
- #
133
- # Primarily useful for testing to ensure clean state between tests.
134
- #
135
- # @return [void]
136
- #
137
- # @example In test setup/teardown
138
- # def teardown
139
- # SwarmSDK::AgentRegistry.clear
140
- # end
141
- def clear
142
- @agents.clear
143
- end
144
- end
145
- end
146
- end
@@ -1,553 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Builders
5
- # Base builder with shared DSL methods for Swarm and Workflow builders
6
- #
7
- # Provides common functionality:
8
- # - Basic configuration (name, id, scratchpad)
9
- # - Agent definition (inline DSL, markdown files, with overrides)
10
- # - All agents configuration
11
- # - External swarms registry
12
- # - Validation helpers
13
- # - Merging logic
14
- #
15
- # Subclasses must implement:
16
- # - build_swarm - Build and return the appropriate instance
17
- # - Type-specific DSL methods (lead for Swarm, node/start_node for Workflow)
18
- #
19
- class BaseBuilder
20
- def initialize(allow_filesystem_tools: nil)
21
- @swarm_id = nil
22
- @swarm_name = nil
23
- @agents = {}
24
- @all_agents_config = nil
25
- @swarm_registry_config = []
26
- @scratchpad = :disabled
27
- @allow_filesystem_tools = allow_filesystem_tools
28
- end
29
-
30
- # Set swarm ID
31
- #
32
- # @param swarm_id [String] Unique identifier for this swarm/workflow
33
- def id(swarm_id)
34
- @swarm_id = swarm_id
35
- end
36
-
37
- # Set swarm/workflow name
38
- def name(swarm_name)
39
- @swarm_name = swarm_name
40
- end
41
-
42
- # Configure scratchpad mode
43
- #
44
- # For Workflow: :enabled (shared across nodes), :per_node (isolated), or :disabled
45
- # For Swarm: :enabled or :disabled
46
- #
47
- # @param mode [Symbol, Boolean] Scratchpad mode
48
- def scratchpad(mode)
49
- @scratchpad = mode
50
- end
51
-
52
- # Register external swarms for composable swarms
53
- #
54
- # @example
55
- # swarms do
56
- # register "code_review", file: "./swarms/code_review.rb"
57
- # register "testing", file: "./swarms/testing.yml", keep_context: false
58
- # end
59
- #
60
- # @yield Block containing register() calls
61
- def swarms(&block)
62
- builder = Swarm::SwarmRegistryBuilder.new
63
- builder.instance_eval(&block)
64
- @swarm_registry_config = builder.registrations
65
- end
66
-
67
- # Define an agent with fluent API, load from markdown, or reference registry
68
- #
69
- # Supports multiple forms:
70
- # 1. Registry lookup: agent :name (pulls from global registry)
71
- # 2. Registry + overrides: agent :name do ... end (when registered)
72
- # 3. Inline DSL: agent :name do ... end (when not registered)
73
- # 4. Markdown content: agent :name, <<~MD ... MD
74
- # 5. Markdown + overrides: agent :name, <<~MD do ... end
75
- #
76
- # @example Inline DSL
77
- # agent :backend do
78
- # model "gpt-5"
79
- # system_prompt "You build APIs"
80
- # tools :Read, :Write
81
- # end
82
- #
83
- # @example Registry lookup (agent must be registered with SwarmSDK.agent)
84
- # agent :backend # Pulls configuration from registry
85
- #
86
- # @example Registry + overrides
87
- # agent :backend do
88
- # # Base config from registry, then apply overrides
89
- # tools :CustomTool # Adds to registry-defined tools
90
- # end
91
- #
92
- # @example Markdown content
93
- # agent :backend, <<~MD
94
- # ---
95
- # description: "Backend developer"
96
- # model: "gpt-4"
97
- # ---
98
- #
99
- # You build APIs.
100
- # MD
101
- def agent(name, content = nil, &block)
102
- name = name.to_sym
103
-
104
- # Case 1: agent :name, <<~MD do ... end (markdown + overrides)
105
- if content.is_a?(String) && block_given? && markdown_content?(content)
106
- load_agent_from_markdown_with_overrides(content, name, &block)
107
-
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
-
112
- # Case 3: agent :name (registry lookup only - no content, no block)
113
- elsif content.nil? && !block_given?
114
- load_agent_from_registry(name)
115
-
116
- # Case 4: agent :name do ... end (with registered agent - registry + overrides)
117
- elsif content.nil? && block_given? && AgentRegistry.registered?(name)
118
- load_agent_from_registry_with_overrides(name, &block)
119
-
120
- # Case 5: agent :name do ... end (inline DSL - not registered)
121
- elsif block_given?
122
- builder = Agent::Builder.new(name)
123
- builder.instance_eval(&block)
124
- @agents[name] = builder
125
-
126
- else
127
- raise ArgumentError,
128
- "Invalid agent definition for '#{name}'. Use:\n " \
129
- "agent :#{name} { ... } # Inline DSL\n " \
130
- "agent :#{name} # Registry lookup\n " \
131
- "agent :#{name} { ... } # Registry + overrides (if registered)\n " \
132
- "agent :#{name}, <<~MD ... MD # Markdown\n " \
133
- "agent :#{name}, <<~MD do ... end # Markdown + overrides"
134
- end
135
- end
136
-
137
- # Configure all agents with a block
138
- #
139
- # @example
140
- # all_agents do
141
- # tools :Read, :Write
142
- #
143
- # hook :pre_tool_use, matcher: "Write" do |ctx|
144
- # # Validation for all agents
145
- # end
146
- # end
147
- def all_agents(&block)
148
- builder = Swarm::AllAgentsBuilder.new
149
- builder.instance_eval(&block)
150
- @all_agents_config = builder
151
- end
152
-
153
- # Build the actual Swarm or Workflow instance
154
- #
155
- # Subclasses must implement this method.
156
- #
157
- # @return [Swarm, Workflow] Configured instance
158
- def build_swarm
159
- raise NotImplementedError, "#{self.class} must implement #build_swarm"
160
- end
161
-
162
- protected
163
-
164
- # Check if a string is markdown content (has frontmatter)
165
- #
166
- # @param str [String] String to check
167
- # @return [Boolean] true if string contains markdown frontmatter
168
- def markdown_content?(str)
169
- str.start_with?("---") || str.include?("\n---\n")
170
- end
171
-
172
- # Load an agent from the global registry
173
- #
174
- # Retrieves the registered agent block and executes it in the context
175
- # of a new Agent::Builder.
176
- #
177
- # @param name [Symbol] Agent name
178
- # @return [void]
179
- # @raise [ConfigurationError] If agent is not registered
180
- #
181
- # @example
182
- # load_agent_from_registry(:backend)
183
- def load_agent_from_registry(name)
184
- registered_proc = AgentRegistry.get(name)
185
- unless registered_proc
186
- raise ConfigurationError,
187
- "Agent '#{name}' not found in registry. " \
188
- "Either define inline with `agent :#{name} do ... end` or " \
189
- "register globally with `SwarmSDK.agent :#{name} do ... end`"
190
- end
191
-
192
- builder = Agent::Builder.new(name)
193
- builder.instance_eval(&registered_proc)
194
- @agents[name] = builder
195
- end
196
-
197
- # Load an agent from the registry with additional overrides
198
- #
199
- # Applies the registered configuration first, then executes the
200
- # override block to customize the agent.
201
- #
202
- # @param name [Symbol] Agent name
203
- # @yield Override block with additional configuration
204
- # @return [void]
205
- #
206
- # @example
207
- # load_agent_from_registry_with_overrides(:backend) do
208
- # tools :CustomTool # Adds to registry-defined tools
209
- # end
210
- def load_agent_from_registry_with_overrides(name, &override_block)
211
- registered_proc = AgentRegistry.get(name)
212
- # Guaranteed to exist since we checked in the condition
213
-
214
- builder = Agent::Builder.new(name)
215
- builder.instance_eval(&registered_proc) # Base config from registry
216
- builder.instance_eval(&override_block) # Apply overrides
217
- @agents[name] = builder
218
- end
219
-
220
- # Load an agent from markdown content
221
- #
222
- # Returns a hash of the agent config (not a Definition yet) so that
223
- # all_agents config can be applied later in the build process.
224
- #
225
- # @param content [String] Markdown content with frontmatter
226
- # @param name_override [Symbol, nil] Optional name to override frontmatter name
227
- # @return [void]
228
- def load_agent_from_markdown(content, name_override = nil)
229
- definition = MarkdownParser.parse(content, name_override)
230
- @agents[definition.name] = { __file_config__: definition.to_h }
231
- end
232
-
233
- # Load an agent from markdown content with DSL overrides
234
- #
235
- # @param content [String] Markdown content with frontmatter
236
- # @param name_override [Symbol, nil] Optional name to override frontmatter name
237
- # @yield Block with DSL overrides
238
- # @return [void]
239
- def load_agent_from_markdown_with_overrides(content, name_override = nil, &block)
240
- definition = MarkdownParser.parse(content, name_override)
241
-
242
- builder = Agent::Builder.new(definition.name)
243
- apply_definition_to_builder(builder, definition.to_h)
244
- builder.instance_eval(&block)
245
-
246
- @agents[definition.name] = builder
247
- end
248
-
249
- # Apply agent definition hash to a builder
250
- #
251
- # @param builder [Agent::Builder] Builder to configure
252
- # @param config [Hash] Configuration hash from definition
253
- # @return [void]
254
- def apply_definition_to_builder(builder, config)
255
- builder.description(config[:description]) if config[:description]
256
- builder.model(config[:model]) if config[:model]
257
- builder.provider(config[:provider]) if config[:provider]
258
- builder.base_url(config[:base_url]) if config[:base_url]
259
- builder.api_version(config[:api_version]) if config[:api_version]
260
- builder.context_window(config[:context_window]) if config[:context_window]
261
- builder.system_prompt(config[:system_prompt]) if config[:system_prompt]
262
- builder.directory(config[:directory]) if config[:directory]
263
- builder.request_timeout(config[:request_timeout]) if config[:request_timeout]
264
- builder.turn_timeout(config[:turn_timeout]) if config[:turn_timeout]
265
- builder.parameters(config[:parameters]) if config[:parameters]
266
- builder.headers(config[:headers]) if config[:headers]
267
- builder.coding_agent(config[:coding_agent]) unless config[:coding_agent].nil?
268
- builder.bypass_permissions(config[:bypass_permissions]) if config[:bypass_permissions]
269
- builder.disable_default_tools(config[:disable_default_tools]) unless config[:disable_default_tools].nil?
270
-
271
- # Add tools from markdown
272
- if config[:tools]&.any?
273
- tool_names = config[:tools].map do |tool|
274
- tool.is_a?(Hash) ? tool[:name] : tool
275
- end
276
- builder.tools(*tool_names)
277
- end
278
-
279
- # Add delegates_to (handle both array and hash formats)
280
- if config[:delegates_to]&.any?
281
- delegation_config = config[:delegates_to]
282
- if delegation_config.is_a?(Hash)
283
- # Hash format: pass as single argument
284
- builder.delegates_to(delegation_config)
285
- elsif delegation_config.is_a?(Array)
286
- # Array format: splat the array
287
- builder.delegates_to(*delegation_config)
288
- end
289
- end
290
-
291
- # Add MCP servers
292
- config[:mcp_servers]&.each do |server|
293
- builder.mcp_server(server[:name], **server.except(:name))
294
- end
295
- end
296
-
297
- # Merge all_agents configuration into each agent
298
- #
299
- # All_agents values are used as defaults - agent-specific values override.
300
- # This applies to both inline DSL agents (Builder) and file-loaded agents (config hash).
301
- #
302
- # @return [void]
303
- def merge_all_agents_config_into_agents
304
- return unless @all_agents_config
305
-
306
- all_agents_hash = @all_agents_config.to_h
307
-
308
- @agents.each_value do |agent_builder_or_config|
309
- if agent_builder_or_config.is_a?(Hash) && agent_builder_or_config.key?(:__file_config__)
310
- # File-loaded agent - merge into the config hash
311
- file_config = agent_builder_or_config[:__file_config__]
312
- merged_config = merge_all_agents_into_config(all_agents_hash, file_config)
313
- agent_builder_or_config[:__file_config__] = merged_config
314
- else
315
- # Builder object (inline DSL agent)
316
- agent_builder = agent_builder_or_config
317
-
318
- apply_all_agents_defaults(agent_builder, all_agents_hash)
319
-
320
- # Merge tools (prepend all_agents tools)
321
- all_agents_tools = @all_agents_config.tools_list
322
- agent_builder.prepend_tools(*all_agents_tools) if all_agents_tools.any?
323
-
324
- # Pass all_agents permissions as default_permissions
325
- if @all_agents_config.permissions_config.any?
326
- agent_builder.default_permissions = @all_agents_config.permissions_config
327
- end
328
- end
329
- end
330
- end
331
-
332
- # Merge all_agents config into file-loaded agent config
333
- #
334
- # @param all_agents_hash [Hash] All_agents configuration
335
- # @param file_config [Hash] File-loaded agent configuration
336
- # @return [Hash] Merged configuration
337
- def merge_all_agents_into_config(all_agents_hash, file_config)
338
- merged = all_agents_hash.dup
339
-
340
- file_config.each do |key, value|
341
- case key
342
- when :tools
343
- merged[:tools] = Array(merged[:tools]) + Array(value)
344
- when :delegates_to
345
- # Handle merging delegation configs (can be array or hash)
346
- existing = merged[:delegates_to] || []
347
- new_value = value || []
348
-
349
- # Convert both to array of delegation configs for merging
350
- existing_array = normalize_delegation_array(existing)
351
- new_array = normalize_delegation_array(new_value)
352
-
353
- merged[:delegates_to] = existing_array + new_array
354
- when :parameters
355
- merged[:parameters] = (merged[:parameters] || {}).merge(value || {})
356
- when :headers
357
- merged[:headers] = (merged[:headers] || {}).merge(value || {})
358
- when :turn_timeout
359
- # Agent-specific turn_timeout overrides all_agents
360
- merged[key] = value
361
- when :request_timeout
362
- # Agent-specific request_timeout overrides all_agents
363
- merged[key] = value
364
- else
365
- merged[key] = value
366
- end
367
- end
368
-
369
- # Pass all_agents permissions as default_permissions
370
- if all_agents_hash[:permissions] && !merged[:default_permissions]
371
- merged[:default_permissions] = all_agents_hash[:permissions]
372
- end
373
-
374
- merged
375
- end
376
-
377
- # Normalize delegation config to array of hashes format
378
- #
379
- # Converts various delegation formats to normalized array for merging:
380
- # - Array of symbols: [:frontend, :backend] → [{agent: :frontend, tool_name: nil}, ...]
381
- # - Hash: {frontend: "Custom"} → [{agent: :frontend, tool_name: "Custom"}, ...]
382
- # - Array of hashes: [{agent: :frontend, tool_name: "Custom"}] → unchanged
383
- #
384
- # @param delegation_config [Array, Hash] Delegation configuration
385
- # @return [Array<Hash>] Normalized array of {agent:, tool_name:} hashes
386
- def normalize_delegation_array(delegation_config)
387
- return [] if delegation_config.nil? || (delegation_config.respond_to?(:empty?) && delegation_config.empty?)
388
-
389
- case delegation_config
390
- when Array
391
- delegation_config.map do |item|
392
- case item
393
- when Symbol, String
394
- { agent: item.to_sym, tool_name: nil }
395
- when Hash
396
- item.key?(:agent) ? item : item.map { |agent, tool_name| { agent: agent.to_sym, tool_name: tool_name } }
397
- end
398
- end.flatten
399
- when Hash
400
- delegation_config.map { |agent, tool_name| { agent: agent.to_sym, tool_name: tool_name } }
401
- else
402
- []
403
- end
404
- end
405
-
406
- # Apply all_agents defaults to an agent builder
407
- #
408
- # @param agent_builder [Agent::Builder] The agent builder to configure
409
- # @param all_agents_hash [Hash] All_agents configuration
410
- # @return [void]
411
- def apply_all_agents_defaults(agent_builder, all_agents_hash)
412
- if all_agents_hash[:model] && !agent_builder.model_set?
413
- agent_builder.model(all_agents_hash[:model])
414
- end
415
-
416
- if all_agents_hash[:provider] && !agent_builder.provider_set?
417
- agent_builder.provider(all_agents_hash[:provider])
418
- end
419
-
420
- if all_agents_hash[:base_url] && !agent_builder.base_url_set?
421
- agent_builder.base_url(all_agents_hash[:base_url])
422
- end
423
-
424
- if all_agents_hash[:api_version] && !agent_builder.api_version_set?
425
- agent_builder.api_version(all_agents_hash[:api_version])
426
- end
427
-
428
- if all_agents_hash[:request_timeout] && !agent_builder.request_timeout_set?
429
- agent_builder.request_timeout(all_agents_hash[:request_timeout])
430
- end
431
-
432
- if all_agents_hash[:turn_timeout] && !agent_builder.turn_timeout_set?
433
- agent_builder.turn_timeout(all_agents_hash[:turn_timeout])
434
- end
435
-
436
- if all_agents_hash[:parameters]
437
- merged_params = all_agents_hash[:parameters].merge(agent_builder.parameters)
438
- agent_builder.parameters(merged_params)
439
- end
440
-
441
- if all_agents_hash[:headers]
442
- merged_headers = all_agents_hash[:headers].merge(agent_builder.headers)
443
- agent_builder.headers(merged_headers)
444
- end
445
-
446
- if !all_agents_hash[:coding_agent].nil? && !agent_builder.coding_agent_set?
447
- agent_builder.coding_agent(all_agents_hash[:coding_agent])
448
- end
449
-
450
- if !all_agents_hash[:streaming].nil? && !agent_builder.streaming_set?
451
- agent_builder.streaming(all_agents_hash[:streaming])
452
- end
453
-
454
- if all_agents_hash[:thinking] && !agent_builder.thinking_set?
455
- agent_builder.thinking(**all_agents_hash[:thinking])
456
- end
457
- end
458
-
459
- # Validate all_agents filesystem tools
460
- #
461
- # @raise [ConfigurationError] If filesystem tools are disabled and all_agents has them
462
- # @return [void]
463
- def validate_all_agents_filesystem_tools
464
- resolved_setting = if @allow_filesystem_tools.nil?
465
- SwarmSDK.config.allow_filesystem_tools
466
- else
467
- @allow_filesystem_tools
468
- end
469
-
470
- return if resolved_setting
471
- return unless @all_agents_config&.tools_list&.any?
472
-
473
- forbidden = @all_agents_config.tools_list.select do |tool|
474
- SwarmSDK::Swarm::ToolConfigurator::FILESYSTEM_TOOLS.include?(tool.to_sym)
475
- end
476
-
477
- return if forbidden.empty?
478
-
479
- raise ConfigurationError,
480
- "Filesystem tools are globally disabled (SwarmSDK.config.allow_filesystem_tools = false) " \
481
- "but all_agents configuration includes: #{forbidden.join(", ")}.\n\n" \
482
- "This is a system-wide security setting that cannot be overridden by swarm configuration.\n" \
483
- "To use filesystem tools, set SwarmSDK.config.allow_filesystem_tools = true before loading the swarm."
484
- end
485
-
486
- # Validate individual agent filesystem tools
487
- #
488
- # @raise [ConfigurationError] If filesystem tools are disabled and any agent has them
489
- # @return [void]
490
- def validate_agent_filesystem_tools
491
- resolved_setting = if @allow_filesystem_tools.nil?
492
- SwarmSDK.config.allow_filesystem_tools
493
- else
494
- @allow_filesystem_tools
495
- end
496
-
497
- return if resolved_setting
498
-
499
- @agents.each do |agent_name, agent_builder_or_config|
500
- tools_list = if agent_builder_or_config.is_a?(Hash) && agent_builder_or_config.key?(:__file_config__)
501
- agent_builder_or_config[:__file_config__][:tools] || []
502
- elsif agent_builder_or_config.is_a?(Agent::Builder)
503
- agent_builder_or_config.tools_list
504
- else
505
- []
506
- end
507
-
508
- tool_names = tools_list.map do |tool|
509
- name = tool.is_a?(Hash) ? tool[:name] : tool
510
- name.to_sym
511
- end
512
-
513
- forbidden = tool_names.select do |tool|
514
- SwarmSDK::Swarm::ToolConfigurator::FILESYSTEM_TOOLS.include?(tool)
515
- end
516
-
517
- next if forbidden.empty?
518
-
519
- raise ConfigurationError,
520
- "Filesystem tools are globally disabled (SwarmSDK.config.allow_filesystem_tools = false) " \
521
- "but agent '#{agent_name}' attempts to use: #{forbidden.join(", ")}.\n\n" \
522
- "This is a system-wide security setting that cannot be overridden by swarm configuration.\n" \
523
- "To use filesystem tools, set SwarmSDK.config.allow_filesystem_tools = true before loading the swarm."
524
- end
525
- end
526
-
527
- # Build agent definitions from builders or file configs
528
- #
529
- # Handles both Agent::Builder (inline DSL) and file configs (from files).
530
- # Merges all_agents config before building.
531
- #
532
- # @return [Hash<Symbol, Agent::Definition>] Agent definitions
533
- def build_agent_definitions
534
- # Merge all_agents config first
535
- merge_all_agents_config_into_agents if @all_agents_config
536
-
537
- # Build definitions
538
- agent_definitions = {}
539
- @agents.each do |agent_name, agent_builder_or_config|
540
- agent_definitions[agent_name] = if agent_builder_or_config.is_a?(Hash) && agent_builder_or_config.key?(:__file_config__)
541
- # File-loaded agent config (with all_agents merged)
542
- Agent::Definition.new(agent_name, agent_builder_or_config[:__file_config__])
543
- else
544
- # Builder object (from inline DSL) - convert to definition
545
- agent_builder_or_config.to_definition
546
- end
547
- end
548
-
549
- agent_definitions
550
- end
551
- end
552
- end
553
- end