swarm_memory 2.1.4 → 2.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_memory/version.rb +1 -1
  3. data/lib/swarm_memory.rb +7 -2
  4. metadata +6 -185
  5. data/lib/claude_swarm/base_executor.rb +0 -133
  6. data/lib/claude_swarm/claude_code_executor.rb +0 -349
  7. data/lib/claude_swarm/claude_mcp_server.rb +0 -78
  8. data/lib/claude_swarm/cli.rb +0 -697
  9. data/lib/claude_swarm/commands/ps.rb +0 -215
  10. data/lib/claude_swarm/commands/show.rb +0 -139
  11. data/lib/claude_swarm/configuration.rb +0 -373
  12. data/lib/claude_swarm/hooks/session_start_hook.rb +0 -42
  13. data/lib/claude_swarm/json_handler.rb +0 -91
  14. data/lib/claude_swarm/mcp_generator.rb +0 -243
  15. data/lib/claude_swarm/openai/chat_completion.rb +0 -256
  16. data/lib/claude_swarm/openai/executor.rb +0 -256
  17. data/lib/claude_swarm/openai/responses.rb +0 -319
  18. data/lib/claude_swarm/orchestrator.rb +0 -878
  19. data/lib/claude_swarm/process_tracker.rb +0 -78
  20. data/lib/claude_swarm/session_cost_calculator.rb +0 -209
  21. data/lib/claude_swarm/session_path.rb +0 -42
  22. data/lib/claude_swarm/settings_generator.rb +0 -77
  23. data/lib/claude_swarm/system_utils.rb +0 -46
  24. data/lib/claude_swarm/templates/generation_prompt.md.erb +0 -230
  25. data/lib/claude_swarm/tools/reset_session_tool.rb +0 -24
  26. data/lib/claude_swarm/tools/session_info_tool.rb +0 -24
  27. data/lib/claude_swarm/tools/task_tool.rb +0 -63
  28. data/lib/claude_swarm/version.rb +0 -5
  29. data/lib/claude_swarm/worktree_manager.rb +0 -475
  30. data/lib/claude_swarm/yaml_loader.rb +0 -22
  31. data/lib/claude_swarm.rb +0 -67
  32. data/lib/swarm_cli/cli.rb +0 -201
  33. data/lib/swarm_cli/command_registry.rb +0 -61
  34. data/lib/swarm_cli/commands/mcp_serve.rb +0 -130
  35. data/lib/swarm_cli/commands/mcp_tools.rb +0 -148
  36. data/lib/swarm_cli/commands/migrate.rb +0 -55
  37. data/lib/swarm_cli/commands/run.rb +0 -173
  38. data/lib/swarm_cli/config_loader.rb +0 -98
  39. data/lib/swarm_cli/formatters/human_formatter.rb +0 -781
  40. data/lib/swarm_cli/formatters/json_formatter.rb +0 -51
  41. data/lib/swarm_cli/interactive_repl.rb +0 -924
  42. data/lib/swarm_cli/mcp_serve_options.rb +0 -44
  43. data/lib/swarm_cli/mcp_tools_options.rb +0 -59
  44. data/lib/swarm_cli/migrate_options.rb +0 -54
  45. data/lib/swarm_cli/migrator.rb +0 -132
  46. data/lib/swarm_cli/options.rb +0 -151
  47. data/lib/swarm_cli/ui/components/agent_badge.rb +0 -33
  48. data/lib/swarm_cli/ui/components/content_block.rb +0 -120
  49. data/lib/swarm_cli/ui/components/divider.rb +0 -57
  50. data/lib/swarm_cli/ui/components/panel.rb +0 -62
  51. data/lib/swarm_cli/ui/components/usage_stats.rb +0 -70
  52. data/lib/swarm_cli/ui/formatters/cost.rb +0 -49
  53. data/lib/swarm_cli/ui/formatters/number.rb +0 -58
  54. data/lib/swarm_cli/ui/formatters/text.rb +0 -77
  55. data/lib/swarm_cli/ui/formatters/time.rb +0 -73
  56. data/lib/swarm_cli/ui/icons.rb +0 -36
  57. data/lib/swarm_cli/ui/renderers/event_renderer.rb +0 -188
  58. data/lib/swarm_cli/ui/state/agent_color_cache.rb +0 -45
  59. data/lib/swarm_cli/ui/state/depth_tracker.rb +0 -40
  60. data/lib/swarm_cli/ui/state/spinner_manager.rb +0 -170
  61. data/lib/swarm_cli/ui/state/usage_tracker.rb +0 -62
  62. data/lib/swarm_cli/version.rb +0 -5
  63. data/lib/swarm_cli.rb +0 -46
  64. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -127
  65. data/lib/swarm_sdk/agent/builder.rb +0 -552
  66. data/lib/swarm_sdk/agent/chat.rb +0 -774
  67. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -268
  68. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  69. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  70. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -78
  71. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -233
  72. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  73. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  74. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -136
  75. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  76. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -98
  77. data/lib/swarm_sdk/agent/context.rb +0 -116
  78. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  79. data/lib/swarm_sdk/agent/definition.rb +0 -477
  80. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -182
  81. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -161
  82. data/lib/swarm_sdk/builders/base_builder.rb +0 -409
  83. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  84. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -39
  85. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  86. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  87. data/lib/swarm_sdk/configuration/parser.rb +0 -353
  88. data/lib/swarm_sdk/configuration/translator.rb +0 -255
  89. data/lib/swarm_sdk/configuration.rb +0 -135
  90. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  91. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -106
  92. data/lib/swarm_sdk/context_compactor.rb +0 -335
  93. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  94. data/lib/swarm_sdk/context_management/context.rb +0 -328
  95. data/lib/swarm_sdk/defaults.rb +0 -196
  96. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  97. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  98. data/lib/swarm_sdk/hooks/context.rb +0 -197
  99. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  100. data/lib/swarm_sdk/hooks/error.rb +0 -29
  101. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  102. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  103. data/lib/swarm_sdk/hooks/result.rb +0 -150
  104. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -255
  105. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  106. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  107. data/lib/swarm_sdk/log_collector.rb +0 -227
  108. data/lib/swarm_sdk/log_stream.rb +0 -127
  109. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  110. data/lib/swarm_sdk/model_aliases.json +0 -8
  111. data/lib/swarm_sdk/models.json +0 -1
  112. data/lib/swarm_sdk/models.rb +0 -120
  113. data/lib/swarm_sdk/node_context.rb +0 -245
  114. data/lib/swarm_sdk/observer/builder.rb +0 -81
  115. data/lib/swarm_sdk/observer/config.rb +0 -45
  116. data/lib/swarm_sdk/observer/manager.rb +0 -236
  117. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  118. data/lib/swarm_sdk/permissions/config.rb +0 -239
  119. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  120. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  121. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  122. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  123. data/lib/swarm_sdk/plugin.rb +0 -309
  124. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  125. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  126. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -117
  127. data/lib/swarm_sdk/restore_result.rb +0 -65
  128. data/lib/swarm_sdk/result.rb +0 -123
  129. data/lib/swarm_sdk/snapshot.rb +0 -156
  130. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  131. data/lib/swarm_sdk/state_restorer.rb +0 -476
  132. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  133. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -683
  134. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -167
  135. data/lib/swarm_sdk/swarm/builder.rb +0 -249
  136. data/lib/swarm_sdk/swarm/executor.rb +0 -213
  137. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -150
  138. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -340
  139. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -154
  140. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  141. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -358
  142. data/lib/swarm_sdk/swarm.rb +0 -717
  143. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  144. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  145. data/lib/swarm_sdk/tools/bash.rb +0 -282
  146. data/lib/swarm_sdk/tools/clock.rb +0 -44
  147. data/lib/swarm_sdk/tools/delegate.rb +0 -267
  148. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  149. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  150. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  151. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  152. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  153. data/lib/swarm_sdk/tools/edit.rb +0 -145
  154. data/lib/swarm_sdk/tools/glob.rb +0 -166
  155. data/lib/swarm_sdk/tools/grep.rb +0 -235
  156. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  157. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -163
  158. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  159. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  160. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  161. data/lib/swarm_sdk/tools/read.rb +0 -261
  162. data/lib/swarm_sdk/tools/registry.rb +0 -205
  163. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  164. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  165. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  166. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  167. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -272
  168. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  169. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  170. data/lib/swarm_sdk/tools/think.rb +0 -98
  171. data/lib/swarm_sdk/tools/todo_write.rb +0 -235
  172. data/lib/swarm_sdk/tools/web_fetch.rb +0 -262
  173. data/lib/swarm_sdk/tools/write.rb +0 -112
  174. data/lib/swarm_sdk/utils.rb +0 -68
  175. data/lib/swarm_sdk/validation_result.rb +0 -33
  176. data/lib/swarm_sdk/version.rb +0 -5
  177. data/lib/swarm_sdk/workflow/agent_config.rb +0 -79
  178. data/lib/swarm_sdk/workflow/builder.rb +0 -143
  179. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  180. data/lib/swarm_sdk/workflow/node_builder.rb +0 -555
  181. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -249
  182. data/lib/swarm_sdk/workflow.rb +0 -554
  183. data/lib/swarm_sdk.rb +0 -524
  184. /data/lib/swarm_memory/{errors.rb → error.rb} +0 -0
@@ -1,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