swarm_sdk 2.2.0 → 2.3.0

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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_sdk/agent/builder.rb +58 -0
  3. data/lib/swarm_sdk/agent/chat.rb +527 -1059
  4. data/lib/swarm_sdk/agent/{chat → chat_helpers}/context_tracker.rb +9 -88
  5. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +204 -0
  6. data/lib/swarm_sdk/agent/{chat → chat_helpers}/hook_integration.rb +111 -44
  7. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +78 -0
  8. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +233 -0
  9. data/lib/swarm_sdk/agent/{chat → chat_helpers}/logging_helpers.rb +1 -1
  10. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +83 -0
  11. data/lib/swarm_sdk/agent/{chat → chat_helpers}/system_reminder_injector.rb +12 -12
  12. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +79 -0
  13. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +98 -0
  14. data/lib/swarm_sdk/agent/context.rb +2 -2
  15. data/lib/swarm_sdk/agent/definition.rb +66 -154
  16. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +4 -2
  17. data/lib/swarm_sdk/agent/system_prompt_builder.rb +161 -0
  18. data/lib/swarm_sdk/builders/base_builder.rb +409 -0
  19. data/lib/swarm_sdk/concerns/cleanupable.rb +39 -0
  20. data/lib/swarm_sdk/concerns/snapshotable.rb +67 -0
  21. data/lib/swarm_sdk/concerns/validatable.rb +55 -0
  22. data/lib/swarm_sdk/configuration/parser.rb +353 -0
  23. data/lib/swarm_sdk/configuration/translator.rb +255 -0
  24. data/lib/swarm_sdk/configuration.rb +65 -543
  25. data/lib/swarm_sdk/context_compactor/token_counter.rb +3 -3
  26. data/lib/swarm_sdk/context_compactor.rb +6 -11
  27. data/lib/swarm_sdk/context_management/builder.rb +128 -0
  28. data/lib/swarm_sdk/context_management/context.rb +328 -0
  29. data/lib/swarm_sdk/defaults.rb +196 -0
  30. data/lib/swarm_sdk/events_to_messages.rb +18 -0
  31. data/lib/swarm_sdk/hooks/shell_executor.rb +2 -1
  32. data/lib/swarm_sdk/log_collector.rb +179 -29
  33. data/lib/swarm_sdk/log_stream.rb +29 -0
  34. data/lib/swarm_sdk/node_context.rb +1 -1
  35. data/lib/swarm_sdk/observer/builder.rb +81 -0
  36. data/lib/swarm_sdk/observer/config.rb +45 -0
  37. data/lib/swarm_sdk/observer/manager.rb +236 -0
  38. data/lib/swarm_sdk/patterns/agent_observer.rb +160 -0
  39. data/lib/swarm_sdk/plugin.rb +93 -3
  40. data/lib/swarm_sdk/snapshot.rb +6 -6
  41. data/lib/swarm_sdk/snapshot_from_events.rb +13 -2
  42. data/lib/swarm_sdk/state_restorer.rb +136 -151
  43. data/lib/swarm_sdk/state_snapshot.rb +65 -100
  44. data/lib/swarm_sdk/swarm/agent_initializer.rb +180 -136
  45. data/lib/swarm_sdk/swarm/builder.rb +44 -578
  46. data/lib/swarm_sdk/swarm/executor.rb +213 -0
  47. data/lib/swarm_sdk/swarm/hook_triggers.rb +150 -0
  48. data/lib/swarm_sdk/swarm/logging_callbacks.rb +340 -0
  49. data/lib/swarm_sdk/swarm/mcp_configurator.rb +7 -4
  50. data/lib/swarm_sdk/swarm/tool_configurator.rb +42 -138
  51. data/lib/swarm_sdk/swarm.rb +137 -679
  52. data/lib/swarm_sdk/tools/bash.rb +11 -3
  53. data/lib/swarm_sdk/tools/delegate.rb +61 -43
  54. data/lib/swarm_sdk/tools/edit.rb +8 -13
  55. data/lib/swarm_sdk/tools/glob.rb +9 -1
  56. data/lib/swarm_sdk/tools/grep.rb +7 -0
  57. data/lib/swarm_sdk/tools/multi_edit.rb +15 -11
  58. data/lib/swarm_sdk/tools/path_resolver.rb +51 -2
  59. data/lib/swarm_sdk/tools/read.rb +11 -13
  60. data/lib/swarm_sdk/tools/registry.rb +122 -10
  61. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +8 -5
  62. data/lib/swarm_sdk/tools/stores/storage.rb +0 -6
  63. data/lib/swarm_sdk/tools/todo_write.rb +7 -0
  64. data/lib/swarm_sdk/tools/web_fetch.rb +3 -2
  65. data/lib/swarm_sdk/tools/write.rb +8 -13
  66. data/lib/swarm_sdk/version.rb +1 -1
  67. data/lib/swarm_sdk/{node → workflow}/agent_config.rb +1 -1
  68. data/lib/swarm_sdk/workflow/builder.rb +143 -0
  69. data/lib/swarm_sdk/workflow/executor.rb +497 -0
  70. data/lib/swarm_sdk/{node/builder.rb → workflow/node_builder.rb} +3 -3
  71. data/lib/swarm_sdk/{node → workflow}/transformer_executor.rb +3 -2
  72. data/lib/swarm_sdk/{node_orchestrator.rb → workflow.rb} +152 -456
  73. data/lib/swarm_sdk.rb +33 -3
  74. metadata +67 -15
  75. data/lib/swarm_sdk/providers/openai_with_responses.rb +0 -589
@@ -0,0 +1,497 @@
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
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SwarmSDK
4
- module Node
5
- # Builder provides DSL for configuring nodes (mini-swarms within a workflow)
4
+ class Workflow
5
+ # NodeBuilder provides DSL for configuring individual nodes within a workflow
6
6
  #
7
7
  # A node represents a stage in a multi-step workflow where a specific set
8
8
  # of agents collaborate. Each node creates an independent swarm execution.
@@ -20,7 +20,7 @@ module SwarmSDK
20
20
  #
21
21
  # depends_on :planning
22
22
  # end
23
- class Builder
23
+ class NodeBuilder
24
24
  attr_reader :name,
25
25
  :agent_configs,
26
26
  :dependencies,
@@ -5,7 +5,7 @@ require "json"
5
5
  require "timeout"
6
6
 
7
7
  module SwarmSDK
8
- module Node
8
+ class Workflow
9
9
  # Executes bash command transformers for node input/output transformation
10
10
  #
11
11
  # Transformers are shell commands that receive NodeContext data on STDIN as JSON
@@ -90,7 +90,8 @@ module SwarmSDK
90
90
  # echo "$CONTENT"
91
91
  # exit 0
92
92
  class TransformerExecutor
93
- DEFAULT_TIMEOUT = 60
93
+ # Backward compatibility alias - use Defaults module for new code
94
+ DEFAULT_TIMEOUT = Defaults::Timeouts::TRANSFORMER_COMMAND_SECONDS
94
95
 
95
96
  # Result object for transformer execution
96
97
  TransformerResult = Struct.new(:success, :content, :skip_execution, :halt, :error_message, keyword_init: true) do