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,497 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- class Workflow
5
- # Handles workflow execution orchestration
6
- #
7
- # Extracted from Workflow#execute to reduce complexity and improve maintainability.
8
- # Orchestrates node execution, transformer handling, and control flow.
9
- #
10
- # @example
11
- # executor = Executor.new(workflow)
12
- # result = executor.run("Build auth system") { |entry| puts entry }
13
- class Executor
14
- # Execution state container
15
- #
16
- # Holds mutable state during workflow execution to avoid instance variable pollution.
17
- ExecutionState = Struct.new(
18
- :current_input,
19
- :results,
20
- :last_result,
21
- :execution_index,
22
- :logs,
23
- keyword_init: true,
24
- )
25
-
26
- # Output transformer context container
27
- #
28
- # Groups parameters for output transformer processing to avoid long parameter lists.
29
- OutputTransformerContext = Struct.new(
30
- :node,
31
- :node_name,
32
- :node_start_time,
33
- :state,
34
- :result,
35
- :skip_execution,
36
- keyword_init: true,
37
- )
38
-
39
- def initialize(workflow)
40
- @workflow = workflow
41
- end
42
-
43
- # Execute the workflow with a prompt
44
- #
45
- # @param prompt [String] Initial prompt for the workflow
46
- # @param inherit_subscriptions [Boolean] Whether to inherit parent log subscriptions
47
- # @yield [Hash] Log entry if block given (for streaming)
48
- # @return [Result] Final result from last node execution
49
- def run(prompt, inherit_subscriptions: true, &block)
50
- @parent_subscriptions = capture_parent_subscriptions if inherit_subscriptions
51
- setup_logging(inherit_subscriptions: inherit_subscriptions, &block)
52
- setup_fiber_context
53
- @workflow.original_prompt = prompt
54
-
55
- state = ExecutionState.new(
56
- current_input: prompt,
57
- results: {},
58
- last_result: nil,
59
- execution_index: 0,
60
- logs: [],
61
- )
62
-
63
- execute_nodes(state)
64
- ensure
65
- cleanup_fiber_context
66
- reset_logging
67
- end
68
-
69
- private
70
-
71
- # Capture parent subscriptions before overwriting Fiber storage
72
- #
73
- # @return [Array<LogCollector::Subscription>] Parent subscriptions
74
- def capture_parent_subscriptions
75
- Fiber[:log_subscriptions] || []
76
- end
77
-
78
- # Setup logging infrastructure if block given
79
- #
80
- # @param inherit_subscriptions [Boolean] Whether to inherit parent subscriptions
81
- # @yield [Hash] Log entry for streaming
82
- # @return [void]
83
- def setup_logging(inherit_subscriptions: true, &block)
84
- @has_logging = block_given?
85
- return unless @has_logging
86
-
87
- Fiber[:log_subscriptions] = if inherit_subscriptions && @parent_subscriptions
88
- # Keep parent subscriptions and add new one
89
- @parent_subscriptions.dup
90
- else
91
- # Isolate: start with fresh subscriptions
92
- []
93
- end
94
-
95
- LogCollector.subscribe do |entry|
96
- block.call(entry)
97
- end
98
- LogStream.emitter = LogCollector
99
- end
100
-
101
- # Setup fiber-local execution context
102
- #
103
- # @return [void]
104
- def setup_fiber_context
105
- Fiber[:execution_id] = generate_execution_id
106
- end
107
-
108
- # Cleanup fiber-local storage
109
- #
110
- # @return [void]
111
- def cleanup_fiber_context
112
- Fiber[:execution_id] = nil
113
- Fiber[:swarm_id] = nil
114
- Fiber[:parent_swarm_id] = nil
115
- Fiber[:log_subscriptions] = nil
116
- end
117
-
118
- # Reset logging state
119
- #
120
- # @return [void]
121
- def reset_logging
122
- return unless @has_logging
123
-
124
- LogCollector.reset!
125
- LogStream.reset!
126
- end
127
-
128
- # Generate unique execution ID for workflow
129
- #
130
- # @return [String] Generated execution ID
131
- def generate_execution_id
132
- "exec_workflow_#{SecureRandom.hex(8)}"
133
- end
134
-
135
- # Main node iteration loop with control flow support
136
- #
137
- # @param state [ExecutionState] Mutable execution state
138
- # @return [Result] Final result
139
- def execute_nodes(state)
140
- while state.execution_index < @workflow.execution_order.size
141
- control_action = execute_single_node(state)
142
-
143
- case control_action[:action]
144
- when :halt
145
- return control_action[:result]
146
- when :goto
147
- state.execution_index = find_node_index(control_action[:target])
148
- state.current_input = control_action[:content]
149
- next
150
- when :continue
151
- state.execution_index += 1
152
- end
153
- end
154
-
155
- state.last_result
156
- end
157
-
158
- # Execute a single node with full lifecycle
159
- #
160
- # @param state [ExecutionState] Mutable execution state
161
- # @return [Hash] Control action (:halt, :goto, or :continue)
162
- def execute_single_node(state)
163
- node_name = @workflow.execution_order[state.execution_index]
164
- node = @workflow.nodes[node_name]
165
- node_start_time = Time.now
166
-
167
- setup_node_fiber_context(node_name)
168
- emit_node_start(node_name, node)
169
-
170
- # Process input transformer (may modify current_input or return control flow)
171
- input_result = process_input_transformer(node, node_name, node_start_time, state)
172
- return input_result if input_result[:action] == :halt || input_result[:action] == :goto
173
-
174
- skip_execution = input_result[:skip]
175
- state.current_input = input_result[:content]
176
-
177
- # Execute node (or skip if requested)
178
- result = execute_node(node, node_name, state.current_input, skip_execution)
179
- state.results[node_name] = result
180
- state.last_result = result
181
-
182
- log_node_error(node_name, result) if result.error
183
-
184
- # Process output transformer (may return control flow)
185
- ctx = OutputTransformerContext.new(
186
- node: node,
187
- node_name: node_name,
188
- node_start_time: node_start_time,
189
- state: state,
190
- result: result,
191
- skip_execution: skip_execution,
192
- )
193
- output_result = process_output_transformer(ctx)
194
-
195
- case output_result[:action]
196
- when :halt
197
- return output_result
198
- when :goto
199
- emit_node_stop(node_name, node, result, Time.now - node_start_time, skip_execution)
200
- return output_result
201
- end
202
-
203
- state.current_input = output_result[:content]
204
-
205
- # Update result for agent-less nodes with transformed content
206
- update_agentless_result(node, node_name, state, result)
207
-
208
- emit_node_stop(node_name, node, result, Time.now - node_start_time, skip_execution)
209
-
210
- { action: :continue }
211
- end
212
-
213
- # Setup fiber-local context for node execution
214
- #
215
- # @param node_name [Symbol] Node name
216
- # @return [void]
217
- def setup_node_fiber_context(node_name)
218
- node_swarm_id = @workflow.swarm_id ? "#{@workflow.swarm_id}/node:#{node_name}" : nil
219
- Fiber[:swarm_id] = node_swarm_id
220
- Fiber[:parent_swarm_id] = @workflow.swarm_id
221
- end
222
-
223
- # Process input transformer and handle control flow
224
- #
225
- # @param node [Workflow::NodeBuilder] Node configuration
226
- # @param node_name [Symbol] Node name
227
- # @param node_start_time [Time] When node started
228
- # @param state [ExecutionState] Current execution state
229
- # @return [Hash] Control action with :skip and :content keys
230
- def process_input_transformer(node, node_name, node_start_time, state)
231
- unless node.has_input_transformer?
232
- return { action: :continue, skip: false, content: state.current_input }
233
- end
234
-
235
- input_context = build_input_context(node, node_name, state)
236
- transformed = node.transform_input(input_context, current_input: state.current_input)
237
-
238
- handle_input_control_flow(transformed, node_name, node, node_start_time)
239
- end
240
-
241
- # Build NodeContext for input transformer
242
- #
243
- # @param node [Workflow::NodeBuilder] Node configuration
244
- # @param node_name [Symbol] Node name
245
- # @param state [ExecutionState] Current execution state
246
- # @return [NodeContext] Context for transformer
247
- def build_input_context(node, node_name, state)
248
- previous_result = resolve_previous_result(node, state)
249
-
250
- NodeContext.for_input(
251
- previous_result: previous_result,
252
- all_results: state.results,
253
- original_prompt: @workflow.original_prompt,
254
- node_name: node_name,
255
- dependencies: node.dependencies,
256
- transformed_content: node.dependencies.size == 1 ? state.current_input : nil,
257
- )
258
- end
259
-
260
- # Resolve previous result based on dependencies
261
- #
262
- # @param node [Workflow::NodeBuilder] Node configuration
263
- # @param state [ExecutionState] Current execution state
264
- # @return [Result, Hash, String] Previous result(s) or prompt
265
- def resolve_previous_result(node, state)
266
- case node.dependencies.size
267
- when 0
268
- state.current_input
269
- when 1
270
- state.results[node.dependencies.first]
271
- else
272
- node.dependencies.to_h { |dep| [dep, state.results[dep]] }
273
- end
274
- end
275
-
276
- # Handle control flow from input transformer
277
- #
278
- # @param transformed [String, Hash] Transformer result
279
- # @param node_name [Symbol] Node name
280
- # @param node [Workflow::NodeBuilder] Node configuration
281
- # @param node_start_time [Time] When node started
282
- # @return [Hash] Control action
283
- def handle_input_control_flow(transformed, node_name, node, node_start_time)
284
- return { action: :continue, skip: false, content: transformed } unless transformed.is_a?(Hash)
285
-
286
- if transformed[:halt_workflow]
287
- halt_result = build_halt_result(transformed[:content], node_name, node_start_time)
288
- emit_node_stop(node_name, node, halt_result, Time.now - node_start_time, false)
289
- { action: :halt, result: halt_result }
290
- elsif transformed[:goto_node]
291
- { action: :goto, target: transformed[:goto_node], content: transformed[:content] }
292
- elsif transformed[:skip_execution]
293
- { action: :continue, skip: true, content: transformed[:content] }
294
- else
295
- { action: :continue, skip: false, content: transformed[:content] }
296
- end
297
- end
298
-
299
- # Execute the node (agent-less or with mini-swarm)
300
- #
301
- # @param node [Workflow::NodeBuilder] Node configuration
302
- # @param node_name [Symbol] Node name
303
- # @param input [String] Input content
304
- # @param skip_execution [Boolean] Whether to skip execution
305
- # @return [Result] Execution result
306
- def execute_node(node, node_name, input, skip_execution)
307
- if skip_execution
308
- build_skip_result(node_name, input)
309
- elsif node.agent_less?
310
- execute_agent_less_node(node, input)
311
- else
312
- execute_swarm_node(node, input)
313
- end
314
- end
315
-
316
- # Build result for skipped execution
317
- #
318
- # @param node_name [Symbol] Node name
319
- # @param content [String] Content to include in result
320
- # @return [Result] Skip result
321
- def build_skip_result(node_name, content)
322
- Result.new(
323
- content: content,
324
- agent: "skipped:#{node_name}",
325
- logs: [],
326
- duration: 0.0,
327
- )
328
- end
329
-
330
- # Execute an agent-less (computation-only) node
331
- #
332
- # @param node [Workflow::NodeBuilder] Agent-less node configuration
333
- # @param input [String] Input content
334
- # @return [Result] Result with input passed through
335
- def execute_agent_less_node(node, input)
336
- Result.new(
337
- content: input,
338
- agent: "computation:#{node.name}",
339
- logs: [],
340
- duration: 0.0,
341
- )
342
- end
343
-
344
- # Execute node with mini-swarm
345
- #
346
- # @param node [Workflow::NodeBuilder] Node configuration
347
- # @param input [String] Input content
348
- # @return [Result] Execution result
349
- def execute_swarm_node(node, input)
350
- mini_swarm = @workflow.build_swarm_for_node(node)
351
- result = mini_swarm.execute(input)
352
- @workflow.cache_agent_instances(mini_swarm, node)
353
- result
354
- end
355
-
356
- # Process output transformer and handle control flow
357
- #
358
- # @param ctx [OutputTransformerContext] Grouped transformer context
359
- # @return [Hash] Control action
360
- def process_output_transformer(ctx)
361
- output_context = NodeContext.for_output(
362
- result: ctx.result,
363
- all_results: ctx.state.results,
364
- original_prompt: @workflow.original_prompt,
365
- node_name: ctx.node_name,
366
- )
367
- transformed = ctx.node.transform_output(output_context)
368
-
369
- handle_output_control_flow(transformed, ctx)
370
- end
371
-
372
- # Handle control flow from output transformer
373
- #
374
- # @param transformed [String, Hash] Transformer result
375
- # @param ctx [OutputTransformerContext] Grouped transformer context
376
- # @return [Hash] Control action
377
- def handle_output_control_flow(transformed, ctx)
378
- return { action: :continue, content: transformed } unless transformed.is_a?(Hash)
379
-
380
- if transformed[:halt_workflow]
381
- halt_result = Result.new(
382
- content: transformed[:content],
383
- agent: ctx.result.agent,
384
- logs: ctx.result.logs,
385
- duration: ctx.result.duration,
386
- )
387
- emit_node_stop(ctx.node_name, ctx.node, halt_result, Time.now - ctx.node_start_time, ctx.skip_execution)
388
- { action: :halt, result: halt_result }
389
- elsif transformed[:goto_node]
390
- { action: :goto, target: transformed[:goto_node], content: transformed[:content] }
391
- else
392
- { action: :continue, content: transformed[:content] || transformed }
393
- end
394
- end
395
-
396
- # Update result for agent-less nodes with transformed content
397
- #
398
- # @param node [Workflow::NodeBuilder] Node configuration
399
- # @param node_name [Symbol] Node name
400
- # @param state [ExecutionState] Current execution state
401
- # @param result [Result] Original result
402
- # @return [void]
403
- def update_agentless_result(node, node_name, state, result)
404
- return unless node.agent_less? && state.current_input != result.content
405
-
406
- updated_result = Result.new(
407
- content: state.current_input,
408
- agent: result.agent,
409
- logs: result.logs,
410
- duration: result.duration,
411
- error: result.error,
412
- )
413
- state.results[node_name] = updated_result
414
- state.last_result = updated_result
415
- end
416
-
417
- # Build result for halted workflow
418
- #
419
- # @param content [String] Content to include
420
- # @param node_name [Symbol] Node name
421
- # @param node_start_time [Time] When node started
422
- # @return [Result] Halt result
423
- def build_halt_result(content, node_name, node_start_time)
424
- Result.new(
425
- content: content,
426
- agent: "halted:#{node_name}",
427
- logs: [],
428
- duration: Time.now - node_start_time,
429
- )
430
- end
431
-
432
- # Log node execution error
433
- #
434
- # @param node_name [Symbol] Node name
435
- # @param result [Result] Execution result with error
436
- # @return [void]
437
- def log_node_error(node_name, result)
438
- RubyLLM.logger.error("Workflow: Node '#{node_name}' failed: #{result.error.message}")
439
- RubyLLM.logger.error(" Backtrace: #{result.error.backtrace&.first(5)&.join("\n ")}")
440
- end
441
-
442
- # Find the index of a node in the execution order
443
- #
444
- # @param node_name [Symbol] Node name to find
445
- # @return [Integer] Index in execution order
446
- # @raise [ConfigurationError] If node not found
447
- def find_node_index(node_name)
448
- index = @workflow.execution_order.index(node_name)
449
- unless index
450
- raise ConfigurationError,
451
- "goto_node target '#{node_name}' not found. Available nodes: #{@workflow.execution_order.join(", ")}"
452
- end
453
- index
454
- end
455
-
456
- # Emit node_start event
457
- #
458
- # @param node_name [Symbol] Name of the node
459
- # @param node [Workflow::NodeBuilder] Node configuration
460
- # @return [void]
461
- def emit_node_start(node_name, node)
462
- return unless LogStream.emitter
463
-
464
- LogStream.emit(
465
- type: "node_start",
466
- node: node_name.to_s,
467
- agent_less: node.agent_less?,
468
- agents: node.agent_configs.map { |ac| ac[:agent].to_s },
469
- dependencies: node.dependencies.map(&:to_s),
470
- timestamp: Time.now.utc.iso8601,
471
- )
472
- end
473
-
474
- # Emit node_stop event
475
- #
476
- # @param node_name [Symbol] Name of the node
477
- # @param node [Workflow::NodeBuilder] Node configuration
478
- # @param result [Result] Node execution result
479
- # @param duration [Float] Node execution duration in seconds
480
- # @param skipped [Boolean] Whether execution was skipped
481
- # @return [void]
482
- def emit_node_stop(node_name, node, result, duration, skipped)
483
- return unless LogStream.emitter
484
-
485
- LogStream.emit(
486
- type: "node_stop",
487
- node: node_name.to_s,
488
- agent_less: node.agent_less?,
489
- skipped: skipped,
490
- agents: node.agent_configs.map { |ac| ac[:agent].to_s },
491
- duration: duration.round(3),
492
- timestamp: Time.now.utc.iso8601,
493
- )
494
- end
495
- end
496
- end
497
- end