Class: Phronomy::Agent::AgentInvocation Private

Inherits:
Object
  • Object
show all
Defined in:
lib/phronomy/agent/agent_invocation.rb

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

Mutable domain state for one Agent invocation.

AgentInvocation interprets Agent-internal events. FSMSession owns the transition mechanics, while Agent::Base projects the terminal outcome to both the Application listener and the returned Task.

Constant Summary collapse

TOOL_EVENT_TYPES =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

%i[
  tool_authorized
  tool_approval_required
  tool_completed
  tool_failed
  tool_rejected
  tool_cancelled
].freeze
LLM_EVENT_TYPES =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

%i[
  llm_completed
  llm_failed
].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(agent:, input:, messages:, config:, approval_policy: nil, approval_listener: nil, event_listener: nil, stream_listener: nil, mode: nil, id: nil) ⇒ AgentInvocation

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of AgentInvocation.



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/phronomy/agent/agent_invocation.rb', line 55

def initialize(
  agent:,
  input:,
  messages:,
  config:,
  approval_policy: nil,
  approval_listener: nil,
  event_listener: nil,
  stream_listener: nil,
  mode: nil,
  id: nil
)
  @agent = agent
  @input = input
  @messages = Array(messages)
  @config = config
  @thread_id = config[:thread_id]
  @id = (id || config[:agent_invocation_id] || SecureRandom.uuid).to_s
  invocation_context = config[:invocation_context]
  invocation_policy = if invocation_context&.respond_to?(:approval_policy)
    invocation_context.approval_policy
  end
  @approval_policy = invocation_policy || approval_policy
  @approval_listener = approval_listener
  @event_listener = event_listener || stream_listener
  @mode = (mode || (stream_listener ? :stream : :invoke)).to_sym

  @chat = nil
  @output = nil
  @usage = nil
  @input_blocked = false
  @output_blocked = false
  @block_error = nil
  @user_message_sent = false
  @pending_tool_calls = []
  @tool_invocations = []
  @approval_request = nil
  @rejected = false
  @human_rejection = false
  @approval_resume_in_progress = false
  @pending_approval_resolution_ids = []
  @error = nil
  @session_id = nil
  @phase = nil
end

Instance Attribute Details

#agentObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



43
44
45
# File 'lib/phronomy/agent/agent_invocation.rb', line 43

def agent
  @agent
end

#approval_listenerObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



43
44
45
# File 'lib/phronomy/agent/agent_invocation.rb', line 43

def approval_listener
  @approval_listener
end

#approval_policyObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



43
44
45
# File 'lib/phronomy/agent/agent_invocation.rb', line 43

def approval_policy
  @approval_policy
end

#approval_requestObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



29
30
31
# File 'lib/phronomy/agent/agent_invocation.rb', line 29

def approval_request
  @approval_request
end

#block_errorObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



29
30
31
# File 'lib/phronomy/agent/agent_invocation.rb', line 29

def block_error
  @block_error
end

#chatObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



29
30
31
# File 'lib/phronomy/agent/agent_invocation.rb', line 29

def chat
  @chat
end

#configObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



43
44
45
# File 'lib/phronomy/agent/agent_invocation.rb', line 43

def config
  @config
end

#errorObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



29
30
31
# File 'lib/phronomy/agent/agent_invocation.rb', line 29

def error
  @error
end

#event_listenerObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



29
30
31
# File 'lib/phronomy/agent/agent_invocation.rb', line 29

def event_listener
  @event_listener
end

#idObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



43
44
45
# File 'lib/phronomy/agent/agent_invocation.rb', line 43

def id
  @id
end

#inputObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



29
30
31
# File 'lib/phronomy/agent/agent_invocation.rb', line 29

def input
  @input
end

#input_blockedObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



29
30
31
# File 'lib/phronomy/agent/agent_invocation.rb', line 29

def input_blocked
  @input_blocked
end

#messagesObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



29
30
31
# File 'lib/phronomy/agent/agent_invocation.rb', line 29

def messages
  @messages
end

#modeObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



43
44
45
# File 'lib/phronomy/agent/agent_invocation.rb', line 43

def mode
  @mode
end

#outputObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



29
30
31
# File 'lib/phronomy/agent/agent_invocation.rb', line 29

def output
  @output
end

#output_blockedObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



29
30
31
# File 'lib/phronomy/agent/agent_invocation.rb', line 29

def output_blocked
  @output_blocked
end

#pending_tool_callsObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



43
44
45
# File 'lib/phronomy/agent/agent_invocation.rb', line 43

def pending_tool_calls
  @pending_tool_calls
end

#phaseObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



43
44
45
# File 'lib/phronomy/agent/agent_invocation.rb', line 43

def phase
  @phase
end

#rejectedObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



29
30
31
# File 'lib/phronomy/agent/agent_invocation.rb', line 29

def rejected
  @rejected
end

#session_idObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



43
44
45
# File 'lib/phronomy/agent/agent_invocation.rb', line 43

def session_id
  @session_id
end

#thread_idObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



43
44
45
# File 'lib/phronomy/agent/agent_invocation.rb', line 43

def thread_id
  @thread_id
end

#tool_invocationsObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



43
44
45
# File 'lib/phronomy/agent/agent_invocation.rb', line 43

def tool_invocations
  @tool_invocations
end

#usageObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



29
30
31
# File 'lib/phronomy/agent/agent_invocation.rb', line 29

def usage
  @usage
end

#user_message_sentObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



29
30
31
# File 'lib/phronomy/agent/agent_invocation.rb', line 29

def user_message_sent
  @user_message_sent
end

Instance Method Details

#accept_tool_calls!(tool_calls) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/phronomy/agent/agent_invocation.rb', line 188

def accept_tool_calls!(tool_calls)
  @user_message_sent = true
  @pending_tool_calls = Array(tool_calls)
  @messages = @chat.messages
  @pending_tool_calls.each do |tool_call|
    deliver_event(
      StreamEvent.new(
        type: :tool_call,
        payload: {tool_call: tool_call}
      )
    )
  end
  self
end

#apply_fsm_action_result(result) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Deprecated internal compatibility hook. FSMSession no longer calls this method; asynchronous results enter through explicit events.



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/phronomy/agent/agent_invocation.rb', line 169

def apply_fsm_action_result(result)
  event_type =
    if result.respond_to?(:error) &&
        result.error &&
        !result.error.is_a?(ToolCallIntercepted)
      :llm_failed
    else
      :llm_completed
    end
  handle_fsm_event(
    Phronomy::Event.new(
      type: event_type,
      target_id: @id,
      payload: result
    )
  )
  self
end

#apply_llm_response!(response) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/phronomy/agent/agent_invocation.rb', line 203

def apply_llm_response!(response)
  unless response
    raise Phronomy::Error, "LLM operation completed without a response"
  end

  @user_message_sent = true
  @output = response.content
  @usage = Phronomy::TokenUsage.from_tokens(response.tokens)
  @messages = @chat.messages
  @pending_tool_calls = []
  self
end

#approval_contextObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/phronomy/agent/agent_invocation.rb', line 225

def approval_context
  return @config[:approval_context] if @config[:approval_context]

  context = @config[:invocation_context]
  return {} unless context

  %i[
    thread_id session_id user_id token_budget
    task_id parent_task_id
  ].each_with_object({}) do |name, result|
    if context.respond_to?(name)
      result[name] = context.public_send(name)
    end
  end
end

#approval_required?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


304
305
306
307
308
309
# File 'lib/phronomy/agent/agent_invocation.rb', line 304

def approval_required?
  !@human_rejection &&
    !@approval_resume_in_progress &&
    preflight_complete? &&
    @tool_invocations.any?(&:awaiting_approval?)
end

#begin_approval_resume!(approved:) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



241
242
243
244
245
246
247
248
249
# File 'lib/phronomy/agent/agent_invocation.rb', line 241

def begin_approval_resume!(approved:)
  @human_rejection = !approved
  @pending_approval_resolution_ids = @tool_invocations
    .select(&:awaiting_approval?)
    .map(&:id)
  @approval_resume_in_progress =
    !@pending_approval_resolution_ids.empty?
  self
end

#clear_tool_batch!Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



127
128
129
130
131
# File 'lib/phronomy/agent/agent_invocation.rb', line 127

def clear_tool_batch!
  @pending_tool_calls = []
  @tool_invocations = []
  @approval_request = nil
end

#handle_fsm_event(event) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/phronomy/agent/agent_invocation.rb', line 133

def handle_fsm_event(event)
  if event.type == :llm_stream_chunk
    deliver_event(
      StreamEvent.new(
        type: :token,
        payload: {content: event.payload.fetch(:content)}
      )
    )
    return true
  end

  if LLM_EVENT_TYPES.include?(event.type)
    apply_llm_event(event)
    return true
  end

  return false unless TOOL_EVENT_TYPES.include?(event.type)

  invocation = tool_invocation(
    event.payload&.fetch(:tool_invocation_id, nil)
  )
  return true unless invocation

  @error ||= invocation.error if invocation.failed? || invocation.cancelled?
  @rejected = true if invocation.rejected?
  if @approval_resume_in_progress &&
      @pending_approval_resolution_ids.delete(invocation.id)
    if @pending_approval_resolution_ids.empty?
      @approval_resume_in_progress = false
    end
  end
  true
end

#input_blocked?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


283
284
285
# File 'lib/phronomy/agent/agent_invocation.rb', line 283

def input_blocked?
  @input_blocked
end

#input_passed?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


279
280
281
# File 'lib/phronomy/agent/agent_invocation.rb', line 279

def input_passed?
  !@input_blocked
end

#merge_config!(values) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



220
221
222
223
# File 'lib/phronomy/agent/agent_invocation.rb', line 220

def merge_config!(values)
  @config.merge!(values) unless values.empty?
  self
end

#output_blocked?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


291
292
293
# File 'lib/phronomy/agent/agent_invocation.rb', line 291

def output_blocked?
  @output_blocked
end

#output_passed?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


287
288
289
# File 'lib/phronomy/agent/agent_invocation.rb', line 287

def output_passed?
  !@output_blocked
end

#preflight_complete?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


299
300
301
302
# File 'lib/phronomy/agent/agent_invocation.rb', line 299

def preflight_complete?
  !@tool_invocations.empty? &&
    @tool_invocations.all?(&:preflight_settled?)
end

#prepare_approval_request!Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



251
252
253
254
# File 'lib/phronomy/agent/agent_invocation.rb', line 251

def prepare_approval_request!
  @approval_request = ToolApprovalRequest.build(self)
  self
end

#ready_to_dispatch?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


311
312
313
314
315
316
317
318
319
# File 'lib/phronomy/agent/agent_invocation.rb', line 311

def ready_to_dispatch?
  !@approval_resume_in_progress &&
    preflight_complete? &&
    @tool_invocations.none?(&:awaiting_approval?) &&
    @tool_invocations.none?(&:rejected?) &&
    @tool_invocations.none?(&:failed?) &&
    @tool_invocations.none?(&:cancelled?) &&
    @tool_invocations.any?(&:authorized?)
end

#record_tool_results!Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/phronomy/agent/agent_invocation.rb', line 256

def record_tool_results!
  @tool_invocations.each do |invocation|
    @chat.add_message(
      role: :tool,
      content: invocation.result.to_s,
      tool_call_id: invocation.tool_call_id
    )
    deliver_event(
      StreamEvent.new(
        type: :tool_result,
        payload: {
          tool_call_id: invocation.tool_call_id,
          tool_name: invocation.tool_name,
          tool_result: invocation.result
        }
      )
    )
  end
  @messages = @chat.messages
  clear_tool_batch!
  self
end

#set_graph_metadata(thread_id: nil, phase: nil) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



114
115
116
117
# File 'lib/phronomy/agent/agent_invocation.rb', line 114

def (thread_id: nil, phase: nil)
  @session_id = thread_id if thread_id
  @phase = phase
end

#stream_listenerObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Compatibility aliases for existing internal callers.



102
103
104
# File 'lib/phronomy/agent/agent_invocation.rb', line 102

def stream_listener
  @event_listener
end

#stream_listener=(listener) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



106
107
108
# File 'lib/phronomy/agent/agent_invocation.rb', line 106

def stream_listener=(listener)
  @event_listener = listener
end

#streaming?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


110
111
112
# File 'lib/phronomy/agent/agent_invocation.rb', line 110

def streaming?
  @mode == :stream
end

#tool_batch_completed?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


349
350
351
352
# File 'lib/phronomy/agent/agent_invocation.rb', line 349

def tool_batch_completed?
  tool_batch_terminal? &&
    @tool_invocations.all?(&:execution_completed?)
end

#tool_batch_failed?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# File 'lib/phronomy/agent/agent_invocation.rb', line 326

def tool_batch_failed?
  return false if @human_rejection || @approval_resume_in_progress

  no_rejection = @tool_invocations.none?(&:rejected?)
  preflight_failure =
    preflight_complete? &&
    @tool_invocations.any? do |invocation|
      invocation.failed? || invocation.cancelled?
    end
  terminal_failure =
    tool_batch_terminal? &&
    @tool_invocations.any? do |invocation|
      invocation.failed? || invocation.cancelled?
    end
  no_rejection && (preflight_failure || terminal_failure)
end

#tool_batch_rejected?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


343
344
345
346
347
# File 'lib/phronomy/agent/agent_invocation.rb', line 343

def tool_batch_rejected?
  return false unless @tool_invocations.any?(&:rejected?)

  @human_rejection ? tool_batch_terminal? : preflight_complete?
end

#tool_batch_terminal?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


321
322
323
324
# File 'lib/phronomy/agent/agent_invocation.rb', line 321

def tool_batch_terminal?
  !@tool_invocations.empty? &&
    @tool_invocations.all?(&:terminal?)
end

#tool_call_pending?Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns:

  • (Boolean)


295
296
297
# File 'lib/phronomy/agent/agent_invocation.rb', line 295

def tool_call_pending?
  !@pending_tool_calls.empty?
end

#tool_invocation(id) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



216
217
218
# File 'lib/phronomy/agent/agent_invocation.rb', line 216

def tool_invocation(id)
  @tool_invocations.find { |invocation| invocation.id == id.to_s }
end