phronomy 0.10.0 → 0.11.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +41 -1
- data/README.md +35 -24
- data/benchmark/bench_agent_invoke.rb +1 -0
- data/lib/phronomy/agent/base.rb +223 -95
- data/lib/phronomy/agent/concerns/error_translation.rb +1 -1
- data/lib/phronomy/agent/concerns/filterable.rb +184 -0
- data/lib/phronomy/agent/concerns/retryable.rb +9 -5
- data/lib/phronomy/agent/context/capability/base.rb +2 -2
- data/lib/phronomy/agent/invocation_context.rb +171 -0
- data/lib/phronomy/agent/invocation_session.rb +337 -0
- data/lib/phronomy/agent/phase_machine_builder.rb +189 -0
- data/lib/phronomy/agent/suspended_session_registry.rb +54 -0
- data/lib/phronomy/agent/tool_call_intercepted.rb +26 -0
- data/lib/phronomy/configuration.rb +0 -6
- data/lib/phronomy/{concurrency → engine/concurrency}/async_queue.rb +68 -5
- data/lib/phronomy/{concurrency → engine/concurrency}/blocking_adapter_pool.rb +9 -3
- data/lib/phronomy/{event_loop.rb → engine/event_loop.rb} +71 -37
- data/lib/phronomy/engine/fsm_session.rb +248 -0
- data/lib/phronomy/{runtime → engine/runtime}/deterministic_scheduler.rb +28 -1
- data/lib/phronomy/{runtime → engine/runtime}/fake_scheduler.rb +1 -1
- data/lib/phronomy/{runtime.rb → engine/runtime.rb} +2 -4
- data/lib/phronomy/{task → engine/task}/backend.rb +2 -2
- data/lib/phronomy/engine/task/deferred_backend.rb +73 -0
- data/lib/phronomy/{task → engine/task}/fiber_backend.rb +1 -1
- data/lib/phronomy/{task → engine/task}/immediate_backend.rb +1 -1
- data/lib/phronomy/{task → engine/task}/mapped_backend.rb +15 -3
- data/lib/phronomy/{task → engine/task}/thread_backend.rb +1 -1
- data/lib/phronomy/{task.rb → engine/task.rb} +19 -7
- data/lib/phronomy/eval/scorer/llm_judge.rb +1 -1
- data/lib/phronomy/filter/base.rb +56 -0
- data/lib/phronomy/{guardrail/prompt_injection_guardrail.rb → filter/prompt_injection_filter.rb} +20 -13
- data/lib/phronomy/filter.rb +5 -0
- data/lib/phronomy/generator_verifier.rb +21 -13
- data/lib/phronomy/multi_agent/orchestrator.rb +4 -4
- data/lib/phronomy/multi_agent/parallel_tool_chat.rb +3 -3
- data/lib/phronomy/multi_agent/team_coordinator.rb +1 -2
- data/lib/phronomy/tools/mcp.rb +1 -1
- data/lib/phronomy/version.rb +1 -1
- data/lib/phronomy/workflow/phase_machine_builder.rb +24 -13
- data/lib/phronomy/workflow.rb +6 -3
- data/lib/phronomy/workflow_context.rb +7 -5
- data/lib/phronomy/workflow_runner.rb +77 -27
- data/lib/phronomy.rb +24 -13
- data/scripts/api_snapshot.rb +2 -2
- metadata +42 -44
- data/lib/phronomy/agent/checkpoint.rb +0 -208
- data/lib/phronomy/agent/checkpoint_store.rb +0 -97
- data/lib/phronomy/agent/concerns/guardrailable.rb +0 -45
- data/lib/phronomy/agent/concerns/suspendable.rb +0 -190
- data/lib/phronomy/agent/suspend_signal.rb +0 -37
- data/lib/phronomy/context.rb +0 -12
- data/lib/phronomy/guardrail/base.rb +0 -45
- data/lib/phronomy/guardrail/input_guardrail.rb +0 -19
- data/lib/phronomy/guardrail/output_guardrail.rb +0 -19
- data/lib/phronomy/guardrail.rb +0 -7
- data/lib/phronomy/tool.rb +0 -9
- data/lib/phronomy/workflow/fsm_session.rb +0 -249
- /data/lib/phronomy/{concurrency → engine/concurrency}/cancellation_scope.rb +0 -0
- /data/lib/phronomy/{concurrency → engine/concurrency}/cancellation_token.rb +0 -0
- /data/lib/phronomy/{concurrency → engine/concurrency}/concurrency_gate.rb +0 -0
- /data/lib/phronomy/{concurrency → engine/concurrency}/deadline.rb +0 -0
- /data/lib/phronomy/{concurrency → engine/concurrency}/gate_registry.rb +0 -0
- /data/lib/phronomy/{concurrency → engine/concurrency}/pool_registry.rb +0 -0
- /data/lib/phronomy/{runtime → engine/runtime}/runtime_metrics.rb +0 -0
- /data/lib/phronomy/{runtime → engine/runtime}/scheduler.rb +0 -0
- /data/lib/phronomy/{runtime → engine/runtime}/scheduler_timer_adapter.rb +0 -0
- /data/lib/phronomy/{runtime → engine/runtime}/task_registry.rb +0 -0
- /data/lib/phronomy/{runtime → engine/runtime}/thread_scheduler.rb +0 -0
- /data/lib/phronomy/{runtime → engine/runtime}/timer_queue.rb +0 -0
- /data/lib/phronomy/{runtime → engine/runtime}/timer_service.rb +0 -0
- /data/lib/phronomy/{task_group.rb → engine/task_group.rb} +0 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
module Agent
|
|
5
|
+
module Concerns
|
|
6
|
+
# Adds input, output, and tool-result filter support to an agent.
|
|
7
|
+
#
|
|
8
|
+
# Filters transform (or block) values at three call sites:
|
|
9
|
+
# - *input* — the raw user input string, before the LLM is called
|
|
10
|
+
# - *output* — the final LLM output string, before it is returned
|
|
11
|
+
# - *tool result* — the return value of each tool call
|
|
12
|
+
#
|
|
13
|
+
# Each filter in the chain receives the value returned by the previous one.
|
|
14
|
+
# A {Phronomy::FilterBlockError} raised inside any filter propagates to the
|
|
15
|
+
# caller.
|
|
16
|
+
#
|
|
17
|
+
# @api private
|
|
18
|
+
module Filterable
|
|
19
|
+
def self.included(base)
|
|
20
|
+
base.extend(ClassMethods)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Class-level DSL mixed into the including agent class.
|
|
24
|
+
module ClassMethods
|
|
25
|
+
# Registers a filter applied to every invocation's user input.
|
|
26
|
+
# Accepts either a {Phronomy::Filter::Base} instance or a subclass;
|
|
27
|
+
# when a class is given it is instantiated with +.new+.
|
|
28
|
+
# @param filter [Phronomy::Filter::Base, Class<Phronomy::Filter::Base>]
|
|
29
|
+
# @return [void]
|
|
30
|
+
# @api public
|
|
31
|
+
def input_filter(filter)
|
|
32
|
+
@_class_input_filters ||= []
|
|
33
|
+
@_class_input_filters << _resolve_filter(filter)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Registers a filter applied to every invocation's final LLM output.
|
|
37
|
+
# Accepts either a {Phronomy::Filter::Base} instance or a subclass;
|
|
38
|
+
# when a class is given it is instantiated with +.new+.
|
|
39
|
+
# @param filter [Phronomy::Filter::Base, Class<Phronomy::Filter::Base>]
|
|
40
|
+
# @return [void]
|
|
41
|
+
# @api public
|
|
42
|
+
def output_filter(filter)
|
|
43
|
+
@_class_output_filters ||= []
|
|
44
|
+
@_class_output_filters << _resolve_filter(filter)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Registers a filter applied to every tool result for all tools.
|
|
48
|
+
# Accepts either a {Phronomy::Filter::Base} instance or a subclass;
|
|
49
|
+
# when a class is given it is instantiated with +.new+.
|
|
50
|
+
# @param filter [Phronomy::Filter::Base, Class<Phronomy::Filter::Base>]
|
|
51
|
+
# @return [void]
|
|
52
|
+
# @api public
|
|
53
|
+
def tool_result_filter(filter)
|
|
54
|
+
@_class_tool_result_filters ||= []
|
|
55
|
+
@_class_tool_result_filters << _resolve_filter(filter)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @return [Array<Phronomy::Filter::Base>]
|
|
59
|
+
# @api private
|
|
60
|
+
def _class_input_filters
|
|
61
|
+
@_class_input_filters || []
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @return [Array<Phronomy::Filter::Base>]
|
|
65
|
+
# @api private
|
|
66
|
+
def _class_output_filters
|
|
67
|
+
@_class_output_filters || []
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @return [Array<Phronomy::Filter::Base>]
|
|
71
|
+
# @api private
|
|
72
|
+
def _class_tool_result_filters
|
|
73
|
+
@_class_tool_result_filters || []
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
# Coerce +filter+ to an instance: if a Class is passed, call +.new+;
|
|
79
|
+
# otherwise return the object as-is.
|
|
80
|
+
# @param filter [Phronomy::Filter::Base, Class<Phronomy::Filter::Base>]
|
|
81
|
+
# @return [Phronomy::Filter::Base]
|
|
82
|
+
# @api private
|
|
83
|
+
def _resolve_filter(filter)
|
|
84
|
+
filter.is_a?(Class) ? filter.new : filter
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Registers an input filter on this instance.
|
|
89
|
+
# Accepts either a {Phronomy::Filter::Base} instance or a subclass;
|
|
90
|
+
# when a class is given it is instantiated with +.new+.
|
|
91
|
+
# Runs in addition to any class-level input filters.
|
|
92
|
+
# @param filter [Phronomy::Filter::Base, Class<Phronomy::Filter::Base>]
|
|
93
|
+
# @return [self]
|
|
94
|
+
# @api public
|
|
95
|
+
def add_input_filter(filter)
|
|
96
|
+
@_instance_input_filters ||= []
|
|
97
|
+
@_instance_input_filters << _resolve_filter(filter)
|
|
98
|
+
self
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Registers an output filter on this instance.
|
|
102
|
+
# Accepts either a {Phronomy::Filter::Base} instance or a subclass;
|
|
103
|
+
# when a class is given it is instantiated with +.new+.
|
|
104
|
+
# Runs in addition to any class-level output filters.
|
|
105
|
+
# @param filter [Phronomy::Filter::Base, Class<Phronomy::Filter::Base>]
|
|
106
|
+
# @return [self]
|
|
107
|
+
# @api public
|
|
108
|
+
def add_output_filter(filter)
|
|
109
|
+
@_instance_output_filters ||= []
|
|
110
|
+
@_instance_output_filters << _resolve_filter(filter)
|
|
111
|
+
self
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Registers a tool result filter on this instance.
|
|
115
|
+
#
|
|
116
|
+
# When called with two arguments, the filter is scoped to the given tool
|
|
117
|
+
# class only. When called with one argument, it applies to all tools.
|
|
118
|
+
# Accepts either a {Phronomy::Filter::Base} instance or a subclass;
|
|
119
|
+
# when a class is given it is instantiated with +.new+.
|
|
120
|
+
#
|
|
121
|
+
# @overload add_tool_result_filter(filter)
|
|
122
|
+
# @param filter [Phronomy::Filter::Base, Class<Phronomy::Filter::Base>] applied to every tool
|
|
123
|
+
# @overload add_tool_result_filter(tool_class, filter)
|
|
124
|
+
# @param tool_class [Class] scope the filter to this tool
|
|
125
|
+
# @param filter [Phronomy::Filter::Base, Class<Phronomy::Filter::Base>]
|
|
126
|
+
# @return [self]
|
|
127
|
+
# @api public
|
|
128
|
+
def add_tool_result_filter(tool_class_or_filter, filter = nil)
|
|
129
|
+
if filter.nil?
|
|
130
|
+
# Single-argument form: apply to all tools.
|
|
131
|
+
@_instance_tool_result_filters ||= []
|
|
132
|
+
@_instance_tool_result_filters << _resolve_filter(tool_class_or_filter)
|
|
133
|
+
else
|
|
134
|
+
# Two-argument form: scoped to one tool class.
|
|
135
|
+
@_scoped_tool_result_filters ||= {}
|
|
136
|
+
(@_scoped_tool_result_filters[tool_class_or_filter] ||= []) << _resolve_filter(filter)
|
|
137
|
+
end
|
|
138
|
+
self
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
private
|
|
142
|
+
|
|
143
|
+
# Run input filters (class-level then instance-level).
|
|
144
|
+
# @param input [String, Hash] the raw user input
|
|
145
|
+
# @return [String, Hash] the (possibly transformed) input
|
|
146
|
+
# @api private
|
|
147
|
+
def run_input_filters!(input)
|
|
148
|
+
class_filters = self.class._class_input_filters
|
|
149
|
+
inst_filters = @_instance_input_filters || []
|
|
150
|
+
(class_filters + inst_filters).inject(input) { |val, f| f.call(val) }
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Run output filters (class-level then instance-level).
|
|
154
|
+
# @param output [String] the LLM output
|
|
155
|
+
# @return [String] the (possibly transformed) output
|
|
156
|
+
# @api private
|
|
157
|
+
def run_output_filters!(output)
|
|
158
|
+
class_filters = self.class._class_output_filters
|
|
159
|
+
inst_filters = @_instance_output_filters || []
|
|
160
|
+
(class_filters + inst_filters).inject(output) { |val, f| f.call(val) }
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Collect all tool-result filters (global + scoped) for a given tool class.
|
|
164
|
+
# @param tool_class [Class]
|
|
165
|
+
# @return [Array<Phronomy::Filter::Base>]
|
|
166
|
+
# @api private
|
|
167
|
+
def _tool_result_filters_for(tool_class)
|
|
168
|
+
global = self.class._class_tool_result_filters + (@_instance_tool_result_filters || [])
|
|
169
|
+
scoped = (@_scoped_tool_result_filters || {})[tool_class] || []
|
|
170
|
+
global + scoped
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Coerce +filter+ to an instance: if a Class is passed, call +.new+;
|
|
174
|
+
# otherwise return the object as-is.
|
|
175
|
+
# @param filter [Phronomy::Filter::Base, Class<Phronomy::Filter::Base>]
|
|
176
|
+
# @return [Phronomy::Filter::Base]
|
|
177
|
+
# @api private
|
|
178
|
+
def _resolve_filter(filter)
|
|
179
|
+
filter.is_a?(Class) ? filter.new : filter
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
@@ -6,7 +6,7 @@ module Phronomy
|
|
|
6
6
|
# Adds configurable retry behaviour to an agent.
|
|
7
7
|
#
|
|
8
8
|
# Included in {Phronomy::Agent::Base}. The retry loop wraps the full
|
|
9
|
-
# #invoke_once call; {Phronomy::
|
|
9
|
+
# #invoke_once call; {Phronomy::FilterBlockError} is never retried.
|
|
10
10
|
# @api private
|
|
11
11
|
module Retryable
|
|
12
12
|
def self.included(base)
|
|
@@ -16,7 +16,7 @@ module Phronomy
|
|
|
16
16
|
# Class-level DSL methods mixed into the including agent class.
|
|
17
17
|
module ClassMethods
|
|
18
18
|
# Configures a retry policy that wraps the full #invoke call.
|
|
19
|
-
#
|
|
19
|
+
# FilterBlockError is never retried regardless of this setting.
|
|
20
20
|
#
|
|
21
21
|
# @param times [Integer] maximum retry attempts (default: 0)
|
|
22
22
|
# @param wait [Symbol, Numeric] :exponential, :linear, or a fixed Float
|
|
@@ -35,7 +35,11 @@ module Phronomy
|
|
|
35
35
|
# @return [Hash, nil]
|
|
36
36
|
attr_reader :_retry_policy
|
|
37
37
|
|
|
38
|
-
# Injectable sleep callable for testing
|
|
38
|
+
# Injectable sleep callable for testing.
|
|
39
|
+
# In the EventLoop-driven invoke path, actual delay is handled by
|
|
40
|
+
# timer_queue (non-blocking); _sleep_proc is called only as an
|
|
41
|
+
# instrumentation hook. The default is a no-op; tests override it
|
|
42
|
+
# with a spy lambda to record sleep durations.
|
|
39
43
|
# @return [#call]
|
|
40
44
|
# @api private
|
|
41
45
|
def _sleep_proc
|
|
@@ -59,8 +63,8 @@ module Phronomy
|
|
|
59
63
|
policy = self.class._retry_policy
|
|
60
64
|
attempt = 0
|
|
61
65
|
begin
|
|
62
|
-
|
|
63
|
-
rescue Phronomy::
|
|
66
|
+
_invoke_via_fsm(input, messages: messages, thread_id: thread_id, config: config)
|
|
67
|
+
rescue Phronomy::FilterBlockError
|
|
64
68
|
raise
|
|
65
69
|
rescue Phronomy::CancellationError
|
|
66
70
|
raise # Never retry after cancellation.
|
|
@@ -92,7 +92,7 @@ module Phronomy
|
|
|
92
92
|
public
|
|
93
93
|
|
|
94
94
|
# Sets the access scope for this tool (metadata; enforcement is the responsibility of
|
|
95
|
-
# the Workflow/
|
|
95
|
+
# the Workflow/Filter layer).
|
|
96
96
|
# @param value [Symbol] e.g. :read_only, :write, :admin
|
|
97
97
|
# @api public
|
|
98
98
|
# mutant:disable - neutral failure: unparser round-trip produces different source
|
|
@@ -218,7 +218,7 @@ module Phronomy
|
|
|
218
218
|
# retried up to +times+ times with the specified wait strategy.
|
|
219
219
|
# Multiple policies can be registered and are evaluated in order.
|
|
220
220
|
#
|
|
221
|
-
#
|
|
221
|
+
# FilterBlockError is never retried regardless of this configuration.
|
|
222
222
|
#
|
|
223
223
|
# @param exception_classes [Array<Class>] exception classes to retry on
|
|
224
224
|
# @param times [Integer] maximum retry attempts (default: 1)
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phronomy
|
|
4
|
+
module Agent
|
|
5
|
+
# Holds the mutable state for a single Agent#invoke execution.
|
|
6
|
+
#
|
|
7
|
+
# An InvocationContext is created at the start of each invoke call and
|
|
8
|
+
# passed through every FSM state as the context object. It plays the same
|
|
9
|
+
# role as WorkflowContext does for Workflow executions.
|
|
10
|
+
#
|
|
11
|
+
# Fields:
|
|
12
|
+
# input — the (possibly filter-transformed) user input
|
|
13
|
+
# messages — conversation history (Array of RubyLLM::Message)
|
|
14
|
+
# chat — the RubyLLM::Chat object built during :building_context
|
|
15
|
+
# output — the final LLM text response
|
|
16
|
+
# usage — Phronomy::TokenUsage after completion
|
|
17
|
+
# tool_call_pending — whether the last LLM response contained a tool call
|
|
18
|
+
# approval_required — whether the pending tool requires human approval
|
|
19
|
+
# input_blocked — whether an input filter called block!
|
|
20
|
+
# output_blocked — whether an output filter called block!
|
|
21
|
+
# block_error — the FilterBlockError raised (for :blocked terminal)
|
|
22
|
+
# pending_tool_call — the intercepted tool_call object (name/arguments/id)
|
|
23
|
+
# user_message_sent — true after the first LLM call (continuation uses nil msg)
|
|
24
|
+
# sync_approval_handler — true when agent has an on_approval_required block
|
|
25
|
+
# session_id — FSM session id (set by set_graph_metadata)
|
|
26
|
+
# phase — terminal FSM phase (set by set_graph_metadata)
|
|
27
|
+
#
|
|
28
|
+
# @api private
|
|
29
|
+
class InvocationContext
|
|
30
|
+
# @return [String, Hash] user input (may be transformed by input filters)
|
|
31
|
+
attr_accessor :input
|
|
32
|
+
|
|
33
|
+
# @return [Array] conversation history
|
|
34
|
+
attr_accessor :messages
|
|
35
|
+
|
|
36
|
+
# @return [Object, nil] RubyLLM::Chat instance
|
|
37
|
+
attr_accessor :chat
|
|
38
|
+
|
|
39
|
+
# @return [String, nil] final LLM output text
|
|
40
|
+
attr_accessor :output
|
|
41
|
+
|
|
42
|
+
# @return [Phronomy::TokenUsage, nil]
|
|
43
|
+
attr_accessor :usage
|
|
44
|
+
|
|
45
|
+
# @return [Boolean] true when calling_llm returned a tool call
|
|
46
|
+
attr_accessor :tool_call_pending
|
|
47
|
+
|
|
48
|
+
# @return [Boolean] true when the pending tool requires human approval
|
|
49
|
+
attr_accessor :approval_required
|
|
50
|
+
|
|
51
|
+
# @return [Boolean] true when an input filter called block!
|
|
52
|
+
attr_accessor :input_blocked
|
|
53
|
+
|
|
54
|
+
# @return [Boolean] true when an output filter called block!
|
|
55
|
+
attr_accessor :output_blocked
|
|
56
|
+
|
|
57
|
+
# @return [Phronomy::FilterBlockError, nil]
|
|
58
|
+
attr_accessor :block_error
|
|
59
|
+
|
|
60
|
+
# @return [Object, nil] intercepted RubyLLM tool_call (name/arguments/id)
|
|
61
|
+
attr_accessor :pending_tool_call
|
|
62
|
+
|
|
63
|
+
# @return [Boolean] true after the first LLM call (continuation passes nil msg)
|
|
64
|
+
attr_accessor :user_message_sent
|
|
65
|
+
|
|
66
|
+
# @return [Boolean] true when Agent.approve was called (signals executing_tool
|
|
67
|
+
# to execute the pending tool rather than re-suspending)
|
|
68
|
+
attr_accessor :approved
|
|
69
|
+
|
|
70
|
+
# @return [Boolean] true when the pending tool call was rejected via
|
|
71
|
+
# Agent.approve(session_id, approved: false)
|
|
72
|
+
attr_accessor :rejected
|
|
73
|
+
|
|
74
|
+
# @return [String, nil] FSM session id (set by set_graph_metadata)
|
|
75
|
+
attr_reader :session_id
|
|
76
|
+
|
|
77
|
+
# @return [Symbol, nil] terminal FSM phase (set by set_graph_metadata)
|
|
78
|
+
attr_reader :phase
|
|
79
|
+
|
|
80
|
+
# @return [Boolean] true when agent has a synchronous on_approval_required handler
|
|
81
|
+
attr_reader :sync_approval_handler
|
|
82
|
+
|
|
83
|
+
# @return [Phronomy::Agent::Base] the agent instance driving this invocation
|
|
84
|
+
attr_reader :agent
|
|
85
|
+
|
|
86
|
+
# @return [Hash] the config hash passed to invoke
|
|
87
|
+
attr_reader :config
|
|
88
|
+
|
|
89
|
+
# @return [String, nil] thread_id from config
|
|
90
|
+
attr_reader :thread_id
|
|
91
|
+
|
|
92
|
+
# @param agent [Phronomy::Agent::Base]
|
|
93
|
+
# @param input [String, Hash]
|
|
94
|
+
# @param messages [Array]
|
|
95
|
+
# @param config [Hash]
|
|
96
|
+
# @api private
|
|
97
|
+
def initialize(agent:, input:, messages:, config:)
|
|
98
|
+
@agent = agent
|
|
99
|
+
@input = input
|
|
100
|
+
@messages = Array(messages)
|
|
101
|
+
@config = config
|
|
102
|
+
@thread_id = config[:thread_id]
|
|
103
|
+
@chat = nil
|
|
104
|
+
@output = nil
|
|
105
|
+
@usage = nil
|
|
106
|
+
@tool_call_pending = false
|
|
107
|
+
@approval_required = false
|
|
108
|
+
@input_blocked = false
|
|
109
|
+
@output_blocked = false
|
|
110
|
+
@block_error = nil
|
|
111
|
+
@pending_tool_call = nil
|
|
112
|
+
@user_message_sent = false
|
|
113
|
+
@approved = false
|
|
114
|
+
@rejected = false
|
|
115
|
+
@sync_approval_handler = !agent.instance_variable_get(:@approval_handler).nil?
|
|
116
|
+
@session_id = nil
|
|
117
|
+
@phase = nil
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Called by FSMSession#finish! and FSMSession#halt! to record the
|
|
121
|
+
# terminal/halted phase and the session id.
|
|
122
|
+
# Mirrors the WorkflowContext interface so FSMSession works with both.
|
|
123
|
+
# @param thread_id [String, nil]
|
|
124
|
+
# @param phase [Symbol, nil]
|
|
125
|
+
# @return [void]
|
|
126
|
+
# @api private
|
|
127
|
+
def set_graph_metadata(thread_id: nil, phase: nil)
|
|
128
|
+
@session_id = thread_id if thread_id
|
|
129
|
+
@phase = phase
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Guard helpers used by PhaseMachineBuilder transitions.
|
|
133
|
+
|
|
134
|
+
# @return [Boolean]
|
|
135
|
+
# @api private
|
|
136
|
+
def input_passed?
|
|
137
|
+
!@input_blocked
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# @return [Boolean]
|
|
141
|
+
# @api private
|
|
142
|
+
def input_blocked?
|
|
143
|
+
@input_blocked
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# @return [Boolean]
|
|
147
|
+
# @api private
|
|
148
|
+
def output_passed?
|
|
149
|
+
!@output_blocked
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# @return [Boolean]
|
|
153
|
+
# @api private
|
|
154
|
+
def output_blocked?
|
|
155
|
+
@output_blocked
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# @return [Boolean]
|
|
159
|
+
# @api private
|
|
160
|
+
def tool_call_pending?
|
|
161
|
+
@tool_call_pending
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# @return [Boolean]
|
|
165
|
+
# @api private
|
|
166
|
+
def approval_required?
|
|
167
|
+
@approval_required
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|