Module: Phronomy::Agent::ToolExecutor Private

Defined in:
lib/phronomy/agent/tool_executor.rb

Overview

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

Centralises Tool execution routing based on execution_mode.

Tool-specific timeout and retry belong to the Tool implementation or its underlying client. This executor only chooses the Phronomy execution resource and propagates cooperative cancellation.

Class Method Summary collapse

Class Method Details

.call_async(tool:, args:, cancellation_token: nil, config: {}, runtime: Phronomy::Runtime.instance) ⇒ 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.

Low-level Tool API used by direct Tool#call_async callers. Agent execution must use .call_invocation_async so authorization cannot be bypassed.

config remains available for invocation metadata, but Phronomy does not interpret it as a Tool timeout or retry policy.



43
44
45
46
47
48
49
50
51
52
53
54
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
# File 'lib/phronomy/agent/tool_executor.rb', line 43

def self.call_async(
  tool:,
  args:,
  cancellation_token: nil,
  config: {},
  runtime: Phronomy::Runtime.instance
)
  ct = cancellation_token
  mode = tool.class.execution_mode

  if mode == :cpu_bound || mode == :external_process
    warn_key = [tool.class.name, mode]
    newly_warned = WARNED_MODES_MUTEX.synchronize { WARNED_MODES.add?(warn_key) }
    if newly_warned
      message = if mode == :cpu_bound
        "[Phronomy] Tool #{tool.class.name} declares execution_mode :cpu_bound, " \
          "which has no dedicated executor. Falling back to blocking_io " \
          "(BlockingAdapterPool). Use :blocking_io explicitly to suppress this warning."
      else
        "[Phronomy] Tool #{tool.class.name} declares execution_mode :external_process, " \
          "which has no dedicated process manager. Falling back to blocking_io " \
          "(BlockingAdapterPool)."
      end
      if Phronomy.configuration.logger
        Phronomy.configuration.logger.warn(message)
      else
        warn message
      end
    end
    mode = :blocking_io
  end

  pool = begin
    runtime&.blocking_io
  rescue
    nil
  end

  if mode == :cooperative || pool.nil?
    runtime.spawn(name: "tool-#{tool.class.name.to_s.split("::").last}") do
      tool.call(args, cancellation_token: ct)
    end
  else
    pool.submit(cancellation_token: ct) do
      tool.call(args, cancellation_token: ct)
    end
  end
end

.call_invocation_async(tool_invocation:, cancellation_token: nil, config: {}, runtime: Phronomy::Runtime.instance) ⇒ 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.

Agent-owned execution boundary. Only a ToolInvocation that has consumed authorization may enter this method.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/phronomy/agent/tool_executor.rb', line 18

def self.call_invocation_async(
  tool_invocation:,
  cancellation_token: nil,
  config: {},
  runtime: Phronomy::Runtime.instance
)
  unless tool_invocation.dispatchable?
    raise Phronomy::ToolError,
      "ToolInvocation #{tool_invocation.id} is not authorized for dispatch"
  end

  call_async(
    tool: tool_invocation.tool,
    args: tool_invocation.arguments,
    cancellation_token: cancellation_token,
    config: config,
    runtime: runtime
  )
end