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,125 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Simple example demonstrating worker functionality with Conductor Ruby SDK
5
+ #
6
+ # Prerequisites:
7
+ # 1. Conductor server running on localhost:7001 (OSS version)
8
+ # 2. A workflow that uses 'simple_task' task type
9
+ # 3. Bundle install completed
10
+ #
11
+ # Usage:
12
+ # bundle exec ruby examples/simple_worker.rb
13
+
14
+ require_relative '../lib/conductor'
15
+
16
+ puts '=' * 60
17
+ puts 'Conductor Ruby SDK - Simple Worker Example'
18
+ puts '=' * 60
19
+
20
+ # Configure Conductor client
21
+ config = Conductor::Configuration.new(
22
+ server_api_url: 'http://localhost:7001/api'
23
+ )
24
+
25
+ puts "\nConnecting to Conductor server at: #{config.server_url}"
26
+
27
+ # Example 1: Class-based worker
28
+ class SimpleWorker
29
+ include Conductor::Worker::WorkerModule
30
+
31
+ worker_task 'simple_task', poll_interval: 1
32
+
33
+ def execute(task)
34
+ puts "\n[SimpleWorker] Executing task: #{task.task_id}"
35
+ puts "[SimpleWorker] Input: #{task.input_data.inspect}"
36
+
37
+ # Get input
38
+ name = get_input(task, 'name', 'World')
39
+
40
+ # Do some work
41
+ sleep 0.5
42
+ result_message = "Hello, #{name}!"
43
+
44
+ # Create result
45
+ result = Conductor::Http::Models::TaskResult.complete
46
+ result.add_output_data('message', result_message)
47
+ result.add_output_data('processed_at', Time.now.to_s)
48
+ result.log("Processed greeting for #{name}")
49
+
50
+ puts "[SimpleWorker] Result: #{result_message}"
51
+ result
52
+ end
53
+ end
54
+
55
+ # Example 2: Block-based worker
56
+ math_worker = Conductor::Worker.define('math_task', poll_interval: 1) do |task|
57
+ puts "\n[MathWorker] Executing task: #{task.task_id}"
58
+ puts "[MathWorker] Input: #{task.input_data.inspect}"
59
+
60
+ a = task.input_data['a'] || 0
61
+ b = task.input_data['b'] || 0
62
+ operation = task.input_data['operation'] || 'add'
63
+
64
+ result = case operation
65
+ when 'add' then a + b
66
+ when 'subtract' then a - b
67
+ when 'multiply' then a * b
68
+ when 'divide' then b.zero? ? 'Error: Division by zero' : a / b
69
+ else 'Unknown operation'
70
+ end
71
+
72
+ puts "[MathWorker] Result: #{a} #{operation} #{b} = #{result}"
73
+
74
+ # Return hash - will be converted to TaskResult automatically
75
+ { result: result, operation: operation }
76
+ end
77
+
78
+ # Example 3: Worker that can fail
79
+ failing_worker = Conductor::Worker.define('failing_task', poll_interval: 1) do |task|
80
+ puts "\n[FailingWorker] Executing task: #{task.task_id}"
81
+
82
+ should_fail = task.input_data['should_fail']
83
+
84
+ if should_fail
85
+ # Return a failed result
86
+ result = Conductor::Http::Models::TaskResult.failed('Task failed as requested')
87
+ result.log('This task was designed to fail')
88
+ result
89
+ else
90
+ # Return success
91
+ { status: 'success', message: 'Task completed successfully' }
92
+ end
93
+ end
94
+
95
+ # Create and configure task runner
96
+ puts "\n[TaskRunner] Creating task runner..."
97
+ runner = Conductor::Worker::TaskRunner.new(config)
98
+
99
+ # Register workers
100
+ runner.register_worker(SimpleWorker.new)
101
+ runner.register_worker(math_worker)
102
+ runner.register_worker(failing_worker)
103
+
104
+ puts '[TaskRunner] Registered 3 workers:'
105
+ puts ' - simple_task (class-based)'
106
+ puts ' - math_task (block-based)'
107
+ puts ' - failing_task (block-based)'
108
+
109
+ # Start the runner
110
+ puts "\n[TaskRunner] Starting worker threads..."
111
+ runner.start(threads: 1)
112
+
113
+ puts "\nWorkers are now polling for tasks. Press Ctrl+C to stop."
114
+ puts '=' * 60
115
+
116
+ # Keep the main thread alive and handle Ctrl+C gracefully
117
+ trap('INT') do
118
+ puts "\n\nReceived interrupt signal..."
119
+ runner.stop
120
+ puts 'Workers stopped. Exiting.'
121
+ exit 0
122
+ end
123
+
124
+ # Keep running
125
+ sleep while runner.running?
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Simple example demonstrating basic workflow operations with Conductor Ruby SDK
5
+ #
6
+ # Prerequisites:
7
+ # 1. Conductor server running on localhost:7001 (OSS version)
8
+ # 2. Bundle install completed
9
+ #
10
+ # Usage:
11
+ # bundle exec ruby examples/simple_workflow.rb
12
+
13
+ require_relative '../lib/conductor'
14
+
15
+ puts '=' * 60
16
+ puts 'Conductor Ruby SDK - Simple Workflow Example'
17
+ puts '=' * 60
18
+
19
+ # Configure Conductor client
20
+ config = Conductor::Configuration.new(
21
+ server_api_url: 'http://localhost:7001/api'
22
+ )
23
+
24
+ puts "\nConnecting to Conductor server at: #{config.server_url}"
25
+
26
+ # Create workflow client
27
+ workflow_client = Conductor::Client::WorkflowClient.new(config)
28
+
29
+ puts 'Workflow client created successfully!'
30
+
31
+ # Example 1: Start a workflow
32
+ puts "\n[Example 1] Starting a workflow..."
33
+ begin
34
+ request = Conductor::Http::Models::StartWorkflowRequest.new(
35
+ name: 'my_workflow',
36
+ version: 1,
37
+ input: {
38
+ 'param1' => 'value1',
39
+ 'param2' => 42
40
+ }
41
+ )
42
+
43
+ workflow_id = workflow_client.start_workflow(request)
44
+ puts " ✓ Workflow started with ID: #{workflow_id}"
45
+
46
+ # Get workflow status
47
+ puts "\n[Example 2] Getting workflow status..."
48
+ workflow = workflow_client.get_workflow(workflow_id)
49
+ puts " ✓ Workflow status: #{begin
50
+ workflow['status']
51
+ rescue StandardError
52
+ 'N/A'
53
+ end}"
54
+ rescue Conductor::ApiError => e
55
+ puts " ✗ Error: #{e.message}"
56
+ puts " Note: Make sure the workflow 'my_workflow' is registered in Conductor"
57
+ end
58
+
59
+ # Example 3: Using convenience method
60
+ puts "\n[Example 3] Starting workflow with convenience method..."
61
+ begin
62
+ workflow_id = workflow_client.start(
63
+ 'my_workflow',
64
+ input: { 'key' => 'value' },
65
+ correlation_id: 'example-123'
66
+ )
67
+ puts " ✓ Workflow started with ID: #{workflow_id}"
68
+ rescue Conductor::ApiError => e
69
+ puts " ✗ Error: #{e.message}"
70
+ end
71
+
72
+ # Example 4: Task result creation
73
+ puts "\n[Example 4] Creating task results..."
74
+ result = Conductor::Http::Models::TaskResult.complete
75
+ result.add_output_data('result', 'success')
76
+ result.log('Task completed successfully')
77
+ puts " ✓ Task result created with status: #{result.status}"
78
+ puts " ✓ Output data: #{result.output_data.inspect}"
79
+ puts " ✓ Logs: #{result.logs.inspect}"
80
+
81
+ # Example 5: WorkflowStatus constants
82
+ puts "\n[Example 5] Workflow status constants..."
83
+ puts " All statuses: #{Conductor::Http::Models::WorkflowStatusConstants::ALL.join(', ')}"
84
+ puts " Running? #{Conductor::Http::Models::WorkflowStatusConstants.running?('RUNNING')}"
85
+ puts " Terminal? #{Conductor::Http::Models::WorkflowStatusConstants.terminal?('COMPLETED')}"
86
+
87
+ puts "\n#{'=' * 60}"
88
+ puts 'Example completed!'
89
+ puts '=' * 60
@@ -0,0 +1,257 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Task Context Example
5
+ #
6
+ # Demonstrates how to use TaskContext to access task information and modify
7
+ # task results during execution.
8
+ #
9
+ # The TaskContext provides:
10
+ # - Access to task metadata (task_id, workflow_id, retry_count, etc.)
11
+ # - Ability to add logs visible in Conductor UI
12
+ # - Ability to set callback delays for polling/retry patterns
13
+ # - Access to input parameters
14
+ #
15
+ # Usage:
16
+ # ruby examples/task_context_example.rb
17
+ #
18
+ # Environment variables:
19
+ # CONDUCTOR_SERVER_URL - Conductor server URL (default: http://localhost:8080/api)
20
+ # CONDUCTOR_AUTH_KEY - Authentication key (optional)
21
+ # CONDUCTOR_AUTH_SECRET - Authentication secret (optional)
22
+ #
23
+
24
+ require 'bundler/setup'
25
+ require 'conductor'
26
+
27
+ #
28
+ # Example 1: Basic TaskContext usage - accessing task info
29
+ #
30
+ Conductor::Worker.define('task_info_example', poll_interval: 100, thread_count: 5) do |task|
31
+ # Get the current task context
32
+ ctx = Conductor::Worker::TaskContext.current
33
+
34
+ # Access task information
35
+ task_id = ctx.task_id
36
+ workflow_id = ctx.workflow_instance_id
37
+ retry_count = ctx.retry_count
38
+ poll_count = task.poll_count || 0
39
+
40
+ puts "Task ID: #{task_id}"
41
+ puts "Workflow ID: #{workflow_id}"
42
+ puts "Retry Count: #{retry_count}"
43
+ puts "Poll Count: #{poll_count}"
44
+
45
+ {
46
+ task_id: task_id,
47
+ workflow_id: workflow_id,
48
+ retry_count: retry_count,
49
+ result: 'processed'
50
+ }
51
+ end
52
+
53
+ #
54
+ # Example 2: Adding logs via TaskContext
55
+ #
56
+ Conductor::Worker.define('logging_example', poll_interval: 100, thread_count: 5) do |task|
57
+ order_id = task.input_data['order_id'] || 'unknown'
58
+ items = task.input_data['items'] || []
59
+
60
+ ctx = Conductor::Worker::TaskContext.current
61
+
62
+ # Add logs as processing progresses
63
+ ctx.add_log("Starting to process order #{order_id}")
64
+ ctx.add_log("Order has #{items.size} items")
65
+
66
+ items.each_with_index do |item, i|
67
+ sleep(0.1) # Simulate processing
68
+ ctx.add_log("Processed item #{i + 1}/#{items.size}: #{item}")
69
+ end
70
+
71
+ ctx.add_log('Order processing completed')
72
+
73
+ {
74
+ order_id: order_id,
75
+ items_processed: items.size,
76
+ status: 'completed'
77
+ }
78
+ end
79
+
80
+ #
81
+ # Example 3: Callback pattern - polling external service
82
+ #
83
+ Conductor::Worker.define('polling_example', poll_interval: 100, thread_count: 10) do |task|
84
+ job_id = task.input_data['job_id'] || 'unknown'
85
+
86
+ ctx = Conductor::Worker::TaskContext.current
87
+
88
+ ctx.add_log("Checking status of job #{job_id}")
89
+
90
+ # Simulate checking external service
91
+ is_complete = rand > 0.7 # 30% chance of completion
92
+
93
+ if is_complete
94
+ ctx.add_log("Job #{job_id} is complete!")
95
+ {
96
+ job_id: job_id,
97
+ status: 'completed',
98
+ result: 'Job finished successfully'
99
+ }
100
+ else
101
+ # Job still running - poll again in 30 seconds
102
+ ctx.add_log("Job #{job_id} still running, will check again in 30s")
103
+ ctx.set_callback_after(30)
104
+
105
+ Conductor::Worker::TaskInProgress.new(
106
+ callback_after_seconds: 30,
107
+ output: {
108
+ job_id: job_id,
109
+ status: 'in_progress',
110
+ message: 'Job still running'
111
+ }
112
+ )
113
+ end
114
+ end
115
+
116
+ #
117
+ # Example 4: Retry logic with context awareness
118
+ #
119
+ Conductor::Worker.define('retry_aware_example', poll_interval: 100, thread_count: 5) do |task|
120
+ operation = task.input_data['operation'] || 'default'
121
+
122
+ ctx = Conductor::Worker::TaskContext.current
123
+
124
+ retry_count = ctx.retry_count
125
+
126
+ if retry_count.positive?
127
+ ctx.add_log("This is retry attempt ##{retry_count}")
128
+ # Could implement exponential backoff, different logic, etc.
129
+ end
130
+
131
+ ctx.add_log("Executing operation: #{operation}")
132
+
133
+ # Simulate operation
134
+ success = rand > 0.3
135
+
136
+ if success
137
+ ctx.add_log('Operation succeeded')
138
+ { status: 'success', operation: operation }
139
+ else
140
+ ctx.add_log('Operation failed, will retry')
141
+ raise StandardError, 'Operation failed'
142
+ end
143
+ end
144
+
145
+ #
146
+ # Example 5: Accessing input parameters via context
147
+ #
148
+ Conductor::Worker.define('input_access_example', poll_interval: 100, thread_count: 5) do |_task|
149
+ ctx = Conductor::Worker::TaskContext.current
150
+
151
+ # Get all input parameters
152
+ input_data = ctx.input
153
+
154
+ ctx.add_log("Received input parameters: #{input_data.keys}")
155
+
156
+ # Process based on input
157
+ input_data.each do |key, value|
158
+ ctx.add_log(" #{key} = #{value}")
159
+ end
160
+
161
+ {
162
+ processed_keys: input_data.keys,
163
+ input_count: input_data.size
164
+ }
165
+ end
166
+
167
+ #
168
+ # Example 6: Long-running task with progress tracking
169
+ #
170
+ Conductor::Worker.define('progress_tracking_example', poll_interval: 100, thread_count: 5) do |task|
171
+ total_steps = task.input_data['total_steps'] || 5
172
+
173
+ ctx = Conductor::Worker::TaskContext.current
174
+ poll_count = task.poll_count || 0
175
+
176
+ ctx.add_log("Progress: step #{poll_count + 1}/#{total_steps}")
177
+
178
+ if poll_count < total_steps - 1
179
+ # Still processing
180
+ progress = ((poll_count + 1).to_f / total_steps * 100).round
181
+
182
+ Conductor::Worker::TaskInProgress.new(
183
+ callback_after_seconds: 1,
184
+ output: {
185
+ current_step: poll_count + 1,
186
+ total_steps: total_steps,
187
+ progress_percent: progress,
188
+ status: 'in_progress'
189
+ }
190
+ )
191
+ else
192
+ # Complete
193
+ ctx.add_log('All steps completed!')
194
+ {
195
+ current_step: total_steps,
196
+ total_steps: total_steps,
197
+ progress_percent: 100,
198
+ status: 'completed'
199
+ }
200
+ end
201
+ end
202
+
203
+ def main
204
+ config = Conductor::Configuration.new(
205
+ server_api_url: ENV.fetch('CONDUCTOR_SERVER_URL', 'http://localhost:8080/api'),
206
+ auth_key: ENV.fetch('CONDUCTOR_AUTH_KEY', nil),
207
+ auth_secret: ENV.fetch('CONDUCTOR_AUTH_SECRET', nil)
208
+ )
209
+
210
+ puts '=' * 60
211
+ puts 'Conductor TaskContext Examples'
212
+ puts '=' * 60
213
+ puts
214
+ puts 'Workers demonstrating TaskContext usage:'
215
+ puts ' - task_info_example - Access task metadata'
216
+ puts ' - logging_example - Add logs to task'
217
+ puts ' - polling_example - Use callback_after for polling'
218
+ puts ' - retry_aware_example - Handle retries intelligently'
219
+ puts ' - input_access_example - Access task input via context'
220
+ puts ' - progress_tracking_example - Track progress across polls'
221
+ puts
222
+ puts 'Key TaskContext Features:'
223
+ puts ' - Access task metadata (ID, workflow ID, retry count)'
224
+ puts ' - Add logs visible in Conductor UI'
225
+ puts ' - Set callback delays for polling patterns'
226
+ puts ' - Thread-safe via Thread.current'
227
+ puts '=' * 60
228
+ puts
229
+ puts 'Starting workers... Press Ctrl+C to stop'
230
+ puts
231
+
232
+ begin
233
+ handler = Conductor::Worker::TaskHandler.new(
234
+ configuration: config,
235
+ scan_for_annotated_workers: true
236
+ )
237
+
238
+ shutdown = false
239
+ Signal.trap('INT') do
240
+ puts "\nShutting down gracefully..."
241
+ shutdown = true
242
+ handler.stop
243
+ end
244
+
245
+ handler.start
246
+ handler.join unless shutdown
247
+ rescue Interrupt
248
+ puts 'Interrupted!'
249
+ rescue StandardError => e
250
+ puts "Error: #{e.message}"
251
+ puts e.backtrace.first(5).join("\n")
252
+ end
253
+
254
+ puts "\nWorkers stopped. Goodbye!"
255
+ end
256
+
257
+ main if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,192 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Example demonstrating TaskRunnerEventsListener for pre/post processing of worker tasks.
5
+ #
6
+ # This example shows how to implement a custom event listener to:
7
+ # - Log task execution events
8
+ # - Add custom headers or context before task execution
9
+ # - Process task results after execution
10
+ # - Track task timing and errors
11
+ # - Implement retry logic or custom error handling
12
+ #
13
+ # The listener pattern is useful for:
14
+ # - Request/response logging
15
+ # - Distributed tracing integration
16
+ # - Custom metrics collection
17
+ # - Authentication/authorization
18
+ # - Data enrichment
19
+ # - Error recovery
20
+ #
21
+ # Usage:
22
+ # ruby task_listener_example.rb
23
+ #
24
+ # Environment variables:
25
+ # CONDUCTOR_SERVER_URL - Conductor server URL (default: http://localhost:8080/api)
26
+ # CONDUCTOR_AUTH_KEY - Authentication key (optional)
27
+ # CONDUCTOR_AUTH_SECRET - Authentication secret (optional)
28
+ #
29
+
30
+ require 'bundler/setup'
31
+ require 'conductor'
32
+ require_relative 'event_listener_examples'
33
+
34
+ # Configure logging
35
+ logger = Logger.new($stdout)
36
+ logger.level = Logger::INFO
37
+ logger.formatter = proc do |severity, datetime, _progname, msg|
38
+ "[#{datetime.strftime('%Y-%m-%d %H:%M:%S')}] #{severity} -- #{msg}\n"
39
+ end
40
+
41
+ #
42
+ # Example worker tasks
43
+ #
44
+
45
+ # Simple calculator worker
46
+ Conductor::Worker.define('calculate', poll_interval: 100, thread_count: 5) do |task|
47
+ n = task.input_data['n'] || 10
48
+
49
+ # Fibonacci calculation (demonstrates CPU-bound work)
50
+ fib = ->(x) { x <= 1 ? x : fib.call(x - 1) + fib.call(x - 2) }
51
+ result = fib.call(n)
52
+
53
+ { fibonacci: result, input: n }
54
+ end
55
+
56
+ # Long-running task with progress tracking
57
+ Conductor::Worker.define('long_running_task', poll_interval: 100, thread_count: 3) do |task|
58
+ job_id = task.input_data['job_id'] || 'unknown'
59
+
60
+ # Access task context for poll count
61
+ ctx = Conductor::Worker::TaskContext.current
62
+ poll_count = task.poll_count || 0
63
+
64
+ ctx.add_log("Processing job #{job_id}, poll #{poll_count}/5")
65
+
66
+ if poll_count < 5
67
+ # Still processing - return TaskInProgress
68
+ Conductor::Worker::TaskInProgress.new(
69
+ callback_after_seconds: 1,
70
+ output: {
71
+ job_id: job_id,
72
+ status: 'processing',
73
+ poll_count: poll_count,
74
+ progress: poll_count * 20,
75
+ message: "Working on job #{job_id}, poll #{poll_count}/5"
76
+ }
77
+ )
78
+ else
79
+ # Complete after 5 polls
80
+ ctx.add_log("Job #{job_id} completed")
81
+ {
82
+ job_id: job_id,
83
+ status: 'completed',
84
+ result: 'success',
85
+ total_time_seconds: 5,
86
+ total_polls: poll_count
87
+ }
88
+ end
89
+ end
90
+
91
+ # Worker that simulates failures for testing error handling
92
+ Conductor::Worker.define('flaky_worker', poll_interval: 100, thread_count: 2) do |task|
93
+ fail_rate = task.input_data['fail_rate'] || 0.3
94
+
95
+ raise StandardError, 'Random failure for testing' if rand < fail_rate
96
+
97
+ { status: 'success', fail_rate: fail_rate }
98
+ end
99
+
100
+ def main
101
+ # Configure Conductor connection
102
+ config = Conductor::Configuration.new(
103
+ server_api_url: ENV['CONDUCTOR_SERVER_URL'] || 'http://localhost:8080/api',
104
+ auth_key: ENV.fetch('CONDUCTOR_AUTH_KEY', nil),
105
+ auth_secret: ENV.fetch('CONDUCTOR_AUTH_SECRET', nil)
106
+ )
107
+
108
+ # Create event listeners
109
+ logger_listener = TaskExecutionLogger.new
110
+ timing_tracker = TaskTimingTracker.new
111
+ tracing_listener = DistributedTracingListener.new
112
+ error_tracker = ErrorTrackingListener.new(
113
+ alert_threshold: 3,
114
+ alert_callback: ->(alert) { puts "[ALERT CALLBACK] #{alert.inspect}" }
115
+ )
116
+
117
+ puts '=' * 80
118
+ puts 'TaskRunnerEventsListener Example'
119
+ puts '=' * 80
120
+ puts
121
+ puts 'This example demonstrates event listeners for task pre/post processing:'
122
+ puts ' 1. TaskExecutionLogger - Logs all task lifecycle events'
123
+ puts ' 2. TaskTimingTracker - Tracks and reports execution statistics'
124
+ puts ' 3. DistributedTracingListener - Simulates distributed tracing'
125
+ puts ' 4. ErrorTrackingListener - Aggregates errors and alerts'
126
+ puts
127
+ puts 'Workers available:'
128
+ puts ' - calculate: Fibonacci calculator'
129
+ puts ' - long_running_task: Multi-poll task with progress tracking'
130
+ puts ' - flaky_worker: Random failure simulation'
131
+ puts
132
+ puts 'Press Ctrl+C to stop...'
133
+ puts '=' * 80
134
+ puts
135
+
136
+ begin
137
+ # Create task handler with multiple listeners
138
+ handler = Conductor::Worker::TaskHandler.new(
139
+ configuration: config,
140
+ scan_for_annotated_workers: true,
141
+ event_listeners: [
142
+ logger_listener,
143
+ timing_tracker,
144
+ tracing_listener,
145
+ error_tracker
146
+ ]
147
+ )
148
+
149
+ # Handle graceful shutdown
150
+ shutdown = false
151
+ Signal.trap('INT') do
152
+ puts "\nShutting down gracefully..."
153
+ shutdown = true
154
+ handler.stop
155
+ end
156
+
157
+ handler.start
158
+
159
+ # Print statistics periodically
160
+ Thread.new do
161
+ loop do
162
+ sleep 30
163
+ break if shutdown
164
+
165
+ puts "\n--- Statistics ---"
166
+ timing_tracker.all_stats.each do |task_type, stats|
167
+ puts "#{task_type}: #{stats.inspect}"
168
+ end
169
+
170
+ error_summary = error_tracker.error_summary
171
+ puts "Errors: #{error_summary.inspect}" unless error_summary.empty?
172
+ puts '------------------\n'
173
+ end
174
+ end
175
+
176
+ handler.join unless shutdown
177
+ rescue Interrupt
178
+ puts 'Interrupted!'
179
+ rescue StandardError => e
180
+ puts "Error: #{e.message}"
181
+ puts e.backtrace.first(5).join("\n")
182
+ end
183
+
184
+ puts "\nFinal Statistics:"
185
+ timing_tracker.all_stats.each do |task_type, stats|
186
+ puts " #{task_type}: #{stats.inspect}"
187
+ end
188
+
189
+ puts "\nWorkers stopped. Goodbye!"
190
+ end
191
+
192
+ main if __FILE__ == $PROGRAM_NAME