phronomy 0.13.0 → 0.15.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +155 -0
- data/README.md +266 -38
- data/benchmark/bench_agent_invoke.rb +2 -3
- data/docs/decisions/004-invoke-timeout-is-not-cancellation.md +14 -67
- data/docs/decisions/011-delegate-transport-policy-to-adapters.md +82 -0
- data/docs/mcp-client.md +75 -0
- data/examples/workflows/agent_event_mapping.rb +104 -0
- data/examples/workflows/generic_task_event_mapping.rb +58 -0
- data/gemfiles/mcp_1_0.gemfile +9 -0
- data/lib/phronomy/agent/agent_invocation.rb +385 -0
- data/lib/phronomy/agent/agent_invocation_registry.rb +75 -0
- data/lib/phronomy/agent/agent_invocation_session_builder.rb +448 -0
- data/lib/phronomy/agent/approval_evaluation_request.rb +102 -0
- data/lib/phronomy/agent/async_event_api.rb +471 -0
- data/lib/phronomy/agent/base.rb +509 -420
- data/lib/phronomy/agent/context/capability/base.rb +57 -119
- data/lib/phronomy/agent/llm_operation_result.rb +23 -0
- data/lib/phronomy/agent/phase_machine_builder.rb +75 -136
- data/lib/phronomy/agent/tool_approval_request.rb +121 -0
- data/lib/phronomy/agent/tool_call_intercepted.rb +11 -15
- data/lib/phronomy/agent/tool_executor.rb +47 -69
- data/lib/phronomy/agent/tool_invocation.rb +634 -0
- data/lib/phronomy/agent/tool_invocation_session_builder.rb +378 -0
- data/lib/phronomy/agent.rb +21 -9
- data/lib/phronomy/configuration.rb +58 -53
- data/lib/phronomy/diagnostics.rb +1 -1
- data/lib/phronomy/engine/concurrency/blocking_adapter_pool.rb +230 -118
- data/lib/phronomy/engine/concurrency/cancellation_token.rb +5 -1
- data/lib/phronomy/engine/concurrency/pool_registry.rb +8 -3
- data/lib/phronomy/engine/event_loop.rb +507 -303
- data/lib/phronomy/engine/fsm_session.rb +181 -140
- data/lib/phronomy/engine/runtime/deterministic_scheduler.rb +1 -1
- data/lib/phronomy/engine/runtime/shutdown_result.rb +62 -0
- data/lib/phronomy/engine/runtime/task_registry.rb +62 -15
- data/lib/phronomy/engine/runtime.rb +247 -57
- data/lib/phronomy/engine/task.rb +5 -10
- data/lib/phronomy/event.rb +8 -8
- data/lib/phronomy/generator_verifier.rb +253 -142
- data/lib/phronomy/invalid_async_entry_action_error.rb +9 -0
- data/lib/phronomy/invalid_async_transition_action_error.rb +11 -0
- data/lib/phronomy/invalid_async_workflow_action_error.rb +9 -0
- data/lib/phronomy/invocation_context.rb +5 -19
- data/lib/phronomy/llm_adapter/base.rb +25 -34
- data/lib/phronomy/metrics.rb +6 -3
- data/lib/phronomy/multi_agent/parallel_tool_chat.rb +54 -89
- data/lib/phronomy/stream_callback_error.rb +35 -0
- data/lib/phronomy/testing/scheduler_helpers.rb +12 -3
- data/lib/phronomy/tools/mcp.rb +410 -81
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/workflow/phase_machine_builder.rb +129 -182
- data/lib/phronomy/workflow.rb +122 -261
- data/lib/phronomy/workflow_context.rb +55 -104
- data/lib/phronomy/workflow_runner.rb +239 -291
- data/lib/phronomy.rb +30 -23
- data/scripts/check_readme_runnable.rb +4 -1
- metadata +63 -11
- data/lib/phronomy/agent/concerns/retryable.rb +0 -103
- data/lib/phronomy/agent/context/capability/scope_policy.rb +0 -54
- data/lib/phronomy/agent/invocation_context.rb +0 -171
- data/lib/phronomy/agent/invocation_session.rb +0 -346
- data/lib/phronomy/agent/suspended_session_registry.rb +0 -54
- data/lib/phronomy/engine/concurrency/concurrency_gate.rb +0 -157
- data/lib/phronomy/engine/concurrency/gate_registry.rb +0 -51
|
@@ -8,6 +8,7 @@ require_relative "runtime/timer_queue"
|
|
|
8
8
|
require_relative "runtime/scheduler_timer_adapter"
|
|
9
9
|
require_relative "runtime/task_registry"
|
|
10
10
|
require_relative "runtime/runtime_metrics"
|
|
11
|
+
require_relative "runtime/shutdown_result"
|
|
11
12
|
require_relative "runtime/timer_service"
|
|
12
13
|
|
|
13
14
|
module Phronomy
|
|
@@ -47,8 +48,68 @@ module Phronomy
|
|
|
47
48
|
#
|
|
48
49
|
# @return [Runtime]
|
|
49
50
|
# @api private
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
@instance_mutex = Mutex.new
|
|
52
|
+
|
|
53
|
+
class << self
|
|
54
|
+
def instance
|
|
55
|
+
instance_mutex.synchronize do
|
|
56
|
+
@instance ||= build_default_runtime
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Compatibility setter retained for existing tests.
|
|
61
|
+
def instance=(runtime)
|
|
62
|
+
replace_default_for_test(runtime)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Test-only, non-creating access to the default Runtime.
|
|
66
|
+
def default_if_initialized_for_test
|
|
67
|
+
instance_mutex.synchronize { @instance }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Test-only replacement. The caller owns both Runtime lifecycles.
|
|
71
|
+
def replace_default_for_test(runtime)
|
|
72
|
+
instance_mutex.synchronize do
|
|
73
|
+
previous = @instance
|
|
74
|
+
@instance = runtime
|
|
75
|
+
previous
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Test-only restoration of a previously captured Runtime.
|
|
80
|
+
def restore_default_for_test(runtime)
|
|
81
|
+
instance_mutex.synchronize { @instance = runtime }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def reset_default!(timeout: Phronomy.configuration.event_loop_stop_grace_seconds)
|
|
85
|
+
runtime = instance_mutex.synchronize { @instance }
|
|
86
|
+
return ShutdownResult.not_started unless runtime
|
|
87
|
+
|
|
88
|
+
result = runtime.shutdown(timeout: timeout)
|
|
89
|
+
unless result.cleanup_complete?
|
|
90
|
+
raise Phronomy::RuntimeShutdownError,
|
|
91
|
+
"Runtime cleanup is incomplete; default Runtime was retained"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
instance_mutex.synchronize do
|
|
95
|
+
@instance = nil if @instance.equal?(runtime)
|
|
96
|
+
end
|
|
97
|
+
result
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Does not create a Runtime or EventLoop.
|
|
101
|
+
def in_event_loop_context?
|
|
102
|
+
runtime = instance_mutex.synchronize { @instance }
|
|
103
|
+
runtime&.event_loop_current? || false
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
def instance_mutex
|
|
109
|
+
@instance_mutex ||= Mutex.new
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def build_default_runtime
|
|
52
113
|
scheduler = case Phronomy.configuration.runtime_backend
|
|
53
114
|
when :cooperative
|
|
54
115
|
Phronomy.configuration.logger&.warn(
|
|
@@ -74,14 +135,6 @@ module Phronomy
|
|
|
74
135
|
end
|
|
75
136
|
end
|
|
76
137
|
|
|
77
|
-
# Replaces the process-wide default Runtime. Useful in tests.
|
|
78
|
-
# @param runtime [Runtime]
|
|
79
|
-
# @return [Runtime]
|
|
80
|
-
# @api private
|
|
81
|
-
def self.instance=(runtime)
|
|
82
|
-
@instance = runtime
|
|
83
|
-
end
|
|
84
|
-
|
|
85
138
|
# Returns +true+ when the calling thread is executing inside an active
|
|
86
139
|
# scheduler task (i.e. {Task.current} is non-nil). Code running inside
|
|
87
140
|
# a {Runtime#spawn} block is always in a scheduler context.
|
|
@@ -118,38 +171,29 @@ module Phronomy
|
|
|
118
171
|
# @return [Scheduler]
|
|
119
172
|
attr_reader :scheduler
|
|
120
173
|
|
|
174
|
+
# @return [Symbol] current Runtime lifecycle state
|
|
175
|
+
# @api private
|
|
176
|
+
def state
|
|
177
|
+
@lifecycle_mutex.synchronize { @state }
|
|
178
|
+
end
|
|
179
|
+
|
|
121
180
|
# @param scheduler [Scheduler] execution backend (default: {ThreadScheduler})
|
|
122
181
|
# @api private
|
|
123
182
|
def initialize(scheduler: ThreadScheduler.new)
|
|
124
183
|
@scheduler = scheduler
|
|
184
|
+
@event_loop_scheduler = ThreadScheduler.new
|
|
125
185
|
@task_registry = TaskRegistry.new
|
|
126
186
|
@metrics = RuntimeMetrics.new
|
|
127
|
-
@gate_registry = Phronomy::Concurrency::GateRegistry.new
|
|
128
|
-
@pool_registry = Phronomy::Concurrency::PoolRegistry.new
|
|
129
187
|
@timer_service = TimerService.new(scheduler)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
# @return [ConcurrencyGate]
|
|
140
|
-
# @api private
|
|
141
|
-
def gate(name)
|
|
142
|
-
@gate_registry.get(name.to_sym)
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
# Drops the cached gate for +name+ so that the next call to {#gate} rebuilds
|
|
146
|
-
# it from the current configuration. Useful in tests.
|
|
147
|
-
#
|
|
148
|
-
# @param name [Symbol]
|
|
149
|
-
# @return [void]
|
|
150
|
-
# @api private
|
|
151
|
-
def reset_gate(name)
|
|
152
|
-
@gate_registry.reset(name.to_sym)
|
|
188
|
+
@pool_registry = Phronomy::Concurrency::PoolRegistry.new(
|
|
189
|
+
timer_queue_provider: -> { @timer_service.timer_queue }
|
|
190
|
+
)
|
|
191
|
+
@lifecycle_mutex = Mutex.new
|
|
192
|
+
@shutdown_mutex = Mutex.new
|
|
193
|
+
@state = :running
|
|
194
|
+
@event_loop = nil
|
|
195
|
+
@failure = nil
|
|
196
|
+
@shutdown_result = nil
|
|
153
197
|
end
|
|
154
198
|
|
|
155
199
|
# Cooperative yield point.
|
|
@@ -226,6 +270,7 @@ module Phronomy
|
|
|
226
270
|
# @return [TaskGroup]
|
|
227
271
|
# @api private
|
|
228
272
|
def task_group(limit: Float::INFINITY, failure_policy: :fail_fast)
|
|
273
|
+
ensure_accepting_work!
|
|
229
274
|
TaskGroup.new(limit: limit, failure_policy: failure_policy, runtime: self)
|
|
230
275
|
end
|
|
231
276
|
|
|
@@ -246,6 +291,7 @@ module Phronomy
|
|
|
246
291
|
# @return [Task]
|
|
247
292
|
# @api private
|
|
248
293
|
def spawn(name: nil, &block)
|
|
294
|
+
ensure_accepting_work!
|
|
249
295
|
type = _task_type(name)
|
|
250
296
|
spawn_at = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
|
251
297
|
@metrics.record_start(type)
|
|
@@ -302,11 +348,15 @@ module Phronomy
|
|
|
302
348
|
# constructing a Runtime with custom pool options or by replacing the
|
|
303
349
|
# shared Runtime via {.instance=} in tests.
|
|
304
350
|
#
|
|
305
|
-
# @param pool_size [Integer] worker thread count
|
|
306
|
-
#
|
|
351
|
+
# @param pool_size [Integer] worker thread count
|
|
352
|
+
# (default: {Phronomy::Configuration#blocking_io_pool_size}, currently 10)
|
|
353
|
+
# @param queue_size [Integer] max pending operations
|
|
354
|
+
# (default: {Phronomy::Configuration#blocking_io_queue_size}, currently 100)
|
|
307
355
|
# @return [BlockingAdapterPool]
|
|
308
356
|
# @api private
|
|
309
|
-
def blocking_io(pool_size:
|
|
357
|
+
def blocking_io(pool_size: Phronomy.configuration.blocking_io_pool_size,
|
|
358
|
+
queue_size: Phronomy.configuration.blocking_io_queue_size)
|
|
359
|
+
ensure_accepting_work!
|
|
310
360
|
@pool_registry.default_pool(pool_size: pool_size, queue_size: queue_size)
|
|
311
361
|
end
|
|
312
362
|
|
|
@@ -326,6 +376,7 @@ module Phronomy
|
|
|
326
376
|
# @return [BlockingAdapterPool]
|
|
327
377
|
# @api private
|
|
328
378
|
def pool(name, size: 10, queue_size: 100)
|
|
379
|
+
ensure_accepting_work!
|
|
329
380
|
@pool_registry.named_pool(name, size: size, queue_size: queue_size)
|
|
330
381
|
end
|
|
331
382
|
|
|
@@ -346,33 +397,172 @@ module Phronomy
|
|
|
346
397
|
# @return [TimerQueue, SchedulerTimerAdapter]
|
|
347
398
|
# @api private
|
|
348
399
|
def timer_queue
|
|
400
|
+
ensure_accepting_work!
|
|
349
401
|
@timer_service.timer_queue
|
|
350
402
|
end
|
|
351
403
|
|
|
352
|
-
#
|
|
353
|
-
#
|
|
354
|
-
#
|
|
355
|
-
#
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
404
|
+
# Returns the Runtime-owned EventLoop, creating it once on first use.
|
|
405
|
+
# During draining an existing loop remains available, but an unused loop
|
|
406
|
+
# is never created after shutdown begins.
|
|
407
|
+
# @api private
|
|
408
|
+
def event_loop
|
|
409
|
+
@lifecycle_mutex.synchronize do
|
|
410
|
+
case @state
|
|
411
|
+
when :running
|
|
412
|
+
@event_loop ||= EventLoop.new(runtime: self)
|
|
413
|
+
when :draining
|
|
414
|
+
return @event_loop if @event_loop
|
|
415
|
+
|
|
416
|
+
raise Phronomy::RuntimeShutdownError,
|
|
417
|
+
"EventLoop was not initialized before Runtime shutdown began"
|
|
418
|
+
else
|
|
419
|
+
raise Phronomy::RuntimeShutdownError,
|
|
420
|
+
"Runtime is #{@state}; EventLoop is unavailable"
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
# Does not create an EventLoop.
|
|
426
|
+
# @api private
|
|
427
|
+
def event_loop_current?
|
|
428
|
+
event_loop = @lifecycle_mutex.synchronize { @event_loop }
|
|
429
|
+
event_loop&.current? || false
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
# Internal EventLoop service spawn. Always uses a real OS thread and is
|
|
433
|
+
# deliberately excluded from the normal TaskRegistry drain.
|
|
364
434
|
# @api private
|
|
365
|
-
def
|
|
366
|
-
@
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
435
|
+
def __spawn_event_loop_service(&block)
|
|
436
|
+
@event_loop_scheduler.spawn(name: "event-loop", parent: nil, &block)
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
# Called only for an unexpected dispatcher failure.
|
|
440
|
+
# @api private
|
|
441
|
+
def __event_loop_failed(error)
|
|
442
|
+
@lifecycle_mutex.synchronize do
|
|
443
|
+
return if @shutdown_result || @state == :terminated
|
|
444
|
+
|
|
445
|
+
@failure ||= error
|
|
446
|
+
@state = :failed
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
# Synchronous, bounded Runtime shutdown. Must be invoked from an external
|
|
451
|
+
# management thread, lifecycle hook, or test teardown—not a Phronomy Task.
|
|
452
|
+
#
|
|
453
|
+
# +timeout+ bounds TaskRegistry and EventLoop graceful shutdown. Existing
|
|
454
|
+
# pool and timer shutdown contracts are unchanged by this proposal.
|
|
455
|
+
# @return [Runtime::ShutdownResult]
|
|
456
|
+
# @api public
|
|
457
|
+
def shutdown(
|
|
458
|
+
timeout: Phronomy.configuration.event_loop_stop_grace_seconds,
|
|
459
|
+
cancel_grace: timeout
|
|
460
|
+
)
|
|
461
|
+
if Phronomy::Task.current
|
|
462
|
+
raise Phronomy::RuntimeShutdownReentrancyError,
|
|
463
|
+
"Runtime#shutdown must be called from an external management thread"
|
|
464
|
+
end
|
|
465
|
+
validate_timeout!(timeout, :timeout)
|
|
466
|
+
validate_timeout!(cancel_grace, :cancel_grace)
|
|
467
|
+
|
|
468
|
+
@shutdown_mutex.synchronize do
|
|
469
|
+
return @shutdown_result if @shutdown_result
|
|
470
|
+
|
|
471
|
+
deadline = monotonic_now + timeout
|
|
472
|
+
event_loop = @lifecycle_mutex.synchronize do
|
|
473
|
+
@state = :draining unless @state == :failed
|
|
474
|
+
@event_loop
|
|
475
|
+
end
|
|
476
|
+
event_loop&.begin_draining
|
|
477
|
+
|
|
478
|
+
task_status = drain_runtime_work(event_loop, deadline)
|
|
479
|
+
|
|
480
|
+
@lifecycle_mutex.synchronize do
|
|
481
|
+
@state = :stopping unless @state == :failed
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
event_loop_status = if event_loop
|
|
485
|
+
event_loop.shutdown(deadline: deadline, cancel_grace: cancel_grace)
|
|
486
|
+
else
|
|
487
|
+
:not_started
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
subsystem_error = shutdown_pools_and_timer
|
|
491
|
+
final_task_status = @task_registry.empty? ? :empty : task_status
|
|
492
|
+
cleanup_complete = final_task_status == :empty &&
|
|
493
|
+
(!event_loop || !event_loop.task_alive?) &&
|
|
494
|
+
event_loop_status != :cancel_timeout &&
|
|
495
|
+
subsystem_error.nil?
|
|
496
|
+
|
|
497
|
+
failure = @lifecycle_mutex.synchronize { @failure } || subsystem_error
|
|
498
|
+
runtime_outcome = if failure || event_loop_status == :failed
|
|
499
|
+
:failed
|
|
500
|
+
else
|
|
501
|
+
:terminated
|
|
502
|
+
end
|
|
503
|
+
result = ShutdownResult.new(
|
|
504
|
+
runtime_outcome: runtime_outcome,
|
|
505
|
+
cleanup_status: cleanup_complete ? :complete : :incomplete,
|
|
506
|
+
event_loop_status: event_loop_status,
|
|
507
|
+
task_registry_status: final_task_status,
|
|
508
|
+
error: failure
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
@lifecycle_mutex.synchronize do
|
|
512
|
+
@state = cleanup_complete ? runtime_outcome : :failed
|
|
513
|
+
@shutdown_result = result
|
|
514
|
+
end
|
|
515
|
+
result
|
|
516
|
+
end
|
|
372
517
|
end
|
|
373
518
|
|
|
374
519
|
private
|
|
375
520
|
|
|
521
|
+
def ensure_accepting_work!
|
|
522
|
+
current_state = @lifecycle_mutex.synchronize { @state }
|
|
523
|
+
return if %i[running draining].include?(current_state)
|
|
524
|
+
|
|
525
|
+
raise Phronomy::RuntimeShutdownError,
|
|
526
|
+
"Runtime is #{current_state}; new work is not accepted"
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
def drain_runtime_work(event_loop, deadline)
|
|
530
|
+
loop do
|
|
531
|
+
task_status = @task_registry.drain_until(deadline)
|
|
532
|
+
return :timeout if task_status == :timeout
|
|
533
|
+
|
|
534
|
+
event_loop_idle = !event_loop || event_loop.wait_until_idle(deadline)
|
|
535
|
+
return :timeout unless event_loop_idle
|
|
536
|
+
return :empty if @task_registry.empty?
|
|
537
|
+
end
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
def shutdown_pools_and_timer
|
|
541
|
+
error = nil
|
|
542
|
+
begin
|
|
543
|
+
@pool_registry.shutdown
|
|
544
|
+
rescue => e
|
|
545
|
+
error ||= e
|
|
546
|
+
ensure
|
|
547
|
+
begin
|
|
548
|
+
@timer_service.shutdown
|
|
549
|
+
rescue => e
|
|
550
|
+
error ||= e
|
|
551
|
+
end
|
|
552
|
+
end
|
|
553
|
+
error
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
def validate_timeout!(value, name)
|
|
557
|
+
return if value.is_a?(Numeric) && value >= 0
|
|
558
|
+
|
|
559
|
+
raise ArgumentError, "#{name} must be a non-negative Numeric"
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
def monotonic_now
|
|
563
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
564
|
+
end
|
|
565
|
+
|
|
376
566
|
TASK_TYPE_PREFIXES = %w[agent tool workflow rag llm vector].freeze
|
|
377
567
|
private_constant :TASK_TYPE_PREFIXES
|
|
378
568
|
|
data/lib/phronomy/engine/task.rb
CHANGED
|
@@ -212,17 +212,12 @@ module Phronomy
|
|
|
212
212
|
# If this task fails or is cancelled, the mapped task also fails/is
|
|
213
213
|
# cancelled with the same error. The block is never called in error cases.
|
|
214
214
|
#
|
|
215
|
-
# The
|
|
216
|
-
#
|
|
217
|
-
#
|
|
218
|
-
# +:action_completed+ path:
|
|
215
|
+
# The transformation runs from the source Task's completion callback.
|
|
216
|
+
# It is a generic value-composition API; Workflow entry actions do not await
|
|
217
|
+
# either the source Task or the mapped Task.
|
|
219
218
|
#
|
|
220
|
-
# @example
|
|
221
|
-
#
|
|
222
|
-
# TranslationAgent.new.invoke_async(ctx.query).map do |result|
|
|
223
|
-
# ctx.merge(answer: result[:output]) # returns WorkflowContext
|
|
224
|
-
# end
|
|
225
|
-
# }
|
|
219
|
+
# @example Transforming an Agent result outside a Workflow entry action
|
|
220
|
+
# output_task = agent.invoke_async("hello").map { |result| result[:output] }
|
|
226
221
|
#
|
|
227
222
|
# @yield [value] the completed value of this task
|
|
228
223
|
# @yieldreturn [Object] the value for the mapped task
|
data/lib/phronomy/event.rb
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Phronomy
|
|
4
|
-
# Immutable event
|
|
4
|
+
# Immutable event used for EventLoop communication.
|
|
5
5
|
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
6
|
+
# User-defined Workflow events carry application-owned payloads unchanged.
|
|
7
|
+
# Correlation identifiers, stale-event decisions, and domain interpretation
|
|
8
|
+
# remain application concerns.
|
|
9
|
+
#
|
|
10
|
+
# @param type [Symbol] event identifier
|
|
11
|
+
# @param target_id [String] FSMSession identifier
|
|
12
|
+
# @param payload [Object] optional event data
|
|
13
13
|
Event = Data.define(:type, :target_id, :payload)
|
|
14
14
|
end
|