phronomy 0.7.1 → 0.8.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/README.md +16 -16
- data/benchmark/bench_context_assembler.rb +2 -2
- data/benchmark/bench_regression.rb +5 -5
- data/benchmark/bench_token_estimator.rb +5 -5
- data/benchmark/bench_tool_schema.rb +1 -1
- data/benchmark/bench_vector_store.rb +1 -1
- data/lib/phronomy/agent/base.rb +86 -123
- data/lib/phronomy/agent/checkpoint.rb +118 -0
- data/lib/phronomy/agent/context/conversation/compaction_context.rb +117 -0
- data/lib/phronomy/agent/context/conversation/trigger_context.rb +43 -0
- data/lib/phronomy/agent/context/conversation/trim_context.rb +82 -0
- data/lib/phronomy/agent/context/instruction/prompt_template.rb +102 -0
- data/lib/phronomy/agent/context/knowledge/embeddings/base.rb +45 -0
- data/lib/phronomy/agent/context/knowledge/embeddings/ruby_llm_embeddings.rb +51 -0
- data/lib/phronomy/agent/context/knowledge/loader/base.rb +31 -0
- data/lib/phronomy/agent/context/knowledge/loader/csv_loader.rb +62 -0
- data/lib/phronomy/agent/context/knowledge/loader/markdown_loader.rb +82 -0
- data/lib/phronomy/agent/context/knowledge/loader/plain_text_loader.rb +28 -0
- data/lib/phronomy/agent/context/knowledge/source/base.rb +60 -0
- data/lib/phronomy/agent/context/knowledge/source/entity_knowledge.rb +102 -0
- data/lib/phronomy/agent/context/knowledge/source/rag_knowledge.rb +63 -0
- data/lib/phronomy/agent/context/knowledge/source/static_knowledge.rb +58 -0
- data/lib/phronomy/agent/context/knowledge/splitter/base.rb +53 -0
- data/lib/phronomy/agent/context/knowledge/splitter/fixed_size_splitter.rb +57 -0
- data/lib/phronomy/agent/context/knowledge/splitter/recursive_splitter.rb +111 -0
- data/lib/phronomy/agent/context/knowledge/vector_store/async_backend.rb +116 -0
- data/lib/phronomy/agent/context/knowledge/vector_store/base.rb +95 -0
- data/lib/phronomy/agent/context/knowledge/vector_store/in_memory.rb +109 -0
- data/lib/phronomy/agent/context/knowledge/vector_store/pgvector.rb +133 -0
- data/lib/phronomy/agent/context/knowledge/vector_store/redis_search.rb +198 -0
- data/lib/phronomy/agent/fsm.rb +1 -1
- data/lib/phronomy/agent/invocation_pipeline.rb +99 -0
- data/lib/phronomy/agent/lifecycle/fsm_session.rb +251 -0
- data/lib/phronomy/agent/lifecycle/phase_machine_builder.rb +249 -0
- data/lib/phronomy/agent/react_agent.rb +19 -14
- data/lib/phronomy/agent/runner.rb +2 -2
- data/lib/phronomy/agent/tool_executor.rb +108 -0
- data/lib/phronomy/concurrency/async_queue.rb +157 -0
- data/lib/phronomy/concurrency/blocking_adapter_pool.rb +443 -0
- data/lib/phronomy/concurrency/cancellation_scope.rb +125 -0
- data/lib/phronomy/concurrency/cancellation_token.rb +140 -0
- data/lib/phronomy/concurrency/concurrency_gate.rb +157 -0
- data/lib/phronomy/concurrency/deadline.rb +65 -0
- data/lib/phronomy/{runtime → concurrency}/gate_registry.rb +1 -1
- data/lib/phronomy/{runtime → concurrency}/pool_registry.rb +1 -1
- data/lib/phronomy/context.rb +2 -8
- data/lib/phronomy/embeddings.rb +2 -2
- data/lib/phronomy/eval/runner.rb +4 -0
- data/lib/phronomy/eval/scorer/llm_judge.rb +12 -1
- data/lib/phronomy/event_loop.rb +7 -7
- data/lib/phronomy/invocation_context.rb +3 -3
- data/lib/phronomy/knowledge_source.rb +0 -5
- data/lib/phronomy/llm_adapter/ruby_llm.rb +17 -11
- data/lib/phronomy/{context → llm_context_window}/assembler.rb +18 -3
- data/lib/phronomy/{context → llm_context_window}/context_version_cache.rb +1 -1
- data/lib/phronomy/{context → llm_context_window}/token_budget.rb +7 -4
- data/lib/phronomy/{context → llm_context_window}/token_estimator.rb +3 -3
- data/lib/phronomy/loader.rb +4 -4
- data/lib/phronomy/{agent → multi_agent}/handoff.rb +2 -2
- data/lib/phronomy/{agent → multi_agent}/orchestrator.rb +6 -6
- data/lib/phronomy/{agent → multi_agent}/parallel_tool_chat.rb +4 -4
- data/lib/phronomy/{agent → multi_agent}/team_coordinator.rb +2 -2
- data/lib/phronomy/runtime.rb +19 -4
- data/lib/phronomy/splitter.rb +3 -3
- data/lib/phronomy/task_group.rb +1 -1
- data/lib/phronomy/tool/base.rb +50 -9
- data/lib/phronomy/tracing/null_tracer.rb +3 -1
- data/lib/phronomy/vector_store.rb +2 -2
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/workflow_context.rb +8 -0
- data/lib/phronomy/workflow_runner.rb +11 -131
- data/lib/phronomy.rb +1 -0
- metadata +44 -42
- data/lib/phronomy/async_queue.rb +0 -155
- data/lib/phronomy/blocking_adapter_pool.rb +0 -435
- data/lib/phronomy/cancellation_scope.rb +0 -123
- data/lib/phronomy/cancellation_token.rb +0 -133
- data/lib/phronomy/concurrency_gate.rb +0 -155
- data/lib/phronomy/context/compaction_context.rb +0 -111
- data/lib/phronomy/context/trigger_context.rb +0 -39
- data/lib/phronomy/context/trim_context.rb +0 -75
- data/lib/phronomy/deadline.rb +0 -63
- data/lib/phronomy/embeddings/base.rb +0 -39
- data/lib/phronomy/embeddings/ruby_llm_embeddings.rb +0 -45
- data/lib/phronomy/fsm_session.rb +0 -247
- data/lib/phronomy/knowledge_source/base.rb +0 -54
- data/lib/phronomy/knowledge_source/entity_knowledge.rb +0 -96
- data/lib/phronomy/knowledge_source/rag_knowledge.rb +0 -57
- data/lib/phronomy/knowledge_source/static_knowledge.rb +0 -52
- data/lib/phronomy/loader/base.rb +0 -25
- data/lib/phronomy/loader/csv_loader.rb +0 -56
- data/lib/phronomy/loader/markdown_loader.rb +0 -76
- data/lib/phronomy/loader/plain_text_loader.rb +0 -22
- data/lib/phronomy/prompt_template.rb +0 -96
- data/lib/phronomy/splitter/base.rb +0 -47
- data/lib/phronomy/splitter/fixed_size_splitter.rb +0 -51
- data/lib/phronomy/splitter/recursive_splitter.rb +0 -105
- data/lib/phronomy/tool_executor.rb +0 -106
- data/lib/phronomy/vector_store/async_backend.rb +0 -110
- data/lib/phronomy/vector_store/base.rb +0 -89
- data/lib/phronomy/vector_store/in_memory.rb +0 -93
- data/lib/phronomy/vector_store/pgvector.rb +0 -127
- data/lib/phronomy/vector_store/redis_search.rb +0 -192
|
@@ -70,6 +70,7 @@ module Phronomy
|
|
|
70
70
|
# :<state> — resuming at <state> (workflow paused before its execution)
|
|
71
71
|
# @return [Symbol]
|
|
72
72
|
# @api public
|
|
73
|
+
# mutant:disable - @phase is always non-nil (set to :__end__ in initialize, only changed by set_graph_metadata which never sets nil), so the || :__end__ fallback branch is never reached — all mutations of the right-hand side are genuine equivalents
|
|
73
74
|
def phase
|
|
74
75
|
@phase || :__end__
|
|
75
76
|
end
|
|
@@ -77,6 +78,7 @@ module Phronomy
|
|
|
77
78
|
# Returns true if the workflow is paused mid-execution (not yet completed).
|
|
78
79
|
# @return [Boolean]
|
|
79
80
|
# @api public
|
|
81
|
+
# mutant:disable - phase != :__end__ vs !phase.eql?(:__end__) vs !phase.equal?(:__end__) are genuine equivalents for Symbol (Symbols are interned so == / eql? / equal? all behave identically)
|
|
80
82
|
def halted?
|
|
81
83
|
phase != :__end__
|
|
82
84
|
end
|
|
@@ -85,12 +87,14 @@ module Phronomy
|
|
|
85
87
|
# @param thread_id [String, nil]
|
|
86
88
|
# @param phase [Symbol, nil]
|
|
87
89
|
# @api public
|
|
90
|
+
# mutant:disable - mutations replacing return value `self` with nil or removing the last line are genuine equivalents: callers chain on the return value only in merge which immediately discards it
|
|
88
91
|
def set_graph_metadata(thread_id: nil, phase: nil)
|
|
89
92
|
@thread_id = thread_id unless thread_id.nil?
|
|
90
93
|
@phase = phase unless phase.nil?
|
|
91
94
|
self
|
|
92
95
|
end
|
|
93
96
|
|
|
97
|
+
# mutant:disable - multiple genuine equivalent mutations: is_a?(Proc) vs instance_of?(Proc) (Proc has no subclasses in practice), config[]/fetch() for always-present :default key, @thread_id=nil removal (unset ivar is already nil), @phase=:__end__ → nil or removal (phase method returns :__end__ via @phase||:__end__ fallback), raise message #{.inspect} vs #{} (spec checks exception class not message text)
|
|
94
98
|
def initialize(**attrs)
|
|
95
99
|
unknown = attrs.keys - self.class.fields.keys
|
|
96
100
|
raise ArgumentError, "Unknown WorkflowContext field(s): #{unknown.inspect}" unless unknown.empty?
|
|
@@ -114,6 +118,7 @@ module Phronomy
|
|
|
114
118
|
# @return [self.class] new context instance
|
|
115
119
|
# @raise [ArgumentError] if updates contains keys that are not declared fields
|
|
116
120
|
# @api public
|
|
121
|
+
# mutant:disable - multiple genuine equivalent mutations: send/public_send/__send__ are identical (all field accessors are public), fields[]/fetch() and field_config[]/fetch() for always-present keys, updates[]/fetch() when updates.key?(name) is already true, Array() wrapping for append fields that always hold Arrays, (send||{})/send equivalence for merge fields that always hold Hashes, deep_dup_value(send) vs send are equivalent under killfork (coverage selection does not trace the deep_dup_value call site across the fork boundary), raise message inspect vs to_s (spec checks exception class only)
|
|
117
122
|
def merge(updates)
|
|
118
123
|
unknown = updates.keys - self.class.fields.keys
|
|
119
124
|
raise ArgumentError, "Unknown WorkflowContext field(s): #{unknown.inspect}" unless unknown.empty?
|
|
@@ -145,6 +150,7 @@ module Phronomy
|
|
|
145
150
|
# Converts user-defined fields to a Hash (excludes internal workflow metadata).
|
|
146
151
|
# @return [Hash]
|
|
147
152
|
# @api public
|
|
153
|
+
# mutant:disable - send/public_send/__send__ are genuine equivalents (all field accessors are public methods)
|
|
148
154
|
def to_h
|
|
149
155
|
self.class.fields.keys.each_with_object({}) do |name, h|
|
|
150
156
|
h[name] = send(name)
|
|
@@ -158,6 +164,7 @@ module Phronomy
|
|
|
158
164
|
# @raise [Phronomy::WorkflowContextOwnershipError] when called from a
|
|
159
165
|
# non-EventLoop thread in EventLoop mode.
|
|
160
166
|
# @api private
|
|
167
|
+
# mutant:disable - multiple genuine equivalent mutations: defined?(Phronomy::EventLoop)&& removal is genuine because EventLoop is always loaded in the killfork environment; true&& is genuine (truthy guard); EventLoop.current? resolves to Phronomy::EventLoop.current? within the Phronomy module; WorkflowContextOwnershipError resolves to Phronomy::WorkflowContextOwnershipError within the module; raise without message or with nil message is genuine (spec checks exception class, not message text)
|
|
161
168
|
def _assert_write_permitted!
|
|
162
169
|
return unless defined?(Phronomy::EventLoop) &&
|
|
163
170
|
Phronomy.configuration.event_loop
|
|
@@ -174,6 +181,7 @@ module Phronomy
|
|
|
174
181
|
# Immutable values (nil, Symbol, Integer, Float, true/false, frozen String) are returned as-is.
|
|
175
182
|
# Other objects are dup'd (best-effort shallow copy for custom types).
|
|
176
183
|
# Objects that cannot be dup'd (e.g. Proc, Method) are returned as-is.
|
|
184
|
+
# mutant:disable - multiple genuine equivalent mutations: each class in the when clause (NilClass/Symbol/Integer/Float/TrueClass/FalseClass) can be removed or replaced with nil because all those types are frozen so the else-branch val.frozen? guard returns the same result; return val vs val is also equivalent; if val.frozen? vs if self.frozen? is equivalent since self is never frozen in this context
|
|
177
185
|
def deep_dup_value(val)
|
|
178
186
|
case val
|
|
179
187
|
when Array
|
|
@@ -56,7 +56,16 @@ module Phronomy
|
|
|
56
56
|
@wait_state_names = wait_state_names
|
|
57
57
|
@state_store = state_store
|
|
58
58
|
@action_timeouts = action_timeouts # { state_name => seconds }
|
|
59
|
-
@phase_machine_class =
|
|
59
|
+
@phase_machine_class = Agent::Lifecycle::PhaseMachineBuilder.new(
|
|
60
|
+
entry_point: @entry_point,
|
|
61
|
+
declared_states: @declared_states,
|
|
62
|
+
wait_state_names: @wait_state_names,
|
|
63
|
+
external_events: @external_events,
|
|
64
|
+
entry_actions: @entry_actions,
|
|
65
|
+
action_timeouts: @action_timeouts,
|
|
66
|
+
auto_transitions: auto_transitions,
|
|
67
|
+
exit_actions: exit_actions
|
|
68
|
+
).build
|
|
60
69
|
end
|
|
61
70
|
|
|
62
71
|
# Executes the workflow from the initial state.
|
|
@@ -160,7 +169,7 @@ module Phronomy
|
|
|
160
169
|
|
|
161
170
|
# Builds an FSMSession for the given context. Used in EventLoop mode.
|
|
162
171
|
def build_session_for(context:, recursion_limit:, resume_event: nil, resume_phase: nil)
|
|
163
|
-
Phronomy::FSMSession.new(
|
|
172
|
+
Phronomy::Agent::Lifecycle::FSMSession.new(
|
|
164
173
|
id: context.thread_id,
|
|
165
174
|
context: context,
|
|
166
175
|
entry_point: @entry_point,
|
|
@@ -310,135 +319,6 @@ module Phronomy
|
|
|
310
319
|
# before_transition from — exit callbacks (invoked when leaving a state)
|
|
311
320
|
#
|
|
312
321
|
# Guard lambdas bridge the PhaseTracker and WorkflowContext via +m.context+.
|
|
313
|
-
def build_phase_machine_class(auto_transitions, exit_actions)
|
|
314
|
-
entry = @entry_point
|
|
315
|
-
all_states = (@declared_states + @wait_state_names + [:__end__]).uniq
|
|
316
|
-
auto_trans = auto_transitions # Array of { from:, to:, guard: }
|
|
317
|
-
ext_events = @external_events
|
|
318
|
-
entry_acts = @entry_actions
|
|
319
|
-
exit_acts = exit_actions
|
|
320
|
-
act_timeouts = @action_timeouts # { state_name => seconds }
|
|
321
|
-
|
|
322
|
-
Class.new do
|
|
323
|
-
# Holds the current WorkflowContext so guards and callbacks can read it.
|
|
324
|
-
attr_accessor :context
|
|
325
|
-
|
|
326
|
-
# Set to true by an entry action that returned an awaitable Task.
|
|
327
|
-
# When true, FSMSession skips the automatic advance_or_halt step and
|
|
328
|
-
# waits for the async worker thread to post a state_completed event back.
|
|
329
|
-
attr_accessor :async_pending
|
|
330
|
-
|
|
331
|
-
state_machine :phase, initial: entry do
|
|
332
|
-
all_states.each { |s| state s }
|
|
333
|
-
|
|
334
|
-
# Auto-fire transitions: all auto transitions unified under :state_completed.
|
|
335
|
-
# Includes unguarded (unconditional) and guarded (conditional) transitions.
|
|
336
|
-
# Declaration order is preserved; guards are evaluated before unguarded fallbacks.
|
|
337
|
-
event :state_completed do
|
|
338
|
-
auto_trans.each do |t|
|
|
339
|
-
if t[:guard]
|
|
340
|
-
guard_proc = t[:guard]
|
|
341
|
-
transition t[:from] => t[:to], :if => ->(m) { guard_proc.call(m.context) }
|
|
342
|
-
else
|
|
343
|
-
transition t[:from] => t[:to]
|
|
344
|
-
end
|
|
345
|
-
end
|
|
346
|
-
end
|
|
347
|
-
|
|
348
|
-
# External events: human-in-the-loop triggers from wait states.
|
|
349
|
-
ext_events.each do |ev_name, transitions|
|
|
350
|
-
event ev_name do
|
|
351
|
-
transitions.each do |t|
|
|
352
|
-
if t[:guard]
|
|
353
|
-
guard_proc = t[:guard]
|
|
354
|
-
transition t[:from] => t[:to], :if => ->(m) { guard_proc.call(m.context) }
|
|
355
|
-
else
|
|
356
|
-
transition t[:from] => t[:to]
|
|
357
|
-
end
|
|
358
|
-
end
|
|
359
|
-
end
|
|
360
|
-
end
|
|
361
|
-
|
|
362
|
-
# Entry callbacks: fire after_transition into each state.
|
|
363
|
-
# Each callable is registered as a separate callback; state_machines
|
|
364
|
-
# accumulates them and fires in declaration order.
|
|
365
|
-
# If the callable returns a WorkflowContext (e.g. via s.merge(...)),
|
|
366
|
-
# the returned context replaces the current one on the tracker.
|
|
367
|
-
entry_acts.each do |state_name, callables|
|
|
368
|
-
callables.each do |callable|
|
|
369
|
-
timeout_secs = act_timeouts[state_name]
|
|
370
|
-
after_transition to: state_name do |machine|
|
|
371
|
-
result = callable.call(machine.context)
|
|
372
|
-
if result.is_a?(Phronomy::Task)
|
|
373
|
-
if Phronomy.configuration.event_loop
|
|
374
|
-
# EventLoop mode: await in a background task so the EventLoop
|
|
375
|
-
# thread is not blocked. Signal async_pending so FSMSession
|
|
376
|
-
# skips the automatic advance_or_halt step.
|
|
377
|
-
machine.async_pending = true
|
|
378
|
-
ctx_ref = machine.context
|
|
379
|
-
thread_id = ctx_ref.thread_id
|
|
380
|
-
Phronomy::Runtime.instance.spawn(name: "wf-await-#{thread_id}") do
|
|
381
|
-
if timeout_secs
|
|
382
|
-
if result.join(timeout_secs).nil?
|
|
383
|
-
result.cancel!
|
|
384
|
-
raise Phronomy::ActionTimeoutError,
|
|
385
|
-
"Action in state #{state_name.inspect} timed out after #{timeout_secs}s"
|
|
386
|
-
end
|
|
387
|
-
end
|
|
388
|
-
task_result = result.await
|
|
389
|
-
if task_result.is_a?(Phronomy::WorkflowContext)
|
|
390
|
-
Phronomy::EventLoop.instance.post(
|
|
391
|
-
Phronomy::Event.new(
|
|
392
|
-
type: :action_completed,
|
|
393
|
-
target_id: thread_id,
|
|
394
|
-
payload: task_result
|
|
395
|
-
)
|
|
396
|
-
)
|
|
397
|
-
else
|
|
398
|
-
Phronomy::EventLoop.instance.post(
|
|
399
|
-
Phronomy::Event.new(type: :state_completed, target_id: thread_id, payload: nil)
|
|
400
|
-
)
|
|
401
|
-
end
|
|
402
|
-
rescue => e
|
|
403
|
-
Phronomy::EventLoop.instance.post(
|
|
404
|
-
Phronomy::Event.new(type: :error, target_id: thread_id, payload: e)
|
|
405
|
-
)
|
|
406
|
-
end
|
|
407
|
-
else
|
|
408
|
-
# Non-EventLoop mode: block synchronously on the task result.
|
|
409
|
-
if timeout_secs
|
|
410
|
-
if result.join(timeout_secs).nil?
|
|
411
|
-
result.cancel!
|
|
412
|
-
raise Phronomy::ActionTimeoutError,
|
|
413
|
-
"Action in state #{state_name.inspect} timed out after #{timeout_secs}s"
|
|
414
|
-
end
|
|
415
|
-
end
|
|
416
|
-
task_result = result.await
|
|
417
|
-
machine.context = task_result if task_result.is_a?(Phronomy::WorkflowContext)
|
|
418
|
-
end
|
|
419
|
-
elsif result.is_a?(Phronomy::WorkflowContext)
|
|
420
|
-
machine.context = result
|
|
421
|
-
end
|
|
422
|
-
end
|
|
423
|
-
end
|
|
424
|
-
end
|
|
425
|
-
|
|
426
|
-
# Exit callbacks: fire before_transition out of each state.
|
|
427
|
-
# Each callable is registered as a separate callback; state_machines
|
|
428
|
-
# accumulates them and fires in declaration order.
|
|
429
|
-
exit_acts.each do |state_name, callables|
|
|
430
|
-
callables.each do |callable|
|
|
431
|
-
before_transition from: state_name do |machine|
|
|
432
|
-
callable.call(machine.context)
|
|
433
|
-
end
|
|
434
|
-
end
|
|
435
|
-
end
|
|
436
|
-
end
|
|
437
|
-
end
|
|
438
|
-
rescue => e
|
|
439
|
-
raise ArgumentError, "Failed to build phase machine: #{e.message}"
|
|
440
|
-
end
|
|
441
|
-
|
|
442
322
|
# Creates a PhaseTracker instance initialized to +from_state+.
|
|
443
323
|
def new_phase_machine(from_state)
|
|
444
324
|
machine = @phase_machine_class.new
|
data/lib/phronomy.rb
CHANGED
|
@@ -8,6 +8,7 @@ loader = Zeitwerk::Loader.for_gem
|
|
|
8
8
|
# Teach Zeitwerk that "llm" maps to "LLM" so that file names such as
|
|
9
9
|
# ruby_llm_embeddings.rb resolve to RubyLLMEmbeddings (not RubyLlmEmbeddings).
|
|
10
10
|
loader.inflector.inflect("ruby_llm_embeddings" => "RubyLLMEmbeddings")
|
|
11
|
+
loader.inflector.inflect("rag_knowledge" => "RAGKnowledge")
|
|
11
12
|
# FSMSession: Zeitwerk would infer "FsmSession" — override to "FSMSession".
|
|
12
13
|
loader.inflector.inflect("fsm_session" => "FSMSession")
|
|
13
14
|
# AgentFSM: Zeitwerk would infer "Fsm" — override to "FSM".
|
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.8.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-05-
|
|
11
|
+
date: 2026-05-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ruby_llm
|
|
@@ -109,34 +109,49 @@ files:
|
|
|
109
109
|
- lib/phronomy/agent/concerns/guardrailable.rb
|
|
110
110
|
- lib/phronomy/agent/concerns/retryable.rb
|
|
111
111
|
- lib/phronomy/agent/concerns/suspendable.rb
|
|
112
|
+
- lib/phronomy/agent/context/conversation/compaction_context.rb
|
|
113
|
+
- lib/phronomy/agent/context/conversation/trigger_context.rb
|
|
114
|
+
- lib/phronomy/agent/context/conversation/trim_context.rb
|
|
115
|
+
- lib/phronomy/agent/context/instruction/prompt_template.rb
|
|
116
|
+
- lib/phronomy/agent/context/knowledge/embeddings/base.rb
|
|
117
|
+
- lib/phronomy/agent/context/knowledge/embeddings/ruby_llm_embeddings.rb
|
|
118
|
+
- lib/phronomy/agent/context/knowledge/loader/base.rb
|
|
119
|
+
- lib/phronomy/agent/context/knowledge/loader/csv_loader.rb
|
|
120
|
+
- lib/phronomy/agent/context/knowledge/loader/markdown_loader.rb
|
|
121
|
+
- lib/phronomy/agent/context/knowledge/loader/plain_text_loader.rb
|
|
122
|
+
- lib/phronomy/agent/context/knowledge/source/base.rb
|
|
123
|
+
- lib/phronomy/agent/context/knowledge/source/entity_knowledge.rb
|
|
124
|
+
- lib/phronomy/agent/context/knowledge/source/rag_knowledge.rb
|
|
125
|
+
- lib/phronomy/agent/context/knowledge/source/static_knowledge.rb
|
|
126
|
+
- lib/phronomy/agent/context/knowledge/splitter/base.rb
|
|
127
|
+
- lib/phronomy/agent/context/knowledge/splitter/fixed_size_splitter.rb
|
|
128
|
+
- lib/phronomy/agent/context/knowledge/splitter/recursive_splitter.rb
|
|
129
|
+
- lib/phronomy/agent/context/knowledge/vector_store/async_backend.rb
|
|
130
|
+
- lib/phronomy/agent/context/knowledge/vector_store/base.rb
|
|
131
|
+
- lib/phronomy/agent/context/knowledge/vector_store/in_memory.rb
|
|
132
|
+
- lib/phronomy/agent/context/knowledge/vector_store/pgvector.rb
|
|
133
|
+
- lib/phronomy/agent/context/knowledge/vector_store/redis_search.rb
|
|
112
134
|
- lib/phronomy/agent/fsm.rb
|
|
113
|
-
- lib/phronomy/agent/
|
|
114
|
-
- lib/phronomy/agent/
|
|
115
|
-
- lib/phronomy/agent/
|
|
135
|
+
- lib/phronomy/agent/invocation_pipeline.rb
|
|
136
|
+
- lib/phronomy/agent/lifecycle/fsm_session.rb
|
|
137
|
+
- lib/phronomy/agent/lifecycle/phase_machine_builder.rb
|
|
116
138
|
- lib/phronomy/agent/react_agent.rb
|
|
117
139
|
- lib/phronomy/agent/runner.rb
|
|
118
140
|
- lib/phronomy/agent/shared_state.rb
|
|
119
141
|
- lib/phronomy/agent/suspend_signal.rb
|
|
120
|
-
- lib/phronomy/agent/
|
|
121
|
-
- lib/phronomy/async_queue.rb
|
|
122
|
-
- lib/phronomy/blocking_adapter_pool.rb
|
|
123
|
-
- lib/phronomy/cancellation_scope.rb
|
|
124
|
-
- lib/phronomy/cancellation_token.rb
|
|
125
|
-
- lib/phronomy/concurrency_gate.rb
|
|
142
|
+
- lib/phronomy/agent/tool_executor.rb
|
|
143
|
+
- lib/phronomy/concurrency/async_queue.rb
|
|
144
|
+
- lib/phronomy/concurrency/blocking_adapter_pool.rb
|
|
145
|
+
- lib/phronomy/concurrency/cancellation_scope.rb
|
|
146
|
+
- lib/phronomy/concurrency/cancellation_token.rb
|
|
147
|
+
- lib/phronomy/concurrency/concurrency_gate.rb
|
|
148
|
+
- lib/phronomy/concurrency/deadline.rb
|
|
149
|
+
- lib/phronomy/concurrency/gate_registry.rb
|
|
150
|
+
- lib/phronomy/concurrency/pool_registry.rb
|
|
126
151
|
- lib/phronomy/configuration.rb
|
|
127
152
|
- lib/phronomy/context.rb
|
|
128
|
-
- lib/phronomy/context/assembler.rb
|
|
129
|
-
- lib/phronomy/context/compaction_context.rb
|
|
130
|
-
- lib/phronomy/context/context_version_cache.rb
|
|
131
|
-
- lib/phronomy/context/token_budget.rb
|
|
132
|
-
- lib/phronomy/context/token_estimator.rb
|
|
133
|
-
- lib/phronomy/context/trigger_context.rb
|
|
134
|
-
- lib/phronomy/context/trim_context.rb
|
|
135
|
-
- lib/phronomy/deadline.rb
|
|
136
153
|
- lib/phronomy/diagnostics.rb
|
|
137
154
|
- lib/phronomy/embeddings.rb
|
|
138
|
-
- lib/phronomy/embeddings/base.rb
|
|
139
|
-
- lib/phronomy/embeddings/ruby_llm_embeddings.rb
|
|
140
155
|
- lib/phronomy/eval.rb
|
|
141
156
|
- lib/phronomy/eval/comparison.rb
|
|
142
157
|
- lib/phronomy/eval/dataset.rb
|
|
@@ -151,7 +166,6 @@ files:
|
|
|
151
166
|
- lib/phronomy/eval/scorer/llm_judge.rb
|
|
152
167
|
- lib/phronomy/event.rb
|
|
153
168
|
- lib/phronomy/event_loop.rb
|
|
154
|
-
- lib/phronomy/fsm_session.rb
|
|
155
169
|
- lib/phronomy/generator_verifier.rb
|
|
156
170
|
- lib/phronomy/guardrail.rb
|
|
157
171
|
- lib/phronomy/guardrail/base.rb
|
|
@@ -160,31 +174,28 @@ files:
|
|
|
160
174
|
- lib/phronomy/guardrail/prompt_injection_guardrail.rb
|
|
161
175
|
- lib/phronomy/invocation_context.rb
|
|
162
176
|
- lib/phronomy/knowledge_source.rb
|
|
163
|
-
- lib/phronomy/knowledge_source/base.rb
|
|
164
|
-
- lib/phronomy/knowledge_source/entity_knowledge.rb
|
|
165
|
-
- lib/phronomy/knowledge_source/rag_knowledge.rb
|
|
166
|
-
- lib/phronomy/knowledge_source/static_knowledge.rb
|
|
167
177
|
- lib/phronomy/llm_adapter.rb
|
|
168
178
|
- lib/phronomy/llm_adapter/base.rb
|
|
169
179
|
- lib/phronomy/llm_adapter/ruby_llm.rb
|
|
180
|
+
- lib/phronomy/llm_context_window/assembler.rb
|
|
181
|
+
- lib/phronomy/llm_context_window/context_version_cache.rb
|
|
182
|
+
- lib/phronomy/llm_context_window/token_budget.rb
|
|
183
|
+
- lib/phronomy/llm_context_window/token_estimator.rb
|
|
170
184
|
- lib/phronomy/loader.rb
|
|
171
|
-
- lib/phronomy/loader/base.rb
|
|
172
|
-
- lib/phronomy/loader/csv_loader.rb
|
|
173
|
-
- lib/phronomy/loader/markdown_loader.rb
|
|
174
|
-
- lib/phronomy/loader/plain_text_loader.rb
|
|
175
185
|
- lib/phronomy/metrics.rb
|
|
186
|
+
- lib/phronomy/multi_agent/handoff.rb
|
|
187
|
+
- lib/phronomy/multi_agent/orchestrator.rb
|
|
188
|
+
- lib/phronomy/multi_agent/parallel_tool_chat.rb
|
|
189
|
+
- lib/phronomy/multi_agent/team_coordinator.rb
|
|
176
190
|
- lib/phronomy/output_parser.rb
|
|
177
191
|
- lib/phronomy/output_parser/base.rb
|
|
178
192
|
- lib/phronomy/output_parser/json_parser.rb
|
|
179
193
|
- lib/phronomy/output_parser/structured_parser.rb
|
|
180
|
-
- lib/phronomy/prompt_template.rb
|
|
181
194
|
- lib/phronomy/ruby_llm_patches.rb
|
|
182
195
|
- lib/phronomy/runnable.rb
|
|
183
196
|
- lib/phronomy/runtime.rb
|
|
184
197
|
- lib/phronomy/runtime/deterministic_scheduler.rb
|
|
185
198
|
- lib/phronomy/runtime/fake_scheduler.rb
|
|
186
|
-
- lib/phronomy/runtime/gate_registry.rb
|
|
187
|
-
- lib/phronomy/runtime/pool_registry.rb
|
|
188
199
|
- lib/phronomy/runtime/runtime_metrics.rb
|
|
189
200
|
- lib/phronomy/runtime/scheduler.rb
|
|
190
201
|
- lib/phronomy/runtime/scheduler_timer_adapter.rb
|
|
@@ -193,9 +204,6 @@ files:
|
|
|
193
204
|
- lib/phronomy/runtime/timer_queue.rb
|
|
194
205
|
- lib/phronomy/runtime/timer_service.rb
|
|
195
206
|
- lib/phronomy/splitter.rb
|
|
196
|
-
- lib/phronomy/splitter/base.rb
|
|
197
|
-
- lib/phronomy/splitter/fixed_size_splitter.rb
|
|
198
|
-
- lib/phronomy/splitter/recursive_splitter.rb
|
|
199
207
|
- lib/phronomy/state_store/base.rb
|
|
200
208
|
- lib/phronomy/state_store/in_memory.rb
|
|
201
209
|
- lib/phronomy/task.rb
|
|
@@ -214,18 +222,12 @@ files:
|
|
|
214
222
|
- lib/phronomy/tool/base.rb
|
|
215
223
|
- lib/phronomy/tool/mcp_tool.rb
|
|
216
224
|
- lib/phronomy/tool/scope_policy.rb
|
|
217
|
-
- lib/phronomy/tool_executor.rb
|
|
218
225
|
- lib/phronomy/tracing.rb
|
|
219
226
|
- lib/phronomy/tracing/base.rb
|
|
220
227
|
- lib/phronomy/tracing/langfuse_tracer.rb
|
|
221
228
|
- lib/phronomy/tracing/null_tracer.rb
|
|
222
229
|
- lib/phronomy/tracing/open_telemetry_tracer.rb
|
|
223
230
|
- lib/phronomy/vector_store.rb
|
|
224
|
-
- lib/phronomy/vector_store/async_backend.rb
|
|
225
|
-
- lib/phronomy/vector_store/base.rb
|
|
226
|
-
- lib/phronomy/vector_store/in_memory.rb
|
|
227
|
-
- lib/phronomy/vector_store/pgvector.rb
|
|
228
|
-
- lib/phronomy/vector_store/redis_search.rb
|
|
229
231
|
- lib/phronomy/version.rb
|
|
230
232
|
- lib/phronomy/workflow.rb
|
|
231
233
|
- lib/phronomy/workflow_context.rb
|
data/lib/phronomy/async_queue.rb
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Phronomy
|
|
4
|
-
# A thread-safe FIFO queue for passing values between concurrent tasks.
|
|
5
|
-
#
|
|
6
|
-
# Wraps +Thread::Queue+ so that callers do not need to reference the Ruby
|
|
7
|
-
# standard-library type directly. A future implementation may replace the
|
|
8
|
-
# backing primitive without changing call sites.
|
|
9
|
-
#
|
|
10
|
-
# @example Producer / consumer
|
|
11
|
-
# queue = Phronomy::AsyncQueue.new
|
|
12
|
-
# Runtime.instance.spawn { queue.push(expensive_io()) }
|
|
13
|
-
# value = queue.pop # blocks until the producer pushes
|
|
14
|
-
# @api private
|
|
15
|
-
class AsyncQueue
|
|
16
|
-
# @param max_size [Integer, nil] optional upper bound on queue depth.
|
|
17
|
-
# When set, {#push} blocks the caller until a slot is available.
|
|
18
|
-
# @api private
|
|
19
|
-
def initialize(max_size: nil)
|
|
20
|
-
@queue = max_size ? SizedQueue.new(max_size) : Thread::Queue.new
|
|
21
|
-
@max_size = max_size
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
# Enqueues +item+.
|
|
25
|
-
# In a cooperative scheduler context with a bounded queue (max_size:), suspends
|
|
26
|
-
# the current Fiber via a scheduler signal when the queue is full rather than
|
|
27
|
-
# blocking the OS thread. Without a scheduler, falls back to the standard
|
|
28
|
-
# SizedQueue blocking behaviour.
|
|
29
|
-
# @param item [Object] value to enqueue
|
|
30
|
-
# @return [self]
|
|
31
|
-
# @api private
|
|
32
|
-
def push(item)
|
|
33
|
-
scheduler = Phronomy::Runtime::Scheduler.current
|
|
34
|
-
if scheduler && @max_size
|
|
35
|
-
_push_cooperative(scheduler, item)
|
|
36
|
-
else
|
|
37
|
-
@queue.push(item)
|
|
38
|
-
scheduler.raise_signal(@coop_signal) if scheduler && @coop_signal
|
|
39
|
-
end
|
|
40
|
-
self
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# Dequeues and returns the next item.
|
|
44
|
-
# In a cooperative scheduler context, suspends the current Fiber (yielding
|
|
45
|
-
# control back to the scheduler) rather than blocking the OS thread.
|
|
46
|
-
#
|
|
47
|
-
# When +timeout+ is given the semantics depend on the active backend:
|
|
48
|
-
#
|
|
49
|
-
# * **Thread backend** (`:thread`) — uses real wall-clock time via
|
|
50
|
-
# +Thread::Queue#pop(timeout:)+. Requires Ruby 3.2+.
|
|
51
|
-
# Returns +nil+ if no item arrives within the specified number of real seconds.
|
|
52
|
-
# * **DeterministicScheduler / `:fiber` backend** — uses the scheduler's
|
|
53
|
-
# *virtual time* (+scheduler.virtual_time+). The timeout elapses only when
|
|
54
|
-
# the virtual clock is advanced (e.g. via {Phronomy::Testing::FakeClock#advance}).
|
|
55
|
-
# In tests this means the timeout is fully deterministic and does not depend on
|
|
56
|
-
# actual elapsed wall time. However, in production `:fiber` mode the timeout
|
|
57
|
-
# may never expire unless the scheduler explicitly advances virtual time.
|
|
58
|
-
#
|
|
59
|
-
# @note The `:fiber` backend is **EXPERIMENTAL**. Real-time timeout behaviour
|
|
60
|
-
# in production workloads is not guaranteed and may differ from wall-clock
|
|
61
|
-
# expectations.
|
|
62
|
-
# @note **Cooperative timeout limitation**: on the cooperative path, the
|
|
63
|
-
# deadline is re-checked *after* a wake-up signal arrives. If virtual time
|
|
64
|
-
# has already passed the deadline when the consumer is woken by a producer
|
|
65
|
-
# push, the consumer returns +nil+ rather than the pushed item. Without any
|
|
66
|
-
# wake-up signal the waiting Fiber remains suspended even after
|
|
67
|
-
# +scheduler.advance+ — the timeout does not self-fire.
|
|
68
|
-
# @param timeout [Numeric, nil] seconds to wait before returning +nil+.
|
|
69
|
-
# Semantics are wall-clock on `:thread` and virtual-time on `:fiber`.
|
|
70
|
-
# @return [Object, nil] the next item, or +nil+ when timeout expires
|
|
71
|
-
# @api private
|
|
72
|
-
def pop(timeout: nil)
|
|
73
|
-
scheduler = Phronomy::Runtime::Scheduler.current
|
|
74
|
-
if scheduler
|
|
75
|
-
_pop_cooperative(scheduler, timeout: timeout)
|
|
76
|
-
elsif timeout
|
|
77
|
-
@queue.pop(timeout: timeout)
|
|
78
|
-
else
|
|
79
|
-
@queue.pop
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# Returns the current number of items in the queue.
|
|
84
|
-
# @return [Integer]
|
|
85
|
-
# @api private
|
|
86
|
-
def size
|
|
87
|
-
@queue.size
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
# Returns +true+ when the queue contains no items.
|
|
91
|
-
# @return [Boolean]
|
|
92
|
-
# @api private
|
|
93
|
-
def empty?
|
|
94
|
-
@queue.empty?
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
# Closes the queue. Subsequent {#pop} calls raise +ClosedQueueError+.
|
|
98
|
-
# @return [self]
|
|
99
|
-
# @api private
|
|
100
|
-
def close
|
|
101
|
-
@queue.close
|
|
102
|
-
self
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
private
|
|
106
|
-
|
|
107
|
-
# Cooperative pop for DeterministicScheduler context.
|
|
108
|
-
# Suspends the current Fiber via the scheduler's signal mechanism rather than
|
|
109
|
-
# blocking the OS thread. Because cooperative mode is single-threaded, the
|
|
110
|
-
# empty?/pop pair is race-free (no other Fiber can run between the two calls).
|
|
111
|
-
# After dequeuing, notifies any push-waiter so that a backpressure-suspended
|
|
112
|
-
# producer can be unblocked.
|
|
113
|
-
# @api private
|
|
114
|
-
# @param scheduler [Runtime::Scheduler]
|
|
115
|
-
# @param timeout [Numeric, nil]
|
|
116
|
-
# @return [Object, nil]
|
|
117
|
-
def _pop_cooperative(scheduler, timeout:)
|
|
118
|
-
@coop_signal ||= scheduler.new_signal
|
|
119
|
-
deadline = timeout ? (scheduler.virtual_time + timeout) : nil
|
|
120
|
-
|
|
121
|
-
loop do
|
|
122
|
-
unless @queue.empty?
|
|
123
|
-
item = @queue.pop(timeout: 0)
|
|
124
|
-
# Notify a push-waiter (bounded queue) that a slot opened up.
|
|
125
|
-
scheduler.raise_signal(@push_signal) if @push_signal
|
|
126
|
-
return item
|
|
127
|
-
end
|
|
128
|
-
return nil if deadline && scheduler.virtual_time >= deadline
|
|
129
|
-
scheduler.wait_for_signal(@coop_signal)
|
|
130
|
-
return nil if deadline && scheduler.virtual_time >= deadline
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
# Cooperative push for DeterministicScheduler context with a bounded queue.
|
|
135
|
-
# Suspends the current Fiber via a scheduler signal when the queue is full,
|
|
136
|
-
# rather than blocking the OS thread.
|
|
137
|
-
# @api private
|
|
138
|
-
# @param scheduler [Runtime::Scheduler]
|
|
139
|
-
# @param item [Object]
|
|
140
|
-
# @return [void]
|
|
141
|
-
def _push_cooperative(scheduler, item)
|
|
142
|
-
@push_signal ||= scheduler.new_signal
|
|
143
|
-
|
|
144
|
-
loop do
|
|
145
|
-
unless @queue.size >= @max_size
|
|
146
|
-
@queue.push(item)
|
|
147
|
-
# Notify any pop-waiter that an item is now available.
|
|
148
|
-
scheduler.raise_signal(@coop_signal) if @coop_signal
|
|
149
|
-
return
|
|
150
|
-
end
|
|
151
|
-
scheduler.wait_for_signal(@push_signal)
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
end
|
|
155
|
-
end
|