phronomy 0.13.0 → 0.14.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 +90 -0
- data/README.md +51 -2
- data/docs/mcp-client.md +75 -0
- data/gemfiles/mcp_1_0.gemfile +9 -0
- data/lib/phronomy/agent/base.rb +17 -17
- data/lib/phronomy/agent/context/capability/base.rb +6 -0
- data/lib/phronomy/agent/invocation_session.rb +10 -4
- data/lib/phronomy/agent/phase_machine_builder.rb +5 -4
- data/lib/phronomy/configuration.rb +15 -46
- 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 +319 -272
- data/lib/phronomy/engine/fsm_session.rb +17 -14
- 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/metrics.rb +4 -3
- data/lib/phronomy/testing/scheduler_helpers.rb +12 -3
- data/lib/phronomy/tools/mcp.rb +385 -81
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/workflow/phase_machine_builder.rb +13 -9
- data/lib/phronomy/workflow_context.rb +1 -2
- data/lib/phronomy/workflow_runner.rb +22 -12
- data/lib/phronomy.rb +24 -19
- metadata +47 -6
- data/lib/phronomy/engine/concurrency/concurrency_gate.rb +0 -157
- data/lib/phronomy/engine/concurrency/gate_registry.rb +0 -51
|
@@ -99,14 +99,19 @@ module Phronomy
|
|
|
99
99
|
# @return [Phronomy::Task]
|
|
100
100
|
# @api private
|
|
101
101
|
def invoke_deferred(input, config: {})
|
|
102
|
+
runtime = Phronomy::Runtime.instance
|
|
103
|
+
event_loop = runtime.event_loop
|
|
102
104
|
state, thread_id, recursion_limit, store = _build_initial_context(input, config)
|
|
103
105
|
result_task = Phronomy::Task.deferred(name: "workflow-async:#{thread_id}")
|
|
104
|
-
|
|
105
|
-
|
|
106
|
+
session = build_session_for(
|
|
107
|
+
context: state,
|
|
108
|
+
recursion_limit: recursion_limit,
|
|
109
|
+
runtime: runtime
|
|
110
|
+
)
|
|
106
111
|
if store && config[:thread_id]
|
|
107
112
|
# Wrap so that state is persisted when the task resolves.
|
|
108
113
|
persist_task = Phronomy::Task.deferred(name: "workflow-async-persist:#{thread_id}")
|
|
109
|
-
|
|
114
|
+
event_loop.register(session, completion: persist_task)
|
|
110
115
|
persist_task.on_complete do |result, error|
|
|
111
116
|
store.save(thread_id, {fields: result.to_h, phase: result.phase.to_s}) unless error
|
|
112
117
|
if error
|
|
@@ -118,7 +123,7 @@ module Phronomy
|
|
|
118
123
|
end
|
|
119
124
|
end
|
|
120
125
|
else
|
|
121
|
-
|
|
126
|
+
event_loop.register(session, completion: result_task)
|
|
122
127
|
end
|
|
123
128
|
result_task
|
|
124
129
|
end
|
|
@@ -202,7 +207,8 @@ module Phronomy
|
|
|
202
207
|
end
|
|
203
208
|
|
|
204
209
|
# Builds an FSMSession for the given context. Used in EventLoop mode.
|
|
205
|
-
def build_session_for(context:, recursion_limit:,
|
|
210
|
+
def build_session_for(context:, recursion_limit:, runtime: Phronomy::Runtime.instance,
|
|
211
|
+
resume_event: nil, resume_phase: nil)
|
|
206
212
|
Phronomy::FSMSession.new(
|
|
207
213
|
id: context.thread_id,
|
|
208
214
|
context: context,
|
|
@@ -214,24 +220,28 @@ module Phronomy
|
|
|
214
220
|
external_events: @external_events,
|
|
215
221
|
phase_machine_class: @phase_machine_class,
|
|
216
222
|
recursion_limit: recursion_limit,
|
|
223
|
+
event_loop: runtime.event_loop,
|
|
224
|
+
timer_queue_provider: -> { runtime.timer_queue },
|
|
217
225
|
action_timeouts: @action_timeouts,
|
|
218
226
|
resume_event: resume_event,
|
|
219
227
|
resume_phase: resume_phase
|
|
220
228
|
)
|
|
221
229
|
end
|
|
222
230
|
|
|
223
|
-
# Executes the workflow via the
|
|
231
|
+
# Executes the workflow via the default Runtime-owned EventLoop.
|
|
224
232
|
# Blocks the calling thread on a completion queue until the workflow
|
|
225
233
|
# finishes, halts at a wait state, or raises an error.
|
|
226
234
|
def run_via_event_loop(context, recursion_limit:, resume_event: nil, resume_phase: nil)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
Phronomy::EventLoop.instance.start
|
|
235
|
+
runtime = Phronomy::Runtime.instance
|
|
236
|
+
event_loop = runtime.event_loop
|
|
230
237
|
session = build_session_for(
|
|
231
|
-
context: context,
|
|
232
|
-
|
|
238
|
+
context: context,
|
|
239
|
+
recursion_limit: recursion_limit,
|
|
240
|
+
runtime: runtime,
|
|
241
|
+
resume_event: resume_event,
|
|
242
|
+
resume_phase: resume_phase
|
|
233
243
|
)
|
|
234
|
-
completion_queue =
|
|
244
|
+
completion_queue = event_loop.register(session)
|
|
235
245
|
result = completion_queue.pop
|
|
236
246
|
raise result if result.is_a?(Exception)
|
|
237
247
|
result
|
data/lib/phronomy.rb
CHANGED
|
@@ -77,6 +77,13 @@ module Phronomy
|
|
|
77
77
|
# @see Phronomy::Runtime.in_scheduler_context?
|
|
78
78
|
class SchedulerReentrancyError < Error; end
|
|
79
79
|
|
|
80
|
+
# Raised when work is submitted to a Runtime whose shutdown has begun, or
|
|
81
|
+
# when a Runtime cannot be reset because owned resources are still alive.
|
|
82
|
+
class RuntimeShutdownError < Error; end
|
|
83
|
+
|
|
84
|
+
# Raised when Runtime#shutdown is invoked from inside a Phronomy::Task.
|
|
85
|
+
class RuntimeShutdownReentrancyError < RuntimeShutdownError; end
|
|
86
|
+
|
|
80
87
|
# Raised by {Phronomy::GeneratorVerifier#invoke} when +raise_if_untrusted: true+
|
|
81
88
|
# and the pipeline's combined confidence score falls below the configured threshold.
|
|
82
89
|
#
|
|
@@ -175,28 +182,26 @@ module Phronomy
|
|
|
175
182
|
@configuration = original
|
|
176
183
|
end
|
|
177
184
|
|
|
178
|
-
#
|
|
179
|
-
#
|
|
185
|
+
# Shuts down and clears the process-wide default Runtime, then resets
|
|
186
|
+
# global configuration. Intended for test suites only.
|
|
180
187
|
#
|
|
181
|
-
#
|
|
182
|
-
#
|
|
183
|
-
# Call once before/after each example to ensure test isolation.
|
|
188
|
+
# Runtime execution failure and resource cleanup are separate. The
|
|
189
|
+
# singleton is cleared when cleanup completed, even if execution failed.
|
|
184
190
|
#
|
|
185
|
-
# @
|
|
186
|
-
#
|
|
191
|
+
# @param timeout [Numeric] maximum graceful wait for Runtime tasks and
|
|
192
|
+
# EventLoop shutdown
|
|
193
|
+
# @return [Phronomy::Runtime::ShutdownResult]
|
|
187
194
|
# @api public
|
|
188
|
-
def reset_runtime!
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
@configuration = Configuration.new
|
|
199
|
-
@configuration.event_loop_stop_grace_seconds = prev_grace if prev_grace
|
|
195
|
+
def reset_runtime!(timeout: configuration.event_loop_stop_grace_seconds)
|
|
196
|
+
previous_grace = @configuration&.event_loop_stop_grace_seconds
|
|
197
|
+
result = Runtime.reset_default!(timeout: timeout)
|
|
198
|
+
|
|
199
|
+
new_configuration = Configuration.new
|
|
200
|
+
if previous_grace
|
|
201
|
+
new_configuration.event_loop_stop_grace_seconds = previous_grace
|
|
202
|
+
end
|
|
203
|
+
@configuration = new_configuration
|
|
204
|
+
result
|
|
200
205
|
end
|
|
201
206
|
end
|
|
202
207
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: phronomy
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.14.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Raizo T.C.S
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-27 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ruby_llm
|
|
@@ -66,18 +66,58 @@ dependencies:
|
|
|
66
66
|
version: '0.6'
|
|
67
67
|
- !ruby/object:Gem::Dependency
|
|
68
68
|
name: mcp
|
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
|
70
|
+
requirements:
|
|
71
|
+
- - "~>"
|
|
72
|
+
- !ruby/object:Gem::Version
|
|
73
|
+
version: '1.0'
|
|
74
|
+
type: :runtime
|
|
75
|
+
prerelease: false
|
|
76
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
77
|
+
requirements:
|
|
78
|
+
- - "~>"
|
|
79
|
+
- !ruby/object:Gem::Version
|
|
80
|
+
version: '1.0'
|
|
81
|
+
- !ruby/object:Gem::Dependency
|
|
82
|
+
name: faraday
|
|
83
|
+
requirement: !ruby/object:Gem::Requirement
|
|
84
|
+
requirements:
|
|
85
|
+
- - ">="
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: '2'
|
|
88
|
+
- - "<"
|
|
89
|
+
- !ruby/object:Gem::Version
|
|
90
|
+
version: '3'
|
|
91
|
+
type: :runtime
|
|
92
|
+
prerelease: false
|
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
94
|
+
requirements:
|
|
95
|
+
- - ">="
|
|
96
|
+
- !ruby/object:Gem::Version
|
|
97
|
+
version: '2'
|
|
98
|
+
- - "<"
|
|
99
|
+
- !ruby/object:Gem::Version
|
|
100
|
+
version: '3'
|
|
101
|
+
- !ruby/object:Gem::Dependency
|
|
102
|
+
name: event_stream_parser
|
|
69
103
|
requirement: !ruby/object:Gem::Requirement
|
|
70
104
|
requirements:
|
|
71
105
|
- - ">="
|
|
72
106
|
- !ruby/object:Gem::Version
|
|
73
|
-
version: '
|
|
107
|
+
version: '1'
|
|
108
|
+
- - "<"
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '2'
|
|
74
111
|
type: :runtime
|
|
75
112
|
prerelease: false
|
|
76
113
|
version_requirements: !ruby/object:Gem::Requirement
|
|
77
114
|
requirements:
|
|
78
115
|
- - ">="
|
|
79
116
|
- !ruby/object:Gem::Version
|
|
80
|
-
version: '
|
|
117
|
+
version: '1'
|
|
118
|
+
- - "<"
|
|
119
|
+
- !ruby/object:Gem::Version
|
|
120
|
+
version: '2'
|
|
81
121
|
description: Phronomy is a Ruby AI agent framework that provides composable building
|
|
82
122
|
blocks — Agents, Workflows, Tools, Filters, and Tracing — for building AI agents
|
|
83
123
|
in Ruby. Powered by RubyLLM for LLM abstraction.
|
|
@@ -115,6 +155,8 @@ files:
|
|
|
115
155
|
- docs/decisions/009-state-store-abstraction.md
|
|
116
156
|
- docs/decisions/010-cooperative-first-concurrency.md
|
|
117
157
|
- docs/decisions/011-build-context-as-single-llm-input-authority.md
|
|
158
|
+
- docs/mcp-client.md
|
|
159
|
+
- gemfiles/mcp_1_0.gemfile
|
|
118
160
|
- lib/phronomy.rb
|
|
119
161
|
- lib/phronomy/agent.rb
|
|
120
162
|
- lib/phronomy/agent/base.rb
|
|
@@ -143,9 +185,7 @@ files:
|
|
|
143
185
|
- lib/phronomy/engine/concurrency/blocking_adapter_pool.rb
|
|
144
186
|
- lib/phronomy/engine/concurrency/cancellation_scope.rb
|
|
145
187
|
- lib/phronomy/engine/concurrency/cancellation_token.rb
|
|
146
|
-
- lib/phronomy/engine/concurrency/concurrency_gate.rb
|
|
147
188
|
- lib/phronomy/engine/concurrency/deadline.rb
|
|
148
|
-
- lib/phronomy/engine/concurrency/gate_registry.rb
|
|
149
189
|
- lib/phronomy/engine/concurrency/pool_registry.rb
|
|
150
190
|
- lib/phronomy/engine/event_loop.rb
|
|
151
191
|
- lib/phronomy/engine/fsm_session.rb
|
|
@@ -155,6 +195,7 @@ files:
|
|
|
155
195
|
- lib/phronomy/engine/runtime/runtime_metrics.rb
|
|
156
196
|
- lib/phronomy/engine/runtime/scheduler.rb
|
|
157
197
|
- lib/phronomy/engine/runtime/scheduler_timer_adapter.rb
|
|
198
|
+
- lib/phronomy/engine/runtime/shutdown_result.rb
|
|
158
199
|
- lib/phronomy/engine/runtime/task_registry.rb
|
|
159
200
|
- lib/phronomy/engine/runtime/thread_scheduler.rb
|
|
160
201
|
- lib/phronomy/engine/runtime/timer_queue.rb
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Phronomy
|
|
4
|
-
module Concurrency
|
|
5
|
-
# A counting semaphore that enforces a concurrency cap across a named
|
|
6
|
-
# resource category (e.g. agent tasks, tool tasks, LLM calls).
|
|
7
|
-
#
|
|
8
|
-
# When +max_concurrent+ is +nil+ the gate is a no-op and all callers
|
|
9
|
-
# pass through immediately without acquiring a slot.
|
|
10
|
-
#
|
|
11
|
-
# Backpressure behaviour when the gate is full is controlled by the
|
|
12
|
-
# +on_full:+ keyword:
|
|
13
|
-
# +:reject+ — raise {Phronomy::BackpressureError} immediately
|
|
14
|
-
# +:wait+ — block the calling fiber/thread until a slot is free
|
|
15
|
-
# +:timeout+ — like +:wait+ but raises {Phronomy::BackpressureError}
|
|
16
|
-
# after +timeout:+ seconds if no slot becomes available
|
|
17
|
-
#
|
|
18
|
-
# @example
|
|
19
|
-
# gate = Phronomy::Concurrency::ConcurrencyGate.new(max_concurrent: 5, name: :agent)
|
|
20
|
-
# gate.acquire(on_full: :reject) do
|
|
21
|
-
# run_agent_task
|
|
22
|
-
# end
|
|
23
|
-
class ConcurrencyGate
|
|
24
|
-
# @param max_concurrent [Integer, nil] concurrency cap; nil = unlimited
|
|
25
|
-
# @param name [Symbol, String, nil] human-readable label used in error messages
|
|
26
|
-
# @api private
|
|
27
|
-
def initialize(max_concurrent:, name: nil)
|
|
28
|
-
@max = max_concurrent
|
|
29
|
-
@name = name
|
|
30
|
-
@mutex = Mutex.new
|
|
31
|
-
@cond = ConditionVariable.new
|
|
32
|
-
@count = 0
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Returns the configured cap (or nil when unlimited).
|
|
36
|
-
attr_reader :max
|
|
37
|
-
|
|
38
|
-
# Returns the name label.
|
|
39
|
-
attr_reader :name
|
|
40
|
-
|
|
41
|
-
# Returns the number of slots currently in use.
|
|
42
|
-
def current_count
|
|
43
|
-
@mutex.synchronize { @count }
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# Acquires a slot, executes +block+, then releases the slot.
|
|
47
|
-
# When the gate is unlimited (max is nil) the block runs directly.
|
|
48
|
-
#
|
|
49
|
-
# @param on_full [:reject, :wait, :timeout] backpressure strategy
|
|
50
|
-
# @param timeout [Numeric, nil] seconds before +:timeout+ gives up
|
|
51
|
-
# @yield
|
|
52
|
-
# @return block return value
|
|
53
|
-
# @raise [Phronomy::BackpressureError] when +:reject+ or +:timeout+ fires
|
|
54
|
-
# @api private
|
|
55
|
-
def acquire(on_full: :wait, timeout: nil, &block)
|
|
56
|
-
return block.call if @max.nil?
|
|
57
|
-
|
|
58
|
-
_acquire_slot(on_full: on_full, timeout: timeout)
|
|
59
|
-
begin
|
|
60
|
-
block.call
|
|
61
|
-
ensure
|
|
62
|
-
_release_slot
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
private
|
|
67
|
-
|
|
68
|
-
def _acquire_slot(on_full:, timeout:)
|
|
69
|
-
scheduler = Phronomy::Runtime::Scheduler.current
|
|
70
|
-
if scheduler
|
|
71
|
-
_acquire_slot_coop(scheduler, on_full: on_full, timeout: timeout)
|
|
72
|
-
else
|
|
73
|
-
_acquire_slot_threaded(on_full: on_full, timeout: timeout)
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def _acquire_slot_coop(scheduler, on_full:, timeout:)
|
|
78
|
-
# In cooperative mode all tasks run on the same thread, so no mutex needed.
|
|
79
|
-
deadline = timeout ? (scheduler.virtual_time + timeout) : nil
|
|
80
|
-
@coop_signal ||= scheduler.new_signal
|
|
81
|
-
|
|
82
|
-
loop do
|
|
83
|
-
if @count < @max
|
|
84
|
-
@count += 1
|
|
85
|
-
return
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
case on_full
|
|
89
|
-
when :reject
|
|
90
|
-
raise Phronomy::BackpressureError,
|
|
91
|
-
"ConcurrencyGate[#{@name}] at capacity (#{@max}); " \
|
|
92
|
-
"increase max_concurrent_#{@name}_tasks or retry later"
|
|
93
|
-
when :timeout
|
|
94
|
-
if deadline && scheduler.virtual_time >= deadline
|
|
95
|
-
raise Phronomy::BackpressureError,
|
|
96
|
-
"ConcurrencyGate[#{@name}] timed out waiting for a free slot (cap: #{@max})"
|
|
97
|
-
end
|
|
98
|
-
scheduler.wait_for_signal(@coop_signal)
|
|
99
|
-
if deadline && scheduler.virtual_time >= deadline
|
|
100
|
-
raise Phronomy::BackpressureError,
|
|
101
|
-
"ConcurrencyGate[#{@name}] timed out waiting for a free slot (cap: #{@max})"
|
|
102
|
-
end
|
|
103
|
-
else # :wait
|
|
104
|
-
scheduler.wait_for_signal(@coop_signal)
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def _acquire_slot_threaded(on_full:, timeout:)
|
|
110
|
-
deadline = timeout ? (Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout) : nil
|
|
111
|
-
|
|
112
|
-
@mutex.synchronize do
|
|
113
|
-
loop do
|
|
114
|
-
if @count < @max
|
|
115
|
-
@count += 1
|
|
116
|
-
return
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
case on_full
|
|
120
|
-
when :reject
|
|
121
|
-
raise Phronomy::BackpressureError,
|
|
122
|
-
"ConcurrencyGate[#{@name}] at capacity (#{@max}); " \
|
|
123
|
-
"increase max_concurrent_#{@name}_tasks or retry later"
|
|
124
|
-
when :timeout
|
|
125
|
-
remaining = deadline ? (deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)) : nil
|
|
126
|
-
if remaining && remaining <= 0
|
|
127
|
-
raise Phronomy::BackpressureError,
|
|
128
|
-
"ConcurrencyGate[#{@name}] timed out waiting for a free slot (cap: #{@max})"
|
|
129
|
-
end
|
|
130
|
-
@cond.wait(@mutex, remaining || nil)
|
|
131
|
-
# re-check deadline after wakeup
|
|
132
|
-
if deadline && Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
|
|
133
|
-
raise Phronomy::BackpressureError,
|
|
134
|
-
"ConcurrencyGate[#{@name}] timed out waiting for a free slot (cap: #{@max})"
|
|
135
|
-
end
|
|
136
|
-
else # :wait
|
|
137
|
-
@cond.wait(@mutex)
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
def _release_slot
|
|
144
|
-
scheduler = Phronomy::Runtime::Scheduler.current
|
|
145
|
-
if scheduler && @coop_signal
|
|
146
|
-
@count -= 1
|
|
147
|
-
scheduler.raise_signal(@coop_signal)
|
|
148
|
-
else
|
|
149
|
-
@mutex.synchronize do
|
|
150
|
-
@count -= 1
|
|
151
|
-
@cond.signal
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
end
|
|
157
|
-
end
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Phronomy
|
|
4
|
-
module Concurrency
|
|
5
|
-
# Lazy cache of {ConcurrencyGate} instances, keyed by resource name.
|
|
6
|
-
#
|
|
7
|
-
# Gate concurrency caps are read from {Phronomy::Configuration} when a gate
|
|
8
|
-
# is first accessed; subsequent calls return the cached instance. Call
|
|
9
|
-
# {#reset} to drop the cache and force a rebuild on the next access.
|
|
10
|
-
# @api private
|
|
11
|
-
class GateRegistry
|
|
12
|
-
GATE_CONFIG_MAP = {
|
|
13
|
-
agent: :max_concurrent_agent_tasks,
|
|
14
|
-
tool: :max_concurrent_tool_tasks,
|
|
15
|
-
workflow: :max_concurrent_workflow_tasks,
|
|
16
|
-
llm: :max_concurrent_llm_calls,
|
|
17
|
-
vector: :max_concurrent_vector_searches
|
|
18
|
-
}.freeze
|
|
19
|
-
private_constant :GATE_CONFIG_MAP
|
|
20
|
-
|
|
21
|
-
def initialize
|
|
22
|
-
@mutex = Mutex.new
|
|
23
|
-
@gates = {}
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# Returns (or lazily creates) the gate for +name+.
|
|
27
|
-
# @param name [Symbol]
|
|
28
|
-
# @return [ConcurrencyGate]
|
|
29
|
-
# @api private
|
|
30
|
-
def get(name)
|
|
31
|
-
@mutex.synchronize { @gates[name] ||= _build(name) }
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# Drops the cached gate for +name+ so the next {#get} rebuilds it.
|
|
35
|
-
# @param name [Symbol]
|
|
36
|
-
# @return [void]
|
|
37
|
-
# @api private
|
|
38
|
-
def reset(name)
|
|
39
|
-
@mutex.synchronize { @gates.delete(name) }
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
private
|
|
43
|
-
|
|
44
|
-
def _build(name)
|
|
45
|
-
config_key = GATE_CONFIG_MAP[name]
|
|
46
|
-
max = config_key ? Phronomy.configuration.public_send(config_key) : nil
|
|
47
|
-
ConcurrencyGate.new(max_concurrent: max, name: name)
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|