conductor_ruby 0.1.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 (143) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +142 -0
  3. data/LICENSE +190 -0
  4. data/README.md +517 -0
  5. data/examples/agentic_workflows/llm_chat.rb +106 -0
  6. data/examples/dynamic_workflow.rb +177 -0
  7. data/examples/event_handler.rb +94 -0
  8. data/examples/event_listener_examples.rb +430 -0
  9. data/examples/helloworld/greetings_worker.rb +24 -0
  10. data/examples/helloworld/helloworld.rb +99 -0
  11. data/examples/kitchensink.rb +213 -0
  12. data/examples/metadata_journey.rb +189 -0
  13. data/examples/metrics_example.rb +284 -0
  14. data/examples/new_dsl_demo.rb +141 -0
  15. data/examples/orkes/http_poll.rb +83 -0
  16. data/examples/orkes/secrets_example.rb +69 -0
  17. data/examples/orkes/wait_for_webhook.rb +90 -0
  18. data/examples/prompt_journey.rb +245 -0
  19. data/examples/rag_workflow.rb +167 -0
  20. data/examples/schedule_journey.rb +244 -0
  21. data/examples/simple_worker.rb +125 -0
  22. data/examples/simple_workflow.rb +89 -0
  23. data/examples/task_context_example.rb +257 -0
  24. data/examples/task_listener_example.rb +192 -0
  25. data/examples/worker_configuration_example.rb +282 -0
  26. data/examples/workflow_dsl.rb +316 -0
  27. data/examples/workflow_ops.rb +305 -0
  28. data/lib/conductor/client/authorization_client.rb +238 -0
  29. data/lib/conductor/client/integration_client.rb +108 -0
  30. data/lib/conductor/client/metadata_client.rb +139 -0
  31. data/lib/conductor/client/prompt_client.rb +58 -0
  32. data/lib/conductor/client/scheduler_client.rb +132 -0
  33. data/lib/conductor/client/schema_client.rb +32 -0
  34. data/lib/conductor/client/secret_client.rb +48 -0
  35. data/lib/conductor/client/task_client.rb +168 -0
  36. data/lib/conductor/client/workflow_client.rb +242 -0
  37. data/lib/conductor/configuration/authentication_settings.rb +17 -0
  38. data/lib/conductor/configuration.rb +103 -0
  39. data/lib/conductor/exceptions.rb +86 -0
  40. data/lib/conductor/http/api/application_resource_api.rb +107 -0
  41. data/lib/conductor/http/api/authorization_resource_api.rb +56 -0
  42. data/lib/conductor/http/api/event_resource_api.rb +133 -0
  43. data/lib/conductor/http/api/gateway_auth_resource_api.rb +48 -0
  44. data/lib/conductor/http/api/group_resource_api.rb +76 -0
  45. data/lib/conductor/http/api/integration_resource_api.rb +145 -0
  46. data/lib/conductor/http/api/metadata_resource_api.rb +231 -0
  47. data/lib/conductor/http/api/prompt_resource_api.rb +81 -0
  48. data/lib/conductor/http/api/role_resource_api.rb +60 -0
  49. data/lib/conductor/http/api/scheduler_resource_api.rb +211 -0
  50. data/lib/conductor/http/api/schema_resource_api.rb +82 -0
  51. data/lib/conductor/http/api/secret_resource_api.rb +134 -0
  52. data/lib/conductor/http/api/task_resource_api.rb +321 -0
  53. data/lib/conductor/http/api/token_resource_api.rb +42 -0
  54. data/lib/conductor/http/api/user_resource_api.rb +59 -0
  55. data/lib/conductor/http/api/workflow_bulk_resource_api.rb +91 -0
  56. data/lib/conductor/http/api/workflow_resource_api.rb +451 -0
  57. data/lib/conductor/http/api_client.rb +437 -0
  58. data/lib/conductor/http/models/authentication_config.rb +67 -0
  59. data/lib/conductor/http/models/authorization_request.rb +39 -0
  60. data/lib/conductor/http/models/base_model.rb +162 -0
  61. data/lib/conductor/http/models/bulk_response.rb +39 -0
  62. data/lib/conductor/http/models/conductor_application.rb +39 -0
  63. data/lib/conductor/http/models/conductor_user.rb +53 -0
  64. data/lib/conductor/http/models/create_or_update_application_request.rb +24 -0
  65. data/lib/conductor/http/models/create_or_update_role_request.rb +27 -0
  66. data/lib/conductor/http/models/event_handler.rb +130 -0
  67. data/lib/conductor/http/models/generate_token_request.rb +27 -0
  68. data/lib/conductor/http/models/group.rb +36 -0
  69. data/lib/conductor/http/models/integration.rb +70 -0
  70. data/lib/conductor/http/models/integration_api.rb +53 -0
  71. data/lib/conductor/http/models/integration_api_update.rb +43 -0
  72. data/lib/conductor/http/models/integration_update.rb +36 -0
  73. data/lib/conductor/http/models/permission.rb +24 -0
  74. data/lib/conductor/http/models/poll_data.rb +33 -0
  75. data/lib/conductor/http/models/prompt_template.rb +59 -0
  76. data/lib/conductor/http/models/prompt_template_test_request.rb +43 -0
  77. data/lib/conductor/http/models/rerun_workflow_request.rb +37 -0
  78. data/lib/conductor/http/models/role.rb +27 -0
  79. data/lib/conductor/http/models/schema_def.rb +59 -0
  80. data/lib/conductor/http/models/search_result.rb +187 -0
  81. data/lib/conductor/http/models/skip_task_request.rb +27 -0
  82. data/lib/conductor/http/models/start_workflow_request.rb +68 -0
  83. data/lib/conductor/http/models/subject_ref.rb +35 -0
  84. data/lib/conductor/http/models/tag_object.rb +36 -0
  85. data/lib/conductor/http/models/target_ref.rb +39 -0
  86. data/lib/conductor/http/models/task.rb +156 -0
  87. data/lib/conductor/http/models/task_def.rb +95 -0
  88. data/lib/conductor/http/models/task_exec_log.rb +30 -0
  89. data/lib/conductor/http/models/task_result.rb +115 -0
  90. data/lib/conductor/http/models/task_result_status.rb +24 -0
  91. data/lib/conductor/http/models/token.rb +33 -0
  92. data/lib/conductor/http/models/upsert_group_request.rb +30 -0
  93. data/lib/conductor/http/models/upsert_user_request.rb +39 -0
  94. data/lib/conductor/http/models/workflow.rb +202 -0
  95. data/lib/conductor/http/models/workflow_def.rb +73 -0
  96. data/lib/conductor/http/models/workflow_schedule.rb +100 -0
  97. data/lib/conductor/http/models/workflow_state_update.rb +30 -0
  98. data/lib/conductor/http/models/workflow_status_constants.rb +57 -0
  99. data/lib/conductor/http/models/workflow_task.rb +169 -0
  100. data/lib/conductor/http/models/workflow_test_request.rb +67 -0
  101. data/lib/conductor/http/rest_client.rb +211 -0
  102. data/lib/conductor/orkes/models/access_key.rb +56 -0
  103. data/lib/conductor/orkes/models/granted_permission.rb +27 -0
  104. data/lib/conductor/orkes/models/metadata_tag.rb +15 -0
  105. data/lib/conductor/orkes/models/rate_limit_tag.rb +15 -0
  106. data/lib/conductor/orkes/orkes_clients.rb +69 -0
  107. data/lib/conductor/version.rb +5 -0
  108. data/lib/conductor/worker/events/conductor_event.rb +40 -0
  109. data/lib/conductor/worker/events/global_dispatcher.rb +37 -0
  110. data/lib/conductor/worker/events/http_events.rb +25 -0
  111. data/lib/conductor/worker/events/listener_registry.rb +40 -0
  112. data/lib/conductor/worker/events/listeners.rb +34 -0
  113. data/lib/conductor/worker/events/sync_event_dispatcher.rb +78 -0
  114. data/lib/conductor/worker/events/task_runner_events.rb +271 -0
  115. data/lib/conductor/worker/events/workflow_events.rb +49 -0
  116. data/lib/conductor/worker/fiber_executor.rb +532 -0
  117. data/lib/conductor/worker/ractor_task_runner.rb +501 -0
  118. data/lib/conductor/worker/task_context.rb +114 -0
  119. data/lib/conductor/worker/task_definition_registrar.rb +322 -0
  120. data/lib/conductor/worker/task_handler.rb +360 -0
  121. data/lib/conductor/worker/task_in_progress.rb +60 -0
  122. data/lib/conductor/worker/task_runner.rb +538 -0
  123. data/lib/conductor/worker/telemetry/metrics_collector.rb +196 -0
  124. data/lib/conductor/worker/telemetry/prometheus_backend.rb +224 -0
  125. data/lib/conductor/worker/worker.rb +355 -0
  126. data/lib/conductor/worker/worker_config.rb +154 -0
  127. data/lib/conductor/worker/worker_registry.rb +71 -0
  128. data/lib/conductor/workflow/dsl/input_ref.rb +37 -0
  129. data/lib/conductor/workflow/dsl/output_ref.rb +44 -0
  130. data/lib/conductor/workflow/dsl/parallel_builder.rb +49 -0
  131. data/lib/conductor/workflow/dsl/switch_builder.rb +74 -0
  132. data/lib/conductor/workflow/dsl/task_ref.rb +178 -0
  133. data/lib/conductor/workflow/dsl/workflow_builder.rb +1016 -0
  134. data/lib/conductor/workflow/dsl/workflow_definition.rb +150 -0
  135. data/lib/conductor/workflow/llm/chat_message.rb +47 -0
  136. data/lib/conductor/workflow/llm/embedding_model.rb +19 -0
  137. data/lib/conductor/workflow/llm/tool_call.rb +43 -0
  138. data/lib/conductor/workflow/llm/tool_spec.rb +46 -0
  139. data/lib/conductor/workflow/task_type.rb +68 -0
  140. data/lib/conductor/workflow/timeout_policy.rb +31 -0
  141. data/lib/conductor/workflow/workflow_executor.rb +373 -0
  142. data/lib/conductor.rb +192 -0
  143. metadata +359 -0
@@ -0,0 +1,284 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Example demonstrating Prometheus metrics collection and HTTP endpoint exposure.
5
+ #
6
+ # This example shows how to:
7
+ # - Enable Prometheus metrics collection for task execution
8
+ # - Expose metrics via HTTP endpoint for scraping
9
+ # - Track task poll times, execution times, errors, and more
10
+ # - Integrate with Prometheus monitoring
11
+ #
12
+ # Metrics collected (canonical harmonized set):
13
+ # - task_poll_total: Total number of task polls
14
+ # - task_poll_time_seconds: Task poll duration (with status label)
15
+ # - task_poll_error_total: Poll errors by exception type
16
+ # - task_execution_started_total: Tasks dispatched to worker function
17
+ # - task_execute_time_seconds: Task execution duration (with status label)
18
+ # - task_execute_error_total: Execution errors by exception
19
+ # - task_result_size_bytes: Task result payload size
20
+ # - task_update_error_total: Failed task updates (CRITICAL)
21
+ # - task_update_time_seconds: Task result update latency
22
+ # - task_paused_total: Workers paused
23
+ # - active_workers: Current active worker count (gauge)
24
+ # - workflow_start_error_total: Workflow start failures
25
+ # - workflow_input_size_bytes: Workflow input payload size
26
+ # - http_api_client_request_seconds: HTTP API client latency
27
+ # - thread_uncaught_exceptions_total: Uncaught thread exceptions
28
+ #
29
+ # Requirements:
30
+ # gem 'prometheus-client'
31
+ #
32
+ # Usage:
33
+ # 1. Run this example: ruby metrics_example.rb
34
+ # 2. View metrics: curl http://localhost:9090/metrics
35
+ # 3. Configure Prometheus to scrape: http://localhost:9090/metrics
36
+ #
37
+ # Environment variables:
38
+ # CONDUCTOR_SERVER_URL - Conductor server URL (default: http://localhost:8080/api)
39
+ # CONDUCTOR_AUTH_KEY - Authentication key (optional)
40
+ # CONDUCTOR_AUTH_SECRET - Authentication secret (optional)
41
+ # METRICS_PORT - Port for metrics HTTP server (default: 9090)
42
+ #
43
+
44
+ require 'bundler/setup'
45
+ require 'conductor'
46
+
47
+ # Check if prometheus-client is available
48
+ begin
49
+ require 'prometheus/client'
50
+ PROMETHEUS_AVAILABLE = true
51
+ rescue LoadError
52
+ PROMETHEUS_AVAILABLE = false
53
+ puts 'WARNING: prometheus-client gem not installed.'
54
+ puts 'Install with: gem install prometheus-client'
55
+ puts "Or add to Gemfile: gem 'prometheus-client'"
56
+ puts
57
+ end
58
+
59
+ #
60
+ # Example worker tasks
61
+ #
62
+
63
+ # Async HTTP simulation worker
64
+ Conductor::Worker.define('async_http_task', poll_interval: 100, thread_count: 10) do |task|
65
+ url = task.input_data['url'] || 'https://api.example.com/data'
66
+ delay = task.input_data['delay'] || 0.1
67
+
68
+ # Simulate async HTTP request
69
+ sleep(delay)
70
+
71
+ {
72
+ url: url,
73
+ status: 'success',
74
+ timestamp: Time.now.iso8601
75
+ }
76
+ end
77
+
78
+ # Data processor worker
79
+ Conductor::Worker.define('async_data_processor', poll_interval: 100, thread_count: 10) do |task|
80
+ data = task.input_data['data'] || 'sample data'
81
+ process_time = task.input_data['process_time'] || 0.5
82
+
83
+ # Simulate data processing
84
+ sleep(process_time)
85
+
86
+ # Process the data
87
+ processed = data.upcase
88
+
89
+ {
90
+ original: data,
91
+ processed: processed,
92
+ length: processed.length,
93
+ processed_at: Time.now.iso8601
94
+ }
95
+ end
96
+
97
+ # Batch processor worker
98
+ Conductor::Worker.define('async_batch_processor', poll_interval: 100, thread_count: 5) do |task|
99
+ items = task.input_data['items'] || []
100
+
101
+ # Process all items
102
+ results = items.map do |item|
103
+ sleep(0.01) # Simulate I/O operation
104
+ "processed_#{item}"
105
+ end
106
+
107
+ {
108
+ input_count: items.size,
109
+ results: results,
110
+ completed_at: Time.now.iso8601
111
+ }
112
+ end
113
+
114
+ # CPU-bound worker
115
+ Conductor::Worker.define('sync_cpu_task', poll_interval: 100, thread_count: 5) do |task|
116
+ n = task.input_data['n'] || 100_000
117
+
118
+ # CPU-bound calculation
119
+ result = (0...n).sum { |i| i * i }
120
+
121
+ { result: result }
122
+ end
123
+
124
+ def main
125
+ metrics_port = (ENV['METRICS_PORT'] || 9090).to_i
126
+
127
+ # Configure Conductor connection
128
+ config = Conductor::Configuration.new(
129
+ server_api_url: ENV['CONDUCTOR_SERVER_URL'] || 'http://localhost:8080/api',
130
+ auth_key: ENV.fetch('CONDUCTOR_AUTH_KEY', nil),
131
+ auth_secret: ENV.fetch('CONDUCTOR_AUTH_SECRET', nil)
132
+ )
133
+
134
+ puts '=' * 80
135
+ puts 'Metrics Collection Example'
136
+ puts '=' * 80
137
+ puts
138
+
139
+ if PROMETHEUS_AVAILABLE
140
+ # Create metrics collector with Prometheus backend
141
+ metrics = Conductor::Worker::Telemetry::MetricsCollector.create(backend: :prometheus)
142
+
143
+ # Start metrics HTTP server
144
+ metrics_server = Conductor::Worker::Telemetry::MetricsServer.new(port: metrics_port)
145
+ metrics_server.start
146
+
147
+ puts 'Metrics mode: Prometheus HTTP'
148
+ puts "Metrics HTTP endpoint: http://localhost:#{metrics_port}/metrics"
149
+ puts "Health check: http://localhost:#{metrics_port}/health"
150
+ else
151
+ # Fall back to null metrics (logging only)
152
+ metrics = Conductor::Worker::Telemetry::MetricsCollector.create(backend: :null)
153
+ metrics_server = nil
154
+
155
+ puts 'Metrics mode: Null (prometheus-client gem not installed)'
156
+ puts 'To enable Prometheus metrics, install: gem install prometheus-client'
157
+ end
158
+
159
+ puts
160
+ puts 'Workers available:'
161
+ puts ' - async_http_task: Simulated HTTP requests (I/O-bound)'
162
+ puts ' - async_data_processor: Data processing'
163
+ puts ' - async_batch_processor: Batch processing'
164
+ puts ' - sync_cpu_task: CPU-bound calculations'
165
+ puts
166
+ puts 'Try these commands:'
167
+ puts " curl http://localhost:#{metrics_port}/metrics"
168
+ puts " watch -n 1 'curl -s http://localhost:#{metrics_port}/metrics | grep task_poll_total'"
169
+ puts
170
+ puts 'Press Ctrl+C to stop...'
171
+ puts '=' * 80
172
+ puts
173
+
174
+ begin
175
+ # Create task handler with metrics enabled
176
+ handler = Conductor::Worker::TaskHandler.new(
177
+ configuration: config,
178
+ scan_for_annotated_workers: true,
179
+ event_listeners: [metrics]
180
+ )
181
+
182
+ # Handle graceful shutdown
183
+ shutdown = false
184
+ Signal.trap('INT') do
185
+ puts "\nShutting down gracefully..."
186
+ shutdown = true
187
+ handler.stop
188
+ end
189
+
190
+ handler.start
191
+ handler.join unless shutdown
192
+ rescue Interrupt
193
+ puts 'Interrupted!'
194
+ rescue StandardError => e
195
+ puts "Error: #{e.message}"
196
+ puts e.backtrace.first(5).join("\n")
197
+ ensure
198
+ metrics_server&.stop
199
+ end
200
+
201
+ puts "\nWorkers stopped. Goodbye!"
202
+ end
203
+
204
+ # Alternative: Custom metrics backend example
205
+ class CustomMetricsBackend
206
+ def initialize
207
+ @counters = Hash.new(0)
208
+ @histograms = Hash.new { |h, k| h[k] = [] }
209
+ @mutex = Mutex.new
210
+ end
211
+
212
+ def increment(name, labels: {})
213
+ key = "#{name}:#{labels.sort.to_h}"
214
+ @mutex.synchronize { @counters[key] += 1 }
215
+ end
216
+
217
+ def observe(name, value, labels: {})
218
+ key = "#{name}:#{labels.sort.to_h}"
219
+ @mutex.synchronize { @histograms[key] << value }
220
+ end
221
+
222
+ def set(name, value, labels: {})
223
+ key = "#{name}:#{labels.sort.to_h}"
224
+ @mutex.synchronize { @counters[key] = value }
225
+ end
226
+
227
+ def report
228
+ @mutex.synchronize do
229
+ puts "\n--- Custom Metrics Report ---"
230
+ puts 'Counters:'
231
+ @counters.each { |k, v| puts " #{k}: #{v}" }
232
+ puts 'Histograms:'
233
+ @histograms.each do |k, values|
234
+ next if values.empty?
235
+
236
+ avg = values.sum / values.size
237
+ puts " #{k}: count=#{values.size}, avg=#{avg.round(2)}, min=#{values.min.round(2)}, max=#{values.max.round(2)}"
238
+ end
239
+ puts '-----------------------------'
240
+ end
241
+ end
242
+ end
243
+
244
+ def main_with_custom_backend
245
+ # Example using a custom metrics backend
246
+ config = Conductor::Configuration.new(
247
+ server_api_url: ENV['CONDUCTOR_SERVER_URL'] || 'http://localhost:8080/api'
248
+ )
249
+
250
+ custom_backend = CustomMetricsBackend.new
251
+ metrics = Conductor::Worker::Telemetry::MetricsCollector.create(backend: custom_backend)
252
+
253
+ puts 'Using custom metrics backend...'
254
+
255
+ handler = Conductor::Worker::TaskHandler.new(
256
+ configuration: config,
257
+ scan_for_annotated_workers: true,
258
+ event_listeners: [metrics]
259
+ )
260
+
261
+ # Report metrics periodically
262
+ reporter = Thread.new do
263
+ loop do
264
+ sleep 30
265
+ custom_backend.report
266
+ end
267
+ end
268
+
269
+ Signal.trap('INT') do
270
+ reporter.kill
271
+ handler.stop
272
+ end
273
+
274
+ handler.start
275
+ handler.join
276
+
277
+ custom_backend.report
278
+ end
279
+
280
+ if __FILE__ == $PROGRAM_NAME
281
+ # Run with Prometheus by default
282
+ # Use main_with_custom_backend for custom backend example
283
+ main
284
+ end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Example demonstrating the new Ruby-idiomatic DSL for Conductor workflows
4
+ #
5
+ # This example shows how to use the new DSL which provides:
6
+ # - Method-per-type approach: simple(), http(), parallel(), etc.
7
+ # - Auto-generated reference names
8
+ # - Hash-style [] for output references
9
+ # - wf[:param] for workflow inputs
10
+ # - Block-based control flow
11
+
12
+ require 'bundler/setup'
13
+ require 'conductor'
14
+
15
+ # Define a workflow using the new DSL
16
+ # Note: executor is optional - only needed for .register() and .execute()
17
+ workflow = Conductor.workflow :order_processing_demo, version: 1 do
18
+ # Access workflow inputs with wf[:param_name]
19
+ # Returns: "${workflow.input.param_name}"
20
+ order_id_input = wf[:order_id]
21
+ user_email_input = wf[:user_email]
22
+
23
+ # Simple tasks return TaskRef objects
24
+ # Access task outputs with task[:field_name]
25
+ user = simple :get_user, user_id: order_id_input
26
+ order = simple :validate_order,
27
+ order_id: order_id_input,
28
+ user_email: user[:email]
29
+
30
+ # HTTP tasks with fluent syntax
31
+ api_response = http :call_payment_api,
32
+ url: 'https://api.payment.example.com/charge',
33
+ method: :post,
34
+ body: {
35
+ amount: order[:total],
36
+ currency: 'USD',
37
+ customer: user[:id]
38
+ },
39
+ headers: {
40
+ 'Authorization' => 'Bearer ${workflow.input.api_key}',
41
+ 'Content-Type' => 'application/json'
42
+ }
43
+
44
+ # Parallel execution - tasks run concurrently
45
+ parallel do
46
+ simple :ship_order,
47
+ order_id: order[:id],
48
+ address: order[:shipping_address]
49
+
50
+ simple :send_confirmation_email,
51
+ to: user_email_input,
52
+ order_id: order[:id]
53
+
54
+ simple :update_inventory,
55
+ product_id: order[:product_id],
56
+ quantity: order[:quantity]
57
+ end
58
+
59
+ # Switch/decision based on output value
60
+ decide order[:region] do
61
+ on 'US' do
62
+ simple :calculate_us_tax, amount: order[:total]
63
+ simple :apply_us_shipping
64
+ end
65
+
66
+ on 'EU' do
67
+ simple :calculate_vat, amount: order[:total]
68
+ simple :apply_eu_shipping
69
+ end
70
+
71
+ on 'UK' do
72
+ simple :calculate_uk_tax, amount: order[:total]
73
+ simple :apply_uk_shipping
74
+ end
75
+
76
+ otherwise do
77
+ simple :apply_international_shipping
78
+ terminate :completed, 'International order processed with standard shipping'
79
+ end
80
+ end
81
+
82
+ # Set workflow variables
83
+ set(
84
+ order_status: 'completed',
85
+ processed_at: '${CPEWF_EPOCH}',
86
+ processor_id: wf[:worker_id]
87
+ )
88
+
89
+ # Define workflow outputs
90
+ output(
91
+ order_id: order[:id],
92
+ status: 'processed',
93
+ total: order[:total],
94
+ payment_result: api_response[:transaction_id]
95
+ )
96
+ end
97
+
98
+ # Display workflow information
99
+ puts "Workflow created: #{workflow.name} (version #{workflow.version})"
100
+ puts "Task count: #{workflow.builder.tasks.size}"
101
+
102
+ # Convert to WorkflowDef to see the generated structure
103
+ workflow_def = workflow.to_workflow_def
104
+ puts "\nGenerated WorkflowDef:"
105
+ puts " Name: #{workflow_def.name}"
106
+ puts " Version: #{workflow_def.version}"
107
+ puts " Tasks: #{workflow_def.tasks.size}"
108
+ puts "\nFirst 3 tasks:"
109
+ workflow_def.tasks.take(3).each_with_index do |task, idx|
110
+ puts " #{idx + 1}. #{task.type}: #{task.task_reference_name}"
111
+ end
112
+
113
+ puts "\nOutput parameters: #{workflow_def.output_parameters.keys.join(', ')}"
114
+
115
+ puts "\n✓ DSL example completed successfully!"
116
+ puts "\nTo register and execute this workflow, provide an executor:"
117
+ puts <<~EXAMPLE
118
+
119
+ # Create executor with configuration
120
+ config = Conductor::Configuration.new
121
+ config.server_url = 'http://localhost:8080/api'
122
+ executor = Conductor::Workflow::WorkflowExecutor.new(config)
123
+
124
+ # Define workflow with executor
125
+ workflow = Conductor.workflow :order_processing_demo, version: 1, executor: executor do
126
+ # ... workflow definition ...
127
+ end
128
+
129
+ # Register the workflow
130
+ workflow.register(overwrite: true)
131
+
132
+ # Execute the workflow
133
+ result = workflow.execute(input: {
134
+ order_id: '12345',
135
+ user_email: 'customer@example.com',
136
+ api_key: 'secret_key',
137
+ worker_id: 'worker-001'
138
+ })
139
+
140
+ puts "Workflow status: \#{result.status}"
141
+ EXAMPLE
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # HTTP Poll Task Example
5
+ #
6
+ # Demonstrates using HTTP_POLL task to poll an external API
7
+ # until a condition is met or timeout occurs.
8
+ #
9
+ # Usage:
10
+ # bundle exec ruby examples/orkes/http_poll.rb
11
+
12
+ require_relative '../../lib/conductor'
13
+
14
+ include Conductor::Workflow
15
+
16
+ def create_http_poll_workflow(workflow_client, workflow_executor)
17
+ workflow = ConductorWorkflow.new(
18
+ workflow_client,
19
+ 'http_poll_workflow_ruby',
20
+ version: 1,
21
+ executor: workflow_executor
22
+ )
23
+ workflow.description('Workflow demonstrating HTTP polling')
24
+
25
+ # HTTP Poll task - polls until condition is met
26
+ poll_task = HttpPollTask.new('poll_status', {
27
+ 'uri' => 'https://httpbin.org/json',
28
+ 'method' => 'GET',
29
+ 'connectionTimeOut' => 5000,
30
+ 'readTimeOut' => 5000
31
+ })
32
+ poll_task.input('terminalCondition', '$.slideshow != null')
33
+ poll_task.input('pollingInterval', 2)
34
+ poll_task.input('pollingStrategy', 'FIXED')
35
+
36
+ # Process the result
37
+ process = SimpleTask.new('process_result', 'process_ref')
38
+ .input('data', poll_task.output('response'))
39
+
40
+ workflow >> poll_task >> process
41
+ workflow.output_parameter('result', process.output)
42
+
43
+ workflow
44
+ end
45
+
46
+ def main
47
+ config = Conductor::Configuration.new
48
+
49
+ puts '=' * 70
50
+ puts 'HTTP Poll Task Example'
51
+ puts '=' * 70
52
+ puts "Server: #{config.server_url}"
53
+ puts
54
+
55
+ clients = Conductor::Orkes::OrkesClients.new(config)
56
+ workflow_executor = clients.get_workflow_executor
57
+ workflow_client = clients.get_workflow_client
58
+
59
+ workflow = create_http_poll_workflow(workflow_client, workflow_executor)
60
+ workflow_executor.register_workflow(workflow, overwrite: true)
61
+ puts "Registered workflow: #{workflow.name}"
62
+
63
+ puts "\nExecuting HTTP poll workflow..."
64
+ result = workflow_executor.execute(
65
+ workflow.name,
66
+ input: {},
67
+ wait_for_seconds: 30
68
+ )
69
+
70
+ puts "Status: #{result.status}"
71
+ puts "Output: #{result.output.inspect}"
72
+ puts "\nView at: #{config.ui_host}/execution/#{result.workflow_id}"
73
+ end
74
+
75
+ if __FILE__ == $PROGRAM_NAME
76
+ begin
77
+ main
78
+ rescue Conductor::ApiError => e
79
+ puts "API Error: #{e.message}"
80
+ rescue StandardError => e
81
+ puts "Error: #{e.message}"
82
+ end
83
+ end
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Secrets Management Example
5
+ #
6
+ # Demonstrates using the SecretClient to manage sensitive data
7
+ # like API keys, passwords, and tokens.
8
+ #
9
+ # Usage:
10
+ # bundle exec ruby examples/orkes/secrets_example.rb
11
+
12
+ require_relative '../../lib/conductor'
13
+
14
+ def main
15
+ config = Conductor::Configuration.new
16
+ clients = Conductor::Orkes::OrkesClients.new(config)
17
+ secret_client = clients.get_secret_client
18
+
19
+ puts '=' * 70
20
+ puts 'Secrets Management Example'
21
+ puts '=' * 70
22
+ puts "Server: #{config.server_url}"
23
+ puts
24
+
25
+ secret_name = "test_api_key_ruby_#{Time.now.to_i}"
26
+
27
+ begin
28
+ # Create a secret
29
+ puts "Creating secret: #{secret_name}"
30
+ secret_client.put_secret(secret_name, 'super-secret-value-12345')
31
+ puts 'Secret created successfully'
32
+
33
+ # List all secrets (names only - values are never exposed)
34
+ puts "\nListing all secrets:"
35
+ secrets = secret_client.list_all_secret_names
36
+ secrets.first(5).each { |s| puts " - #{s}" }
37
+ puts " ... (#{secrets.length} total)"
38
+
39
+ # Check if secret exists
40
+ exists = secret_client.secret_exists?(secret_name)
41
+ puts "\nSecret '#{secret_name}' exists: #{exists}"
42
+
43
+ # Update the secret
44
+ puts "\nUpdating secret..."
45
+ secret_client.put_secret(secret_name, 'updated-secret-value-67890')
46
+ puts 'Secret updated'
47
+ ensure
48
+ # Clean up
49
+ puts "\nDeleting secret..."
50
+ begin
51
+ secret_client.delete_secret(secret_name)
52
+ puts 'Secret deleted'
53
+ rescue StandardError => e
54
+ puts "Could not delete: #{e.message}"
55
+ end
56
+ end
57
+
58
+ puts "\nSecrets example complete!"
59
+ end
60
+
61
+ if __FILE__ == $PROGRAM_NAME
62
+ begin
63
+ main
64
+ rescue Conductor::ApiError => e
65
+ puts "API Error: #{e.message}"
66
+ rescue StandardError => e
67
+ puts "Error: #{e.message}"
68
+ end
69
+ end
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Wait for Webhook Example
5
+ #
6
+ # Demonstrates using the WAIT_FOR_WEBHOOK task to pause workflow
7
+ # execution until an external webhook is received.
8
+ #
9
+ # Usage:
10
+ # bundle exec ruby examples/orkes/wait_for_webhook.rb
11
+
12
+ require_relative '../../lib/conductor'
13
+
14
+ include Conductor::Workflow
15
+
16
+ def create_webhook_workflow(workflow_client, workflow_executor)
17
+ workflow = ConductorWorkflow.new(
18
+ workflow_client,
19
+ 'webhook_workflow_ruby',
20
+ version: 1,
21
+ executor: workflow_executor
22
+ )
23
+ workflow.description('Workflow that waits for external webhook')
24
+
25
+ # Initial task
26
+ init = SimpleTask.new('init_process', 'init_ref')
27
+ .input('order_id', workflow.input('order_id'))
28
+
29
+ # Wait for webhook - pauses until external signal received
30
+ wait_webhook = WaitForWebhookTask.new('wait_for_payment')
31
+ .input('matches', {
32
+ '$.[?(@.order_id == "${workflow.input.order_id}")]' => true
33
+ })
34
+
35
+ # Process after webhook received
36
+ process = SimpleTask.new('process_payment', 'process_ref')
37
+ .input('payment_data', '${wait_for_payment.output}')
38
+
39
+ workflow >> init >> wait_webhook >> process
40
+
41
+ workflow.output_parameter('result', '${process_ref.output}')
42
+ workflow
43
+ end
44
+
45
+ def main
46
+ config = Conductor::Configuration.new
47
+
48
+ puts '=' * 70
49
+ puts 'Wait for Webhook Example'
50
+ puts '=' * 70
51
+ puts "Server: #{config.server_url}"
52
+ puts
53
+
54
+ clients = Conductor::Orkes::OrkesClients.new(config)
55
+ workflow_executor = clients.get_workflow_executor
56
+ workflow_client = clients.get_workflow_client
57
+
58
+ workflow = create_webhook_workflow(workflow_client, workflow_executor)
59
+ workflow_executor.register_workflow(workflow, overwrite: true)
60
+ puts "Registered workflow: #{workflow.name}"
61
+
62
+ # Start workflow
63
+ workflow_id = workflow_executor.start_workflow(
64
+ Conductor::Http::Models::StartWorkflowRequest.new(
65
+ name: workflow.name,
66
+ version: 1,
67
+ input: { 'order_id' => 'ORD-12345' }
68
+ )
69
+ )
70
+
71
+ puts "Started workflow: #{workflow_id}"
72
+ puts
73
+ puts 'Workflow is now waiting for webhook...'
74
+ puts
75
+ puts 'To complete the workflow, send a webhook:'
76
+ puts " POST #{config.server_url}/webhook/#{workflow.name}"
77
+ puts ' Body: {"order_id": "ORD-12345", "payment_status": "completed"}'
78
+ puts
79
+ puts "Monitor at: #{config.ui_host}/execution/#{workflow_id}"
80
+ end
81
+
82
+ if __FILE__ == $PROGRAM_NAME
83
+ begin
84
+ main
85
+ rescue Conductor::ApiError => e
86
+ puts "API Error: #{e.message}"
87
+ rescue StandardError => e
88
+ puts "Error: #{e.message}"
89
+ end
90
+ end