phronomy 0.5.3 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +41 -0
- data/README.md +24 -25
- data/lib/phronomy/agent/base.rb +88 -2
- data/lib/phronomy/agent/fsm.rb +165 -0
- data/lib/phronomy/agent/orchestrator.rb +100 -19
- data/lib/phronomy/agent/parallel_tool_chat.rb +75 -0
- data/lib/phronomy/configuration.rb +6 -0
- data/lib/phronomy/context.rb +0 -1
- data/lib/phronomy/event.rb +14 -0
- data/lib/phronomy/event_loop.rb +147 -0
- data/lib/phronomy/fsm_session.rb +194 -0
- data/lib/phronomy/generator_verifier.rb +22 -22
- data/lib/phronomy/guardrail.rb +0 -1
- data/lib/phronomy/vector_store/base.rb +15 -0
- data/lib/phronomy/vector_store/in_memory.rb +11 -1
- data/lib/phronomy/vector_store/pgvector.rb +8 -2
- data/lib/phronomy/vector_store/redis_search.rb +16 -3
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/workflow.rb +83 -71
- data/lib/phronomy/workflow_context.rb +1 -1
- data/lib/phronomy/workflow_runner.rb +167 -112
- data/lib/phronomy.rb +4 -0
- metadata +7 -6
- data/lib/phronomy/context/builder.rb +0 -92
- data/lib/phronomy/guardrail/builtin/pii_pattern_detector.rb +0 -100
- data/lib/phronomy/guardrail/builtin/prompt_injection_detector.rb +0 -67
- data/lib/phronomy/guardrail/builtin.rb +0 -16
data/lib/phronomy/workflow.rb
CHANGED
|
@@ -8,18 +8,21 @@ module Phronomy
|
|
|
8
8
|
#
|
|
9
9
|
# Defines agent workflows in terms of *states* and *events* backed by
|
|
10
10
|
# Phronomy::WorkflowRunner. This is the primary high-level API
|
|
11
|
-
# for
|
|
11
|
+
# for workflow-based execution in phronomy.
|
|
12
12
|
#
|
|
13
13
|
# == Basic usage
|
|
14
14
|
#
|
|
15
15
|
# app = Phronomy::Workflow.define(MyContext) do
|
|
16
16
|
# initial :fetch
|
|
17
17
|
#
|
|
18
|
-
# state :fetch
|
|
19
|
-
# state :process
|
|
18
|
+
# state :fetch
|
|
19
|
+
# state :process
|
|
20
20
|
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
21
|
+
# entry :fetch, FETCH_NODE
|
|
22
|
+
# entry :process, PROCESS_NODE
|
|
23
|
+
#
|
|
24
|
+
# transition from: :fetch, to: :process
|
|
25
|
+
# transition from: :process, to: :__finish__
|
|
23
26
|
# end
|
|
24
27
|
#
|
|
25
28
|
# result = app.invoke({ url: "https://example.com" })
|
|
@@ -29,15 +32,18 @@ module Phronomy
|
|
|
29
32
|
# app = Phronomy::Workflow.define(MyContext) do
|
|
30
33
|
# initial :propose
|
|
31
34
|
#
|
|
32
|
-
# state :propose
|
|
35
|
+
# state :propose
|
|
33
36
|
# wait_state :awaiting_approval
|
|
34
|
-
# state :execute
|
|
37
|
+
# state :execute
|
|
38
|
+
#
|
|
39
|
+
# entry :propose, PROPOSE_NODE
|
|
40
|
+
# entry :execute, EXECUTE_NODE
|
|
35
41
|
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
42
|
+
# transition from: :propose, to: :awaiting_approval
|
|
43
|
+
# transition from: :execute, to: :__finish__
|
|
38
44
|
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
45
|
+
# transition from: :awaiting_approval, on: :approve, to: :execute
|
|
46
|
+
# transition from: :awaiting_approval, on: :reject, to: :propose
|
|
41
47
|
# end
|
|
42
48
|
#
|
|
43
49
|
# halted = app.invoke({ ... })
|
|
@@ -45,8 +51,8 @@ module Phronomy
|
|
|
45
51
|
#
|
|
46
52
|
# == Conditional transitions
|
|
47
53
|
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
54
|
+
# transition from: :decide, guard: ->(s) { s.score > 5 }, to: :high
|
|
55
|
+
# transition from: :decide, to: :low # fallback (no guard)
|
|
50
56
|
#
|
|
51
57
|
class Workflow
|
|
52
58
|
include Phronomy::Runnable
|
|
@@ -91,7 +97,7 @@ module Phronomy
|
|
|
91
97
|
@runner.send_event(state: state, event: event, input: input)
|
|
92
98
|
end
|
|
93
99
|
|
|
94
|
-
# Streaming execution. Yields {
|
|
100
|
+
# Streaming execution. Yields { state: Symbol, context: Object } after each state action.
|
|
95
101
|
# @param input [Hash]
|
|
96
102
|
# @param config [Hash]
|
|
97
103
|
# @yield [Hash]
|
|
@@ -112,12 +118,14 @@ module Phronomy
|
|
|
112
118
|
def initialize(context_class)
|
|
113
119
|
@context_class = context_class
|
|
114
120
|
@initial = nil
|
|
115
|
-
#
|
|
116
|
-
@
|
|
117
|
-
#
|
|
118
|
-
@
|
|
119
|
-
#
|
|
120
|
-
@
|
|
121
|
+
# Ordered list of declared state names (action states only, not wait states).
|
|
122
|
+
@declared_states = []
|
|
123
|
+
# { state_name => [callable, ...] } — entry actions registered via entry()
|
|
124
|
+
@entry_actions = {}
|
|
125
|
+
# { state_name => [callable, ...] } — exit actions registered via exit()
|
|
126
|
+
@exit_actions = {}
|
|
127
|
+
# Array of { from:, to:, guard:, on: } — all transitions in declaration order
|
|
128
|
+
@transitions = []
|
|
121
129
|
# Set of wait state names
|
|
122
130
|
@wait_state_names = []
|
|
123
131
|
end
|
|
@@ -131,83 +139,87 @@ module Phronomy
|
|
|
131
139
|
# rubocop:enable Style/TrivialAccessors
|
|
132
140
|
|
|
133
141
|
# Declares an action state.
|
|
134
|
-
# @param name
|
|
135
|
-
# @param action [#call, nil]
|
|
136
|
-
#
|
|
142
|
+
# @param name [Symbol] state name
|
|
143
|
+
# @param action [#call, nil] optional entry action shorthand.
|
|
144
|
+
# +state :generate, action: MY_PROC+ is equivalent to
|
|
145
|
+
# +state :generate; entry :generate, MY_PROC+.
|
|
137
146
|
def state(name, action: nil)
|
|
138
|
-
@
|
|
147
|
+
@declared_states << name
|
|
148
|
+
entry(name, action) if action
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Declares an entry action for a state.
|
|
152
|
+
# The callable is invoked when the workflow enters +name+.
|
|
153
|
+
# It receives the current context and should mutate it in place.
|
|
154
|
+
# Return value is ignored.
|
|
155
|
+
# Multiple calls for the same state are allowed; callables fire in declaration order.
|
|
156
|
+
# @param name [Symbol] state name
|
|
157
|
+
# @param callable [#call] receives context, mutates it in place
|
|
158
|
+
def entry(name, callable)
|
|
159
|
+
(@entry_actions[name] ||= []) << callable
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Declares an exit action for a state.
|
|
163
|
+
# The callable is invoked when the workflow leaves +name+.
|
|
164
|
+
# It receives the current context and should mutate it in place.
|
|
165
|
+
# Return value is ignored.
|
|
166
|
+
# Multiple calls for the same state are allowed; callables fire in declaration order.
|
|
167
|
+
# @param name [Symbol] state name
|
|
168
|
+
# @param callable [#call] receives context, mutates it in place
|
|
169
|
+
def exit(name, callable)
|
|
170
|
+
(@exit_actions[name] ||= []) << callable
|
|
139
171
|
end
|
|
140
172
|
|
|
141
173
|
# Declares a wait state that automatically halts execution when reached.
|
|
142
|
-
# No action is registered; the workflow pauses here until an event resumes it.
|
|
174
|
+
# No entry action is registered; the workflow pauses here until an event resumes it.
|
|
143
175
|
# @param name [Symbol] wait state name (conventionally :awaiting_something)
|
|
144
176
|
def wait_state(name)
|
|
145
177
|
@wait_state_names << name
|
|
146
178
|
end
|
|
147
179
|
|
|
148
|
-
# Declares
|
|
149
|
-
#
|
|
150
|
-
#
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
# Declares an event-driven transition.
|
|
157
|
-
# When +guard:+ is provided, the transition is taken only if the guard
|
|
158
|
-
# returns truthy for the current context. Multiple events with the same
|
|
159
|
-
# name and source are evaluated in declaration order; the first passing
|
|
160
|
-
# guard wins.
|
|
161
|
-
# @param name [Symbol] event name
|
|
162
|
-
# @param from [Symbol] source state where this event can be fired
|
|
180
|
+
# Declares a transition between states.
|
|
181
|
+
# Auto-fire transitions (no +on:+) fire automatically when an action state's
|
|
182
|
+
# action completes. External transitions (+on: :event_name+) are triggered
|
|
183
|
+
# manually via +send_event+.
|
|
184
|
+
# When +guard:+ is provided the transition is taken only if the guard returns
|
|
185
|
+
# truthy for the current context. Multiple transitions from the same source are
|
|
186
|
+
# evaluated in declaration order; the first passing guard wins.
|
|
187
|
+
# @param from [Symbol] source state
|
|
163
188
|
# @param to [Symbol] destination state or :__finish__
|
|
164
189
|
# @param guard [Proc, nil] optional guard — receives context, returns truthy/falsy
|
|
165
|
-
|
|
190
|
+
# @param on [Symbol, nil] named event for manual triggers (e.g. :approve)
|
|
191
|
+
def transition(from:, to:, guard: nil, on: nil)
|
|
166
192
|
dest = (to == :__finish__) ? FINISH : to
|
|
167
|
-
@
|
|
193
|
+
@transitions << {from: from, to: dest, guard: guard, on: on}
|
|
168
194
|
end
|
|
169
195
|
|
|
170
196
|
# Builds and returns a Phronomy::Workflow backed by a WorkflowRunner.
|
|
171
197
|
def build
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
# After-transitions: { from => to }
|
|
175
|
-
# Unconditional transitions that fire automatically after an action state completes.
|
|
176
|
-
after_transitions = @after_transitions.each_with_object({}) do |t, h|
|
|
177
|
-
h[t[:from]] = t[:to]
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
# Route transitions: { from => {event_name:, entries: [{guard:, to:}, ...]} }
|
|
181
|
-
# Events declared from action states (not wait states) fire automatically
|
|
182
|
-
# after the action completes. The event name is used to register the
|
|
183
|
-
# state_machines event and may be any symbol (e.g. :route, :route_review).
|
|
184
|
-
# Declaration order is preserved so guarded entries appear before fallbacks.
|
|
185
|
-
route_transitions = {}
|
|
198
|
+
entry_actions = @entry_actions.dup
|
|
199
|
+
exit_actions = @exit_actions.dup
|
|
186
200
|
|
|
187
|
-
#
|
|
188
|
-
#
|
|
201
|
+
# Auto-fire transitions (no :on): fire automatically when action completes.
|
|
202
|
+
# External events (with :on): triggered manually via send_event.
|
|
203
|
+
auto_transitions = []
|
|
189
204
|
external_events = {}
|
|
190
205
|
|
|
191
|
-
@
|
|
192
|
-
if
|
|
193
|
-
|
|
194
|
-
external_events[t[:
|
|
195
|
-
external_events[t[:name]] << {from: t[:from], to: t[:to], guard: t[:guard]}
|
|
206
|
+
@transitions.each do |t|
|
|
207
|
+
if t[:on]
|
|
208
|
+
external_events[t[:on]] ||= []
|
|
209
|
+
external_events[t[:on]] << {from: t[:from], to: t[:to], guard: t[:guard]}
|
|
196
210
|
else
|
|
197
|
-
|
|
198
|
-
# The event name is taken from the first declaration for each from-state.
|
|
199
|
-
route_transitions[t[:from]] ||= {event_name: t[:name], entries: []}
|
|
200
|
-
route_transitions[t[:from]][:entries] << {guard: t[:guard], to: t[:to]}
|
|
211
|
+
auto_transitions << {from: t[:from], to: t[:to], guard: t[:guard]}
|
|
201
212
|
end
|
|
202
213
|
end
|
|
203
214
|
|
|
204
215
|
runner = Phronomy::WorkflowRunner.new(
|
|
205
216
|
state_class: @context_class,
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
217
|
+
entry_actions: entry_actions,
|
|
218
|
+
exit_actions: exit_actions,
|
|
219
|
+
declared_states: @declared_states.dup,
|
|
220
|
+
auto_transitions: auto_transitions,
|
|
209
221
|
external_events: external_events,
|
|
210
|
-
entry_point: @initial ||
|
|
222
|
+
entry_point: @initial || @declared_states.first,
|
|
211
223
|
wait_state_names: @wait_state_names
|
|
212
224
|
)
|
|
213
225
|
|
|
@@ -49,7 +49,7 @@ module Phronomy
|
|
|
49
49
|
# Encoding:
|
|
50
50
|
# :__end__ — workflow completed (or not yet started)
|
|
51
51
|
# :awaiting_<name> — halted at a wait_state(:awaiting_<name>) declaration
|
|
52
|
-
# :<
|
|
52
|
+
# :<state> — resuming at <state> (workflow paused before its execution)
|
|
53
53
|
# @return [Symbol]
|
|
54
54
|
def phase
|
|
55
55
|
@phase || :__end__
|