phronomy 0.3.0 → 0.4.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.
@@ -137,20 +137,42 @@ module Phronomy
137
137
  current_node = from_node || @entry_point
138
138
  tracker = new_phase_machine(current_node)
139
139
  tracker.context = state
140
+ # Event queue: decouple node execution from transition firing.
141
+ # Events are enqueued after a node completes and processed at the top
142
+ # of the next iteration so that guards always see the freshest context.
143
+ event_queue = []
140
144
  step = 0
141
145
 
142
- while current_node && current_node != FINISH
143
- if step >= recursion_limit
144
- raise Phronomy::RecursionLimitError,
145
- "Recursion limit (#{recursion_limit}) exceeded"
146
+ loop do
147
+ break if current_node == FINISH
148
+
149
+ # -- Process next pending event -----------------------------------------
150
+ # Dequeue one event and fire it against the state machine. Guards are
151
+ # evaluated here (at fire time) so they see the context written by the
152
+ # node that enqueued the event.
153
+ if (event = event_queue.shift)
154
+ if step >= recursion_limit
155
+ raise Phronomy::RecursionLimitError,
156
+ "Recursion limit (#{recursion_limit}) exceeded"
157
+ end
158
+
159
+ fire_event!(tracker, event, current_node)
160
+ next_phase = tracker.phase.to_sym
161
+ # When next_phase == current_node no transition matched → terminal node.
162
+ current_node = (next_phase == current_node) ? FINISH : next_phase
163
+ step += 1
164
+ next
146
165
  end
147
166
 
148
- # Auto-halt at wait states: save context and return to caller.
167
+ # -- Queue empty: check for halt -----------------------------------------
168
+ # Auto-halt at wait states: persist phase in context and return to caller.
169
+ # The caller resumes via send_event, which starts a fresh run_graph call.
149
170
  if @wait_state_names.include?(current_node)
150
171
  state.set_graph_metadata(thread_id: state.thread_id, phase: current_node)
151
172
  return state
152
173
  end
153
174
 
175
+ # -- Execute node action ------------------------------------------------
154
176
  node_fn = @nodes[current_node]
155
177
  raise ArgumentError, "Node #{current_node.inspect} is not defined" unless node_fn
156
178
 
@@ -165,27 +187,22 @@ module Phronomy
165
187
  "expected Hash, #{@state_class}, or nil"
166
188
  end
167
189
 
168
- # Update tracker so guards see the freshest context.
190
+ # Update tracker so guards see the freshest context when the event fires.
169
191
  tracker.context = state
170
192
 
171
193
  event_block&.call({node: current_node, state: state})
172
194
 
173
- # Delegate transition decision to state_machines.
195
+ # -- Enqueue transition event -------------------------------------------
196
+ # node_completed: generic event for all after-transitions (unconditional).
197
+ # route event: user-named event carrying guarded conditional branches.
198
+ # No enqueue: terminal node — next iteration exits via FINISH check.
174
199
  if @after_transitions.key?(current_node)
175
- fire_event!(tracker, :"advance_#{current_node}", current_node)
200
+ event_queue << :node_completed
176
201
  elsif @route_transitions.key?(current_node)
177
- ev_name = @route_transitions[current_node][:event_name]
178
- fire_event!(tracker, ev_name, current_node)
202
+ event_queue << @route_transitions[current_node][:event_name]
203
+ else
204
+ current_node = FINISH
179
205
  end
180
- # Nodes with no declared outgoing transition are treated as terminal:
181
- # next_phase == current_node triggers the FINISH assignment below.
182
-
183
- next_phase = tracker.phase.to_sym
184
- # When next_phase == current_node: no transition fired (terminal node) → end.
185
- # When next_phase == :__end__ (== FINISH): route led to finish → exit loop.
186
- current_node = (next_phase == current_node) ? FINISH : next_phase
187
-
188
- step += 1
189
206
  end
190
207
 
191
208
  state.set_graph_metadata(thread_id: state.thread_id, phase: :__end__)
@@ -225,9 +242,11 @@ module Phronomy
225
242
  state_machine :phase, initial: entry do
226
243
  all_states.each { |s| state s }
227
244
 
228
- # 1. After-transitions: unconditional, fire on action completion.
229
- after_trans.each do |from, to|
230
- event :"advance_#{from}" do
245
+ # 1. After-transitions: one generic :node_completed event covers all
246
+ # unconditional transitions. This keeps event names independent of
247
+ # source state names and matches standard state machine semantics.
248
+ event :node_completed do
249
+ after_trans.each do |from, to|
231
250
  transition from => to
232
251
  end
233
252
  end
data/lib/phronomy.rb CHANGED
@@ -27,6 +27,23 @@ module Phronomy
27
27
 
28
28
  class HandoffError < Error; end
29
29
 
30
+ # Raised by {Phronomy::GeneratorVerifier#invoke} when +raise_if_untrusted: true+
31
+ # and the pipeline's combined confidence score falls below the configured threshold.
32
+ #
33
+ # @example
34
+ # rescue Phronomy::LowConfidenceError => e
35
+ # puts e.result.confidence # => e.g. 0.45
36
+ # puts e.result.output # best-effort answer despite low confidence
37
+ class LowConfidenceError < Error
38
+ # @return [Phronomy::GeneratorVerifier::Result] the untrusted result
39
+ attr_reader :result
40
+
41
+ def initialize(result)
42
+ @result = result
43
+ super("Answer confidence #{result.confidence} is below the required threshold")
44
+ end
45
+ end
46
+
30
47
  class GuardrailError < Error
31
48
  attr_reader :guardrail
32
49
 
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.3.0
4
+ version: 0.4.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-17 00:00:00.000000000 Z
11
+ date: 2026-05-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby_llm
@@ -52,9 +52,8 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0.6'
55
- description: Phronomy provides Agent, Workflow, Memory, Tool, Guardrail, RAG, and
56
- Multi-agent capabilities for building AI agents in Ruby and Rails. Powered by RubyLLM
57
- for LLM abstraction.
55
+ description: Phronomy provides Agent, Workflow, Tool, Guardrail, RAG, and Multi-agent
56
+ capabilities for building AI agents in Ruby. Powered by RubyLLM for LLM abstraction.
58
57
  email:
59
58
  - raizo.tcs@gmail.com
60
59
  executables: []
@@ -75,9 +74,12 @@ files:
75
74
  - lib/phronomy/agent/before_completion_context.rb
76
75
  - lib/phronomy/agent/checkpoint.rb
77
76
  - lib/phronomy/agent/handoff.rb
77
+ - lib/phronomy/agent/orchestrator.rb
78
78
  - lib/phronomy/agent/react_agent.rb
79
79
  - lib/phronomy/agent/runner.rb
80
+ - lib/phronomy/agent/shared_state.rb
80
81
  - lib/phronomy/agent/suspend_signal.rb
82
+ - lib/phronomy/agent/team_coordinator.rb
81
83
  - lib/phronomy/configuration.rb
82
84
  - lib/phronomy/context.rb
83
85
  - lib/phronomy/context/assembler.rb
@@ -103,6 +105,7 @@ files:
103
105
  - lib/phronomy/eval/scorer/exact_match.rb
104
106
  - lib/phronomy/eval/scorer/includes_scorer.rb
105
107
  - lib/phronomy/eval/scorer/llm_judge.rb
108
+ - lib/phronomy/generator_verifier.rb
106
109
  - lib/phronomy/guardrail.rb
107
110
  - lib/phronomy/guardrail/base.rb
108
111
  - lib/phronomy/guardrail/builtin.rb
@@ -142,7 +145,6 @@ files:
142
145
  - lib/phronomy/tracing/langfuse_tracer.rb
143
146
  - lib/phronomy/tracing/null_tracer.rb
144
147
  - lib/phronomy/tracing/open_telemetry_tracer.rb
145
- - lib/phronomy/trust_pipeline.rb
146
148
  - lib/phronomy/vector_store.rb
147
149
  - lib/phronomy/vector_store/base.rb
148
150
  - lib/phronomy/vector_store/in_memory.rb