phronomy 0.9.0 → 0.9.1

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.
@@ -3,8 +3,7 @@
3
3
  module Phronomy
4
4
  module Tools
5
5
  # Wraps a Phronomy::Agent::Base subclass as a callable tool so that a parent
6
- # ReactAgent (or any agent that supports tools) can delegate sub-tasks to a
7
- # fully-capable agent.
6
+ # agent can delegate sub-tasks to a fully-capable sub-agent.
8
7
  #
9
8
  # Use Agent.from_agent to generate a concrete tool class. The generated
10
9
  # class is anonymous; assign it to a constant when you need a stable name.
@@ -16,7 +15,7 @@ module Phronomy
16
15
  # description: "Summarizes a long text and returns a brief summary"
17
16
  # )
18
17
  #
19
- # class OrchestratorAgent < Phronomy::Agent::ReactAgent
18
+ # class OrchestratorAgent < Phronomy::Agent::Base
20
19
  # model "openai/gpt-4o-mini"
21
20
  # instructions "You are an orchestrator that delegates to specialist agents."
22
21
  # tools SummarizerTool
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phronomy
4
- VERSION = "0.9.0"
4
+ VERSION = "0.9.1"
5
5
  end
@@ -0,0 +1,249 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phronomy
4
+ class Workflow
5
+ # Event-driven execution wrapper for a single workflow run.
6
+ #
7
+ # Created by WorkflowRunner and registered with EventLoop. All public methods
8
+ # are called from the EventLoop thread — FSMSession is NOT thread-safe and must
9
+ # not be accessed concurrently from multiple threads.
10
+ #
11
+ # == Lifecycle
12
+ #
13
+ # register(session) → EventLoop posts :start → session.start
14
+ # ↓ (auto-transition present)
15
+ # EventLoop posts :state_completed → session.handle
16
+ # ↓ (repeat)
17
+ # session posts :finished or :halted
18
+ # ↓
19
+ # EventLoop pushes ctx to completion_queue → caller unblocks
20
+ #
21
+ # == Async IO pattern (EventLoop mode only)
22
+ #
23
+ # When a state has no auto-transition and is not a wait_state, but has an
24
+ # external event registered (e.g. +transition from: :fetching, on: :fetch_done+),
25
+ # the FSMSession stays registered in the EventLoop and waits for that event.
26
+ # The entry action is expected to spawn an IO thread that posts the event back:
27
+ #
28
+ # entry :fetching, ->(ctx) {
29
+ # Thread.new {
30
+ # ctx.result = http.get(ctx.url)
31
+ # Phronomy::EventLoop.instance.post(
32
+ # Phronomy::Event.new(type: :fetch_done, target_id: ctx.thread_id, payload: nil)
33
+ # )
34
+ # }
35
+ # }
36
+ # transition from: :fetching, on: :fetch_done, to: :process
37
+ class FSMSession
38
+ FINISH = WorkflowRunner::FINISH
39
+
40
+ # @return [String] workflow thread_id (matches WorkflowContext#thread_id)
41
+ attr_reader :id
42
+
43
+ # @param id [String]
44
+ # @param context [Object] includes Phronomy::WorkflowContext
45
+ # @param entry_point [Symbol] initial state name
46
+ # @param entry_actions [Hash] { state_name => [callable, ...] }
47
+ # @param auto_state_set [Hash] { state_name => true }
48
+ # @param declared_states [Array<Symbol>] all action state names
49
+ # @param wait_state_names [Array<Symbol>]
50
+ # @param external_events [Hash] { event_name => [{from:, to:, guard:}] }
51
+ # @param phase_machine_class [Class] state_machines-backed phase tracker class
52
+ # @param recursion_limit [Integer]
53
+ # @param action_timeouts [Hash] { state_name => seconds }
54
+ # @param resume_event [Symbol, nil] external event to fire when resuming
55
+ # @param resume_phase [Symbol, nil] wait state name to resume from
56
+ # @api private
57
+ def initialize(id:, context:, entry_point:, entry_actions:, auto_state_set:,
58
+ declared_states:, wait_state_names:, external_events:, phase_machine_class:,
59
+ recursion_limit:, action_timeouts: {}, resume_event: nil, resume_phase: nil)
60
+ @id = id
61
+ @ctx = context
62
+ @entry_point = entry_point
63
+ @entry_actions = entry_actions
64
+ @auto_state_set = auto_state_set
65
+ @declared_states = declared_states
66
+ @wait_state_names = wait_state_names
67
+ @external_events = external_events
68
+ @phase_machine_class = phase_machine_class
69
+ @recursion_limit = recursion_limit
70
+ @action_timeouts = action_timeouts
71
+ @resume_event = resume_event
72
+ @resume_phase = resume_phase
73
+ @step = 0
74
+ @done = false
75
+ @current_state = nil
76
+ @tracker = nil
77
+ end
78
+
79
+ # Begins workflow execution. Called by EventLoop on :start event.
80
+ def start
81
+ if @resume_event
82
+ # Resume from wait state: position tracker at the wait state, then fire the
83
+ # external event. state_machines fires before_transition (exit) and
84
+ # after_transition (entry) callbacks, so both actions execute here.
85
+ @current_state = @resume_phase
86
+ @tracker = build_tracker(@current_state)
87
+ @tracker.context = @ctx
88
+ fire_and_advance!(@resume_event)
89
+ else
90
+ # Fresh start: state_machines does not fire callbacks on initialization,
91
+ # so we invoke the entry action for the initial state manually.
92
+ @current_state = @entry_point
93
+ @tracker = build_tracker(@current_state)
94
+ @tracker.context = @ctx
95
+ (@entry_actions[@current_state] || []).each do |c|
96
+ result = c.call(@ctx)
97
+ if result.is_a?(Phronomy::Task)
98
+ # Awaitable action: spawn a task to await without blocking EventLoop.
99
+ @tracker.async_pending = true
100
+ session_id = @id
101
+ current_state_name = @current_state
102
+ timeout_secs = @action_timeouts[current_state_name]
103
+ Phronomy::Runtime.instance.spawn(name: "fsm-await-#{session_id}") do
104
+ if timeout_secs
105
+ if result.join(timeout_secs).nil?
106
+ result.cancel!
107
+ raise Phronomy::ActionTimeoutError,
108
+ "Action in state #{current_state_name.inspect} timed out after #{timeout_secs}s"
109
+ end
110
+ end
111
+ task_result = result.await
112
+ if task_result.is_a?(Phronomy::WorkflowContext)
113
+ event_loop.post(Event.new(type: :action_completed, target_id: session_id, payload: task_result))
114
+ else
115
+ event_loop.post(Event.new(type: :state_completed, target_id: session_id, payload: nil))
116
+ end
117
+ rescue => e
118
+ event_loop.post(Event.new(type: :error, target_id: session_id, payload: e))
119
+ end
120
+ break # Only one async action at a time per state
121
+ elsif result.is_a?(Phronomy::WorkflowContext)
122
+ @ctx = result
123
+ end
124
+ end
125
+ @tracker.context = @ctx
126
+ advance_or_halt unless @tracker.async_pending
127
+ end
128
+ rescue => e
129
+ finish_with_error(e)
130
+ end
131
+
132
+ # Processes an event dispatched from EventLoop.
133
+ # Called for :state_completed, :action_completed, and all user-defined external events.
134
+ #
135
+ # @param event [Phronomy::Event]
136
+ # @api private
137
+ def handle(event)
138
+ return if @done
139
+
140
+ if event.type == :action_completed
141
+ # An awaitable entry action completed: update context and advance.
142
+ @ctx = event.payload if event.payload.is_a?(Phronomy::WorkflowContext)
143
+ @tracker.context = @ctx
144
+ @tracker.async_pending = false # Reset flag set by start or fire_and_advance!
145
+ advance_or_halt
146
+ return
147
+ end
148
+
149
+ fire_and_advance!(event.type)
150
+ rescue => e
151
+ finish_with_error(e)
152
+ end
153
+
154
+ private
155
+
156
+ # Fires event_name on the phase tracker, updates @current_state, then
157
+ # calls advance_or_halt to decide what to do next.
158
+ def fire_and_advance!(event_name)
159
+ if @step >= @recursion_limit
160
+ raise Phronomy::RecursionLimitError,
161
+ "Recursion limit (#{@recursion_limit}) exceeded"
162
+ end
163
+
164
+ fire_event!(@tracker, event_name, @current_state)
165
+ @ctx = @tracker.context
166
+ next_phase = @tracker.phase.to_sym
167
+ # When next_phase == @current_state, no transition matched → treat as terminal.
168
+ @current_state = (next_phase == @current_state) ? FINISH : next_phase
169
+ @step += 1
170
+
171
+ # If an entry action returned a Task, the after_transition callback set
172
+ # async_pending = true and spawned a thread. Skip advance_or_halt — the
173
+ # background thread will post :action_completed or :state_completed.
174
+ if @tracker.async_pending
175
+ @tracker.async_pending = false
176
+ return
177
+ end
178
+
179
+ advance_or_halt
180
+ end
181
+
182
+ # Determines the next action after the FSM has entered @current_state.
183
+ def advance_or_halt
184
+ return finish! if @current_state == FINISH
185
+
186
+ if @wait_state_names.include?(@current_state)
187
+ return halt!
188
+ end
189
+
190
+ if @auto_state_set.key?(@current_state)
191
+ event_loop.post(Event.new(type: :state_completed, target_id: @id, payload: nil))
192
+ return
193
+ end
194
+
195
+ if has_external_event_from?(@current_state)
196
+ # Async IO pattern: the entry action spawned an IO thread that will post
197
+ # an external event back. Stay registered; do nothing here.
198
+ return
199
+ end
200
+
201
+ # No transition declared — validate the state is known, then treat as terminal.
202
+ unless @declared_states.include?(@current_state)
203
+ raise ArgumentError, "State #{@current_state.inspect} is not defined"
204
+ end
205
+
206
+ finish!
207
+ end
208
+
209
+ def finish!
210
+ @done = true
211
+ @ctx.set_graph_metadata(thread_id: @id, phase: :__end__)
212
+ event_loop.post(Event.new(type: :finished, target_id: @id, payload: @ctx))
213
+ end
214
+
215
+ def halt!
216
+ @done = true
217
+ @ctx.set_graph_metadata(thread_id: @id, phase: @current_state)
218
+ event_loop.post(Event.new(type: :halted, target_id: @id, payload: @ctx))
219
+ end
220
+
221
+ def finish_with_error(err)
222
+ @done = true
223
+ event_loop.post(Event.new(type: :error, target_id: @id, payload: err))
224
+ end
225
+
226
+ def fire_event!(tracker, event_name, from_state)
227
+ return if tracker.send(event_name)
228
+
229
+ raise ArgumentError,
230
+ "Transition from #{from_state.inspect} via event #{event_name.inspect} failed. " \
231
+ "Ensure at least one guard matches or add a fallback (no-guard) transition."
232
+ end
233
+
234
+ def has_external_event_from?(state)
235
+ @external_events.any? { |_, transitions| transitions.any? { |t| t[:from] == state } }
236
+ end
237
+
238
+ def build_tracker(from_state)
239
+ machine = @phase_machine_class.new
240
+ machine.instance_variable_set(:@phase, from_state.to_s)
241
+ machine
242
+ end
243
+
244
+ def event_loop
245
+ Phronomy::EventLoop.instance
246
+ end
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,247 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "state_machines"
4
+
5
+ module Phronomy
6
+ class Workflow
7
+ # Builds the anonymous state-machine Class used by {WorkflowRunner} to track
8
+ # workflow phase transitions.
9
+ #
10
+ # Extracted from {WorkflowRunner#build_phase_machine_class} to reduce the
11
+ # span of WorkflowRunner's initializer and to give the FSM construction
12
+ # logic an explicit, testable home.
13
+ #
14
+ # Call {#build} to obtain the generated +Class+. The returned class responds
15
+ # to +#context+ / +#context=+ and +#async_pending+ / +#async_pending=+, and
16
+ # has a +state_machine :phase+ definition with all registered transitions and
17
+ # callbacks.
18
+ #
19
+ # @api private
20
+ class PhaseMachineBuilder
21
+ # @param entry_point [Symbol] initial state for the phase machine
22
+ # @param declared_states [Array<Symbol>] all states declared in the workflow
23
+ # @param wait_state_names [Array<Symbol>] states that wait for external events
24
+ # @param external_events [Hash{Symbol => Array<Hash>}]
25
+ # +{ event_name => [{from:, to:, guard:}, ...] }+
26
+ # @param entry_actions [Hash{Symbol => Array<#call>}]
27
+ # +{ state_name => [callable, ...] }+
28
+ # @param action_timeouts [Hash{Symbol => Numeric}]
29
+ # +{ state_name => seconds }+
30
+ # @param auto_transitions [Array<Hash>]
31
+ # +[{ from:, to:, guard: }, ...]+ — all auto-fire transitions
32
+ # @param exit_actions [Hash{Symbol => Array<#call>}]
33
+ # +{ state_name => [callable, ...] }+
34
+ # @api private
35
+ def initialize(
36
+ entry_point:,
37
+ declared_states:,
38
+ wait_state_names:,
39
+ external_events:,
40
+ entry_actions:,
41
+ action_timeouts:,
42
+ auto_transitions:,
43
+ exit_actions:
44
+ )
45
+ @entry_point = entry_point
46
+ @declared_states = declared_states
47
+ @wait_state_names = wait_state_names
48
+ @external_events = external_events
49
+ @entry_actions = entry_actions
50
+ @action_timeouts = action_timeouts
51
+ @auto_transitions = auto_transitions
52
+ @exit_actions = exit_actions
53
+ end
54
+
55
+ # Constructs and returns the anonymous phase-machine Class.
56
+ #
57
+ # @return [Class] an anonymous class with a +state_machine :phase+ definition
58
+ # @raise [ArgumentError] if state_machines raises during class construction
59
+ # @api private
60
+ def build
61
+ entry = @entry_point
62
+ all_states = (@declared_states + @wait_state_names + [:__end__]).uniq
63
+ auto_trans = @auto_transitions
64
+ ext_events = @external_events
65
+ entry_acts = @entry_actions
66
+ exit_acts = @exit_actions
67
+ act_timeouts = @action_timeouts
68
+ build_cb = method(:build_entry_callback)
69
+
70
+ Class.new do
71
+ # Holds the current WorkflowContext so guards and callbacks can read it.
72
+ attr_accessor :context
73
+
74
+ # Set to true by an entry action that returned an awaitable Task.
75
+ # When true, FSMSession skips the automatic advance_or_halt step and
76
+ # waits for the async worker thread to post a state_completed event back.
77
+ attr_accessor :async_pending
78
+
79
+ state_machine :phase, initial: entry do
80
+ all_states.each { |s| state s }
81
+
82
+ # Auto-fire transitions: all auto transitions unified under :state_completed.
83
+ # Includes unguarded (unconditional) and guarded (conditional) transitions.
84
+ # Declaration order is preserved; guards are evaluated before unguarded fallbacks.
85
+ event :state_completed do
86
+ auto_trans.each do |t|
87
+ if t[:guard]
88
+ guard_proc = t[:guard]
89
+ transition t[:from] => t[:to], :if => ->(m) { guard_proc.call(m.context) }
90
+ else
91
+ transition t[:from] => t[:to]
92
+ end
93
+ end
94
+ end
95
+
96
+ # External events: human-in-the-loop triggers from wait states.
97
+ ext_events.each do |ev_name, transitions|
98
+ event ev_name do
99
+ transitions.each do |t|
100
+ if t[:guard]
101
+ guard_proc = t[:guard]
102
+ transition t[:from] => t[:to], :if => ->(m) { guard_proc.call(m.context) }
103
+ else
104
+ transition t[:from] => t[:to]
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ # Entry callbacks: fire after_transition into each state.
111
+ # Each callable is registered as a separate callback; state_machines
112
+ # accumulates them and fires in declaration order.
113
+ # If the callable returns a WorkflowContext (e.g. via s.merge(...)),
114
+ # the returned context replaces the current one on the tracker.
115
+ entry_acts.each do |state_name, callables|
116
+ callables.each do |callable|
117
+ cb = build_cb.call(callable, state_name, act_timeouts[state_name])
118
+ after_transition to: state_name, &cb
119
+ end
120
+ end
121
+
122
+ # Exit callbacks: fire before_transition out of each state.
123
+ # Each callable is registered as a separate callback; state_machines
124
+ # accumulates them and fires in declaration order.
125
+ exit_acts.each do |state_name, callables|
126
+ callables.each do |callable|
127
+ before_transition from: state_name do |machine|
128
+ callable.call(machine.context)
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
134
+ rescue => e
135
+ raise ArgumentError, "Failed to build phase machine: #{e.message}"
136
+ end
137
+
138
+ private
139
+
140
+ # Returns a proc suitable for use as an +after_transition+ callback.
141
+ #
142
+ # The returned proc accepts a single argument (the phase machine instance),
143
+ # invokes the entry action callable with the current context, then delegates
144
+ # the result to {#handle_entry_action_result}. Capturing this in the
145
+ # builder's scope lets the anonymous +Class.new+ block stay slim.
146
+ #
147
+ # @param callable [#call] the entry action
148
+ # @param state_name [Symbol] name of the target state (for error messages)
149
+ # @param timeout_secs [Numeric, nil] seconds before ActionTimeoutError
150
+ # @return [Proc]
151
+ # @api private
152
+ def build_entry_callback(callable, state_name, timeout_secs)
153
+ handle = method(:handle_entry_action_result)
154
+ ->(machine) {
155
+ result = callable.call(machine.context)
156
+ handle.call(machine, result, state_name, timeout_secs)
157
+ }
158
+ end
159
+
160
+ # Dispatches the return value of an entry action callable.
161
+ #
162
+ # - +Phronomy::Task+ → async or blocking task handling
163
+ # - +Phronomy::WorkflowContext+ → replaces the machine's context directly
164
+ # - anything else → ignored
165
+ #
166
+ # @param machine [Object] phase machine instance
167
+ # @param result [Object] return value of the entry callable
168
+ # @param state_name [Symbol] name of the entered state
169
+ # @param timeout_secs [Numeric, nil] optional timeout in seconds
170
+ # @api private
171
+ def handle_entry_action_result(machine, result, state_name, timeout_secs)
172
+ if result.is_a?(Phronomy::Task)
173
+ if Phronomy.configuration.event_loop
174
+ dispatch_task_in_event_loop(machine, result, state_name, timeout_secs)
175
+ else
176
+ await_task_blocking(machine, result, state_name, timeout_secs)
177
+ end
178
+ elsif result.is_a?(Phronomy::WorkflowContext)
179
+ machine.context = result
180
+ end
181
+ end
182
+
183
+ # Handles a +Phronomy::Task+ return value in EventLoop mode.
184
+ #
185
+ # Marks the machine as async-pending and spawns a cooperative background
186
+ # task that awaits the result, then posts the appropriate event back to
187
+ # the EventLoop. +FSMSession+ will skip the automatic +advance_or_halt+
188
+ # step while +async_pending+ is true.
189
+ #
190
+ # @param machine [Object] phase machine instance
191
+ # @param result [Phronomy::Task]
192
+ # @param state_name [Symbol]
193
+ # @param timeout_secs [Numeric, nil]
194
+ # @api private
195
+ def dispatch_task_in_event_loop(machine, result, state_name, timeout_secs)
196
+ machine.async_pending = true
197
+ thread_id = machine.context.thread_id
198
+ Phronomy::Runtime.instance.spawn(name: "wf-await-#{thread_id}") do
199
+ enforce_timeout!(result, state_name, timeout_secs)
200
+ task_result = result.await
201
+ ev = if task_result.is_a?(Phronomy::WorkflowContext)
202
+ Phronomy::Event.new(type: :action_completed, target_id: thread_id, payload: task_result)
203
+ else
204
+ Phronomy::Event.new(type: :state_completed, target_id: thread_id, payload: nil)
205
+ end
206
+ Phronomy::EventLoop.instance.post(ev)
207
+ rescue => e
208
+ Phronomy::EventLoop.instance.post(
209
+ Phronomy::Event.new(type: :error, target_id: thread_id, payload: e)
210
+ )
211
+ end
212
+ end
213
+
214
+ # Handles a +Phronomy::Task+ return value in non-EventLoop mode.
215
+ #
216
+ # Blocks the current execution context until the task completes or the
217
+ # optional timeout elapses.
218
+ #
219
+ # @param machine [Object] phase machine instance
220
+ # @param result [Phronomy::Task]
221
+ # @param state_name [Symbol]
222
+ # @param timeout_secs [Numeric, nil]
223
+ # @api private
224
+ def await_task_blocking(machine, result, state_name, timeout_secs)
225
+ enforce_timeout!(result, state_name, timeout_secs)
226
+ task_result = result.await
227
+ machine.context = task_result if task_result.is_a?(Phronomy::WorkflowContext)
228
+ end
229
+
230
+ # Raises +ActionTimeoutError+ if the task does not complete within
231
+ # +timeout_secs+. No-op when +timeout_secs+ is +nil+.
232
+ #
233
+ # @param result [Phronomy::Task]
234
+ # @param state_name [Symbol]
235
+ # @param timeout_secs [Numeric, nil]
236
+ # @api private
237
+ def enforce_timeout!(result, state_name, timeout_secs)
238
+ return unless timeout_secs
239
+ return unless result.join(timeout_secs).nil?
240
+
241
+ result.cancel!
242
+ raise Phronomy::ActionTimeoutError,
243
+ "Action in state #{state_name.inspect} timed out after #{timeout_secs}s"
244
+ end
245
+ end
246
+ end
247
+ end
@@ -56,7 +56,7 @@ 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 = Agent::Lifecycle::PhaseMachineBuilder.new(
59
+ @phase_machine_class = Workflow::PhaseMachineBuilder.new(
60
60
  entry_point: @entry_point,
61
61
  declared_states: @declared_states,
62
62
  wait_state_names: @wait_state_names,
@@ -169,7 +169,7 @@ module Phronomy
169
169
 
170
170
  # Builds an FSMSession for the given context. Used in EventLoop mode.
171
171
  def build_session_for(context:, recursion_limit:, resume_event: nil, resume_phase: nil)
172
- Phronomy::Agent::Lifecycle::FSMSession.new(
172
+ Phronomy::Workflow::FSMSession.new(
173
173
  id: context.thread_id,
174
174
  context: context,
175
175
  entry_point: @entry_point,
data/lib/phronomy.rb CHANGED
@@ -12,8 +12,6 @@ loader.inflector.inflect("ruby_llm_embeddings" => "RubyLLMEmbeddings")
12
12
  loader.inflector.inflect("rag" => "RAG")
13
13
  # FSMSession: Zeitwerk would infer "FsmSession" — override to "FSMSession".
14
14
  loader.inflector.inflect("fsm_session" => "FSMSession")
15
- # AgentFSM: Zeitwerk would infer "Fsm" — override to "FSM".
16
- loader.inflector.inflect("fsm" => "FSM")
17
15
  # LLMAdapter: Zeitwerk would infer "LlmAdapter" — override to "LLMAdapter".
18
16
  loader.inflector.inflect("llm_adapter" => "LLMAdapter")
19
17
  # LLMAdapter::RubyLLM: "ruby_llm" maps to "RubyLLM" (not "RubyLlm").
@@ -98,6 +96,14 @@ module Phronomy
98
96
  end
99
97
  end
100
98
 
99
+ # Raised when {Agent::Base#resume} (or the class-level equivalent) is called
100
+ # with a {Agent::Checkpoint} whose +checkpoint_id+ has already been consumed
101
+ # by a previous +resume+ call on the same store.
102
+ #
103
+ # This protects against duplicate resume executions caused by webhook retries
104
+ # or queue message redelivery.
105
+ class CheckpointAlreadyResumedError < Error; end
106
+
101
107
  # Raised when an operation is submitted to a {BlockingAdapterPool} that has
102
108
  # already been shut down via {BlockingAdapterPool#shutdown}.
103
109
  class PoolShutdownError < Error; end
@@ -30,7 +30,6 @@ PUBLIC_API_ENTRIES = [
30
30
  Phronomy::Runnable,
31
31
  Phronomy::Agent::Context::Instruction::PromptTemplate,
32
32
  # Beta
33
- Phronomy::Agent::ReactAgent,
34
33
  Phronomy::MultiAgent::Orchestrator,
35
34
  Phronomy::MultiAgent::TeamCoordinator,
36
35
  Phronomy::Guardrail::InputGuardrail,
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.9.0
4
+ version: 0.9.1
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-06-01 00:00:00.000000000 Z
11
+ date: 2026-06-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby_llm
@@ -106,6 +106,7 @@ files:
106
106
  - lib/phronomy/agent/base.rb
107
107
  - lib/phronomy/agent/before_completion_context.rb
108
108
  - lib/phronomy/agent/checkpoint.rb
109
+ - lib/phronomy/agent/checkpoint_store.rb
109
110
  - lib/phronomy/agent/concerns/before_completion.rb
110
111
  - lib/phronomy/agent/concerns/error_translation.rb
111
112
  - lib/phronomy/agent/concerns/guardrailable.rb
@@ -117,11 +118,6 @@ files:
117
118
  - lib/phronomy/agent/context/knowledge/base.rb
118
119
  - lib/phronomy/agent/context/knowledge/entity_knowledge.rb
119
120
  - lib/phronomy/agent/context/knowledge/static_knowledge.rb
120
- - lib/phronomy/agent/fsm.rb
121
- - lib/phronomy/agent/invocation_pipeline.rb
122
- - lib/phronomy/agent/lifecycle/fsm_session.rb
123
- - lib/phronomy/agent/lifecycle/phase_machine_builder.rb
124
- - lib/phronomy/agent/react_agent.rb
125
121
  - lib/phronomy/agent/runner.rb
126
122
  - lib/phronomy/agent/shared_state.rb
127
123
  - lib/phronomy/agent/suspend_signal.rb
@@ -226,6 +222,8 @@ files:
226
222
  - lib/phronomy/vector_store/splitter/recursive_splitter.rb
227
223
  - lib/phronomy/version.rb
228
224
  - lib/phronomy/workflow.rb
225
+ - lib/phronomy/workflow/fsm_session.rb
226
+ - lib/phronomy/workflow/phase_machine_builder.rb
229
227
  - lib/phronomy/workflow_context.rb
230
228
  - lib/phronomy/workflow_runner.rb
231
229
  - scripts/api_snapshot.rb