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,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Conductor
4
+ module Worker
5
+ module Events
6
+ # Thread-safe synchronous event dispatcher
7
+ # Dispatches events to registered listeners in the calling thread
8
+ # Listener exceptions are isolated and logged, never propagating to callers
9
+ class SyncEventDispatcher
10
+ def initialize
11
+ @listeners = Hash.new { |h, k| h[k] = [] }
12
+ @mutex = Mutex.new
13
+ end
14
+
15
+ # Register a listener for an event type
16
+ # @param event_type [Class] Event class to listen for
17
+ # @param listener [Proc, #call] Callable to invoke when event is published
18
+ # @return [self]
19
+ def register(event_type, listener)
20
+ @mutex.synchronize do
21
+ @listeners[event_type] << listener unless @listeners[event_type].include?(listener)
22
+ end
23
+ self
24
+ end
25
+
26
+ # Unregister a listener for an event type
27
+ # @param event_type [Class] Event class
28
+ # @param listener [Proc, #call] Listener to remove
29
+ # @return [self]
30
+ def unregister(event_type, listener)
31
+ @mutex.synchronize do
32
+ @listeners[event_type].delete(listener)
33
+ end
34
+ self
35
+ end
36
+
37
+ # Publish an event to all registered listeners
38
+ # Listeners are called synchronously in the calling thread
39
+ # Exceptions in listeners are caught and logged, not propagated
40
+ # @param event [ConductorEvent] Event to publish
41
+ # @return [self]
42
+ def publish(event)
43
+ listeners = @mutex.synchronize { @listeners[event.class].dup }
44
+
45
+ listeners.each do |listener|
46
+ listener.call(event)
47
+ rescue StandardError => e
48
+ # Listener failure is isolated - never breaks the worker
49
+ warn "[Conductor] Event listener error for #{event.class}: #{e.message}"
50
+ end
51
+
52
+ self
53
+ end
54
+
55
+ # Check if there are listeners registered for an event type
56
+ # @param event_type [Class] Event class
57
+ # @return [Boolean]
58
+ def has_listeners?(event_type)
59
+ @mutex.synchronize { @listeners[event_type].any? }
60
+ end
61
+
62
+ # Get the number of listeners for an event type
63
+ # @param event_type [Class] Event class
64
+ # @return [Integer]
65
+ def listener_count(event_type)
66
+ @mutex.synchronize { @listeners[event_type].size }
67
+ end
68
+
69
+ # Clear all listeners (primarily for testing)
70
+ # @return [self]
71
+ def clear
72
+ @mutex.synchronize { @listeners.clear }
73
+ self
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,271 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'conductor_event'
4
+
5
+ module Conductor
6
+ module Worker
7
+ module Events
8
+ # Published when polling starts
9
+ class PollStarted < TaskRunnerEvent
10
+ # @return [String] Unique worker identifier
11
+ attr_reader :worker_id
12
+ # @return [Integer] Number of polls performed so far
13
+ attr_reader :poll_count
14
+
15
+ # @param task_type [String] Task definition name
16
+ # @param worker_id [String] Unique worker identifier
17
+ # @param poll_count [Integer] Number of polls performed
18
+ def initialize(task_type:, worker_id:, poll_count:)
19
+ super(task_type: task_type)
20
+ @worker_id = worker_id
21
+ @poll_count = poll_count
22
+ end
23
+
24
+ def to_h
25
+ super.merge(worker_id: @worker_id, poll_count: @poll_count)
26
+ end
27
+ end
28
+
29
+ # Published when polling completes successfully
30
+ class PollCompleted < TaskRunnerEvent
31
+ # @return [Float] Duration of poll in milliseconds
32
+ attr_reader :duration_ms
33
+ # @return [Integer] Number of tasks received
34
+ attr_reader :tasks_received
35
+
36
+ # @param task_type [String] Task definition name
37
+ # @param duration_ms [Float] Duration of poll in milliseconds
38
+ # @param tasks_received [Integer] Number of tasks received
39
+ def initialize(task_type:, duration_ms:, tasks_received:)
40
+ super(task_type: task_type)
41
+ @duration_ms = duration_ms
42
+ @tasks_received = tasks_received
43
+ end
44
+
45
+ def to_h
46
+ super.merge(duration_ms: @duration_ms, tasks_received: @tasks_received)
47
+ end
48
+ end
49
+
50
+ # Published when polling fails
51
+ class PollFailure < TaskRunnerEvent
52
+ # @return [Float] Duration of poll in milliseconds
53
+ attr_reader :duration_ms
54
+ # @return [Exception] The exception that caused the failure
55
+ attr_reader :cause
56
+
57
+ # @param task_type [String] Task definition name
58
+ # @param duration_ms [Float] Duration of poll in milliseconds
59
+ # @param cause [Exception] The exception that caused the failure
60
+ def initialize(task_type:, duration_ms:, cause:)
61
+ super(task_type: task_type)
62
+ @duration_ms = duration_ms
63
+ @cause = cause
64
+ end
65
+
66
+ def to_h
67
+ super.merge(
68
+ duration_ms: @duration_ms,
69
+ cause: @cause.class.name,
70
+ cause_message: @cause.message
71
+ )
72
+ end
73
+ end
74
+
75
+ # Published when task execution starts
76
+ class TaskExecutionStarted < TaskRunnerEvent
77
+ # @return [String] Unique task identifier
78
+ attr_reader :task_id
79
+ # @return [String] Unique worker identifier
80
+ attr_reader :worker_id
81
+ # @return [String] Workflow instance identifier
82
+ attr_reader :workflow_instance_id
83
+
84
+ # @param task_type [String] Task definition name
85
+ # @param task_id [String] Unique task identifier
86
+ # @param worker_id [String] Unique worker identifier
87
+ # @param workflow_instance_id [String] Workflow instance identifier
88
+ def initialize(task_type:, task_id:, worker_id:, workflow_instance_id:)
89
+ super(task_type: task_type)
90
+ @task_id = task_id
91
+ @worker_id = worker_id
92
+ @workflow_instance_id = workflow_instance_id
93
+ end
94
+
95
+ def to_h
96
+ super.merge(
97
+ task_id: @task_id,
98
+ worker_id: @worker_id,
99
+ workflow_instance_id: @workflow_instance_id
100
+ )
101
+ end
102
+ end
103
+
104
+ # Published when task execution completes successfully
105
+ class TaskExecutionCompleted < TaskRunnerEvent
106
+ # @return [String] Unique task identifier
107
+ attr_reader :task_id
108
+ # @return [String] Unique worker identifier
109
+ attr_reader :worker_id
110
+ # @return [String] Workflow instance identifier
111
+ attr_reader :workflow_instance_id
112
+ # @return [Float] Duration of execution in milliseconds
113
+ attr_reader :duration_ms
114
+ # @return [Integer, nil] Size of output data in bytes
115
+ attr_reader :output_size_bytes
116
+
117
+ # @param task_type [String] Task definition name
118
+ # @param task_id [String] Unique task identifier
119
+ # @param worker_id [String] Unique worker identifier
120
+ # @param workflow_instance_id [String] Workflow instance identifier
121
+ # @param duration_ms [Float] Duration of execution in milliseconds
122
+ # @param output_size_bytes [Integer, nil] Size of output data in bytes
123
+ def initialize(task_type:, task_id:, worker_id:, workflow_instance_id:,
124
+ duration_ms:, output_size_bytes: nil)
125
+ super(task_type: task_type)
126
+ @task_id = task_id
127
+ @worker_id = worker_id
128
+ @workflow_instance_id = workflow_instance_id
129
+ @duration_ms = duration_ms
130
+ @output_size_bytes = output_size_bytes
131
+ end
132
+
133
+ def to_h
134
+ super.merge(
135
+ task_id: @task_id,
136
+ worker_id: @worker_id,
137
+ workflow_instance_id: @workflow_instance_id,
138
+ duration_ms: @duration_ms,
139
+ output_size_bytes: @output_size_bytes
140
+ )
141
+ end
142
+ end
143
+
144
+ # Published when task execution fails
145
+ class TaskExecutionFailure < TaskRunnerEvent
146
+ # @return [String] Unique task identifier
147
+ attr_reader :task_id
148
+ # @return [String] Unique worker identifier
149
+ attr_reader :worker_id
150
+ # @return [String] Workflow instance identifier
151
+ attr_reader :workflow_instance_id
152
+ # @return [Float] Duration of execution in milliseconds
153
+ attr_reader :duration_ms
154
+ # @return [Exception] The exception that caused the failure
155
+ attr_reader :cause
156
+ # @return [Boolean] Whether the error is retryable
157
+ attr_reader :is_retryable
158
+
159
+ # @param task_type [String] Task definition name
160
+ # @param task_id [String] Unique task identifier
161
+ # @param worker_id [String] Unique worker identifier
162
+ # @param workflow_instance_id [String] Workflow instance identifier
163
+ # @param duration_ms [Float] Duration of execution in milliseconds
164
+ # @param cause [Exception] The exception that caused the failure
165
+ # @param is_retryable [Boolean] Whether the error is retryable (default: true)
166
+ def initialize(task_type:, task_id:, worker_id:, workflow_instance_id:,
167
+ duration_ms:, cause:, is_retryable: true)
168
+ super(task_type: task_type)
169
+ @task_id = task_id
170
+ @worker_id = worker_id
171
+ @workflow_instance_id = workflow_instance_id
172
+ @duration_ms = duration_ms
173
+ @cause = cause
174
+ @is_retryable = is_retryable
175
+ end
176
+
177
+ def to_h
178
+ super.merge(
179
+ task_id: @task_id,
180
+ worker_id: @worker_id,
181
+ workflow_instance_id: @workflow_instance_id,
182
+ duration_ms: @duration_ms,
183
+ cause: @cause.class.name,
184
+ cause_message: @cause.message,
185
+ is_retryable: @is_retryable
186
+ )
187
+ end
188
+ end
189
+
190
+ # Published when task update completes successfully
191
+ class TaskUpdateCompleted < TaskRunnerEvent
192
+ attr_reader :task_id, :worker_id, :workflow_instance_id, :duration_ms
193
+
194
+ def initialize(task_type:, task_id:, worker_id:, workflow_instance_id:, duration_ms:)
195
+ super(task_type: task_type)
196
+ @task_id = task_id
197
+ @worker_id = worker_id
198
+ @workflow_instance_id = workflow_instance_id
199
+ @duration_ms = duration_ms
200
+ end
201
+
202
+ def to_h
203
+ super.merge(
204
+ task_id: @task_id, worker_id: @worker_id,
205
+ workflow_instance_id: @workflow_instance_id, duration_ms: @duration_ms
206
+ )
207
+ end
208
+ end
209
+
210
+ # Published when task update fails after all retries
211
+ # This is a CRITICAL event - the task result is lost
212
+ class TaskUpdateFailure < TaskRunnerEvent
213
+ attr_reader :task_id, :worker_id, :workflow_instance_id,
214
+ :cause, :retry_count, :task_result, :duration_ms
215
+
216
+ def initialize(task_type:, task_id:, worker_id:, workflow_instance_id:,
217
+ cause:, retry_count:, task_result:, duration_ms: nil)
218
+ super(task_type: task_type)
219
+ @task_id = task_id
220
+ @worker_id = worker_id
221
+ @workflow_instance_id = workflow_instance_id
222
+ @cause = cause
223
+ @retry_count = retry_count
224
+ @task_result = task_result
225
+ @duration_ms = duration_ms
226
+ end
227
+
228
+ def to_h
229
+ super.merge(
230
+ task_id: @task_id, worker_id: @worker_id,
231
+ workflow_instance_id: @workflow_instance_id,
232
+ cause: @cause.class.name, cause_message: @cause.message,
233
+ retry_count: @retry_count, duration_ms: @duration_ms
234
+ )
235
+ end
236
+ end
237
+
238
+ # Published when a poll iteration is skipped because the worker is paused
239
+ class TaskPaused < TaskRunnerEvent; end
240
+
241
+ # Published when a worker thread terminates with an uncaught exception
242
+ class ThreadUncaughtException < ConductorEvent
243
+ attr_reader :cause, :task_type
244
+
245
+ def initialize(cause:, task_type: nil)
246
+ super()
247
+ @cause = cause
248
+ @task_type = task_type
249
+ end
250
+
251
+ def to_h
252
+ super.merge(cause: @cause.class.name, cause_message: @cause.message, task_type: @task_type)
253
+ end
254
+ end
255
+
256
+ # Published when the active-worker count changes for a task type
257
+ class ActiveWorkersChanged < TaskRunnerEvent
258
+ attr_reader :count
259
+
260
+ def initialize(task_type:, count:)
261
+ super(task_type: task_type)
262
+ @count = count
263
+ end
264
+
265
+ def to_h
266
+ super.merge(count: @count)
267
+ end
268
+ end
269
+ end
270
+ end
271
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'conductor_event'
4
+
5
+ module Conductor
6
+ module Worker
7
+ module Events
8
+ class WorkflowEvent < ConductorEvent
9
+ attr_reader :workflow_type, :version
10
+
11
+ def initialize(workflow_type:, version: nil)
12
+ super()
13
+ @workflow_type = workflow_type
14
+ @version = version
15
+ end
16
+
17
+ def to_h
18
+ super.merge(workflow_type: @workflow_type, version: @version)
19
+ end
20
+ end
21
+
22
+ class WorkflowStartError < WorkflowEvent
23
+ attr_reader :cause
24
+
25
+ def initialize(workflow_type:, cause:, version: nil)
26
+ super(workflow_type: workflow_type, version: version)
27
+ @cause = cause
28
+ end
29
+
30
+ def to_h
31
+ super.merge(cause: @cause.class.name, cause_message: @cause.message)
32
+ end
33
+ end
34
+
35
+ class WorkflowInputSize < WorkflowEvent
36
+ attr_reader :size_bytes
37
+
38
+ def initialize(workflow_type:, size_bytes:, version: nil)
39
+ super(workflow_type: workflow_type, version: version)
40
+ @size_bytes = size_bytes
41
+ end
42
+
43
+ def to_h
44
+ super.merge(size_bytes: @size_bytes)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end