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,648 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- class Swarm
5
- # Handles the complex 5-pass agent initialization process
6
- #
7
- # Responsibilities:
8
- # - Create all agent chat instances (pass 1)
9
- # - Register delegation tools (pass 2)
10
- # - Setup agent contexts (pass 3)
11
- # - Configure hook system (pass 4)
12
- # - Apply YAML hooks if present (pass 5)
13
- #
14
- # This encapsulates the complex initialization logic that was previously
15
- # embedded in Swarm#initialize_agents.
16
- class AgentInitializer
17
- # Initialize with swarm reference (all data accessible via swarm)
18
- #
19
- # @param swarm [Swarm] The parent swarm instance
20
- def initialize(swarm)
21
- @swarm = swarm
22
- @agents = {}
23
- @agent_contexts = {}
24
- end
25
-
26
- # Initialize all agents with their chat instances and tools
27
- #
28
- # This implements a 6-pass algorithm:
29
- # 1. Create all Agent::Chat instances
30
- # 2. Register delegation tools (agents can call each other)
31
- # 3. Setup agent contexts for tracking
32
- # 4. Configure hook system
33
- # 5. Apply YAML hooks (if loaded from YAML)
34
- # 6. Activate tools (Plan 025: populate @llm_chat.tools from registry after plugins)
35
- #
36
- # @return [Hash] agents hash { agent_name => Agent::Chat }
37
- def initialize_all
38
- pass_1_create_agents
39
- pass_2_register_delegation_tools
40
- pass_3_setup_contexts
41
- pass_4_configure_hooks
42
- pass_5_apply_yaml_hooks
43
- pass_6_activate_tools # Plan 025: Activate tools after all plugins registered
44
-
45
- @agents
46
- end
47
-
48
- # Provide access to agent contexts for Swarm
49
- attr_reader :agent_contexts
50
-
51
- # Initialize a single agent in isolation (for observer agents)
52
- #
53
- # Creates an isolated agent chat without delegation tools,
54
- # suitable for observer agents that don't need to delegate.
55
- # Reuses existing create_agent_chat infrastructure.
56
- #
57
- # @param agent_name [Symbol] Name of agent to initialize
58
- # @return [Agent::Chat] Isolated agent chat instance
59
- # @raise [ConfigurationError] If agent not found
60
- #
61
- # @example
62
- # initializer = AgentInitializer.new(swarm)
63
- # chat = initializer.initialize_isolated_agent(:profiler)
64
- # chat.ask("Analyze this prompt")
65
- def initialize_isolated_agent(agent_name)
66
- agent_def = @swarm.agent_definitions[agent_name]
67
- raise ConfigurationError, "Agent '#{agent_name}' not found" unless agent_def
68
-
69
- # Ensure plugin storages are created (needed by ToolConfigurator)
70
- create_plugin_storages if @swarm.plugin_storages.empty?
71
-
72
- # Reuse existing create_agent_chat infrastructure
73
- tool_configurator = ToolConfigurator.new(
74
- @swarm,
75
- @swarm.scratchpad_storage,
76
- @swarm.plugin_storages,
77
- )
78
-
79
- # Create chat using same method as pass_1_create_agents
80
- # This gives us full tool setup, MCP servers, etc.
81
- create_agent_chat(agent_name, agent_def, tool_configurator)
82
- end
83
-
84
- # Create a tool that delegates work to another agent
85
- #
86
- # This method is public for testing delegation from Swarm.
87
- #
88
- # @param name [String] Delegate agent name
89
- # @param description [String] Delegate agent description
90
- # @param delegate_chat [Agent::Chat] The delegate's chat instance
91
- # @param agent_name [Symbol] Name of the delegating agent
92
- # @param delegating_chat [Agent::Chat, nil] The chat instance of the agent doing the delegating
93
- # @param custom_tool_name [String, nil] Optional custom tool name (overrides auto-generated name)
94
- # @param preserve_context [Boolean] Whether to preserve conversation context between delegations (default: true)
95
- # @return [Tools::Delegate] Delegation tool
96
- def create_delegation_tool(name:, description:, delegate_chat:, agent_name:, delegating_chat: nil, custom_tool_name: nil, preserve_context: true)
97
- Tools::Delegate.new(
98
- delegate_name: name,
99
- delegate_description: description,
100
- delegate_chat: delegate_chat,
101
- agent_name: agent_name,
102
- swarm: @swarm,
103
- delegating_chat: delegating_chat,
104
- custom_tool_name: custom_tool_name,
105
- preserve_context: preserve_context,
106
- )
107
- end
108
-
109
- private
110
-
111
- # Pass 1: Create primary agent chat instances
112
- #
113
- # Only creates agents that will actually be used as primaries:
114
- # - The lead agent
115
- # - Agents with shared_across_delegations: true (shared delegates)
116
- # - Agents not used as delegates (standalone agents)
117
- #
118
- # Agents that are ONLY delegates with shared_across_delegations: false
119
- # are NOT created here - they'll be created as delegation instances in pass 2a.
120
- #
121
- # Agent creation is parallelized using Async::Barrier for faster initialization.
122
- def pass_1_create_agents
123
- # Create plugin storages for agents
124
- create_plugin_storages
125
-
126
- tool_configurator = ToolConfigurator.new(@swarm, @swarm.scratchpad_storage, @swarm.plugin_storages)
127
-
128
- # Filter agents that need primary creation
129
- agents_to_create = @swarm.agent_definitions.reject do |name, agent_definition|
130
- should_skip_primary_creation?(name, agent_definition)
131
- end
132
-
133
- # Create agents in parallel using Async::Barrier
134
- results = create_agents_in_parallel(agents_to_create, tool_configurator)
135
-
136
- # Store results and notify plugins (sequential for safety)
137
- results.each do |name, chat, agent_definition|
138
- @agents[name] = chat
139
- notify_plugins_agent_initialized(name, chat, agent_definition, tool_configurator)
140
- end
141
- end
142
-
143
- # Create multiple agents in parallel using Async fibers
144
- #
145
- # @param agents_to_create [Hash] Hash of { name => agent_definition }
146
- # @param tool_configurator [ToolConfigurator] Shared tool configurator
147
- # @return [Array<Array>] Array of [name, chat, agent_definition] tuples
148
- def create_agents_in_parallel(agents_to_create, tool_configurator)
149
- return [] if agents_to_create.empty?
150
-
151
- results = []
152
- errors = []
153
- mutex = Mutex.new
154
-
155
- Sync do
156
- barrier = Async::Barrier.new
157
-
158
- agents_to_create.each do |name, agent_definition|
159
- barrier.async do
160
- chat = create_agent_chat(name, agent_definition, tool_configurator)
161
- mutex.synchronize { results << [name, chat, agent_definition] }
162
- rescue StandardError => e
163
- # Catch errors to avoid Async warning logs (which fail in tests with StringIO)
164
- mutex.synchronize { errors << [name, e] }
165
- end
166
- end
167
-
168
- barrier.wait
169
- end
170
-
171
- # Re-raise first error if any occurred
172
- unless errors.empty?
173
- # Emit events for all errors (not just the first)
174
- errors.each do |agent_name, err|
175
- LogStream.emit(
176
- type: "agent_initialization_error",
177
- agent: agent_name,
178
- error_class: err.class.name,
179
- error_message: err.message,
180
- timestamp: Time.now.utc.iso8601,
181
- )
182
- end
183
-
184
- # Re-raise first error with context
185
- name, error = errors.first
186
- raise error.class, "Agent '#{name}' initialization failed: #{error.message}", error.backtrace
187
- end
188
-
189
- results
190
- end
191
-
192
- # Pass 2: Wire delegation tools (lazy loading for isolated delegates)
193
- #
194
- # This pass wires delegation tools for primary agents:
195
- # - Shared delegates use the primary agent instance
196
- # - Isolated delegates use LazyDelegateChat (created on first use)
197
- #
198
- # Sub-pass 2a (eager creation) is REMOVED - delegation instances are now lazy.
199
- # Sub-pass 2c (nested delegation) is handled by LazyDelegateChat when initialized.
200
- def pass_2_register_delegation_tools
201
- tool_configurator = ToolConfigurator.new(@swarm, @swarm.scratchpad_storage, @swarm.plugin_storages)
202
-
203
- # Wire primary agents to delegates (shared primaries or lazy loaders)
204
- @swarm.agent_definitions.each do |delegator_name, delegator_def|
205
- delegator_chat = @agents[delegator_name]
206
-
207
- # Skip if delegator doesn't exist as primary (wasn't created in pass_1)
208
- next unless delegator_chat
209
-
210
- delegator_def.delegation_configs.each do |delegation_config|
211
- wire_delegation(
212
- delegator_name: delegator_name,
213
- delegator_chat: delegator_chat,
214
- delegation_config: delegation_config,
215
- tool_configurator: tool_configurator,
216
- )
217
- end
218
- end
219
-
220
- # NOTE: Nested delegation wiring is now handled by LazyDelegateChat#wire_delegation_tools
221
- # when the lazy delegate is first accessed.
222
- end
223
-
224
- # Wire a single delegation from one agent to a delegate
225
- #
226
- # For isolated delegates, creates a LazyDelegateChat wrapper instead of
227
- # eagerly creating the chat instance.
228
- #
229
- # @param delegator_name [Symbol, String] Name of the agent doing the delegating
230
- # @param delegator_chat [Agent::Chat] Chat instance of the delegator
231
- # @param delegation_config [Hash] Delegation configuration with :agent, :tool_name, and :preserve_context keys
232
- # @param tool_configurator [ToolConfigurator] Tool configuration helper
233
- # @return [void]
234
- def wire_delegation(delegator_name:, delegator_chat:, delegation_config:, tool_configurator:)
235
- delegate_name_sym = delegation_config[:agent]
236
- delegate_name_str = delegate_name_sym.to_s
237
- custom_tool_name = delegation_config[:tool_name]
238
- preserve_context = delegation_config.fetch(:preserve_context, true)
239
-
240
- # Check if target is a registered swarm
241
- if @swarm.swarm_registry&.registered?(delegate_name_str)
242
- wire_swarm_delegation(delegator_name, delegator_chat, delegate_name_str, custom_tool_name, preserve_context)
243
- elsif @swarm.agent_definitions.key?(delegate_name_sym)
244
- wire_agent_delegation(
245
- delegator_name: delegator_name,
246
- delegator_chat: delegator_chat,
247
- delegate_name_sym: delegate_name_sym,
248
- custom_tool_name: custom_tool_name,
249
- preserve_context: preserve_context,
250
- )
251
- else
252
- raise ConfigurationError,
253
- "Agent '#{delegator_name}' delegates to unknown target '#{delegate_name_str}' (not a local agent or registered swarm)"
254
- end
255
- end
256
-
257
- # Wire delegation to an external swarm
258
- #
259
- # @param delegator_name [Symbol, String] Name of the delegating agent
260
- # @param delegator_chat [Agent::Chat] Chat instance of the delegator
261
- # @param swarm_name [String] Name of the registered swarm
262
- # @param custom_tool_name [String, nil] Optional custom tool name
263
- # @param preserve_context [Boolean] Whether to preserve context between delegations
264
- # @return [void]
265
- def wire_swarm_delegation(delegator_name, delegator_chat, swarm_name, custom_tool_name, preserve_context)
266
- tool = create_delegation_tool(
267
- name: swarm_name,
268
- description: "External swarm: #{swarm_name}",
269
- delegate_chat: nil, # Swarm delegation - no direct chat
270
- agent_name: delegator_name,
271
- delegating_chat: delegator_chat,
272
- custom_tool_name: custom_tool_name,
273
- preserve_context: preserve_context,
274
- )
275
-
276
- # Register in tool registry (Plan 025)
277
- delegator_chat.tool_registry.register(
278
- tool,
279
- source: :delegation,
280
- metadata: { delegate_name: swarm_name, delegation_type: :swarm, preserve_context: preserve_context },
281
- )
282
- end
283
-
284
- # Wire delegation to a local agent
285
- #
286
- # For shared delegates, uses the primary agent instance.
287
- # For isolated delegates, creates a LazyDelegateChat wrapper that
288
- # defers creation until first use.
289
- #
290
- # @param delegator_name [Symbol, String] Name of the delegating agent
291
- # @param delegator_chat [Agent::Chat] Chat instance of the delegator
292
- # @param delegate_name_sym [Symbol] Name of the delegate agent
293
- # @param custom_tool_name [String, nil] Optional custom tool name
294
- # @param preserve_context [Boolean] Whether to preserve context between delegations
295
- # @return [void]
296
- def wire_agent_delegation(delegator_name:, delegator_chat:, delegate_name_sym:, custom_tool_name:, preserve_context:)
297
- delegate_definition = @swarm.agent_definitions[delegate_name_sym]
298
-
299
- # Determine which chat instance to use
300
- target_chat = if delegate_definition.shared_across_delegations
301
- # Shared mode: use primary agent (semaphore-protected)
302
- @agents[delegate_name_sym]
303
- else
304
- # Isolated mode: use lazy loader (created on first delegation)
305
- instance_name = "#{delegate_name_sym}@#{delegator_name}"
306
-
307
- LazyDelegateChat.new(
308
- instance_name: instance_name,
309
- base_name: delegate_name_sym,
310
- agent_definition: delegate_definition,
311
- swarm: @swarm,
312
- )
313
- end
314
-
315
- # Create delegation tool pointing to chosen instance (or lazy loader)
316
- tool = create_delegation_tool(
317
- name: delegate_name_sym.to_s,
318
- description: delegate_definition.description,
319
- delegate_chat: target_chat,
320
- agent_name: delegator_name,
321
- delegating_chat: delegator_chat,
322
- custom_tool_name: custom_tool_name,
323
- preserve_context: preserve_context,
324
- )
325
-
326
- # Register in tool registry (Plan 025)
327
- delegator_chat.tool_registry.register(
328
- tool,
329
- source: :delegation,
330
- metadata: {
331
- delegate_name: delegate_name_sym,
332
- delegation_mode: delegate_definition.shared_across_delegations ? :shared : :isolated,
333
- preserve_context: preserve_context,
334
- },
335
- )
336
- end
337
-
338
- # Pass 3: Setup agent contexts
339
- #
340
- # Create Agent::Context for each agent to track delegations and metadata.
341
- # This is needed regardless of whether logging is enabled.
342
- #
343
- # NOTE: Delegation instances are now lazy-loaded, so their contexts are
344
- # set up in LazyDelegateChat#setup_context when first accessed.
345
- def pass_3_setup_contexts
346
- # Setup contexts for PRIMARY agents only
347
- # (Delegation instances handle their own context setup via LazyDelegateChat)
348
- @agents.each do |agent_name, chat|
349
- setup_agent_context(agent_name, @swarm.agent_definitions[agent_name], chat, is_delegation: false)
350
- end
351
- end
352
-
353
- # Setup context for an agent (primary or delegation instance)
354
- def setup_agent_context(agent_name, agent_definition, chat, is_delegation: false)
355
- # Generate actual tool names (custom or auto-generated) for context tracking
356
- delegate_tool_names = agent_definition.delegation_configs.map do |delegation_config|
357
- # Use custom name if provided, otherwise auto-generate
358
- delegation_config[:tool_name] || Tools::Delegate.tool_name_for(delegation_config[:agent])
359
- end
360
-
361
- context = Agent::Context.new(
362
- name: agent_name,
363
- swarm_id: @swarm.swarm_id,
364
- parent_swarm_id: @swarm.parent_swarm_id,
365
- delegation_tools: delegate_tool_names,
366
- metadata: { is_delegation_instance: is_delegation },
367
- )
368
-
369
- # Store context (only for primaries)
370
- @agent_contexts[agent_name] = context unless is_delegation
371
-
372
- # Always set agent context on chat
373
- chat.setup_context(context) if chat.respond_to?(:setup_context)
374
-
375
- # Configure logging if enabled
376
- return unless LogStream.emitter
377
-
378
- chat.setup_logging if chat.respond_to?(:setup_logging)
379
-
380
- # Emit validation warnings (only for primaries, not each delegation instance)
381
- emit_validation_warnings(agent_name, agent_definition) unless is_delegation
382
- end
383
-
384
- # Emit validation warnings as log events
385
- #
386
- # This validates the agent definition and emits any warnings as log events
387
- # through LogStream (so formatters can handle them).
388
- #
389
- # @param agent_name [Symbol] Agent name
390
- # @param agent_definition [Agent::Definition] Agent definition to validate
391
- # @return [void]
392
- def emit_validation_warnings(agent_name, agent_definition)
393
- warnings = agent_definition.validate
394
-
395
- warnings.each do |warning|
396
- case warning[:type]
397
- when :model_not_found
398
- LogStream.emit(
399
- type: "model_lookup_warning",
400
- agent: agent_name,
401
- model: warning[:model],
402
- error_message: warning[:error_message],
403
- suggestions: warning[:suggestions],
404
- timestamp: Time.now.utc.iso8601,
405
- )
406
- end
407
- end
408
- end
409
-
410
- # Pass 4: Configure hook system
411
- #
412
- # Setup the callback system for each agent, integrating with RubyLLM callbacks.
413
- #
414
- # NOTE: Delegation instances are now lazy-loaded, so their hooks are
415
- # configured in LazyDelegateChat#configure_hooks when first accessed.
416
- def pass_4_configure_hooks
417
- # Configure hooks for PRIMARY agents only
418
- # (Delegation instances handle their own hook setup via LazyDelegateChat)
419
- @agents.each do |agent_name, chat|
420
- configure_hooks_for_agent(agent_name, chat)
421
- end
422
- end
423
-
424
- # Configure hooks for an agent (primary or delegation instance)
425
- def configure_hooks_for_agent(agent_name, chat)
426
- base_name = extract_base_name(agent_name)
427
- agent_definition = @swarm.agent_definitions[base_name]
428
-
429
- chat.setup_hooks(
430
- registry: @swarm.hook_registry,
431
- agent_definition: agent_definition,
432
- swarm: @swarm,
433
- ) if chat.respond_to?(:setup_hooks)
434
- end
435
-
436
- # Pass 5: Apply YAML hooks
437
- #
438
- # If the swarm was loaded from YAML with agent-specific hooks,
439
- # apply them now via HooksAdapter.
440
- #
441
- # NOTE: Delegation instances are now lazy-loaded, so their YAML hooks are
442
- # applied in LazyDelegateChat#apply_yaml_hooks when first accessed.
443
- def pass_5_apply_yaml_hooks
444
- return unless @swarm.config_for_hooks
445
-
446
- # Apply YAML hooks to PRIMARY agents only
447
- # (Delegation instances handle their own YAML hooks via LazyDelegateChat)
448
- @agents.each do |agent_name, chat|
449
- apply_yaml_hooks_for_agent(agent_name, chat)
450
- end
451
- end
452
-
453
- # Apply YAML hooks for an agent (primary or delegation instance)
454
- def apply_yaml_hooks_for_agent(agent_name, chat)
455
- base_name = extract_base_name(agent_name)
456
- agent_config = @swarm.config_for_hooks.agents[base_name]
457
- return unless agent_config
458
-
459
- # Configuration.agents now returns hashes, not Definitions
460
- hooks = agent_config.is_a?(Hash) ? agent_config[:hooks] : agent_config.hooks
461
- return unless hooks&.any?
462
-
463
- Hooks::Adapter.apply_agent_hooks(chat, agent_name, hooks, @swarm.name)
464
- end
465
-
466
- # Pass 6: Activate tools after all plugins have registered (Plan 025)
467
- #
468
- # This must be the LAST pass because:
469
- # - Plugins register tools in on_agent_initialized (e.g., LoadSkill from memory plugin)
470
- # - Tools must be activated AFTER all registration is complete
471
- # - This populates @llm_chat.tools from the registry
472
- #
473
- # NOTE: Delegation instances are now lazy-loaded, so their tools are
474
- # activated in LazyDelegateChat#initialize_chat when first accessed.
475
- #
476
- # @return [void]
477
- def pass_6_activate_tools
478
- # Activate tools for PRIMARY agents only
479
- # (Delegation instances handle their own tool activation via LazyDelegateChat)
480
- @agents.each_value(&:activate_tools_for_prompt)
481
- end
482
-
483
- # Create Agent::Chat instance with rate limiting
484
- #
485
- # @param agent_name [Symbol] Agent name
486
- # @param agent_definition [Agent::Definition] Agent definition object
487
- # @param tool_configurator [ToolConfigurator] Tool configuration helper
488
- # @return [Agent::Chat] Configured agent chat instance
489
- def create_agent_chat(agent_name, agent_definition, tool_configurator)
490
- chat = Agent::Chat.new(
491
- definition: agent_definition.to_h,
492
- agent_name: agent_name,
493
- global_semaphore: @swarm.global_semaphore,
494
- )
495
-
496
- # Set agent name on provider for logging (if provider supports it)
497
- chat.provider.agent_name = agent_name if chat.provider.respond_to?(:agent_name=)
498
-
499
- # Register tools using ToolConfigurator
500
- tool_configurator.register_all_tools(
501
- chat: chat,
502
- agent_name: agent_name,
503
- agent_definition: agent_definition,
504
- )
505
-
506
- # Register MCP servers using McpConfigurator
507
- if agent_definition.mcp_servers.any?
508
- mcp_configurator = McpConfigurator.new(@swarm)
509
- mcp_configurator.register_mcp_servers(chat, agent_definition.mcp_servers, agent_name: agent_name)
510
- end
511
-
512
- # Setup tool activation dependencies (Plan 025)
513
- chat.setup_tool_activation(
514
- tool_configurator: tool_configurator,
515
- agent_definition: agent_definition,
516
- )
517
-
518
- # NOTE: activate_tools_for_prompt is called in Pass 5 after all plugins
519
- # have registered their tools (e.g., LoadSkill from memory plugin)
520
-
521
- chat
522
- end
523
-
524
- # NOTE: create_agent_chat_for_delegation and register_delegation_tools were removed.
525
- # Delegation instances are now lazy-loaded via LazyDelegateChat.
526
-
527
- # Create plugin storages for all agents
528
- #
529
- # Iterates through all registered plugins and asks each to create
530
- # storage for agents that need it.
531
- #
532
- # @return [void]
533
- def create_plugin_storages
534
- PluginRegistry.all.each do |plugin|
535
- @swarm.agent_definitions.each do |agent_name, agent_definition|
536
- # Check if this plugin needs storage for this agent
537
- next unless plugin.memory_configured?(agent_definition)
538
-
539
- # Get plugin config for this agent
540
- config = get_plugin_config(agent_definition, plugin.name)
541
- next unless config
542
-
543
- # Parse config through plugin
544
- parsed_config = plugin.parse_config(config)
545
-
546
- # Create plugin storage
547
- storage = plugin.create_storage(agent_name: agent_name, config: parsed_config)
548
-
549
- # Store in plugin_storages: { plugin_name => { agent_name => storage } }
550
- @swarm.plugin_storages[plugin.name] ||= {}
551
- @swarm.plugin_storages[plugin.name][agent_name] = storage
552
- end
553
- end
554
- end
555
-
556
- # Get plugin-specific config from agent definition
557
- #
558
- # Uses the generic plugin_configs accessor to retrieve plugin-specific config.
559
- # E.g., memory plugin config is accessed via `agent_definition.plugin_config(:memory)`
560
- #
561
- # @param agent_definition [Agent::Definition] Agent definition
562
- # @param plugin_name [Symbol] Plugin name
563
- # @return [Object, nil] Plugin config or nil
564
- def get_plugin_config(agent_definition, plugin_name)
565
- # Use generic plugin config accessor
566
- agent_definition.plugin_config(plugin_name)
567
- end
568
-
569
- # Notify all plugins that an agent was initialized
570
- #
571
- # Plugins can register additional tools, mark tools immutable, etc.
572
- #
573
- # @param agent_name [Symbol] Agent name
574
- # @param chat [Agent::Chat] Chat instance
575
- # @param agent_definition [Agent::Definition] Agent definition
576
- # @param tool_configurator [ToolConfigurator] Tool configurator
577
- # @return [void]
578
- def notify_plugins_agent_initialized(agent_name, chat, agent_definition, tool_configurator)
579
- PluginRegistry.all.each do |plugin|
580
- # Get plugin storage for this agent (if any)
581
- plugin_storages = @swarm.plugin_storages[plugin.name] || {}
582
- storage = plugin_storages[agent_name]
583
-
584
- # Build context for plugin
585
- context = {
586
- storage: storage,
587
- agent_definition: agent_definition,
588
- tool_configurator: tool_configurator,
589
- }
590
-
591
- # Notify plugin
592
- plugin.on_agent_initialized(agent_name: agent_name, agent: chat, context: context)
593
- end
594
- end
595
-
596
- # Determine if we should skip creating a primary agent
597
- #
598
- # Skip if:
599
- # - NOT the lead agent, AND
600
- # - Has shared_across_delegations: false (isolated mode), AND
601
- # - Is only referenced as a delegate (not used standalone)
602
- #
603
- # @param name [Symbol] Agent name
604
- # @param agent_definition [Agent::Definition] Agent definition
605
- # @return [Boolean] True if should skip primary creation
606
- def should_skip_primary_creation?(name, agent_definition)
607
- # Always create lead agent
608
- return false if name == @swarm.lead_agent
609
-
610
- # If shared mode, create primary (delegates will use it)
611
- return false if agent_definition.shared_across_delegations
612
-
613
- # Skip if only used as a delegate
614
- only_referenced_as_delegate?(name)
615
- end
616
-
617
- # Check if an agent is only referenced as a delegate
618
- #
619
- # @param name [Symbol] Agent name
620
- # @return [Boolean] True if only referenced as delegate
621
- def only_referenced_as_delegate?(name)
622
- # Check if any agent delegates to this one
623
- referenced_as_delegate = @swarm.agent_definitions.any? do |_agent_name, definition|
624
- definition.delegates_to.include?(name)
625
- end
626
-
627
- # Skip if referenced as delegate (and not lead, already checked above)
628
- referenced_as_delegate
629
- end
630
-
631
- # Extract base agent name from instance name
632
- #
633
- # @param instance_name [Symbol, String] Instance name (may be delegation instance)
634
- # @return [Symbol] Base agent name
635
- def extract_base_name(instance_name)
636
- instance_name.to_s.split("@").first.to_sym
637
- end
638
-
639
- # Check if instance name is a delegation instance
640
- #
641
- # @param instance_name [Symbol, String] Instance name
642
- # @return [Boolean] True if delegation instance (contains '@')
643
- def delegation_instance?(instance_name)
644
- instance_name.to_s.include?("@")
645
- end
646
- end
647
- end
648
- end