swarm_memory 2.1.4 → 2.1.6

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 (184) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_memory/version.rb +1 -1
  3. data/lib/swarm_memory.rb +7 -2
  4. metadata +6 -185
  5. data/lib/claude_swarm/base_executor.rb +0 -133
  6. data/lib/claude_swarm/claude_code_executor.rb +0 -349
  7. data/lib/claude_swarm/claude_mcp_server.rb +0 -78
  8. data/lib/claude_swarm/cli.rb +0 -697
  9. data/lib/claude_swarm/commands/ps.rb +0 -215
  10. data/lib/claude_swarm/commands/show.rb +0 -139
  11. data/lib/claude_swarm/configuration.rb +0 -373
  12. data/lib/claude_swarm/hooks/session_start_hook.rb +0 -42
  13. data/lib/claude_swarm/json_handler.rb +0 -91
  14. data/lib/claude_swarm/mcp_generator.rb +0 -243
  15. data/lib/claude_swarm/openai/chat_completion.rb +0 -256
  16. data/lib/claude_swarm/openai/executor.rb +0 -256
  17. data/lib/claude_swarm/openai/responses.rb +0 -319
  18. data/lib/claude_swarm/orchestrator.rb +0 -878
  19. data/lib/claude_swarm/process_tracker.rb +0 -78
  20. data/lib/claude_swarm/session_cost_calculator.rb +0 -209
  21. data/lib/claude_swarm/session_path.rb +0 -42
  22. data/lib/claude_swarm/settings_generator.rb +0 -77
  23. data/lib/claude_swarm/system_utils.rb +0 -46
  24. data/lib/claude_swarm/templates/generation_prompt.md.erb +0 -230
  25. data/lib/claude_swarm/tools/reset_session_tool.rb +0 -24
  26. data/lib/claude_swarm/tools/session_info_tool.rb +0 -24
  27. data/lib/claude_swarm/tools/task_tool.rb +0 -63
  28. data/lib/claude_swarm/version.rb +0 -5
  29. data/lib/claude_swarm/worktree_manager.rb +0 -475
  30. data/lib/claude_swarm/yaml_loader.rb +0 -22
  31. data/lib/claude_swarm.rb +0 -67
  32. data/lib/swarm_cli/cli.rb +0 -201
  33. data/lib/swarm_cli/command_registry.rb +0 -61
  34. data/lib/swarm_cli/commands/mcp_serve.rb +0 -130
  35. data/lib/swarm_cli/commands/mcp_tools.rb +0 -148
  36. data/lib/swarm_cli/commands/migrate.rb +0 -55
  37. data/lib/swarm_cli/commands/run.rb +0 -173
  38. data/lib/swarm_cli/config_loader.rb +0 -98
  39. data/lib/swarm_cli/formatters/human_formatter.rb +0 -781
  40. data/lib/swarm_cli/formatters/json_formatter.rb +0 -51
  41. data/lib/swarm_cli/interactive_repl.rb +0 -924
  42. data/lib/swarm_cli/mcp_serve_options.rb +0 -44
  43. data/lib/swarm_cli/mcp_tools_options.rb +0 -59
  44. data/lib/swarm_cli/migrate_options.rb +0 -54
  45. data/lib/swarm_cli/migrator.rb +0 -132
  46. data/lib/swarm_cli/options.rb +0 -151
  47. data/lib/swarm_cli/ui/components/agent_badge.rb +0 -33
  48. data/lib/swarm_cli/ui/components/content_block.rb +0 -120
  49. data/lib/swarm_cli/ui/components/divider.rb +0 -57
  50. data/lib/swarm_cli/ui/components/panel.rb +0 -62
  51. data/lib/swarm_cli/ui/components/usage_stats.rb +0 -70
  52. data/lib/swarm_cli/ui/formatters/cost.rb +0 -49
  53. data/lib/swarm_cli/ui/formatters/number.rb +0 -58
  54. data/lib/swarm_cli/ui/formatters/text.rb +0 -77
  55. data/lib/swarm_cli/ui/formatters/time.rb +0 -73
  56. data/lib/swarm_cli/ui/icons.rb +0 -36
  57. data/lib/swarm_cli/ui/renderers/event_renderer.rb +0 -188
  58. data/lib/swarm_cli/ui/state/agent_color_cache.rb +0 -45
  59. data/lib/swarm_cli/ui/state/depth_tracker.rb +0 -40
  60. data/lib/swarm_cli/ui/state/spinner_manager.rb +0 -170
  61. data/lib/swarm_cli/ui/state/usage_tracker.rb +0 -62
  62. data/lib/swarm_cli/version.rb +0 -5
  63. data/lib/swarm_cli.rb +0 -46
  64. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -127
  65. data/lib/swarm_sdk/agent/builder.rb +0 -552
  66. data/lib/swarm_sdk/agent/chat.rb +0 -774
  67. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -268
  68. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  69. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  70. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -78
  71. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -233
  72. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  73. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  74. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -136
  75. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  76. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -98
  77. data/lib/swarm_sdk/agent/context.rb +0 -116
  78. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  79. data/lib/swarm_sdk/agent/definition.rb +0 -477
  80. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -182
  81. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -161
  82. data/lib/swarm_sdk/builders/base_builder.rb +0 -409
  83. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  84. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -39
  85. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  86. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  87. data/lib/swarm_sdk/configuration/parser.rb +0 -353
  88. data/lib/swarm_sdk/configuration/translator.rb +0 -255
  89. data/lib/swarm_sdk/configuration.rb +0 -135
  90. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  91. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -106
  92. data/lib/swarm_sdk/context_compactor.rb +0 -335
  93. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  94. data/lib/swarm_sdk/context_management/context.rb +0 -328
  95. data/lib/swarm_sdk/defaults.rb +0 -196
  96. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  97. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  98. data/lib/swarm_sdk/hooks/context.rb +0 -197
  99. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  100. data/lib/swarm_sdk/hooks/error.rb +0 -29
  101. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  102. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  103. data/lib/swarm_sdk/hooks/result.rb +0 -150
  104. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -255
  105. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  106. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  107. data/lib/swarm_sdk/log_collector.rb +0 -227
  108. data/lib/swarm_sdk/log_stream.rb +0 -127
  109. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  110. data/lib/swarm_sdk/model_aliases.json +0 -8
  111. data/lib/swarm_sdk/models.json +0 -1
  112. data/lib/swarm_sdk/models.rb +0 -120
  113. data/lib/swarm_sdk/node_context.rb +0 -245
  114. data/lib/swarm_sdk/observer/builder.rb +0 -81
  115. data/lib/swarm_sdk/observer/config.rb +0 -45
  116. data/lib/swarm_sdk/observer/manager.rb +0 -236
  117. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  118. data/lib/swarm_sdk/permissions/config.rb +0 -239
  119. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  120. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  121. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  122. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  123. data/lib/swarm_sdk/plugin.rb +0 -309
  124. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  125. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  126. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -117
  127. data/lib/swarm_sdk/restore_result.rb +0 -65
  128. data/lib/swarm_sdk/result.rb +0 -123
  129. data/lib/swarm_sdk/snapshot.rb +0 -156
  130. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  131. data/lib/swarm_sdk/state_restorer.rb +0 -476
  132. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  133. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -683
  134. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -167
  135. data/lib/swarm_sdk/swarm/builder.rb +0 -249
  136. data/lib/swarm_sdk/swarm/executor.rb +0 -213
  137. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -150
  138. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -340
  139. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -154
  140. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  141. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -358
  142. data/lib/swarm_sdk/swarm.rb +0 -717
  143. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  144. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  145. data/lib/swarm_sdk/tools/bash.rb +0 -282
  146. data/lib/swarm_sdk/tools/clock.rb +0 -44
  147. data/lib/swarm_sdk/tools/delegate.rb +0 -267
  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 -163
  158. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  159. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  160. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  161. data/lib/swarm_sdk/tools/read.rb +0 -261
  162. data/lib/swarm_sdk/tools/registry.rb +0 -205
  163. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  164. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  165. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  166. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  167. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -272
  168. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  169. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  170. data/lib/swarm_sdk/tools/think.rb +0 -98
  171. data/lib/swarm_sdk/tools/todo_write.rb +0 -235
  172. data/lib/swarm_sdk/tools/web_fetch.rb +0 -262
  173. data/lib/swarm_sdk/tools/write.rb +0 -112
  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 -79
  178. data/lib/swarm_sdk/workflow/builder.rb +0 -143
  179. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  180. data/lib/swarm_sdk/workflow/node_builder.rb +0 -555
  181. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -249
  182. data/lib/swarm_sdk/workflow.rb +0 -554
  183. data/lib/swarm_sdk.rb +0 -524
  184. /data/lib/swarm_memory/{errors.rb → error.rb} +0 -0
@@ -1,717 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- # Swarm orchestrates multiple AI agents with shared rate limiting and coordination.
5
- #
6
- # This is the main user-facing API for SwarmSDK. Users create swarms using:
7
- # - Ruby DSL: SwarmSDK.build { ... } (Recommended)
8
- # - YAML String: SwarmSDK.load(yaml, base_dir:)
9
- # - YAML File: SwarmSDK.load_file(path)
10
- # - Direct API: Swarm.new + add_agent (Advanced)
11
- #
12
- # ## Ruby DSL (Recommended)
13
- #
14
- # swarm = SwarmSDK.build do
15
- # name "Development Team"
16
- # lead :backend
17
- #
18
- # agent :backend do
19
- # model "gpt-5"
20
- # description "Backend developer"
21
- # prompt "You build APIs"
22
- # tools :Read, :Edit, :Bash
23
- # end
24
- # end
25
- # result = swarm.execute("Build authentication")
26
- #
27
- # ## YAML String API
28
- #
29
- # yaml = File.read("swarm.yml")
30
- # swarm = SwarmSDK.load(yaml, base_dir: "/path/to/project")
31
- # result = swarm.execute("Build authentication")
32
- #
33
- # ## YAML File API (Convenience)
34
- #
35
- # swarm = SwarmSDK.load_file("swarm.yml")
36
- # result = swarm.execute("Build authentication")
37
- #
38
- # ## Direct API (Advanced)
39
- #
40
- # swarm = Swarm.new(name: "Development Team")
41
- #
42
- # backend_agent = Agent::Definition.new(:backend, {
43
- # description: "Backend developer",
44
- # model: "gpt-5",
45
- # system_prompt: "You build APIs and databases...",
46
- # tools: [:Read, :Edit, :Bash],
47
- # delegates_to: [:database]
48
- # })
49
- # swarm.add_agent(backend_agent)
50
- #
51
- # swarm.lead = :backend
52
- # result = swarm.execute("Build authentication")
53
- #
54
- # ## Architecture
55
- #
56
- # All APIs converge on Agent::Definition for validation.
57
- # Swarm delegates to specialized concerns:
58
- # - Agent::Definition: Validates configuration, builds system prompts
59
- # - AgentInitializer: Complex 5-pass agent setup
60
- # - ToolConfigurator: Tool creation and permissions (via AgentInitializer)
61
- # - McpConfigurator: MCP client management (via AgentInitializer)
62
- #
63
- class Swarm
64
- include Concerns::Snapshotable
65
- include Concerns::Validatable
66
- include Concerns::Cleanupable
67
- include LoggingCallbacks
68
- include HookTriggers
69
-
70
- # Backward compatibility aliases - use Defaults module for new code
71
- DEFAULT_MCP_LOG_LEVEL = Defaults::Logging::MCP_LOG_LEVEL
72
-
73
- # Default tools available to all agents
74
- DEFAULT_TOOLS = ToolConfigurator::DEFAULT_TOOLS
75
-
76
- attr_reader :name, :agents, :lead_agent, :mcp_clients, :delegation_instances, :agent_definitions, :swarm_id, :parent_swarm_id, :swarm_registry, :scratchpad_storage, :allow_filesystem_tools, :hook_registry, :global_semaphore, :plugin_storages, :config_for_hooks, :observer_configs
77
- attr_accessor :delegation_call_stack
78
-
79
- # Check if scratchpad tools are enabled
80
- #
81
- # @return [Boolean]
82
- def scratchpad_enabled?
83
- @scratchpad_mode == :enabled
84
- end
85
- attr_writer :config_for_hooks
86
-
87
- # Check if first message has been sent (for system reminder injection)
88
- #
89
- # @return [Boolean]
90
- def first_message_sent?
91
- @first_message_sent
92
- end
93
-
94
- # Set first message sent flag (used by snapshot/restore)
95
- #
96
- # @param value [Boolean] New value
97
- # @return [void]
98
- attr_writer :first_message_sent
99
-
100
- # Class-level MCP log level configuration
101
- @mcp_log_level = DEFAULT_MCP_LOG_LEVEL
102
- @mcp_logging_configured = false
103
-
104
- class << self
105
- attr_accessor :mcp_log_level
106
-
107
- # Configure MCP client logging globally
108
- #
109
- # This should be called before creating any swarms that use MCP servers.
110
- # The configuration is global and affects all MCP clients.
111
- #
112
- # @param level [Integer] Log level (Logger::DEBUG, Logger::INFO, Logger::WARN, Logger::ERROR, Logger::FATAL)
113
- # @return [void]
114
- def configure_mcp_logging(level = DEFAULT_MCP_LOG_LEVEL)
115
- @mcp_log_level = level
116
- apply_mcp_logging_configuration
117
- end
118
-
119
- # Apply MCP logging configuration to RubyLLM::MCP
120
- #
121
- # @return [void]
122
- def apply_mcp_logging_configuration
123
- return if @mcp_logging_configured
124
-
125
- RubyLLM::MCP.configure do |config|
126
- config.log_level = @mcp_log_level
127
- end
128
-
129
- @mcp_logging_configured = true
130
- end
131
- end
132
-
133
- # Initialize a new Swarm
134
- #
135
- # @param name [String] Human-readable swarm name
136
- # @param swarm_id [String, nil] Optional swarm ID (auto-generated if not provided)
137
- # @param parent_swarm_id [String, nil] Optional parent swarm ID (nil for root swarms)
138
- # @param global_concurrency [Integer] Max concurrent LLM calls across entire swarm
139
- # @param default_local_concurrency [Integer] Default max concurrent tool calls per agent
140
- # @param scratchpad [Tools::Stores::Scratchpad, nil] Optional scratchpad instance (for testing/internal use)
141
- # @param scratchpad_mode [Symbol, String] Scratchpad mode (:enabled or :disabled). :per_node not allowed for non-node swarms.
142
- # @param allow_filesystem_tools [Boolean, nil] Whether to allow filesystem tools (nil uses global setting)
143
- def initialize(name:, swarm_id: nil, parent_swarm_id: nil, global_concurrency: Defaults::Concurrency::GLOBAL_LIMIT, default_local_concurrency: Defaults::Concurrency::LOCAL_LIMIT, scratchpad: nil, scratchpad_mode: :enabled, allow_filesystem_tools: nil)
144
- @name = name
145
- @swarm_id = swarm_id || generate_swarm_id(name)
146
- @parent_swarm_id = parent_swarm_id
147
- @global_concurrency = global_concurrency
148
- @default_local_concurrency = default_local_concurrency
149
-
150
- # Handle scratchpad_mode parameter
151
- # For Swarm: :enabled or :disabled (not :per_node - that's for nodes)
152
- @scratchpad_mode = validate_swarm_scratchpad_mode(scratchpad_mode)
153
-
154
- # Resolve allow_filesystem_tools with priority:
155
- # 1. Explicit parameter (if not nil)
156
- # 2. Global settings
157
- @allow_filesystem_tools = if allow_filesystem_tools.nil?
158
- SwarmSDK.settings.allow_filesystem_tools
159
- else
160
- allow_filesystem_tools
161
- end
162
-
163
- # Swarm registry for managing sub-swarms (initialized later if needed)
164
- @swarm_registry = nil
165
-
166
- # Delegation call stack for circular dependency detection
167
- @delegation_call_stack = []
168
-
169
- # Shared semaphore for all agents
170
- @global_semaphore = Async::Semaphore.new(@global_concurrency)
171
-
172
- # Shared scratchpad storage for all agents (volatile)
173
- # Use provided scratchpad storage (for testing) or create volatile one based on mode
174
- @scratchpad_storage = if scratchpad
175
- scratchpad # Testing/internal use - explicit instance provided
176
- elsif @scratchpad_mode == :enabled
177
- Tools::Stores::ScratchpadStorage.new
178
- end
179
-
180
- # Per-agent plugin storages (persistent)
181
- # Format: { plugin_name => { agent_name => storage } }
182
- # Will be populated when agents are initialized
183
- @plugin_storages = {}
184
-
185
- # Hook registry for named hooks and swarm defaults
186
- @hook_registry = Hooks::Registry.new
187
-
188
- # Register default logging hooks
189
- register_default_logging_callbacks
190
-
191
- # Agent definitions and instances
192
- @agent_definitions = {}
193
- @agents = {}
194
- @delegation_instances = {} # { "delegate@delegator" => Agent::Chat }
195
- @agents_initialized = false
196
- @agent_contexts = {}
197
-
198
- # MCP clients per agent (for cleanup)
199
- @mcp_clients = Hash.new { |h, k| h[k] = [] }
200
-
201
- @lead_agent = nil
202
-
203
- # Track if first message has been sent
204
- @first_message_sent = false
205
-
206
- # Track if agent_start events have been emitted
207
- # This prevents duplicate emissions and ensures events are emitted when logging is ready
208
- @agent_start_events_emitted = false
209
-
210
- # Observer agent configurations
211
- @observer_configs = []
212
- @observer_manager = nil
213
- end
214
-
215
- # Add an agent to the swarm
216
- #
217
- # Accepts only Agent::Definition objects. This ensures all validation
218
- # happens in a single place (Agent::Definition) and keeps the API clean.
219
- #
220
- # If the definition doesn't specify max_concurrent_tools, the swarm's
221
- # default_local_concurrency is applied.
222
- #
223
- # @param definition [Agent::Definition] Fully configured agent definition
224
- # @return [self]
225
- #
226
- # @example
227
- # definition = Agent::Definition.new(:backend, {
228
- # description: "Backend developer",
229
- # model: "gpt-5",
230
- # system_prompt: "You build APIs"
231
- # })
232
- # swarm.add_agent(definition)
233
- def add_agent(definition)
234
- unless definition.is_a?(Agent::Definition)
235
- raise ArgumentError, "Expected Agent::Definition, got #{definition.class}"
236
- end
237
-
238
- name = definition.name
239
- raise ConfigurationError, "Agent '#{name}' already exists" if @agent_definitions.key?(name)
240
-
241
- # Apply swarm's default_local_concurrency if max_concurrent_tools not set
242
- definition.max_concurrent_tools = @default_local_concurrency if definition.max_concurrent_tools.nil?
243
-
244
- @agent_definitions[name] = definition
245
- self
246
- end
247
-
248
- # Set the lead agent (entry point for swarm execution)
249
- #
250
- # @param name [Symbol, String] Name of agent to make lead
251
- # @return [self]
252
- def lead=(name)
253
- name = name.to_sym
254
-
255
- unless @agent_definitions.key?(name)
256
- raise ConfigurationError, "Cannot set lead: agent '#{name}' not found"
257
- end
258
-
259
- @lead_agent = name
260
- end
261
-
262
- # Execute a task using the lead agent
263
- #
264
- # The lead agent can delegate to other agents via tool calls,
265
- # and the entire swarm coordinates with shared rate limiting.
266
- # Supports reprompting via swarm_stop hooks.
267
- #
268
- # By default, this method blocks until execution completes. Set wait: false
269
- # to return an Async::Task immediately, enabling cancellation via task.stop.
270
- #
271
- # @param prompt [String] Task to execute
272
- # @param wait [Boolean] If true (default), blocks until execution completes.
273
- # If false, returns Async::Task immediately for non-blocking execution.
274
- # @yield [Hash] Log entry if block given (for streaming)
275
- # @return [Result, Async::Task] Result if wait: true, Async::Task if wait: false
276
- #
277
- # @example Blocking execution (default)
278
- # result = swarm.execute("Build auth")
279
- # puts result.content
280
- #
281
- # @example Non-blocking execution with cancellation
282
- # task = swarm.execute("Build auth", wait: false) { |event| puts event }
283
- # # ... do other work ...
284
- # task.stop # Cancel anytime
285
- # result = task.wait # Returns nil for cancelled tasks
286
- def execute(prompt, wait: true, &block)
287
- raise ConfigurationError, "No lead agent set. Set lead= first." unless @lead_agent
288
-
289
- logs = []
290
- current_prompt = prompt
291
- has_logging = block_given?
292
-
293
- # Save original Fiber storage for restoration (preserves parent context for nested swarms)
294
- original_fiber_storage = {
295
- execution_id: Fiber[:execution_id],
296
- swarm_id: Fiber[:swarm_id],
297
- parent_swarm_id: Fiber[:parent_swarm_id],
298
- }
299
-
300
- # Set fiber-local execution context
301
- # Use ||= to inherit parent's execution_id if one exists (for mini-swarms)
302
- Fiber[:execution_id] ||= generate_execution_id
303
- Fiber[:swarm_id] = @swarm_id
304
- Fiber[:parent_swarm_id] = @parent_swarm_id
305
-
306
- # Setup logging FIRST if block given (so swarm_start event can be emitted)
307
- setup_logging(logs, &block) if has_logging
308
-
309
- # Setup observer execution if any observers configured
310
- # MUST happen AFTER setup_logging (which clears Fiber[:log_subscriptions])
311
- setup_observer_execution if @observer_configs.any?
312
-
313
- # Trigger swarm_start hooks (before any execution)
314
- current_prompt = apply_swarm_start_hooks(current_prompt)
315
-
316
- # Trigger first_message hooks on first execution
317
- unless @first_message_sent
318
- trigger_first_message(current_prompt)
319
- @first_message_sent = true
320
- end
321
-
322
- # Lazy initialization of agents (with optional logging)
323
- initialize_agents unless @agents_initialized
324
-
325
- # Emit agent_start events if agents were initialized before logging was set up
326
- emit_retroactive_agent_start_events if has_logging
327
-
328
- # Delegate to Executor for actual execution
329
- executor = Executor.new(self)
330
- @current_task = executor.run(
331
- current_prompt,
332
- wait: wait,
333
- logs: logs,
334
- has_logging: has_logging,
335
- original_fiber_storage: original_fiber_storage,
336
- )
337
- end
338
-
339
- # Get an agent chat instance by name
340
- #
341
- # @param name [Symbol, String] Agent name
342
- # @return [AgentChat] Agent chat instance
343
- def agent(name)
344
- name = name.to_sym
345
- initialize_agents unless @agents_initialized
346
-
347
- @agents[name] || raise(AgentNotFoundError, "Agent '#{name}' not found")
348
- end
349
-
350
- # Get an agent definition by name
351
- #
352
- # Use this to access and modify agent configuration:
353
- # swarm.agent_definition(:backend).bypass_permissions = true
354
- #
355
- # @param name [Symbol, String] Agent name
356
- # @return [AgentDefinition] Agent definition object
357
- def agent_definition(name)
358
- name = name.to_sym
359
-
360
- @agent_definitions[name] || raise(AgentNotFoundError, "Agent '#{name}' not found")
361
- end
362
-
363
- # Get all agent names
364
- #
365
- # @return [Array<Symbol>] Agent names
366
- def agent_names
367
- @agent_definitions.keys
368
- end
369
-
370
- # Implement Snapshotable interface
371
- def primary_agents
372
- @agents
373
- end
374
-
375
- def delegation_instances_hash
376
- @delegation_instances
377
- end
378
-
379
- # NOTE: validate() and emit_validation_warnings() are provided by Concerns::Validatable
380
- # Note: cleanup() is provided by Concerns::Cleanupable
381
-
382
- # Register a named hook that can be referenced in agent configurations
383
- #
384
- # Named hooks are stored in the registry and can be referenced by symbol
385
- # in agent YAML configurations or programmatically.
386
- #
387
- # @param name [Symbol] Unique hook name
388
- # @param block [Proc] Hook implementation
389
- # @return [self]
390
- #
391
- # @example Register a validation hook
392
- # swarm.register_hook(:validate_code) do |context|
393
- # raise SwarmSDK::Hooks::Error, "Invalid" unless valid?(context.tool_call)
394
- # end
395
- def register_hook(name, &block)
396
- @hook_registry.register(name, &block)
397
- self
398
- end
399
-
400
- # Reset context for all agents
401
- #
402
- # Clears conversation history for all agents. This is used by composable swarms
403
- # to reset sub-swarm context when keep_context: false is specified.
404
- #
405
- # @return [void]
406
- def reset_context!
407
- @agents.each_value do |agent_chat|
408
- agent_chat.clear_conversation if agent_chat.respond_to?(:clear_conversation)
409
- end
410
- end
411
-
412
- # Add observer configuration
413
- #
414
- # Called by Swarm::Builder to register observer agent configurations.
415
- # Validates that the referenced agent exists.
416
- #
417
- # @param config [Observer::Config] Observer configuration
418
- # @return [void]
419
- def add_observer_config(config)
420
- validate_observer_agent(config.agent_name)
421
- @observer_configs << config
422
- end
423
-
424
- # Wait for all observer tasks to complete
425
- #
426
- # Called by Executor to wait for observer agents before cleanup.
427
- # Safe to call even if no observers are configured.
428
- #
429
- # @return [void]
430
- def wait_for_observers
431
- @observer_manager&.wait_for_completion
432
- end
433
-
434
- # Cleanup observer subscriptions
435
- #
436
- # Called by Executor.cleanup_after_execution to unsubscribe observers.
437
- # Matches the MCP cleanup pattern.
438
- #
439
- # @return [void]
440
- def cleanup_observers
441
- @observer_manager&.cleanup
442
- @observer_manager = nil
443
- end
444
-
445
- # Create snapshot of current conversation state
446
- #
447
- # Returns a Snapshot object containing:
448
- # - All agent conversations (@messages arrays)
449
- # - Agent context state (warnings, compression, TodoWrite tracking, skills)
450
- # - Delegation instance conversations
451
- # - Scratchpad contents (volatile shared storage)
452
- # - Read tracking state (which files each agent has read with digests)
453
- # - Memory read tracking state (which memory entries each agent has read with digests)
454
- #
455
- # Configuration (agent definitions, tools, prompts) stays in your YAML/DSL
456
- # and is NOT included in snapshots.
457
- #
458
- # @return [Snapshot] Snapshot object with convenient serialization methods
459
- #
460
- # @example Save snapshot to JSON file
461
- # snapshot = swarm.snapshot
462
- # snapshot.write_to_file("session.json")
463
- #
464
- # @example Convert to hash or JSON string
465
- # snapshot = swarm.snapshot
466
- # hash = snapshot.to_hash
467
- # json_string = snapshot.to_json
468
- def snapshot
469
- StateSnapshot.new(self).snapshot
470
- end
471
-
472
- # Restore conversation state from snapshot
473
- #
474
- # Accepts a Snapshot object, hash, or JSON string. Validates compatibility
475
- # between snapshot and current swarm configuration, restores agent conversations,
476
- # context state, scratchpad, and read tracking. Returns RestoreResult with
477
- # warnings about any agents that couldn't be restored due to configuration
478
- # mismatches.
479
- #
480
- # The swarm must be created with the SAME configuration (agent definitions,
481
- # tools, prompts) as when the snapshot was created. Only conversation state
482
- # is restored from the snapshot.
483
- #
484
- # @param snapshot [Snapshot, Hash, String] Snapshot object, hash, or JSON string
485
- # @return [RestoreResult] Result with warnings about skipped agents
486
- #
487
- # @example Restore from Snapshot object
488
- # swarm = SwarmSDK.build { ... } # Same config as snapshot
489
- # snapshot = Snapshot.from_file("session.json")
490
- # result = swarm.restore(snapshot)
491
- # if result.success?
492
- # puts "All agents restored"
493
- # else
494
- # puts result.summary
495
- # result.warnings.each { |w| puts " - #{w[:message]}" }
496
- # end
497
- #
498
- # Restore swarm state from snapshot
499
- #
500
- # By default, uses current system prompts from agent definitions (YAML + SDK defaults + plugin injections).
501
- # Set preserve_system_prompts: true to use historical prompts from snapshot.
502
- #
503
- # @param snapshot [Snapshot, Hash, String] Snapshot object, hash, or JSON string
504
- # @param preserve_system_prompts [Boolean] Use historical system prompts instead of current config (default: false)
505
- # @return [RestoreResult] Result with warnings about partial restores
506
- def restore(snapshot, preserve_system_prompts: false)
507
- StateRestorer.new(self, snapshot, preserve_system_prompts: preserve_system_prompts).restore
508
- end
509
-
510
- # Override swarm IDs for composable swarms
511
- #
512
- # Used by SwarmLoader to set hierarchical IDs when loading sub-swarms.
513
- # This is called after the swarm is built to ensure proper parent/child relationships.
514
- #
515
- # @param swarm_id [String] New swarm ID
516
- # @param parent_swarm_id [String] New parent swarm ID
517
- # @return [void]
518
- def override_swarm_ids(swarm_id:, parent_swarm_id:)
519
- @swarm_id = swarm_id
520
- @parent_swarm_id = parent_swarm_id
521
- end
522
-
523
- # Set swarm registry for composable swarms
524
- #
525
- # Used by Builder to set the registry after swarm creation.
526
- # This must be called before agent initialization to enable swarm delegation.
527
- #
528
- # @param registry [SwarmRegistry] Configured swarm registry
529
- # @return [void]
530
- attr_writer :swarm_registry
531
-
532
- # --- Internal API (for Executor use only) ---
533
- # Hook triggers for swarm lifecycle events are provided by HookTriggers module
534
-
535
- private
536
-
537
- # Apply swarm_start hooks to prompt
538
- #
539
- # @param prompt [String] Original prompt
540
- # @return [String] Modified prompt (possibly with hook context appended)
541
- def apply_swarm_start_hooks(prompt)
542
- swarm_start_result = trigger_swarm_start(prompt)
543
- if swarm_start_result&.replace?
544
- "#{prompt}\n\n<hook-context>\n#{swarm_start_result.value}\n</hook-context>"
545
- else
546
- prompt
547
- end
548
- end
549
-
550
- # Validate that observer agent exists
551
- #
552
- # @param agent_name [Symbol] Name of the observer agent
553
- # @raise [ConfigurationError] If agent not found
554
- # @return [void]
555
- def validate_observer_agent(agent_name)
556
- return if @agent_definitions.key?(agent_name)
557
-
558
- raise ConfigurationError,
559
- "Observer agent '#{agent_name}' not found. " \
560
- "Define the agent first with `agent :#{agent_name} do ... end`"
561
- end
562
-
563
- # Setup observer manager and subscriptions
564
- #
565
- # Creates Observer::Manager and registers event subscriptions.
566
- # Must be called AFTER setup_logging (which clears Fiber[:log_subscriptions]).
567
- #
568
- # @return [void]
569
- def setup_observer_execution
570
- @observer_manager = Observer::Manager.new(self)
571
- @observer_configs.each { |c| @observer_manager.add_config(c) }
572
- @observer_manager.setup
573
- end
574
-
575
- # Validate and normalize scratchpad mode for Swarm
576
- #
577
- # Regular Swarms support :enabled or :disabled.
578
- # Rejects :per_node since it only makes sense for Workflow with multiple nodes.
579
- #
580
- # @param value [Symbol, String] Scratchpad mode (strings from YAML converted to symbols)
581
- # @return [Symbol] :enabled or :disabled
582
- # @raise [ArgumentError] If :per_node used, or invalid value
583
- def validate_swarm_scratchpad_mode(value)
584
- # Convert strings from YAML to symbols
585
- value = value.to_sym if value.is_a?(String)
586
-
587
- case value
588
- when :enabled, :disabled
589
- value
590
- when :per_node
591
- raise ArgumentError,
592
- "scratchpad: :per_node is only valid for Workflow with nodes. " \
593
- "For regular Swarms, use :enabled or :disabled."
594
- else
595
- raise ArgumentError,
596
- "Invalid scratchpad mode for Swarm: #{value.inspect}. " \
597
- "Use :enabled or :disabled."
598
- end
599
- end
600
-
601
- # Generate a unique swarm ID from name
602
- #
603
- # Creates a swarm ID by sanitizing the name and appending a random suffix.
604
- # Used when swarm_id is not explicitly provided.
605
- #
606
- # @param name [String] Swarm name
607
- # @return [String] Generated swarm ID (e.g., "dev_team_a3f2b1c8")
608
- def generate_swarm_id(name)
609
- sanitized = name.to_s.gsub(/[^a-z0-9_-]/i, "_").downcase
610
- "#{sanitized}_#{SecureRandom.hex(4)}"
611
- end
612
-
613
- # Generate a unique execution ID
614
- #
615
- # Creates an execution ID that uniquely identifies a single swarm.execute() call.
616
- # Format: "exec_{swarm_id}_{random_hex}"
617
- #
618
- # @return [String] Generated execution ID (e.g., "exec_main_a3f2b1c8")
619
- def generate_execution_id
620
- "exec_#{@swarm_id}_#{SecureRandom.hex(8)}"
621
- end
622
-
623
- # Initialize all agents using AgentInitializer
624
- #
625
- # This is called automatically (lazy initialization) by execute() and agent().
626
- # Delegates to AgentInitializer which handles the complex 5-pass setup.
627
- #
628
- # @return [void]
629
- def initialize_agents
630
- return if @agents_initialized
631
-
632
- initializer = AgentInitializer.new(self)
633
-
634
- @agents = initializer.initialize_all
635
- @agent_contexts = initializer.agent_contexts
636
- @agents_initialized = true
637
-
638
- # NOTE: agent_start events are emitted in execute() when logging is set up
639
- # This ensures events are never lost, even if agents are initialized early (e.g., by restore())
640
- end
641
-
642
- # Normalize tools to internal format (kept for add_agent)
643
- #
644
- # Handles both Ruby API (simple symbols) and YAML API (already parsed configs)
645
- #
646
- # @param tools [Array] Tool specifications
647
- # @return [Array<Hash>] Normalized tool configs
648
- def normalize_tools(tools)
649
- Array(tools).map do |tool|
650
- case tool
651
- when Symbol, String
652
- # Simple tool from Ruby API
653
- { name: tool.to_sym, permissions: nil }
654
- when Hash
655
- # Already in config format from YAML (has :name and :permissions keys)
656
- if tool.key?(:name)
657
- tool
658
- else
659
- # Inline permissions format: { Write: { allowed_paths: [...] } }
660
- tool_name = tool.keys.first.to_sym
661
- { name: tool_name, permissions: tool[tool_name] }
662
- end
663
- else
664
- raise ConfigurationError, "Invalid tool specification: #{tool.inspect}"
665
- end
666
- end
667
- end
668
-
669
- # Delegation methods for testing (delegate to concerns)
670
- # These allow tests to verify behavior without depending on internal structure
671
-
672
- # Create a tool instance (delegates to ToolConfigurator)
673
- def create_tool_instance(tool_name, agent_name, directory)
674
- ToolConfigurator.new(self, @scratchpad_storage, @plugin_storages).create_tool_instance(tool_name, agent_name, directory)
675
- end
676
-
677
- # Wrap tool with permissions (delegates to ToolConfigurator)
678
- def wrap_tool_with_permissions(tool_instance, permissions_config, agent_definition)
679
- ToolConfigurator.new(self, @scratchpad_storage, @plugin_storages).wrap_tool_with_permissions(tool_instance, permissions_config, agent_definition)
680
- end
681
-
682
- # Build MCP transport config (delegates to McpConfigurator)
683
- def build_mcp_transport_config(transport_type, config)
684
- McpConfigurator.new(self).build_transport_config(transport_type, config)
685
- end
686
-
687
- # Create delegation tool (delegates to AgentInitializer)
688
- def create_delegation_tool(name:, description:, delegate_chat:, agent_name:)
689
- AgentInitializer.new(self)
690
- .create_delegation_tool(name: name, description: description, delegate_chat: delegate_chat, agent_name: agent_name)
691
- end
692
-
693
- # Extract loggable info from plugin config
694
- #
695
- # Attempts to extract useful information from plugin configuration
696
- # for logging purposes. Handles MemoryConfig, Hashes, and other objects.
697
- #
698
- # @param config [Object] Plugin configuration object
699
- # @return [Hash, nil] Extracted config info or nil
700
- def extract_plugin_config_info(config)
701
- return if config.nil?
702
-
703
- # Handle MemoryConfig object (has directory method)
704
- if config.respond_to?(:directory)
705
- return { directory: config.directory }
706
- end
707
-
708
- # Handle Hash
709
- if config.is_a?(Hash)
710
- return config.slice(:directory, "directory", :adapter, "adapter")
711
- end
712
-
713
- # Unknown config type
714
- nil
715
- end
716
- end
717
- end