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
CALLBACK_FAILED_EVENTS =

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[application_callback_failed].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(agent:, input:, config:, approval_policy: nil, approval_listener: nil, event_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.



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
100
101
# File 'lib/phronomy/agent/agent_invocation.rb', line 58

def initialize(
  agent:,
  input:,
  config:,
  approval_policy: nil,
  approval_listener: nil,
  event_listener: nil,
  mode: nil,
  id: nil
)
  @agent = agent
  @input = input
  @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
  @mode = (mode || :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
  @current_llm_call_id = nil
  @tool_batch_llm_call_id = 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.



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

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.



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

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.



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

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.



31
32
33
# File 'lib/phronomy/agent/agent_invocation.rb', line 31

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.



31
32
33
# File 'lib/phronomy/agent/agent_invocation.rb', line 31

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.



31
32
33
# File 'lib/phronomy/agent/agent_invocation.rb', line 31

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.



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

def config
  @config
end

#current_llm_call_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.



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

def current_llm_call_id
  @current_llm_call_id
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.



31
32
33
# File 'lib/phronomy/agent/agent_invocation.rb', line 31

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.



31
32
33
# File 'lib/phronomy/agent/agent_invocation.rb', line 31

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.



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

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.



31
32
33
# File 'lib/phronomy/agent/agent_invocation.rb', line 31

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.



31
32
33
# File 'lib/phronomy/agent/agent_invocation.rb', line 31

def input_blocked
  @input_blocked
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.



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

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.



31
32
33
# File 'lib/phronomy/agent/agent_invocation.rb', line 31

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.



31
32
33
# File 'lib/phronomy/agent/agent_invocation.rb', line 31

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.



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

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.



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

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.



31
32
33
# File 'lib/phronomy/agent/agent_invocation.rb', line 31

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.



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

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.



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

def thread_id
  @thread_id
end

#tool_batch_llm_call_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.



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

def tool_batch_llm_call_id
  @tool_batch_llm_call_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.



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

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.



31
32
33
# File 'lib/phronomy/agent/agent_invocation.rb', line 31

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.



31
32
33
# File 'lib/phronomy/agent/agent_invocation.rb', line 31

def user_message_sent
  @user_message_sent
end

Instance Method Details

#accept_tool_calls!(tool_calls, llm_call_id: 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.



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

def accept_tool_calls!(tool_calls, llm_call_id: nil)
  @user_message_sent = true
  @pending_tool_calls = Array(tool_calls)
  @tool_batch_llm_call_id = (llm_call_id || @current_llm_call_id)&.to_s
  @current_llm_call_id = nil
  @pending_tool_calls.each do |tool_call|
    deliver_event(
      StreamEvent.new(
        type: :tool_call,
        payload: {
          tool_call: tool_call,
          llm_call_id: @tool_batch_llm_call_id
        }.compact
      )
    )
  end
  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.



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

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)
  @pending_tool_calls = []
  @current_llm_call_id = nil
  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.



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/phronomy/agent/agent_invocation.rb', line 212

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)


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

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.



228
229
230
231
232
233
234
235
236
# File 'lib/phronomy/agent/agent_invocation.rb', line 228

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

#begin_llm_call!(llm_call_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.



112
113
114
115
# File 'lib/phronomy/agent/agent_invocation.rb', line 112

def begin_llm_call!(llm_call_id)
  @current_llm_call_id = llm_call_id.to_s
  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.



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

def clear_tool_batch!
  @pending_tool_calls = []
  @tool_invocations = []
  @approval_request = nil
  @tool_batch_llm_call_id = 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.



132
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
166
167
168
169
# File 'lib/phronomy/agent/agent_invocation.rb', line 132

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

  if CALLBACK_FAILED_EVENTS.include?(event.type)
    @error ||= event.payload.fetch(:failure).to_stream_callback_error
    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)


276
277
278
# File 'lib/phronomy/agent/agent_invocation.rb', line 276

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)


272
273
274
# File 'lib/phronomy/agent/agent_invocation.rb', line 272

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.



207
208
209
210
# File 'lib/phronomy/agent/agent_invocation.rb', line 207

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)


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

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)


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

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)


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

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.



238
239
240
241
# File 'lib/phronomy/agent/agent_invocation.rb', line 238

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)


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

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.



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/phronomy/agent/agent_invocation.rb', line 243

def record_tool_results!
  @tool_invocations.each do |invocation|
    tool_content = invocation.result.to_s
    @chat.add_message(
      role: :tool,
      content: tool_content,
      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,
          tool_message: {
            "role" => "tool",
            "content" => tool_content,
            "tool_call_id" => invocation.tool_call_id.to_s
          },
          llm_call_id: @tool_batch_llm_call_id
        }.compact
      )
    )
  end
  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.



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

def (thread_id: nil, phase: nil)
  @session_id = thread_id if thread_id
  @phase = phase
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)


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

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)


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

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)


319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/phronomy/agent/agent_invocation.rb', line 319

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)


336
337
338
339
340
# File 'lib/phronomy/agent/agent_invocation.rb', line 336

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)


314
315
316
317
# File 'lib/phronomy/agent/agent_invocation.rb', line 314

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)


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

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.



203
204
205
# File 'lib/phronomy/agent/agent_invocation.rb', line 203

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