phronomy 0.17.0 → 0.18.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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +97 -1134
  3. data/README.md +68 -1280
  4. data/benchmark/bench_regression.rb +25 -5
  5. data/docs/changelog/0.14-and-earlier.md +1137 -0
  6. data/docs/decisions/008-orchestrator-uses-os-threads.md +46 -48
  7. data/docs/decisions/010-cooperative-first-concurrency.md +155 -235
  8. data/docs/features.md +87 -0
  9. data/docs/getting-started.md +351 -0
  10. data/docs/migrations/0.15.md +35 -0
  11. data/docs/migrations/0.16.md +43 -0
  12. data/docs/runtime-and-concurrency.md +258 -0
  13. data/examples/workflows/generic_task_event_mapping.rb +14 -6
  14. data/lib/phronomy/agent/agent_invocation_session_builder.rb +2 -2
  15. data/lib/phronomy/agent/async_event_api.rb +3 -3
  16. data/lib/phronomy/agent/base.rb +35 -19
  17. data/lib/phronomy/agent/context/capability/base.rb +13 -3
  18. data/lib/phronomy/agent/execution_coordinator.rb +6 -6
  19. data/lib/phronomy/agent/shared_state.rb +2 -0
  20. data/lib/phronomy/agent/tool_executor.rb +29 -71
  21. data/lib/phronomy/agent/tool_invocation.rb +97 -47
  22. data/lib/phronomy/agent/tool_invocation_session_builder.rb +55 -161
  23. data/lib/phronomy/configuration.rb +5 -29
  24. data/lib/phronomy/diagnostics.rb +12 -41
  25. data/lib/phronomy/engine/concurrency/async_queue.rb +5 -188
  26. data/lib/phronomy/engine/concurrency/cancellation_scope.rb +6 -7
  27. data/lib/phronomy/engine/concurrency/cancellation_token.rb +48 -3
  28. data/lib/phronomy/engine/concurrency/deadline.rb +2 -3
  29. data/lib/phronomy/engine/concurrency/offload_pool.rb +696 -0
  30. data/lib/phronomy/engine/concurrency/pool_registry.rb +5 -5
  31. data/lib/phronomy/engine/event_loop.rb +89 -190
  32. data/lib/phronomy/engine/runtime/timer_queue.rb +48 -71
  33. data/lib/phronomy/engine/runtime/timer_service.rb +13 -21
  34. data/lib/phronomy/engine/runtime.rb +45 -158
  35. data/lib/phronomy/engine/task.rb +136 -277
  36. data/lib/phronomy/llm_adapter/base.rb +14 -14
  37. data/lib/phronomy/llm_adapter/ruby_llm.rb +3 -4
  38. data/lib/phronomy/llm_adapter.rb +2 -2
  39. data/lib/phronomy/metrics.rb +15 -30
  40. data/lib/phronomy/multi_agent/fan_out_invocation.rb +146 -0
  41. data/lib/phronomy/multi_agent/fan_out_session_builder.rb +125 -0
  42. data/lib/phronomy/multi_agent/handoff.rb +1 -0
  43. data/lib/phronomy/multi_agent/orchestrator.rb +147 -99
  44. data/lib/phronomy/multi_agent/team_coordinator.rb +2 -0
  45. data/lib/phronomy/testing/eval/comparison.rb +23 -0
  46. data/lib/phronomy/testing/eval/dataset.rb +27 -0
  47. data/lib/phronomy/testing/eval/eval_case.rb +13 -0
  48. data/lib/phronomy/testing/eval/eval_result.rb +16 -0
  49. data/lib/phronomy/testing/eval/metrics.rb +43 -0
  50. data/lib/phronomy/testing/eval/runner.rb +52 -0
  51. data/lib/phronomy/testing/eval/scorer/base.rb +15 -0
  52. data/lib/phronomy/testing/eval/scorer/exact_match.rb +25 -0
  53. data/lib/phronomy/testing/eval/scorer/includes_scorer.rb +25 -0
  54. data/lib/phronomy/testing/eval/scorer/llm_judge.rb +46 -0
  55. data/lib/phronomy/testing/eval/scorer.rb +10 -0
  56. data/lib/phronomy/testing/eval.rb +9 -0
  57. data/lib/phronomy/testing/fake_clock.rb +6 -53
  58. data/lib/phronomy/testing.rb +2 -6
  59. data/lib/phronomy/tools/agent.rb +141 -6
  60. data/lib/phronomy/vector_store/async_backend.rb +21 -17
  61. data/lib/phronomy/vector_store/base.rb +2 -2
  62. data/lib/phronomy/vector_store/embeddings/base.rb +6 -5
  63. data/lib/phronomy/version.rb +1 -1
  64. data/lib/phronomy/workflow_runner.rb +2 -4
  65. data/lib/phronomy.rb +7 -4
  66. data/scripts/api_snapshot.rb +4 -4
  67. metadata +23 -31
  68. data/lib/phronomy/engine/concurrency/blocking_adapter_pool.rb +0 -561
  69. data/lib/phronomy/engine/runtime/deterministic_scheduler.rb +0 -439
  70. data/lib/phronomy/engine/runtime/fake_scheduler.rb +0 -165
  71. data/lib/phronomy/engine/runtime/runtime_metrics.rb +0 -116
  72. data/lib/phronomy/engine/runtime/scheduler.rb +0 -98
  73. data/lib/phronomy/engine/runtime/scheduler_timer_adapter.rb +0 -79
  74. data/lib/phronomy/engine/runtime/task_registry.rb +0 -95
  75. data/lib/phronomy/engine/runtime/thread_scheduler.rb +0 -30
  76. data/lib/phronomy/engine/task/backend.rb +0 -80
  77. data/lib/phronomy/engine/task/deferred_backend.rb +0 -73
  78. data/lib/phronomy/engine/task/fiber_backend.rb +0 -157
  79. data/lib/phronomy/engine/task/immediate_backend.rb +0 -89
  80. data/lib/phronomy/engine/task/mapped_backend.rb +0 -90
  81. data/lib/phronomy/engine/task/thread_backend.rb +0 -84
  82. data/lib/phronomy/engine/task_group.rb +0 -193
  83. data/lib/phronomy/eval/comparison.rb +0 -47
  84. data/lib/phronomy/eval/dataset.rb +0 -45
  85. data/lib/phronomy/eval/eval_case.rb +0 -17
  86. data/lib/phronomy/eval/eval_result.rb +0 -29
  87. data/lib/phronomy/eval/metrics.rb +0 -66
  88. data/lib/phronomy/eval/runner.rb +0 -94
  89. data/lib/phronomy/eval/scorer/base.rb +0 -22
  90. data/lib/phronomy/eval/scorer/exact_match.rb +0 -31
  91. data/lib/phronomy/eval/scorer/includes_scorer.rb +0 -32
  92. data/lib/phronomy/eval/scorer/llm_judge.rb +0 -72
  93. data/lib/phronomy/eval/scorer.rb +0 -9
  94. data/lib/phronomy/eval.rb +0 -7
  95. data/lib/phronomy/testing/fake_scheduler.rb +0 -104
  96. data/lib/phronomy/testing/scheduler_helpers.rb +0 -68
@@ -6,7 +6,7 @@ module Phronomy
6
6
  # optionally a {Deadline}.
7
7
  #
8
8
  # +CancellationScope+ replaces ad-hoc +Timeout.timeout+ calls in agent and
9
- # tool code. All work performed within a scope should observe the scope's
9
+ # tool code. All work performed within a scope should observe the scope's
10
10
  # token; when the scope is cancelled (explicitly or by deadline expiry) the
11
11
  # token is cancelled and all child tasks that check it will stop.
12
12
  #
@@ -18,10 +18,9 @@ module Phronomy
18
18
  #
19
19
  # @example Explicit cancellation
20
20
  # scope = Phronomy::Concurrency::CancellationScope.new
21
- # Phronomy::Runtime.instance.spawn(name: "worker") do
22
- # scope.token.raise_if_cancelled!
23
- # # ... do work ...
24
- # end
21
+ # operation = Phronomy::Runtime.instance.offload.submit(
22
+ # cancellation_token: scope.token
23
+ # ) { synchronous_operation }
25
24
  # scope.cancel! if some_condition
26
25
  class CancellationScope
27
26
  # @return [CancellationToken] the token owned by this scope
@@ -33,7 +32,7 @@ module Phronomy
33
32
  # @param parent_token [CancellationToken, nil] when provided, cancellation of
34
33
  # the parent token is propagated to this scope's token via a callback
35
34
  # (for explicit cancel) and/or the Runtime timer queue (for monotonic
36
- # deadline expiry). No polling thread is spawned.
35
+ # deadline expiry). No polling thread is spawned.
37
36
  # @api private
38
37
  def initialize(parent_token: nil)
39
38
  @token = Phronomy::Concurrency::CancellationToken.new
@@ -92,7 +91,7 @@ module Phronomy
92
91
  end
93
92
 
94
93
  # Pops from +queue+ with a timeout derived from the attached deadline (or
95
- # +fallback_timeout+ seconds when no deadline is set). If the pop times out,
94
+ # +fallback_timeout+ seconds when no deadline is set). If the pop times out,
96
95
  # the scope is cancelled and the block is called (or a {TimeoutError} raised).
97
96
  #
98
97
  # @param queue [Phronomy::Concurrency::AsyncQueue] the queue to pop from
@@ -28,8 +28,20 @@ module Phronomy
28
28
  [remaining, 0.0].max
29
29
  end
30
30
 
31
+ # Registers a callback for explicit cancellation.
32
+ #
33
+ # Deadline expiry by itself only changes {#cancelled?}. Components that need
34
+ # callback delivery for a monotonic deadline must promote that deadline to
35
+ # +cancel!+ through the Runtime timer queue.
36
+ #
37
+ # Cancellation callbacks are independent notifications. Failure of one
38
+ # callback is logged and does not suppress delivery to other callbacks.
39
+ #
40
+ # @return [self]
31
41
  # @api public
32
42
  def on_cancel(&block)
43
+ raise ArgumentError, "on_cancel requires a block" unless block
44
+
33
45
  already_cancelled = @mutex.synchronize do
34
46
  if @cancelled
35
47
  true
@@ -38,19 +50,29 @@ module Phronomy
38
50
  false
39
51
  end
40
52
  end
41
- block.call if already_cancelled
53
+ deliver_cancel_callback(block) if already_cancelled
42
54
  self
43
55
  end
44
56
 
57
+ # Explicitly cancels the token and invokes each currently registered callback
58
+ # once. The callback registry is cleared before callbacks run so completed
59
+ # registrations are not retained for the lifetime of a long-lived token.
60
+ #
61
+ # Cancellation callbacks are isolated from one another: a StandardError raised
62
+ # by one callback is logged and the remaining callbacks are still delivered.
63
+ #
64
+ # @return [self]
45
65
  # @api public
46
66
  def cancel!
47
67
  callbacks = @mutex.synchronize do
48
68
  return self if @cancelled
49
69
 
50
70
  @cancelled = true
51
- @cancel_callbacks.dup
71
+ callbacks = @cancel_callbacks
72
+ @cancel_callbacks = []
73
+ callbacks
52
74
  end
53
- callbacks.each(&:call)
75
+ callbacks.each { |callback| deliver_cancel_callback(callback) }
54
76
  self
55
77
  end
56
78
 
@@ -66,6 +88,29 @@ module Phronomy
66
88
  def raise_if_cancelled!(message = "invocation cancelled")
67
89
  raise Phronomy::CancellationError, message if cancelled?
68
90
  end
91
+
92
+ private
93
+
94
+ def deliver_cancel_callback(callback)
95
+ callback.call
96
+ rescue => error
97
+ Phronomy.configuration.logger&.error do
98
+ "[CancellationToken] on_cancel callback raised #{error.class}: #{error.message}"
99
+ end
100
+ end
101
+
102
+ # Removes an explicit-cancellation callback that is no longer needed.
103
+ #
104
+ # This exists so framework operation registrations do not keep their captured
105
+ # state alive when the operation completes before the token is cancelled. It is
106
+ # intentionally not part of the public cancellation API.
107
+ #
108
+ # A concurrent +cancel!+ may already have taken the callback for delivery;
109
+ # callers must therefore make their callback idempotent.
110
+ def unregister_cancel_callback(callback)
111
+ @mutex.synchronize { @cancel_callbacks.delete(callback) }
112
+ self
113
+ end
69
114
  end
70
115
  end
71
116
  end
@@ -44,9 +44,8 @@ module Phronomy
44
44
  end
45
45
 
46
46
  # Attaches this deadline to a {CancellationToken} by cancelling the token
47
- # when the deadline expires. Uses the Runtime timer queue (a single
48
- # background thread shared by all deadlines) instead of spawning one thread
49
- # per deadline.
47
+ # when the deadline expires. The Runtime timer queue is driven by EventLoop;
48
+ # no timer-specific OS thread is created.
50
49
  #
51
50
  # @param token [CancellationToken]
52
51
  # @param timer_queue [Runtime::TimerQueue, nil] queue to register with;