phronomy 0.16.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 (123) hide show
  1. checksums.yaml +4 -4
  2. data/.mutant.yml +8 -9
  3. data/CHANGELOG.md +151 -1134
  4. data/CONTRIBUTING.md +28 -16
  5. data/README.md +69 -1249
  6. data/benchmark/baseline.json +2 -3
  7. data/benchmark/bench_agent_invoke.rb +4 -4
  8. data/benchmark/bench_context_assembler.rb +134 -34
  9. data/benchmark/bench_regression.rb +26 -6
  10. data/benchmark/bench_tool_schema.rb +2 -35
  11. data/docs/changelog/0.14-and-earlier.md +1137 -0
  12. data/docs/decisions/005-static-knowledge-class-level-cache.md +12 -1
  13. data/docs/decisions/008-orchestrator-uses-os-threads.md +46 -48
  14. data/docs/decisions/010-cooperative-first-concurrency.md +155 -228
  15. data/docs/decisions/011-build-context-as-single-llm-input-authority.md +2 -2
  16. data/docs/decisions/013-journal-backed-knowledge-as-context-candidates.md +122 -0
  17. data/docs/features.md +87 -0
  18. data/docs/getting-started.md +351 -0
  19. data/docs/migrations/0.15.md +35 -0
  20. data/docs/migrations/0.16.md +43 -0
  21. data/docs/runtime-and-concurrency.md +258 -0
  22. data/examples/workflows/generic_task_event_mapping.rb +14 -6
  23. data/lib/phronomy/agent/agent_invocation.rb +2 -36
  24. data/lib/phronomy/agent/agent_invocation_session_builder.rb +157 -94
  25. data/lib/phronomy/agent/agent_root.rb +1 -2
  26. data/lib/phronomy/agent/async_event_api.rb +3 -3
  27. data/lib/phronomy/agent/base.rb +170 -333
  28. data/lib/phronomy/agent/context/capability/base.rb +177 -298
  29. data/lib/phronomy/agent/context_assembler.rb +65 -29
  30. data/lib/phronomy/agent/context_parts/unit_builders/dependency_aware_unit_builder.rb +19 -89
  31. data/lib/phronomy/agent/context_plan_validator.rb +0 -33
  32. data/lib/phronomy/agent/execution_coordinator.rb +6 -7
  33. data/lib/phronomy/agent/journal_projection.rb +28 -2
  34. data/lib/phronomy/agent/ruby_llm_materializer.rb +2 -111
  35. data/lib/phronomy/agent/shared_state.rb +48 -138
  36. data/lib/phronomy/agent/token_budget_resolver.rb +5 -4
  37. data/lib/phronomy/agent/tool_executor.rb +29 -71
  38. data/lib/phronomy/agent/tool_invocation.rb +180 -336
  39. data/lib/phronomy/agent/tool_invocation_session_builder.rb +55 -161
  40. data/lib/phronomy/agent.rb +6 -10
  41. data/lib/phronomy/configuration.rb +4 -171
  42. data/lib/phronomy/diagnostics.rb +12 -41
  43. data/lib/phronomy/engine/concurrency/async_queue.rb +5 -188
  44. data/lib/phronomy/engine/concurrency/cancellation_scope.rb +6 -7
  45. data/lib/phronomy/engine/concurrency/cancellation_token.rb +51 -79
  46. data/lib/phronomy/engine/concurrency/deadline.rb +2 -3
  47. data/lib/phronomy/engine/concurrency/offload_pool.rb +696 -0
  48. data/lib/phronomy/engine/concurrency/pool_registry.rb +5 -5
  49. data/lib/phronomy/engine/event_loop.rb +89 -190
  50. data/lib/phronomy/engine/runtime/timer_queue.rb +48 -71
  51. data/lib/phronomy/engine/runtime/timer_service.rb +13 -21
  52. data/lib/phronomy/engine/runtime.rb +49 -377
  53. data/lib/phronomy/engine/task.rb +136 -277
  54. data/lib/phronomy/llm_adapter/base.rb +14 -14
  55. data/lib/phronomy/llm_adapter/ruby_llm.rb +3 -4
  56. data/lib/phronomy/llm_adapter.rb +2 -2
  57. data/lib/phronomy/llm_context_window/token_budget.rb +8 -79
  58. data/lib/phronomy/metrics.rb +15 -30
  59. data/lib/phronomy/multi_agent/fan_out_invocation.rb +146 -0
  60. data/lib/phronomy/multi_agent/fan_out_session_builder.rb +125 -0
  61. data/lib/phronomy/multi_agent/handoff.rb +1 -0
  62. data/lib/phronomy/multi_agent/orchestrator.rb +252 -256
  63. data/lib/phronomy/multi_agent/team_coordinator.rb +44 -133
  64. data/lib/phronomy/testing/eval/comparison.rb +23 -0
  65. data/lib/phronomy/testing/eval/dataset.rb +27 -0
  66. data/lib/phronomy/testing/eval/eval_case.rb +13 -0
  67. data/lib/phronomy/testing/eval/eval_result.rb +16 -0
  68. data/lib/phronomy/testing/eval/metrics.rb +43 -0
  69. data/lib/phronomy/testing/eval/runner.rb +52 -0
  70. data/lib/phronomy/testing/eval/scorer/base.rb +15 -0
  71. data/lib/phronomy/testing/eval/scorer/exact_match.rb +25 -0
  72. data/lib/phronomy/testing/eval/scorer/includes_scorer.rb +25 -0
  73. data/lib/phronomy/testing/eval/scorer/llm_judge.rb +46 -0
  74. data/lib/phronomy/testing/eval/scorer.rb +10 -0
  75. data/lib/phronomy/testing/eval.rb +9 -0
  76. data/lib/phronomy/testing/fake_clock.rb +6 -53
  77. data/lib/phronomy/testing.rb +2 -6
  78. data/lib/phronomy/tools/agent.rb +141 -6
  79. data/lib/phronomy/vector_store/async_backend.rb +21 -17
  80. data/lib/phronomy/vector_store/base.rb +2 -2
  81. data/lib/phronomy/vector_store/embeddings/base.rb +6 -5
  82. data/lib/phronomy/vector_store/in_memory.rb +2 -2
  83. data/lib/phronomy/version.rb +1 -1
  84. data/lib/phronomy/workflow_runner.rb +2 -4
  85. data/lib/phronomy.rb +7 -121
  86. data/scripts/api_snapshot.rb +4 -15
  87. metadata +24 -38
  88. data/lib/phronomy/agent/context/knowledge/base.rb +0 -58
  89. data/lib/phronomy/agent/context/knowledge/entity_knowledge.rb +0 -102
  90. data/lib/phronomy/agent/context/knowledge/static_knowledge.rb +0 -58
  91. data/lib/phronomy/agent/fsm_runtime_adapter.rb +0 -210
  92. data/lib/phronomy/engine/concurrency/blocking_adapter_pool.rb +0 -561
  93. data/lib/phronomy/engine/runtime/deterministic_scheduler.rb +0 -439
  94. data/lib/phronomy/engine/runtime/fake_scheduler.rb +0 -165
  95. data/lib/phronomy/engine/runtime/runtime_metrics.rb +0 -116
  96. data/lib/phronomy/engine/runtime/scheduler.rb +0 -98
  97. data/lib/phronomy/engine/runtime/scheduler_timer_adapter.rb +0 -79
  98. data/lib/phronomy/engine/runtime/task_registry.rb +0 -95
  99. data/lib/phronomy/engine/runtime/thread_scheduler.rb +0 -30
  100. data/lib/phronomy/engine/task/backend.rb +0 -80
  101. data/lib/phronomy/engine/task/deferred_backend.rb +0 -73
  102. data/lib/phronomy/engine/task/fiber_backend.rb +0 -157
  103. data/lib/phronomy/engine/task/immediate_backend.rb +0 -89
  104. data/lib/phronomy/engine/task/mapped_backend.rb +0 -90
  105. data/lib/phronomy/engine/task/thread_backend.rb +0 -84
  106. data/lib/phronomy/engine/task_group.rb +0 -265
  107. data/lib/phronomy/eval/comparison.rb +0 -47
  108. data/lib/phronomy/eval/dataset.rb +0 -45
  109. data/lib/phronomy/eval/eval_case.rb +0 -17
  110. data/lib/phronomy/eval/eval_result.rb +0 -29
  111. data/lib/phronomy/eval/metrics.rb +0 -66
  112. data/lib/phronomy/eval/runner.rb +0 -94
  113. data/lib/phronomy/eval/scorer/base.rb +0 -22
  114. data/lib/phronomy/eval/scorer/exact_match.rb +0 -31
  115. data/lib/phronomy/eval/scorer/includes_scorer.rb +0 -32
  116. data/lib/phronomy/eval/scorer/llm_judge.rb +0 -72
  117. data/lib/phronomy/eval/scorer.rb +0 -9
  118. data/lib/phronomy/eval.rb +0 -7
  119. data/lib/phronomy/knowledge_source.rb +0 -12
  120. data/lib/phronomy/llm_context_window/assembler.rb +0 -191
  121. data/lib/phronomy/llm_context_window/context_version_cache.rb +0 -52
  122. data/lib/phronomy/testing/fake_scheduler.rb +0 -104
  123. data/lib/phronomy/testing/scheduler_helpers.rb +0 -68
@@ -2,32 +2,16 @@
2
2
 
3
3
  module Phronomy
4
4
  module Testing
5
- # A deterministic, manually-advanced clock for use in tests.
6
- #
7
- # Replaces real +Process.clock_gettime+ calls so that time-sensitive code
8
- # can be tested without relying on wall-clock sleeps.
9
- #
10
- # @example
11
- # clock = Phronomy::Testing::FakeClock.new
12
- # clock.now # => 0.0
13
- # clock.advance(5) # advance by 5 seconds
14
- # clock.now # => 5.0
5
+ # Deterministic manually-advanced clock for tests.
15
6
  class FakeClock
16
- # @return [Float] the current logical time in seconds since the epoch (t=0)
17
7
  attr_reader :now
18
8
 
19
9
  def initialize
20
10
  @now = 0.0
21
- @callbacks = [] # [[fire_at, block], ...]
11
+ @callbacks = []
22
12
  @mutex = Mutex.new
23
13
  end
24
14
 
25
- # Advance the clock by +seconds+ and fire any registered callbacks whose
26
- # deadline has passed.
27
- #
28
- # @param seconds [Numeric]
29
- # @return [self]
30
- # @api private
31
15
  def advance(seconds)
32
16
  @mutex.synchronize do
33
17
  @now += seconds.to_f
@@ -36,65 +20,34 @@ module Phronomy
36
20
  self
37
21
  end
38
22
 
39
- # Register a one-shot callback that fires when the clock reaches +at+.
40
- #
41
- # @param at [Numeric] logical time to fire
42
- # @yield called with no arguments when the clock reaches +at+
43
- # @return [self]
44
- # @api private
45
23
  def at(at, &block)
46
24
  @mutex.synchronize { @callbacks << [at.to_f, block] }
47
25
  self
48
26
  end
49
27
 
50
- # Schedule a one-shot callback to fire after +seconds+ from the current
51
- # logical time. This is the same interface as {Runtime::TimerQueue#schedule}
52
- # so that a +FakeClock+ can be passed as a +timer_queue:+ argument in tests.
53
- #
54
- # @param seconds [Numeric] delay in logical seconds
55
- # @yield called when the clock reaches the scheduled time
56
- # @return [self]
57
- # @api private
58
28
  def schedule(seconds:, &block)
59
29
  at(@now + seconds.to_f, &block)
60
30
  end
61
31
 
62
- # Returns the number of pending (un-fired) callbacks.
63
- # @return [Integer]
64
- # @api private
65
32
  def pending_callbacks
66
33
  @mutex.synchronize { @callbacks.size }
67
34
  end
68
35
 
69
- # Returns the logical time of the next pending callback, or +nil+ if
70
- # there are no pending callbacks.
71
- #
72
- # @return [Float, nil]
73
- # @api private
74
36
  def next_timer_at
75
37
  @mutex.synchronize { @callbacks.min_by { |(t, _)| t }&.first }
76
38
  end
77
39
 
78
- # Advance the clock exactly to the next pending callback and fire it.
79
- # Raises +RuntimeError+ when there are no pending callbacks.
80
- #
81
- # @return [self]
82
- # @api private
83
40
  def advance_to_next_timer
84
41
  target = next_timer_at
85
42
  raise "No pending timers to advance to" unless target
86
-
87
43
  advance(target - @now)
88
44
  end
89
45
 
90
- # Returns descriptive entries for all pending callbacks.
91
- # Used by {Phronomy::Runtime::FakeScheduler#pending_timers}.
92
- #
93
- # @return [Array<Hash>] each entry: +{ fire_at:, description: nil }+
94
- # @api private
95
46
  def pending_timer_entries
96
47
  @mutex.synchronize do
97
- @callbacks.sort_by { |(t, _)| t }.map { |(t, _)| {fire_at: t, description: nil} }
48
+ @callbacks.sort_by { |(t, _)| t }.map do |(time, _)|
49
+ {fire_at: time, description: nil}
50
+ end
98
51
  end
99
52
  end
100
53
 
@@ -102,7 +55,7 @@ module Phronomy
102
55
 
103
56
  def fire_expired_callbacks!
104
57
  fired, @callbacks = @callbacks.partition { |(t, _)| t <= @now }
105
- fired.sort_by { |(t, _)| t }.each { |(_, cb)| cb.call }
58
+ fired.sort_by { |(t, _)| t }.each { |(_, callback)| callback.call }
106
59
  end
107
60
  end
108
61
  end
@@ -1,12 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- # Test helpers for deterministic, timer-independent testing.
5
- #
6
- # @example
7
- # require "phronomy/testing"
8
- # clock = Phronomy::Testing::FakeClock.new
9
- # scheduler = Phronomy::Testing::FakeScheduler.new
4
+ # Test-only helpers. Nothing under this namespace participates in the
5
+ # production Runtime control plane.
10
6
  module Testing
11
7
  end
12
8
  end
@@ -2,11 +2,17 @@
2
2
 
3
3
  module Phronomy
4
4
  module Tools
5
- # Wraps a Phronomy::Agent::Base subclass as a callable tool so that a parent
6
- # agent can delegate a one-shot sub-task through the same stateful pipeline.
5
+ # Wraps a Phronomy::Agent::Base subclass as a callable Tool.
6
+ #
7
+ # Agent-backed Tools are logically asynchronous rather than offloaded
8
+ # synchronous operations. Their ToolInvocation starts the child Agent and then
9
+ # returns to EventLoop immediately; the Tool completion handle settles when the
10
+ # child Agent FSMSession finishes. An OffloadPool worker is therefore never
11
+ # consumed merely to wait for another Agent.
7
12
  class Agent < Phronomy::Agent::Context::Capability::Base
8
- description "Wraps an agent as a tool"
9
- param :input, type: :string, desc: "The input to forward to the wrapped agent"
13
+ execution_mode :cooperative
14
+ description "Wraps a Phronomy::Agent as a Tool"
15
+ param :input, type: :string, desc: "The input to forward to the wrapped Agent"
10
16
 
11
17
  class << self
12
18
  def from_agent(agent_class, tool_name: nil, description: nil)
@@ -25,13 +31,38 @@ module Phronomy
25
31
 
26
32
  klass.tool_name(effective_name)
27
33
  klass.description(effective_desc)
28
- klass.define_method(:execute) do |input:|
34
+
35
+ # Preserve the synchronous Tool API for top-level callers. ToolInvocation
36
+ # never uses this path for Agent-backed Tools; it calls #call_async.
37
+ klass.define_method(:execute) do |input:, cancellation_token: nil|
38
+ invoke_options = {}
39
+ if cancellation_token
40
+ invoke_options[:config] = {cancellation_token: cancellation_token}
41
+ end
29
42
  result = Phronomy::Agent.run_once(
30
43
  definition: agent_class,
31
- input: input
44
+ input: input,
45
+ **invoke_options
32
46
  )
33
47
  result[:output].to_s
34
48
  end
49
+
50
+ # Internal asynchronous execution protocol used by Agent#call_async.
51
+ # The child Agent owns its own FSMSession/EventLoop lifecycle; this
52
+ # method only returns its completion handle and performs a short map.
53
+ klass.define_method(:execute_async) do |input:, cancellation_token: nil, config: {}|
54
+ persistence = Phronomy::Persistence::InMemory.new
55
+ agent = agent_class.create(persistence: persistence)
56
+ task_config = (config || {}).dup
57
+ if cancellation_token && !task_config[:cancellation_token]
58
+ task_config[:cancellation_token] = cancellation_token
59
+ end
60
+
61
+ agent.invoke_async(input, config: task_config).map do |result|
62
+ result[:output].to_s
63
+ end
64
+ end
65
+ klass.send(:private, :execute_async)
35
66
  klass
36
67
  end
37
68
 
@@ -49,6 +80,110 @@ module Phronomy
49
80
  .sub(/_tool$/, "")
50
81
  end
51
82
  end
83
+
84
+ # Agent-backed Tools have an asynchronous implementation that does not use
85
+ # ToolExecutor/OffloadPool. Validation and Tool error policy still match
86
+ # Capability::Base#call.
87
+ def call_async(
88
+ args,
89
+ cancellation_token: nil,
90
+ config: {}
91
+ )
92
+ cancellation_token&.raise_if_cancelled!
93
+ validated_args, schema_error = send(:validate_and_coerce, args)
94
+
95
+ if schema_error
96
+ return schema_error_task(schema_error)
97
+ end
98
+
99
+ source = execute_async(
100
+ **(validated_args || {}),
101
+ cancellation_token: cancellation_token,
102
+ config: config || {}
103
+ )
104
+ unless source.respond_to?(:on_complete)
105
+ raise Phronomy::ToolError,
106
+ "#{self.class.name} asynchronous execution must return a completion handle"
107
+ end
108
+
109
+ result_task = Phronomy::Task.deferred(name: "agent-tool-#{name}")
110
+ source.on_complete do |result, error|
111
+ if error
112
+ settle_async_error(result_task, error)
113
+ next
114
+ end
115
+
116
+ begin
117
+ result_task.complete(send(:truncate_result_if_needed, result))
118
+ rescue => result_error
119
+ settle_async_error(result_task, result_error)
120
+ end
121
+ end
122
+ result_task
123
+ rescue Phronomy::ToolError, Phronomy::CancellationError => error
124
+ failed_task(error)
125
+ rescue => error
126
+ result_task = Phronomy::Task.deferred(name: "agent-tool-#{name}")
127
+ settle_async_error(result_task, error)
128
+ result_task
129
+ end
130
+
131
+ private
132
+
133
+ # Subclasses created by .from_agent and Orchestrator override this method.
134
+ # It deliberately remains private so it is not part of the public Tool API.
135
+ def execute_async(input:, cancellation_token: nil, config: {})
136
+ task = Phronomy::Task.deferred(name: "agent-tool-#{name}-fallback")
137
+ begin
138
+ task.complete(execute(input: input, cancellation_token: cancellation_token))
139
+ rescue => error
140
+ task.fail(error)
141
+ end
142
+ task
143
+ end
144
+
145
+ def schema_error_task(schema_error)
146
+ task = Phronomy::Task.deferred(name: "agent-tool-#{name}-schema")
147
+ if self.class.on_schema_error == :raise
148
+ task.fail(Phronomy::ToolError.new(
149
+ "#{self.class.name} schema error: #{schema_error}"
150
+ ))
151
+ else
152
+ task.complete("Schema validation failed: #{schema_error}")
153
+ end
154
+ task
155
+ end
156
+
157
+ def failed_task(error)
158
+ Phronomy::Task.deferred(name: "agent-tool-#{name}-failed").tap do |task|
159
+ task.fail(error)
160
+ end
161
+ end
162
+
163
+ def settle_async_error(task, error)
164
+ if error.is_a?(Phronomy::ToolError) || error.is_a?(Phronomy::CancellationError)
165
+ task.fail(error)
166
+ return task
167
+ end
168
+
169
+ if self.class.on_error == :suppress
170
+ message = "[Phronomy] Tool #{self.class.name} suppressed error: " \
171
+ "#{error.class}: #{error.message}"
172
+ if Phronomy.configuration.logger
173
+ Phronomy.configuration.logger.warn(message)
174
+ else
175
+ warn message
176
+ end
177
+ task.complete("Tool error suppressed: #{error.message}")
178
+ else
179
+ wrapped = Phronomy::ToolError.new(
180
+ "#{self.class.name} execution failed: #{error.message}"
181
+ )
182
+ wrapped.set_backtrace(error.backtrace)
183
+ task.fail(wrapped)
184
+ end
185
+ task
186
+ end
52
187
  end
53
188
  end
54
189
  end
@@ -7,7 +7,7 @@ module Phronomy
7
7
  # Mixing this module into a VectorStore class provides three choices:
8
8
  #
9
9
  # 1. **Do nothing** — inherits default implementations from {VectorStore::Base}
10
- # that route through {BlockingAdapterPool} (the previous behaviour).
10
+ # that route through {OffloadPool}.
11
11
  #
12
12
  # 2. **Override selectively** — override only the async methods where the
13
13
  # backend has a native async driver, while the remaining methods fall back
@@ -30,7 +30,7 @@ module Phronomy
30
30
  module AsyncBackend
31
31
  # Async variant of {VectorStore::Base#add}.
32
32
  #
33
- # Submits the add call to {BlockingAdapterPool} by default.
33
+ # Submits the add call to {OffloadPool} by default.
34
34
  # Override to use a native async driver.
35
35
  #
36
36
  # @param id [String]
@@ -38,12 +38,13 @@ module Phronomy
38
38
  # @param metadata [Hash]
39
39
  # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
40
40
  # @param timeout [Numeric, nil]
41
- # @return [BlockingAdapterPool::PendingOperation]
41
+ # @return [OffloadPool::PendingOperation]
42
42
  # @api public
43
43
  def add_async(id:, embedding:, metadata: {}, cancellation_token: nil, timeout: nil)
44
- Phronomy::Runtime.instance.blocking_io.submit(
44
+ Phronomy::Runtime.instance.offload.submit(
45
45
  timeout: timeout,
46
- cancellation_token: cancellation_token
46
+ cancellation_token: cancellation_token,
47
+ on_full: :raise
47
48
  ) do
48
49
  add(id: id, embedding: embedding, metadata: metadata, cancellation_token: cancellation_token)
49
50
  end
@@ -51,19 +52,20 @@ module Phronomy
51
52
 
52
53
  # Async variant of {VectorStore::Base#search}.
53
54
  #
54
- # Submits the search call to {BlockingAdapterPool} by default.
55
+ # Submits the search call to {OffloadPool} by default.
55
56
  # Override to use a native async driver.
56
57
  #
57
58
  # @param query_embedding [Array<Float>]
58
59
  # @param k [Integer]
59
60
  # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
60
61
  # @param timeout [Numeric, nil]
61
- # @return [BlockingAdapterPool::PendingOperation]
62
+ # @return [OffloadPool::PendingOperation]
62
63
  # @api public
63
64
  def search_async(query_embedding:, k: 5, cancellation_token: nil, timeout: nil)
64
- Phronomy::Runtime.instance.blocking_io.submit(
65
+ Phronomy::Runtime.instance.offload.submit(
65
66
  timeout: timeout,
66
- cancellation_token: cancellation_token
67
+ cancellation_token: cancellation_token,
68
+ on_full: :raise
67
69
  ) do
68
70
  search(query_embedding: query_embedding, k: k, cancellation_token: cancellation_token)
69
71
  end
@@ -71,18 +73,19 @@ module Phronomy
71
73
 
72
74
  # Async variant of {VectorStore::Base#remove}.
73
75
  #
74
- # Submits the remove call to {BlockingAdapterPool} by default.
76
+ # Submits the remove call to {OffloadPool} by default.
75
77
  # Override to use a native async driver.
76
78
  #
77
79
  # @param id [String]
78
80
  # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
79
81
  # @param timeout [Numeric, nil]
80
- # @return [BlockingAdapterPool::PendingOperation]
82
+ # @return [OffloadPool::PendingOperation]
81
83
  # @api public
82
84
  def remove_async(id:, cancellation_token: nil, timeout: nil)
83
- Phronomy::Runtime.instance.blocking_io.submit(
85
+ Phronomy::Runtime.instance.offload.submit(
84
86
  timeout: timeout,
85
- cancellation_token: cancellation_token
87
+ cancellation_token: cancellation_token,
88
+ on_full: :raise
86
89
  ) do
87
90
  remove(id: id)
88
91
  end
@@ -90,17 +93,18 @@ module Phronomy
90
93
 
91
94
  # Async variant of {VectorStore::Base#clear}.
92
95
  #
93
- # Submits the clear call to {BlockingAdapterPool} by default.
96
+ # Submits the clear call to {OffloadPool} by default.
94
97
  # Override to use a native async driver.
95
98
  #
96
99
  # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
97
100
  # @param timeout [Numeric, nil]
98
- # @return [BlockingAdapterPool::PendingOperation]
101
+ # @return [OffloadPool::PendingOperation]
99
102
  # @api public
100
103
  def clear_async(cancellation_token: nil, timeout: nil)
101
- Phronomy::Runtime.instance.blocking_io.submit(
104
+ Phronomy::Runtime.instance.offload.submit(
102
105
  timeout: timeout,
103
- cancellation_token: cancellation_token
106
+ cancellation_token: cancellation_token,
107
+ on_full: :raise
104
108
  ) do
105
109
  clear
106
110
  end
@@ -9,8 +9,8 @@ module Phronomy
9
9
  #
10
10
  # Async methods (`search_async`, `add_async`, `remove_async`, `clear_async`)
11
11
  # are provided by the {AsyncBackend} mixin which defaults to routing calls
12
- # through {BlockingAdapterPool}. Backends with native async drivers may
13
- # override individual async methods without touching the pool at all.
12
+ # through {OffloadPool}. Backends with native async drivers may override
13
+ # individual async methods without touching the pool at all.
14
14
  class Base
15
15
  include AsyncBackend
16
16
 
@@ -19,18 +19,19 @@ module Phronomy
19
19
  raise NotImplementedError, "#{self.class}#embed is not implemented"
20
20
  end
21
21
 
22
- # Submits an {#embed} call to {BlockingAdapterPool} and returns a
23
- # {BlockingAdapterPool::PendingOperation}.
22
+ # Submits an {#embed} call to {OffloadPool} and returns an
23
+ # {OffloadPool::PendingOperation}.
24
24
  #
25
25
  # @param text [String]
26
26
  # @param cancellation_token [Phronomy::Concurrency::CancellationToken, nil]
27
27
  # @param timeout [Numeric, nil] seconds before the operation is abandoned
28
- # @return [BlockingAdapterPool::PendingOperation]
28
+ # @return [OffloadPool::PendingOperation]
29
29
  # @api public
30
30
  def embed_async(text, cancellation_token = nil, timeout: nil)
31
- Phronomy::Runtime.instance.blocking_io.submit(
31
+ Phronomy::Runtime.instance.offload.submit(
32
32
  timeout: timeout,
33
- cancellation_token: cancellation_token
33
+ cancellation_token: cancellation_token,
34
+ on_full: :raise
34
35
  ) do
35
36
  embed(text, cancellation_token)
36
37
  end
@@ -4,8 +4,8 @@ module Phronomy
4
4
  module VectorStore
5
5
  # Pure-Ruby in-memory vector store using cosine similarity.
6
6
  #
7
- # Intended for tests, short-lived agents, and Retrieval::Semantic scenarios where
8
- # the message count is small enough that a linear scan is fast enough.
7
+ # Intended for tests, short-lived agents, and small retrieval workloads where
8
+ # the document count is small enough that a linear scan is fast enough.
9
9
  #
10
10
  # @example
11
11
  # store = Phronomy::VectorStore::InMemory.new
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- VERSION = "0.16.0"
4
+ VERSION = "0.18.0"
5
5
  end
@@ -311,13 +311,11 @@ module Phronomy
311
311
  end
312
312
 
313
313
  def complete_task(task, value)
314
- task.backend.unblock(value, nil)
315
- task.transition!(:completed, value: value)
314
+ task.complete(value)
316
315
  end
317
316
 
318
317
  def fail_task(task, error)
319
- task.backend.unblock(nil, error)
320
- task.transition!(:failed, error: error)
318
+ task.fail(error)
321
319
  end
322
320
 
323
321
  def failed_task(name, error)