Class: ActionAgent::ToolDiscovery
- Inherits:
-
Object
- Object
- ActionAgent::ToolDiscovery
- Defined in:
- app/services/action_agent/tool_discovery.rb
Overview
Builds the dashboard's tool and MCP-server inventory by reading what agents actually called, rather than what someone remembered to register.
Four record sources are merged, because each one sees a different slice of the same traffic:
- The offered tool roster — the tools array in each generation
request, recorded on the prompt span as
prompt.input.toolsand on each solid_agent generation asprovenance["tools"]. The only source that sees a tool the model was given and never called, and the only one carrying descriptions and parameter names. - Telemetry tool spans — the only source for agents running in a host app and reporting in over the wire, and where timing and error rates come from.
- solid_agent generations (+agent_generations.tool_calls+) — every tool call the model requested, including ones that never produced a span because the run died first.
- solid_agent messages (+agent_messages+ with
role: "tool") — the results that came back, which is where a tool's arguments survive even when telemetry is disabled.
Counting a tool once per source would multiply-count a dashboard-executed
run, which writes all four. So each source feeds a distinct counter
(+calls+, requested, results) and calls falls back to the largest
observed count when telemetry is absent — a self-hosted install with
telemetry off still gets real numbers instead of zeros.
MCP attribution comes from ActiveAgent::Telemetry::ToolOrigin (the
mcp__server__tool convention, tagged onto spans at instrumentation
time), then McpCatalog's hints for bare tool names, then the tool is
treated as a method the agent class defines.
Scopes are passed in rather than derived, so the caller's ownership rules (single-user, per-user, or multi-tenant) decide what is visible.
Constant Summary collapse
- DEFAULT_WINDOW_HOURS =
24 * 7
- MAX_WINDOW_HOURS =
24 * 90
- MAX_SCAN_ROWS =
The inventory is derived by reading records rather than by maintaining a summary table, and the window alone does not bound how many rows a busy account has in it — a 90-day window over a high-traffic workspace is millions. Scans stop here and say so rather than pinning a worker for the length of a request. A precomputed summary is the real answer; this keeps the page responsive until there is one.
20_000- ORIGIN_MCP =
Origin values, matching the framework's ToolOrigin plus the dashboard-only "builtin" bucket for AgentToolbox's executable tools.
"mcp"- ORIGIN_BUILTIN =
"builtin"- ORIGIN_AGENT =
"agent"
Instance Attribute Summary collapse
-
#agents ⇒ Object
readonly
Returns the value of attribute agents.
-
#since ⇒ Object
readonly
Returns the value of attribute since.
-
#traces ⇒ Object
readonly
Returns the value of attribute traces.
-
#window_hours ⇒ Object
readonly
Returns the value of attribute window_hours.
Class Method Summary collapse
-
.builtin_tools ⇒ Object
Tools AgentToolbox implements inside the dashboard.
Instance Method Summary collapse
-
#detected_tools ⇒ Array<Hash>
Detected tools, most-used first.
-
#initialize(traces:, agents:, hours: DEFAULT_WINDOW_HOURS) ⇒ ToolDiscovery
constructor
A new instance of ToolDiscovery.
-
#inventory ⇒ Hash
The full inventory: every tool seen in the window, plus the MCP servers they roll up into and the configured-but-unused surface.
-
#servers(tools = detected_tools) ⇒ Array<Hash>
MCP servers, detected traffic joined against the catalog so unused defaults are still listed (as
status: "available").
Constructor Details
#initialize(traces:, agents:, hours: DEFAULT_WINDOW_HOURS) ⇒ ToolDiscovery
Returns a new instance of ToolDiscovery.
61 62 63 64 65 66 |
# File 'app/services/action_agent/tool_discovery.rb', line 61 def initialize(traces:, agents:, hours: DEFAULT_WINDOW_HOURS) @traces = traces @agents = agents @window_hours = hours.to_i.clamp(1, MAX_WINDOW_HOURS) @since = @window_hours.hours.ago end |
Instance Attribute Details
#agents ⇒ Object (readonly)
Returns the value of attribute agents.
56 57 58 |
# File 'app/services/action_agent/tool_discovery.rb', line 56 def agents @agents end |
#since ⇒ Object (readonly)
Returns the value of attribute since.
56 57 58 |
# File 'app/services/action_agent/tool_discovery.rb', line 56 def since @since end |
#traces ⇒ Object (readonly)
Returns the value of attribute traces.
56 57 58 |
# File 'app/services/action_agent/tool_discovery.rb', line 56 def traces @traces end |
#window_hours ⇒ Object (readonly)
Returns the value of attribute window_hours.
56 57 58 |
# File 'app/services/action_agent/tool_discovery.rb', line 56 def window_hours @window_hours end |
Class Method Details
.builtin_tools ⇒ Object
Tools AgentToolbox implements inside the dashboard. Neither MCP nor agent-defined — the engine runs them — so they get their own origin rather than being mislabeled as methods on the agent class. Resolved lazily: referencing an autoloaded constant while this class is being defined would bind whatever happened to be loaded first.
111 112 113 114 |
# File 'app/services/action_agent/tool_discovery.rb', line 111 def self.builtin_tools @builtin_tools ||= AgentToolbox::DEFINITIONS.values.flatten .map { |definition| definition[:name].to_s }.to_set end |
Instance Method Details
#detected_tools ⇒ Array<Hash>
Detected tools, most-used first.
86 87 88 89 90 91 92 93 94 95 96 |
# File 'app/services/action_agent/tool_discovery.rb', line 86 def detected_tools index = {} merge_declared_tools(index) merge_trace_tools(index) merge_generation_tools(index) (index) merge_configured_tools(index) index.values.map { |entry| finalize(entry) }.sort_by { |tool| [ -tool[:calls], tool[:name] ] } end |
#inventory ⇒ Hash
The full inventory: every tool seen in the window, plus the MCP servers they roll up into and the configured-but-unused surface.
72 73 74 75 76 77 78 79 80 81 |
# File 'app/services/action_agent/tool_discovery.rb', line 72 def inventory tools = detected_tools { tools: tools, servers: servers_for(tools), summary: summary_for(tools), window_hours: window_hours, sources: source_availability } end |
#servers(tools = detected_tools) ⇒ Array<Hash>
MCP servers, detected traffic joined against the catalog so unused
defaults are still listed (as status: "available").
102 103 104 |
# File 'app/services/action_agent/tool_discovery.rb', line 102 def servers(tools = detected_tools) servers_for(tools) end |