swarm_sdk 2.7.14 → 3.0.0.alpha2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_sdk/ruby_llm_patches/chat_callbacks_patch.rb +16 -0
  3. data/lib/swarm_sdk/ruby_llm_patches/init.rb +4 -1
  4. data/lib/swarm_sdk/v3/agent.rb +1165 -0
  5. data/lib/swarm_sdk/v3/agent_builder.rb +533 -0
  6. data/lib/swarm_sdk/v3/agent_definition.rb +330 -0
  7. data/lib/swarm_sdk/v3/configuration.rb +490 -0
  8. data/lib/swarm_sdk/v3/debug_log.rb +86 -0
  9. data/lib/swarm_sdk/v3/event_stream.rb +130 -0
  10. data/lib/swarm_sdk/v3/hooks/context.rb +112 -0
  11. data/lib/swarm_sdk/v3/hooks/result.rb +115 -0
  12. data/lib/swarm_sdk/v3/hooks/runner.rb +128 -0
  13. data/lib/swarm_sdk/v3/mcp/connector.rb +183 -0
  14. data/lib/swarm_sdk/v3/mcp/mcp_error.rb +15 -0
  15. data/lib/swarm_sdk/v3/mcp/server_definition.rb +125 -0
  16. data/lib/swarm_sdk/v3/mcp/ssl_http_transport.rb +103 -0
  17. data/lib/swarm_sdk/v3/mcp/stdio_transport.rb +135 -0
  18. data/lib/swarm_sdk/v3/mcp/tool_proxy.rb +53 -0
  19. data/lib/swarm_sdk/v3/memory/adapters/base.rb +297 -0
  20. data/lib/swarm_sdk/v3/memory/adapters/faiss_support.rb +194 -0
  21. data/lib/swarm_sdk/v3/memory/adapters/filesystem_adapter.rb +212 -0
  22. data/lib/swarm_sdk/v3/memory/adapters/sqlite_adapter.rb +507 -0
  23. data/lib/swarm_sdk/v3/memory/adapters/vector_utils.rb +88 -0
  24. data/lib/swarm_sdk/v3/memory/card.rb +206 -0
  25. data/lib/swarm_sdk/v3/memory/cluster.rb +146 -0
  26. data/lib/swarm_sdk/v3/memory/compressor.rb +496 -0
  27. data/lib/swarm_sdk/v3/memory/consolidator.rb +427 -0
  28. data/lib/swarm_sdk/v3/memory/context_builder.rb +339 -0
  29. data/lib/swarm_sdk/v3/memory/edge.rb +105 -0
  30. data/lib/swarm_sdk/v3/memory/embedder.rb +185 -0
  31. data/lib/swarm_sdk/v3/memory/exposure_tracker.rb +104 -0
  32. data/lib/swarm_sdk/v3/memory/ingestion_pipeline.rb +394 -0
  33. data/lib/swarm_sdk/v3/memory/retriever.rb +289 -0
  34. data/lib/swarm_sdk/v3/memory/store.rb +489 -0
  35. data/lib/swarm_sdk/v3/skills/loader.rb +147 -0
  36. data/lib/swarm_sdk/v3/skills/manifest.rb +45 -0
  37. data/lib/swarm_sdk/v3/sub_task_agent.rb +248 -0
  38. data/lib/swarm_sdk/v3/tools/base.rb +80 -0
  39. data/lib/swarm_sdk/v3/tools/bash.rb +174 -0
  40. data/lib/swarm_sdk/v3/tools/clock.rb +32 -0
  41. data/lib/swarm_sdk/v3/tools/document_converters/base.rb +84 -0
  42. data/lib/swarm_sdk/v3/tools/document_converters/docx_converter.rb +120 -0
  43. data/lib/swarm_sdk/v3/tools/document_converters/pdf_converter.rb +111 -0
  44. data/lib/swarm_sdk/v3/tools/document_converters/xlsx_converter.rb +128 -0
  45. data/lib/swarm_sdk/v3/tools/edit.rb +111 -0
  46. data/lib/swarm_sdk/v3/tools/glob.rb +96 -0
  47. data/lib/swarm_sdk/v3/tools/grep.rb +200 -0
  48. data/lib/swarm_sdk/v3/tools/message_teammate.rb +15 -0
  49. data/lib/swarm_sdk/v3/tools/message_user.rb +15 -0
  50. data/lib/swarm_sdk/v3/tools/read.rb +213 -0
  51. data/lib/swarm_sdk/v3/tools/read_tracker.rb +40 -0
  52. data/lib/swarm_sdk/v3/tools/registry.rb +208 -0
  53. data/lib/swarm_sdk/v3/tools/sub_task.rb +183 -0
  54. data/lib/swarm_sdk/v3/tools/think.rb +88 -0
  55. data/lib/swarm_sdk/v3/tools/write.rb +87 -0
  56. data/lib/swarm_sdk/v3.rb +145 -0
  57. metadata +88 -149
  58. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -175
  59. data/lib/swarm_sdk/agent/builder.rb +0 -705
  60. data/lib/swarm_sdk/agent/chat.rb +0 -1438
  61. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -375
  62. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  63. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  64. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -85
  65. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -290
  66. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  67. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  68. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -134
  69. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  70. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -146
  71. data/lib/swarm_sdk/agent/context.rb +0 -115
  72. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  73. data/lib/swarm_sdk/agent/definition.rb +0 -588
  74. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -226
  75. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -173
  76. data/lib/swarm_sdk/agent/tool_registry.rb +0 -189
  77. data/lib/swarm_sdk/agent_registry.rb +0 -146
  78. data/lib/swarm_sdk/builders/base_builder.rb +0 -558
  79. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  80. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -42
  81. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  82. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  83. data/lib/swarm_sdk/config.rb +0 -368
  84. data/lib/swarm_sdk/configuration/parser.rb +0 -397
  85. data/lib/swarm_sdk/configuration/translator.rb +0 -285
  86. data/lib/swarm_sdk/configuration.rb +0 -165
  87. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  88. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -102
  89. data/lib/swarm_sdk/context_compactor.rb +0 -335
  90. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  91. data/lib/swarm_sdk/context_management/context.rb +0 -328
  92. data/lib/swarm_sdk/custom_tool_registry.rb +0 -226
  93. data/lib/swarm_sdk/defaults.rb +0 -251
  94. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  95. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  96. data/lib/swarm_sdk/hooks/context.rb +0 -197
  97. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  98. data/lib/swarm_sdk/hooks/error.rb +0 -29
  99. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  100. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  101. data/lib/swarm_sdk/hooks/result.rb +0 -150
  102. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -256
  103. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  104. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  105. data/lib/swarm_sdk/log_collector.rb +0 -227
  106. data/lib/swarm_sdk/log_stream.rb +0 -127
  107. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  108. data/lib/swarm_sdk/model_aliases.json +0 -8
  109. data/lib/swarm_sdk/models.json +0 -44002
  110. data/lib/swarm_sdk/models.rb +0 -161
  111. data/lib/swarm_sdk/node_context.rb +0 -245
  112. data/lib/swarm_sdk/observer/builder.rb +0 -81
  113. data/lib/swarm_sdk/observer/config.rb +0 -45
  114. data/lib/swarm_sdk/observer/manager.rb +0 -248
  115. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  116. data/lib/swarm_sdk/permissions/config.rb +0 -239
  117. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  118. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  119. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  120. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  121. data/lib/swarm_sdk/plugin.rb +0 -309
  122. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  123. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  124. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -119
  125. data/lib/swarm_sdk/restore_result.rb +0 -65
  126. data/lib/swarm_sdk/result.rb +0 -241
  127. data/lib/swarm_sdk/snapshot.rb +0 -156
  128. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  129. data/lib/swarm_sdk/state_restorer.rb +0 -476
  130. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  131. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -648
  132. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -204
  133. data/lib/swarm_sdk/swarm/builder.rb +0 -256
  134. data/lib/swarm_sdk/swarm/executor.rb +0 -446
  135. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -162
  136. data/lib/swarm_sdk/swarm/lazy_delegate_chat.rb +0 -372
  137. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -361
  138. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -290
  139. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  140. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -392
  141. data/lib/swarm_sdk/swarm.rb +0 -973
  142. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  143. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  144. data/lib/swarm_sdk/tools/base.rb +0 -63
  145. data/lib/swarm_sdk/tools/bash.rb +0 -280
  146. data/lib/swarm_sdk/tools/clock.rb +0 -46
  147. data/lib/swarm_sdk/tools/delegate.rb +0 -389
  148. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  149. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  150. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  151. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  152. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  153. data/lib/swarm_sdk/tools/edit.rb +0 -145
  154. data/lib/swarm_sdk/tools/glob.rb +0 -166
  155. data/lib/swarm_sdk/tools/grep.rb +0 -235
  156. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  157. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -167
  158. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  159. data/lib/swarm_sdk/tools/mcp_tool_stub.rb +0 -198
  160. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  161. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  162. data/lib/swarm_sdk/tools/read.rb +0 -261
  163. data/lib/swarm_sdk/tools/registry.rb +0 -205
  164. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  165. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  166. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  167. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  168. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -273
  169. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  170. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  171. data/lib/swarm_sdk/tools/think.rb +0 -100
  172. data/lib/swarm_sdk/tools/todo_write.rb +0 -237
  173. data/lib/swarm_sdk/tools/web_fetch.rb +0 -264
  174. data/lib/swarm_sdk/tools/write.rb +0 -112
  175. data/lib/swarm_sdk/transcript_builder.rb +0 -278
  176. data/lib/swarm_sdk/utils.rb +0 -68
  177. data/lib/swarm_sdk/validation_result.rb +0 -33
  178. data/lib/swarm_sdk/version.rb +0 -5
  179. data/lib/swarm_sdk/workflow/agent_config.rb +0 -95
  180. data/lib/swarm_sdk/workflow/builder.rb +0 -227
  181. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  182. data/lib/swarm_sdk/workflow/node_builder.rb +0 -593
  183. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -250
  184. data/lib/swarm_sdk/workflow.rb +0 -589
  185. data/lib/swarm_sdk.rb +0 -721
@@ -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