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.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +16 -16
  3. data/benchmark/bench_context_assembler.rb +2 -2
  4. data/benchmark/bench_regression.rb +5 -5
  5. data/benchmark/bench_token_estimator.rb +5 -5
  6. data/benchmark/bench_tool_schema.rb +1 -1
  7. data/benchmark/bench_vector_store.rb +1 -1
  8. data/lib/phronomy/agent/base.rb +86 -123
  9. data/lib/phronomy/agent/checkpoint.rb +118 -0
  10. data/lib/phronomy/agent/context/conversation/compaction_context.rb +117 -0
  11. data/lib/phronomy/agent/context/conversation/trigger_context.rb +43 -0
  12. data/lib/phronomy/agent/context/conversation/trim_context.rb +82 -0
  13. data/lib/phronomy/agent/context/instruction/prompt_template.rb +102 -0
  14. data/lib/phronomy/agent/context/knowledge/embeddings/base.rb +45 -0
  15. data/lib/phronomy/agent/context/knowledge/embeddings/ruby_llm_embeddings.rb +51 -0
  16. data/lib/phronomy/agent/context/knowledge/loader/base.rb +31 -0
  17. data/lib/phronomy/agent/context/knowledge/loader/csv_loader.rb +62 -0
  18. data/lib/phronomy/agent/context/knowledge/loader/markdown_loader.rb +82 -0
  19. data/lib/phronomy/agent/context/knowledge/loader/plain_text_loader.rb +28 -0
  20. data/lib/phronomy/agent/context/knowledge/source/base.rb +60 -0
  21. data/lib/phronomy/agent/context/knowledge/source/entity_knowledge.rb +102 -0
  22. data/lib/phronomy/agent/context/knowledge/source/rag_knowledge.rb +63 -0
  23. data/lib/phronomy/agent/context/knowledge/source/static_knowledge.rb +58 -0
  24. data/lib/phronomy/agent/context/knowledge/splitter/base.rb +53 -0
  25. data/lib/phronomy/agent/context/knowledge/splitter/fixed_size_splitter.rb +57 -0
  26. data/lib/phronomy/agent/context/knowledge/splitter/recursive_splitter.rb +111 -0
  27. data/lib/phronomy/agent/context/knowledge/vector_store/async_backend.rb +116 -0
  28. data/lib/phronomy/agent/context/knowledge/vector_store/base.rb +95 -0
  29. data/lib/phronomy/agent/context/knowledge/vector_store/in_memory.rb +109 -0
  30. data/lib/phronomy/agent/context/knowledge/vector_store/pgvector.rb +133 -0
  31. data/lib/phronomy/agent/context/knowledge/vector_store/redis_search.rb +198 -0
  32. data/lib/phronomy/agent/fsm.rb +1 -1
  33. data/lib/phronomy/agent/invocation_pipeline.rb +99 -0
  34. data/lib/phronomy/agent/lifecycle/fsm_session.rb +251 -0
  35. data/lib/phronomy/agent/lifecycle/phase_machine_builder.rb +249 -0
  36. data/lib/phronomy/agent/react_agent.rb +19 -14
  37. data/lib/phronomy/agent/runner.rb +2 -2
  38. data/lib/phronomy/agent/tool_executor.rb +108 -0
  39. data/lib/phronomy/concurrency/async_queue.rb +157 -0
  40. data/lib/phronomy/concurrency/blocking_adapter_pool.rb +443 -0
  41. data/lib/phronomy/concurrency/cancellation_scope.rb +125 -0
  42. data/lib/phronomy/concurrency/cancellation_token.rb +140 -0
  43. data/lib/phronomy/concurrency/concurrency_gate.rb +157 -0
  44. data/lib/phronomy/concurrency/deadline.rb +65 -0
  45. data/lib/phronomy/{runtime → concurrency}/gate_registry.rb +1 -1
  46. data/lib/phronomy/{runtime → concurrency}/pool_registry.rb +1 -1
  47. data/lib/phronomy/context.rb +2 -8
  48. data/lib/phronomy/embeddings.rb +2 -2
  49. data/lib/phronomy/eval/runner.rb +4 -0
  50. data/lib/phronomy/eval/scorer/llm_judge.rb +12 -1
  51. data/lib/phronomy/event_loop.rb +7 -7
  52. data/lib/phronomy/invocation_context.rb +3 -3
  53. data/lib/phronomy/knowledge_source.rb +0 -5
  54. data/lib/phronomy/llm_adapter/ruby_llm.rb +17 -11
  55. data/lib/phronomy/{context → llm_context_window}/assembler.rb +18 -3
  56. data/lib/phronomy/{context → llm_context_window}/context_version_cache.rb +1 -1
  57. data/lib/phronomy/{context → llm_context_window}/token_budget.rb +7 -4
  58. data/lib/phronomy/{context → llm_context_window}/token_estimator.rb +3 -3
  59. data/lib/phronomy/loader.rb +4 -4
  60. data/lib/phronomy/{agent → multi_agent}/handoff.rb +2 -2
  61. data/lib/phronomy/{agent → multi_agent}/orchestrator.rb +6 -6
  62. data/lib/phronomy/{agent → multi_agent}/parallel_tool_chat.rb +4 -4
  63. data/lib/phronomy/{agent → multi_agent}/team_coordinator.rb +2 -2
  64. data/lib/phronomy/runtime.rb +19 -4
  65. data/lib/phronomy/splitter.rb +3 -3
  66. data/lib/phronomy/task_group.rb +1 -1
  67. data/lib/phronomy/tool/base.rb +50 -9
  68. data/lib/phronomy/tracing/null_tracer.rb +3 -1
  69. data/lib/phronomy/vector_store.rb +2 -2
  70. data/lib/phronomy/version.rb +1 -1
  71. data/lib/phronomy/workflow_context.rb +8 -0
  72. data/lib/phronomy/workflow_runner.rb +11 -131
  73. data/lib/phronomy.rb +1 -0
  74. metadata +44 -42
  75. data/lib/phronomy/async_queue.rb +0 -155
  76. data/lib/phronomy/blocking_adapter_pool.rb +0 -435
  77. data/lib/phronomy/cancellation_scope.rb +0 -123
  78. data/lib/phronomy/cancellation_token.rb +0 -133
  79. data/lib/phronomy/concurrency_gate.rb +0 -155
  80. data/lib/phronomy/context/compaction_context.rb +0 -111
  81. data/lib/phronomy/context/trigger_context.rb +0 -39
  82. data/lib/phronomy/context/trim_context.rb +0 -75
  83. data/lib/phronomy/deadline.rb +0 -63
  84. data/lib/phronomy/embeddings/base.rb +0 -39
  85. data/lib/phronomy/embeddings/ruby_llm_embeddings.rb +0 -45
  86. data/lib/phronomy/fsm_session.rb +0 -247
  87. data/lib/phronomy/knowledge_source/base.rb +0 -54
  88. data/lib/phronomy/knowledge_source/entity_knowledge.rb +0 -96
  89. data/lib/phronomy/knowledge_source/rag_knowledge.rb +0 -57
  90. data/lib/phronomy/knowledge_source/static_knowledge.rb +0 -52
  91. data/lib/phronomy/loader/base.rb +0 -25
  92. data/lib/phronomy/loader/csv_loader.rb +0 -56
  93. data/lib/phronomy/loader/markdown_loader.rb +0 -76
  94. data/lib/phronomy/loader/plain_text_loader.rb +0 -22
  95. data/lib/phronomy/prompt_template.rb +0 -96
  96. data/lib/phronomy/splitter/base.rb +0 -47
  97. data/lib/phronomy/splitter/fixed_size_splitter.rb +0 -51
  98. data/lib/phronomy/splitter/recursive_splitter.rb +0 -105
  99. data/lib/phronomy/tool_executor.rb +0 -106
  100. data/lib/phronomy/vector_store/async_backend.rb +0 -110
  101. data/lib/phronomy/vector_store/base.rb +0 -89
  102. data/lib/phronomy/vector_store/in_memory.rb +0 -93
  103. data/lib/phronomy/vector_store/pgvector.rb +0 -127
  104. 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 = build_phase_machine_class(auto_transitions, exit_actions)
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.7.1
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-26 00:00:00.000000000 Z
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/handoff.rb
114
- - lib/phronomy/agent/orchestrator.rb
115
- - lib/phronomy/agent/parallel_tool_chat.rb
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/team_coordinator.rb
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
@@ -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