actionagent 1.2.2 → 1.3.0
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/README.md +14 -3
- data/app/assets/builds/action_agent.css +1 -1
- data/app/assets/builds/action_agent.js +48 -44
- data/app/controllers/action_agent/api/agent_runs_controller.rb +25 -7
- data/app/controllers/action_agent/api/agents_controller.rb +76 -48
- data/app/controllers/action_agent/api/analytics_controller.rb +31 -9
- data/app/controllers/action_agent/api/base_controller.rb +16 -0
- data/app/controllers/action_agent/api/evaluations_controller.rb +237 -7
- data/app/controllers/action_agent/api/mcp_controller.rb +13 -3
- data/app/controllers/action_agent/api/mcp_servers_controller.rb +28 -8
- data/app/controllers/action_agent/api/metrics_controller.rb +44 -11
- data/app/controllers/action_agent/api/provider_models_controller.rb +1 -1
- data/app/controllers/action_agent/api/sandboxes_controller.rb +6 -0
- data/app/controllers/action_agent/api/session_recordings_controller.rb +34 -12
- data/app/controllers/action_agent/api/templates_controller.rb +25 -21
- data/app/controllers/action_agent/api/traces_controller.rb +25 -5
- data/app/controllers/action_agent/api/usage_controller.rb +20 -0
- data/app/controllers/action_agent/application_controller.rb +25 -2
- data/app/controllers/action_agent/dashboard_controller.rb +2 -1
- data/app/controllers/concerns/action_agent/api/agent_serialization.rb +53 -0
- data/app/jobs/action_agent/agent_execution_job.rb +40 -20
- data/app/jobs/action_agent/application_job.rb +7 -3
- data/app/jobs/action_agent/evaluation_run_job.rb +18 -0
- data/app/jobs/action_agent/sandbox_cleanup_job.rb +13 -10
- data/app/models/action_agent/agent.rb +14 -5
- data/app/models/action_agent/agent_template.rb +22 -7
- data/app/models/action_agent/evaluation.rb +64 -4
- data/app/models/action_agent/evaluation_run.rb +182 -2
- data/app/models/action_agent/evaluation_scenario.rb +59 -0
- data/app/models/action_agent/evaluation_scenario_result.rb +66 -0
- data/app/models/action_agent/recording_action.rb +11 -7
- data/app/models/action_agent/sandbox_session.rb +1 -1
- data/app/models/action_agent/session_recording.rb +31 -8
- data/app/models/action_agent/telemetry_trace.rb +110 -2
- data/app/models/concerns/action_agent/adapter_aware.rb +19 -0
- data/app/models/concerns/action_agent/ownable.rb +15 -2
- data/app/queries/action_agent/metrics_report.rb +498 -0
- data/app/services/action_agent/agent_toolbox.rb +4 -4
- data/app/services/action_agent/evaluation_tool_resolver.rb +154 -0
- data/app/services/action_agent/mcp_catalog.rb +46 -8
- data/app/services/action_agent/mcp_recording_middleware.rb +2 -2
- data/app/services/action_agent/playwright_mcp_client.rb +6 -6
- data/app/services/action_agent/sandbox_orchestrator.rb +12 -1
- data/app/services/action_agent/scenario_evaluation_runner.rb +226 -0
- data/app/services/action_agent/tool_discovery.rb +22 -8
- data/config/routes.rb +25 -2
- data/lib/action_agent/engine.rb +101 -19
- data/lib/action_agent/version.rb +1 -1
- data/lib/action_agent.rb +72 -6
- data/lib/generators/action_agent/install_generator.rb +20 -7
- data/lib/generators/action_agent/templates/create_active_agent_evaluation_scenarios.rb.erb +79 -0
- data/lib/tasks/action_agent.rake +9 -0
- metadata +19 -6
data/config/routes.rb
CHANGED
|
@@ -70,7 +70,7 @@ ActionAgent::Engine.routes.draw do
|
|
|
70
70
|
resources :tools, only: [ :index ]
|
|
71
71
|
|
|
72
72
|
# MCP services — detected servers unioned with the default catalog
|
|
73
|
-
# (
|
|
73
|
+
# (MCPCatalog), plus on-demand sandbox provisioning. Keys are catalog
|
|
74
74
|
# slugs like "sequential-thinking", so the id segment allows dashes.
|
|
75
75
|
resources :mcp_servers, only: [ :index, :show ], id: /[^\/]+/ do
|
|
76
76
|
member do
|
|
@@ -113,10 +113,17 @@ ActionAgent::Engine.routes.draw do
|
|
|
113
113
|
# Conversations (contexts, messages, generations) behind Interactions.
|
|
114
114
|
resources :interactions, only: [ :index, :show ]
|
|
115
115
|
|
|
116
|
-
# Agent output evaluations.
|
|
116
|
+
# Agent output evaluations. A scenario suite also manages its scenarios
|
|
117
|
+
# here, and exposes each run's per-scenario, per-model results.
|
|
117
118
|
resources :evaluations, only: [ :index, :show, :create, :destroy ] do
|
|
118
119
|
member do
|
|
119
120
|
post :run
|
|
121
|
+
get "runs/:run_id", action: :show_run, as: :run_result
|
|
122
|
+
get "runs/:run_id/report", action: :run_report, as: :run_report
|
|
123
|
+
get :scenarios
|
|
124
|
+
put :scenarios, action: :replace_scenarios
|
|
125
|
+
patch "scenarios/:scenario_id", action: :update_scenario, as: :scenario
|
|
126
|
+
delete "scenarios/:scenario_id", action: :destroy_scenario
|
|
120
127
|
end
|
|
121
128
|
end
|
|
122
129
|
|
|
@@ -128,6 +135,12 @@ ActionAgent::Engine.routes.draw do
|
|
|
128
135
|
# Model catalogs for the agent builder (Ollama queried live from the
|
|
129
136
|
# configured host; hosted providers curated).
|
|
130
137
|
resources :provider_models, only: [ :index ]
|
|
138
|
+
|
|
139
|
+
# The plan meter the Organization view and the Run Agents quota banner
|
|
140
|
+
# read. The engine meters nothing itself: a host that tracks usage
|
|
141
|
+
# against a plan answers through ActionAgent.usage_resolver, and a bare
|
|
142
|
+
# mount reports unlimited rather than 404.
|
|
143
|
+
resource :usage, only: [ :show ], controller: "usage"
|
|
131
144
|
end
|
|
132
145
|
|
|
133
146
|
# The account's agents presented as an authenticated MCP server (tools +
|
|
@@ -136,6 +149,16 @@ ActionAgent::Engine.routes.draw do
|
|
|
136
149
|
# namespace's session-authenticated controllers.
|
|
137
150
|
post "mcp", to: "api/mcp#create"
|
|
138
151
|
|
|
152
|
+
# MCP Streamable HTTP (2025-03-26): a client MAY open the server-to-client
|
|
153
|
+
# SSE stream with GET, and ends a session with DELETE. This facade offers
|
|
154
|
+
# no stream and keeps no sessions, so both answer 405 with Allow: POST —
|
|
155
|
+
# the clean "not offered" signal SDK clients expect, instead of the
|
|
156
|
+
# dashboard's HTML page parsed as an event stream. A browser's GET (Accept
|
|
157
|
+
# prefers HTML) is the MCP Services view's deep link, and falls through to
|
|
158
|
+
# the catch-all below like any other client-side route.
|
|
159
|
+
match "mcp", to: "api/mcp#unsupported", via: [ :get, :delete ],
|
|
160
|
+
constraints: ->(request) { request.delete? || !ActionAgent::Engine.html_request?(request) }
|
|
161
|
+
|
|
139
162
|
# Everything else under the mount is a client-side route: render the
|
|
140
163
|
# dashboard and let the browser resolve it. Anchored last so it can only
|
|
141
164
|
# ever catch what the routes above did not, and refuses /api paths so a
|
data/lib/action_agent/engine.rb
CHANGED
|
@@ -14,14 +14,37 @@ module ActionAgent
|
|
|
14
14
|
engine_name "action_agent"
|
|
15
15
|
|
|
16
16
|
# Basenames this engine spells differently from Zeitwerk's default
|
|
17
|
-
# camelization, consulted by the inflections initializer below.
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
# camelization, consulted by the inflections initializer below. An engine
|
|
18
|
+
# file that wants a genuine acronym in its constant adds its basename here
|
|
19
|
+
# rather than relying on the host to register one.
|
|
20
|
+
INFLECTION_OVERRIDES = {
|
|
21
|
+
"mcp_catalog" => "MCPCatalog",
|
|
22
|
+
"mcp_controller" => "MCPController",
|
|
23
|
+
"mcp_recording_middleware" => "MCPRecordingMiddleware",
|
|
24
|
+
"mcp_servers_controller" => "MCPServersController",
|
|
25
|
+
"playwright_mcp_client" => "PlaywrightMCPClient"
|
|
26
|
+
}.freeze
|
|
27
|
+
|
|
28
|
+
# The spelling default camelization produces for each overridden file,
|
|
29
|
+
# mapped to the constant the file actually defines. The const_missing shim
|
|
30
|
+
# below uses it to answer lookups that camelized with default inflections
|
|
31
|
+
# (a plain host's router, or caller code written against the pre-acronym
|
|
32
|
+
# names) with the acronym constant.
|
|
33
|
+
DEFAULT_SPELLINGS = INFLECTION_OVERRIDES.to_h { |basename, constant|
|
|
34
|
+
[ Zeitwerk::Inflector.new.camelize(basename, nil), constant ]
|
|
35
|
+
}.freeze
|
|
22
36
|
|
|
23
37
|
config.action_agent = ActiveSupport::OrderedOptions.new
|
|
24
38
|
|
|
39
|
+
# Whether a request is a browser asking for a page, as opposed to an API
|
|
40
|
+
# or MCP client: the routes use it to tell the dashboard's client-side
|
|
41
|
+
# deep links apart from protocol traffic on the same path.
|
|
42
|
+
def self.html_request?(request)
|
|
43
|
+
Array(request.accepts).any? { |type| type.respond_to?(:html?) && type.html? }
|
|
44
|
+
rescue StandardError
|
|
45
|
+
false
|
|
46
|
+
end
|
|
47
|
+
|
|
25
48
|
# The dashboard's JS and CSS ship prebuilt in the gem. Adding the
|
|
26
49
|
# directory to the host app's asset paths is what lets a plain
|
|
27
50
|
# `mount ActionAgent::Engine` work without the host running a
|
|
@@ -34,14 +57,13 @@ module ActionAgent
|
|
|
34
57
|
end
|
|
35
58
|
|
|
36
59
|
# This engine's constants are spelled the way Zeitwerk's own inflector
|
|
37
|
-
# spells them — Api,
|
|
60
|
+
# spells them — Api, ApiKey — but an engine's files are
|
|
38
61
|
# autoloaded by the host's `rails.main` loader, under the *host's*
|
|
39
62
|
# inflections. A host that declares `inflect.acronym "API"` or "MCP" (both
|
|
40
63
|
# common, and documented by Rails) makes Zeitwerk expect
|
|
41
|
-
# ActionAgent::API::TracesController
|
|
42
|
-
# that
|
|
43
|
-
#
|
|
44
|
-
# raises Zeitwerk::NameError.
|
|
64
|
+
# ActionAgent::API::TracesController from a file
|
|
65
|
+
# that defines ActionAgent::Api::TracesController. The constant never
|
|
66
|
+
# resolves and the request raises Zeitwerk::NameError.
|
|
45
67
|
#
|
|
46
68
|
# Every path under this engine therefore camelizes with Zeitwerk's default
|
|
47
69
|
# rules, ignoring whatever acronyms the host has registered. Applied by
|
|
@@ -51,7 +73,8 @@ module ActionAgent
|
|
|
51
73
|
# engine's files from the host's.
|
|
52
74
|
#
|
|
53
75
|
# Basenames whose spelling this engine cannot express through default
|
|
54
|
-
# camelization (a genuine acronym it wants uppercased) go in
|
|
76
|
+
# camelization (a genuine acronym it wants uppercased) go in
|
|
77
|
+
# INFLECTION_OVERRIDES.
|
|
55
78
|
initializer "action_agent.inflections", before: :set_autoload_paths do
|
|
56
79
|
engine_root = File.join(root.to_s, "")
|
|
57
80
|
default = Zeitwerk::Inflector.new
|
|
@@ -74,15 +97,22 @@ module ActionAgent
|
|
|
74
97
|
# So the namespace answers to both. `const_missing` rather than an eager
|
|
75
98
|
# alias because the controllers are autoloaded on demand, and naming them at
|
|
76
99
|
# boot would load the whole dashboard.
|
|
77
|
-
# The router does not consult the autoloader's inflector, so
|
|
78
|
-
#
|
|
79
|
-
#
|
|
80
|
-
# all-caps run in a missing constant is retried
|
|
81
|
-
# camelization produces
|
|
82
|
-
#
|
|
83
|
-
#
|
|
100
|
+
# The router does not consult the autoloader's inflector, so its lookups
|
|
101
|
+
# miss in both directions. An acronym host asks for
|
|
102
|
+
# ActionAgent::API::TracesController while the constant is
|
|
103
|
+
# Api::TracesController: an all-caps run in a missing constant is retried
|
|
104
|
+
# in the spelling default camelization produces (API -> Api). A plain host
|
|
105
|
+
# asks for Api::McpServersController while the constant is
|
|
106
|
+
# MCPServersController (the file is in INFLECTION_OVERRIDES): a missing
|
|
107
|
+
# constant matching an override's default spelling is retried as the
|
|
108
|
+
# acronym constant, via DEFAULT_SPELLINGS — which also keeps caller code
|
|
109
|
+
# written against the pre-acronym names resolving. Only the engine's own
|
|
110
|
+
# namespaces are touched, and only for a constant that is already missing.
|
|
84
111
|
inflection_shim = Module.new do
|
|
85
112
|
def const_missing(name)
|
|
113
|
+
acronym = ActionAgent::Engine::DEFAULT_SPELLINGS[name.to_s]
|
|
114
|
+
return const_get(acronym, false) if acronym && const_defined?(acronym, false)
|
|
115
|
+
|
|
86
116
|
relaxed = name.to_s.gsub(/([A-Z])([A-Z]+)(?=[A-Z][a-z]|\d|\z)/) { "#{$1}#{$2.downcase}" }
|
|
87
117
|
|
|
88
118
|
return super if relaxed == name.to_s || !const_defined?(relaxed, false)
|
|
@@ -102,11 +132,63 @@ module ActionAgent
|
|
|
102
132
|
end
|
|
103
133
|
end
|
|
104
134
|
|
|
135
|
+
# API keys and provider credentials are encrypted at rest (`encrypts` on
|
|
136
|
+
# ApiKey and ProviderKey), which needs Active Record Encryption keys the
|
|
137
|
+
# host app may never have generated — `rails db:encryption:init` is a
|
|
138
|
+
# step most installs skip, and the engine's own reference host had
|
|
139
|
+
# skipped it too. Without keys, every credential write and every
|
|
140
|
+
# authenticated MCP request raised Errors::Configuration as an HTML 500.
|
|
141
|
+
#
|
|
142
|
+
# Rails seeds ActiveRecord::Encryption from credentials and
|
|
143
|
+
# config.active_record.encryption in its own
|
|
144
|
+
# "active_record_encryption.configuration" initializer. Keys derived
|
|
145
|
+
# after that one land in config but never reach the encryptor, so the
|
|
146
|
+
# config reads back correct while every credential write raises
|
|
147
|
+
# Errors::Configuration — which is what a host that skipped
|
|
148
|
+
# db:encryption:init actually hits. Naming it in `before:` is what puts
|
|
149
|
+
# the derivation ahead of it. When the host set no keys — neither here
|
|
150
|
+
# nor in credentials — derive stable ones from secret_key_base, overridable
|
|
151
|
+
# through the ACTIVE_RECORD_ENCRYPTION_* variables, exactly as the
|
|
152
|
+
# activeagents.ai platform does. Host-provided keys are never
|
|
153
|
+
# overridden, and the derivation is stable as long as secret_key_base is.
|
|
154
|
+
initializer "action_agent.active_record_encryption", before: "active_record_encryption.configuration" do |app|
|
|
155
|
+
next unless ActionAgent.encrypt_credentials
|
|
156
|
+
next unless app.config.respond_to?(:active_record)
|
|
157
|
+
|
|
158
|
+
encryption = app.config.active_record.encryption
|
|
159
|
+
configured = %i[primary_key deterministic_key key_derivation_salt].any? { |key| encryption[key].present? }
|
|
160
|
+
configured ||= begin
|
|
161
|
+
app.credentials.dig(:active_record_encryption, :primary_key).present?
|
|
162
|
+
rescue StandardError
|
|
163
|
+
false
|
|
164
|
+
end
|
|
165
|
+
next if configured
|
|
166
|
+
|
|
167
|
+
derive = lambda do |purpose|
|
|
168
|
+
app.key_generator.generate_key("active_record_encryption.#{purpose}", 32).unpack1("H*")
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
encryption.primary_key = ENV["ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY"].presence || derive.call("primary_key")
|
|
172
|
+
encryption.deterministic_key = ENV["ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY"].presence || derive.call("deterministic_key")
|
|
173
|
+
encryption.key_derivation_salt = ENV["ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT"].presence || derive.call("key_derivation_salt")
|
|
174
|
+
end
|
|
175
|
+
|
|
105
176
|
initializer "action_agent.assets", before: :append_assets_path do |app|
|
|
106
177
|
builds = root.join("app", "assets", "builds").to_s
|
|
107
178
|
next unless File.directory?(builds)
|
|
108
179
|
|
|
109
|
-
|
|
180
|
+
# The prebuilt bundles are served through the host's asset pipeline —
|
|
181
|
+
# propshaft (the Rails default) or sprockets-rails. A host without one
|
|
182
|
+
# (a `rails --api` app, or one that removed propshaft) used to get a
|
|
183
|
+
# silently blank dashboard with two 404s; say why instead.
|
|
184
|
+
unless app.config.respond_to?(:assets)
|
|
185
|
+
Rails.logger&.warn(
|
|
186
|
+
"[ActionAgent] the dashboard's assets cannot be served: this app has no asset pipeline. " \
|
|
187
|
+
"Add propshaft (or sprockets-rails) to the Gemfile so action_agent.js and action_agent.css " \
|
|
188
|
+
"are served from the engine's app/assets/builds."
|
|
189
|
+
)
|
|
190
|
+
next
|
|
191
|
+
end
|
|
110
192
|
|
|
111
193
|
if app.config.assets.respond_to?(:paths)
|
|
112
194
|
app.config.assets.paths << builds unless app.config.assets.paths.include?(builds)
|
data/lib/action_agent/version.rb
CHANGED
data/lib/action_agent.rb
CHANGED
|
@@ -83,7 +83,10 @@ require "action_agent/compatibility"
|
|
|
83
83
|
#
|
|
84
84
|
# ActionAgent.configure do |config|
|
|
85
85
|
# config.authentication_method = ->(controller) { controller.authenticate_admin! }
|
|
86
|
-
#
|
|
86
|
+
# # Sandboxes run in the in-memory mock unless the app registers a real
|
|
87
|
+
# # backend (see sandbox_backends) and names it here:
|
|
88
|
+
# config.sandbox_backends = { "incus" => "IncusSandboxService" }
|
|
89
|
+
# config.sandbox_service = :incus
|
|
87
90
|
# end
|
|
88
91
|
#
|
|
89
92
|
# == Multi-tenant Mode
|
|
@@ -171,7 +174,10 @@ module ActionAgent
|
|
|
171
174
|
# @return [String, nil]
|
|
172
175
|
attr_accessor :layout
|
|
173
176
|
|
|
174
|
-
#
|
|
177
|
+
# Which sandbox backend to provision with: :mock (the only one the
|
|
178
|
+
# engine ships — an in-memory fake that runs nothing) or the name of a
|
|
179
|
+
# backend the host registered in sandbox_backends. An unregistered name
|
|
180
|
+
# falls back to :mock with a logged warning.
|
|
175
181
|
# @return [Symbol]
|
|
176
182
|
attr_accessor :sandbox_service
|
|
177
183
|
|
|
@@ -217,9 +223,11 @@ module ActionAgent
|
|
|
217
223
|
# @return [Proc, nil]
|
|
218
224
|
attr_accessor :provider_credentials_resolver
|
|
219
225
|
|
|
220
|
-
#
|
|
221
|
-
# { "cloud_run" => "CloudRunService" }. The engine ships :mock
|
|
222
|
-
#
|
|
226
|
+
# Sandbox backends contributed by the host app, as
|
|
227
|
+
# { "cloud_run" => "CloudRunService" }. The engine ships only :mock;
|
|
228
|
+
# every real backend (Docker/Incus, Cloud Run, Kubernetes) lives in the
|
|
229
|
+
# app that operates it, which registers it here and selects it with
|
|
230
|
+
# sandbox_service.
|
|
223
231
|
# @return [Hash{String => String}]
|
|
224
232
|
attr_accessor :sandbox_backends
|
|
225
233
|
|
|
@@ -234,6 +242,37 @@ module ActionAgent
|
|
|
234
242
|
# @return [String, nil]
|
|
235
243
|
attr_accessor :upgrade_url
|
|
236
244
|
|
|
245
|
+
# The host app's sign-out endpoint, which the header's "Sign out" item
|
|
246
|
+
# POSTs to (with _method=delete and the CSRF token). The engine has no
|
|
247
|
+
# session of its own; unset, the menu item is not shown.
|
|
248
|
+
# @return [String, nil]
|
|
249
|
+
attr_accessor :sign_out_path
|
|
250
|
+
|
|
251
|
+
# Where a browser is sent when it asks for a dashboard page without a
|
|
252
|
+
# valid session — the host app's sign-in page. Unset, an unauthenticated
|
|
253
|
+
# page request gets a minimal session-expired page instead of a bare
|
|
254
|
+
# 401; API clients always get the 401.
|
|
255
|
+
# @return [String, nil]
|
|
256
|
+
attr_accessor :sign_in_path
|
|
257
|
+
|
|
258
|
+
# Answers GET <mount>/api/usage — the plan meter the Organization view
|
|
259
|
+
# and the Run Agents quota banner read. Receives (owner) and returns a
|
|
260
|
+
# Hash in the platform's shape:
|
|
261
|
+
#
|
|
262
|
+
# { runs_used: 12, runs_limit: 100, runs_remaining: 88,
|
|
263
|
+
# can_run: true, plan: "pro" }
|
|
264
|
+
#
|
|
265
|
+
# Unset means unlimited: the engine reports UNLIMITED_USAGE and the
|
|
266
|
+
# views hide the meter.
|
|
267
|
+
# @return [Proc, nil]
|
|
268
|
+
attr_accessor :usage_resolver
|
|
269
|
+
|
|
270
|
+
# What a dashboard with no usage_resolver reports: no limit, nothing
|
|
271
|
+
# counted, always allowed.
|
|
272
|
+
UNLIMITED_USAGE = {
|
|
273
|
+
runs_used: 0, runs_limit: nil, runs_remaining: nil, can_run: true, plan: nil, unlimited: true
|
|
274
|
+
}.freeze
|
|
275
|
+
|
|
237
276
|
# Called after the dashboard performs a metered action, as
|
|
238
277
|
# (owner, kind) — the counterpart to quota_checker, for host apps that
|
|
239
278
|
# track usage against a plan. Unset means nothing is counted.
|
|
@@ -262,6 +301,16 @@ module ActionAgent
|
|
|
262
301
|
# @return [Boolean]
|
|
263
302
|
attr_accessor :encrypt_credentials
|
|
264
303
|
|
|
304
|
+
# MCP servers the host app itself serves or connects, appended to the
|
|
305
|
+
# built-in catalog (MCPCatalog) so the MCP Services view lists them and
|
|
306
|
+
# telemetry traffic attributes to them. Each entry is a hash shaped like
|
|
307
|
+
# a catalog entry: +key+ and +name+ at minimum, plus any of the optional
|
|
308
|
+
# fields (+description+, +transport+, +url+, +categories+, +docs_url+,
|
|
309
|
+
# +first_party+); +tool_hints+ names the bare tool names that belong to
|
|
310
|
+
# the server. A built-in entry keeps its key on collision.
|
|
311
|
+
# @return [Array<Hash>]
|
|
312
|
+
attr_accessor :mcp_catalog
|
|
313
|
+
|
|
265
314
|
# Value stored in polymorphic *_type columns for dashboard agents
|
|
266
315
|
# (agent_memories.memorable_type, agent_contexts.contextable_type).
|
|
267
316
|
# Unset means the class name. A host app whose existing rows were
|
|
@@ -292,6 +341,19 @@ module ActionAgent
|
|
|
292
341
|
nil
|
|
293
342
|
end
|
|
294
343
|
|
|
344
|
+
# The usage meter for +owner+. Never raises: a bookkeeping failure must
|
|
345
|
+
# not take the views that display it down with it.
|
|
346
|
+
#
|
|
347
|
+
# @return [Hash] the platform's usage shape, UNLIMITED_USAGE by default
|
|
348
|
+
def usage_for(owner)
|
|
349
|
+
return UNLIMITED_USAGE.dup if usage_resolver.nil?
|
|
350
|
+
|
|
351
|
+
usage_resolver.call(owner) || UNLIMITED_USAGE.dup
|
|
352
|
+
rescue StandardError => e
|
|
353
|
+
Rails.logger.warn("[ActionAgent] usage lookup failed: #{e.message}")
|
|
354
|
+
UNLIMITED_USAGE.dup
|
|
355
|
+
end
|
|
356
|
+
|
|
295
357
|
# Asks the host app whether +owner+ may perform +kind+.
|
|
296
358
|
#
|
|
297
359
|
# @return [String, Hash, nil] denial message or payload, nil when allowed
|
|
@@ -381,7 +443,7 @@ module ActionAgent
|
|
|
381
443
|
@trace_model_class = nil
|
|
382
444
|
@use_inertia = false
|
|
383
445
|
@layout = nil
|
|
384
|
-
@sandbox_service = :
|
|
446
|
+
@sandbox_service = :mock
|
|
385
447
|
@sandbox_limits = nil
|
|
386
448
|
@storage_service = nil
|
|
387
449
|
@ingest_api_key = nil
|
|
@@ -396,7 +458,11 @@ module ActionAgent
|
|
|
396
458
|
@trace_retention = nil
|
|
397
459
|
@trace_owner_resolver = nil
|
|
398
460
|
@usage_recorder = nil
|
|
461
|
+
@usage_resolver = nil
|
|
399
462
|
@upgrade_url = nil
|
|
463
|
+
@sign_out_path = nil
|
|
464
|
+
@sign_in_path = nil
|
|
465
|
+
@mcp_catalog = []
|
|
400
466
|
end
|
|
401
467
|
end
|
|
402
468
|
|
|
@@ -64,12 +64,20 @@ module ActionAgent
|
|
|
64
64
|
|
|
65
65
|
if existing_migration?("create_active_agent_dashboard_tables")
|
|
66
66
|
say_status :skip, "create_active_agent_dashboard_tables already exists", :yellow
|
|
67
|
-
|
|
67
|
+
else
|
|
68
|
+
migration_template(
|
|
69
|
+
"create_active_agent_dashboard_tables.rb.erb",
|
|
70
|
+
"db/migrate/create_active_agent_dashboard_tables.rb"
|
|
71
|
+
)
|
|
68
72
|
end
|
|
69
73
|
|
|
74
|
+
# Scenario suites arrived after the dashboard tables shipped, so an
|
|
75
|
+
# install that already has those still needs this one.
|
|
76
|
+
return if existing_migration?("create_active_agent_evaluation_scenarios")
|
|
77
|
+
|
|
70
78
|
migration_template(
|
|
71
|
-
"
|
|
72
|
-
"db/migrate/
|
|
79
|
+
"create_active_agent_evaluation_scenarios.rb.erb",
|
|
80
|
+
"db/migrate/create_active_agent_evaluation_scenarios.rb"
|
|
73
81
|
)
|
|
74
82
|
end
|
|
75
83
|
|
|
@@ -102,10 +110,15 @@ module ActionAgent
|
|
|
102
110
|
say "\n"
|
|
103
111
|
say "Next steps:"
|
|
104
112
|
say " 1. Run migrations: rails db:migrate"
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
113
|
+
# Nested under the environment key: ActiveAgent::Configuration.load
|
|
114
|
+
# reads only the current environment's subtree when the file has one,
|
|
115
|
+
# and the framework's own generator writes an env-keyed file — so a
|
|
116
|
+
# top-level telemetry: block is silently ignored.
|
|
117
|
+
say " 2. Configure telemetry in config/active_agent.yml, under each environment key:"
|
|
118
|
+
say " development:"
|
|
119
|
+
say " telemetry:"
|
|
120
|
+
say " enabled: true"
|
|
121
|
+
say " local_storage: true"
|
|
109
122
|
say " 3. Visit /activeagents to view the dashboard"
|
|
110
123
|
unless options[:traces_only]
|
|
111
124
|
say "\n"
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Adds scenario suites to evaluations: the user-authored prompts an
|
|
4
|
+
# evaluation replays through its agent (once per candidate model), and the
|
|
5
|
+
# per-scenario, per-model result each replay produced. Emitted alongside the
|
|
6
|
+
# dashboard tables on a fresh install, and on its own for an install that
|
|
7
|
+
# predates scenario evaluations.
|
|
8
|
+
#
|
|
9
|
+
# Table names follow ActionAgent.table_name_prefix, and JSON columns are
|
|
10
|
+
# jsonb on PostgreSQL and json elsewhere, the same way as
|
|
11
|
+
# create_active_agent_dashboard_tables.
|
|
12
|
+
class CreateActiveAgentEvaluationScenarios < ActiveRecord::Migration<%= migration_version %>
|
|
13
|
+
def change
|
|
14
|
+
prefix = ActionAgent.table_name_prefix
|
|
15
|
+
|
|
16
|
+
create_table "#{prefix}evaluation_scenarios" do |t|
|
|
17
|
+
t.bigint :evaluation_id, null: false
|
|
18
|
+
t.string :key, null: false
|
|
19
|
+
t.string :group
|
|
20
|
+
t.text :prompt, null: false
|
|
21
|
+
t.text :notes
|
|
22
|
+
t.column :expectations, json_type, **json_default({})
|
|
23
|
+
t.integer :position, default: 0, null: false
|
|
24
|
+
t.boolean :enabled, default: true, null: false
|
|
25
|
+
t.timestamps
|
|
26
|
+
t.index [ :evaluation_id, :key ], unique: true, name: "index_#{prefix}evaluation_scenarios_on_evaluation_and_key"
|
|
27
|
+
t.index [ :evaluation_id, :group ], name: "index_#{prefix}evaluation_scenarios_on_evaluation_and_group"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
create_table "#{prefix}evaluation_scenario_results" do |t|
|
|
31
|
+
t.bigint :evaluation_run_id, null: false
|
|
32
|
+
t.bigint :evaluation_scenario_id, null: false
|
|
33
|
+
t.bigint :agent_run_id
|
|
34
|
+
t.string :model, null: false
|
|
35
|
+
t.string :provider
|
|
36
|
+
t.integer :status, default: 0, null: false
|
|
37
|
+
t.float :score
|
|
38
|
+
t.column :scores, json_type, **json_default({})
|
|
39
|
+
t.text :output
|
|
40
|
+
t.column :tool_calls, json_type, **json_default([])
|
|
41
|
+
t.integer :duration_ms
|
|
42
|
+
t.integer :input_tokens
|
|
43
|
+
t.integer :output_tokens
|
|
44
|
+
t.decimal :cost, precision: 12, scale: 6
|
|
45
|
+
t.string :fault
|
|
46
|
+
t.text :recommendation
|
|
47
|
+
t.column :diagnosis, json_type, **json_default({})
|
|
48
|
+
t.text :error_message
|
|
49
|
+
t.timestamps
|
|
50
|
+
t.index :evaluation_run_id, name: "index_#{prefix}evaluation_scenario_results_on_run"
|
|
51
|
+
t.index :evaluation_scenario_id, name: "index_#{prefix}evaluation_scenario_results_on_scenario"
|
|
52
|
+
t.index [ :evaluation_run_id, :model ], name: "index_#{prefix}evaluation_scenario_results_on_run_and_model"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Which scenarios and models a run covered, so a partial run (one group,
|
|
56
|
+
# one question) reads as such rather than as the whole suite.
|
|
57
|
+
unless column_exists?("#{prefix}evaluation_runs", :selection)
|
|
58
|
+
add_column "#{prefix}evaluation_runs", :selection, json_type, **json_default({})
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def json_type
|
|
65
|
+
@json_type ||= postgres? ? :jsonb : :json
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# MySQL rejects a default on a JSON column outright, so the column is
|
|
69
|
+
# created without one there.
|
|
70
|
+
def json_default(value, null: nil)
|
|
71
|
+
return {} unless postgres?
|
|
72
|
+
|
|
73
|
+
null.nil? ? { default: value } : { default: value, null: null }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def postgres?
|
|
77
|
+
connection.adapter_name.to_s.downcase.include?("postgres")
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :action_agent do
|
|
4
|
+
desc "Seed the dashboard's default agent templates (idempotent — existing slugs are kept)"
|
|
5
|
+
task seed_templates: :environment do
|
|
6
|
+
ActionAgent::AgentTemplate.seed_defaults!
|
|
7
|
+
puts "Agent template library: #{ActionAgent::AgentTemplate.count} templates"
|
|
8
|
+
end
|
|
9
|
+
end
|
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: actionagent
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Justin Bowen
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-09-09 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: activeagent
|
|
@@ -15,7 +16,7 @@ dependencies:
|
|
|
15
16
|
requirements:
|
|
16
17
|
- - ">="
|
|
17
18
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '1.
|
|
19
|
+
version: '1.4'
|
|
19
20
|
- - "<"
|
|
20
21
|
- !ruby/object:Gem::Version
|
|
21
22
|
version: '2'
|
|
@@ -25,7 +26,7 @@ dependencies:
|
|
|
25
26
|
requirements:
|
|
26
27
|
- - ">="
|
|
27
28
|
- !ruby/object:Gem::Version
|
|
28
|
-
version: '1.
|
|
29
|
+
version: '1.4'
|
|
29
30
|
- - "<"
|
|
30
31
|
- !ruby/object:Gem::Version
|
|
31
32
|
version: '2'
|
|
@@ -142,11 +143,14 @@ files:
|
|
|
142
143
|
- app/controllers/action_agent/api/tools_controller.rb
|
|
143
144
|
- app/controllers/action_agent/api/trace_reports_controller.rb
|
|
144
145
|
- app/controllers/action_agent/api/traces_controller.rb
|
|
146
|
+
- app/controllers/action_agent/api/usage_controller.rb
|
|
145
147
|
- app/controllers/action_agent/application_controller.rb
|
|
146
148
|
- app/controllers/action_agent/dashboard_controller.rb
|
|
147
149
|
- app/controllers/action_agent/traces_controller.rb
|
|
150
|
+
- app/controllers/concerns/action_agent/api/agent_serialization.rb
|
|
148
151
|
- app/jobs/action_agent/agent_execution_job.rb
|
|
149
152
|
- app/jobs/action_agent/application_job.rb
|
|
153
|
+
- app/jobs/action_agent/evaluation_run_job.rb
|
|
150
154
|
- app/jobs/action_agent/process_telemetry_traces_job.rb
|
|
151
155
|
- app/jobs/action_agent/sandbox_cleanup_job.rb
|
|
152
156
|
- app/jobs/action_agent/sandbox_provision_job.rb
|
|
@@ -165,6 +169,8 @@ files:
|
|
|
165
169
|
- app/models/action_agent/application_record.rb
|
|
166
170
|
- app/models/action_agent/evaluation.rb
|
|
167
171
|
- app/models/action_agent/evaluation_run.rb
|
|
172
|
+
- app/models/action_agent/evaluation_scenario.rb
|
|
173
|
+
- app/models/action_agent/evaluation_scenario_result.rb
|
|
168
174
|
- app/models/action_agent/model_pricing.rb
|
|
169
175
|
- app/models/action_agent/provider_key.rb
|
|
170
176
|
- app/models/action_agent/recording_action.rb
|
|
@@ -178,6 +184,7 @@ files:
|
|
|
178
184
|
- app/models/concerns/action_agent/ownable.rb
|
|
179
185
|
- app/models/concerns/action_agent/session_recordable.rb
|
|
180
186
|
- app/queries/action_agent/agent_executions.rb
|
|
187
|
+
- app/queries/action_agent/metrics_report.rb
|
|
181
188
|
- app/serializers/action_agent/agent_message_serializer.rb
|
|
182
189
|
- app/serializers/action_agent/interaction_preview.rb
|
|
183
190
|
- app/serializers/action_agent/telemetry_trace_serializer.rb
|
|
@@ -187,11 +194,13 @@ files:
|
|
|
187
194
|
- app/services/action_agent/agent_scorecard.rb
|
|
188
195
|
- app/services/action_agent/agent_toolbox.rb
|
|
189
196
|
- app/services/action_agent/evaluation_runner_service.rb
|
|
197
|
+
- app/services/action_agent/evaluation_tool_resolver.rb
|
|
190
198
|
- app/services/action_agent/mcp_catalog.rb
|
|
191
199
|
- app/services/action_agent/mcp_recording_middleware.rb
|
|
192
200
|
- app/services/action_agent/mock_sandbox_backend.rb
|
|
193
201
|
- app/services/action_agent/playwright_mcp_client.rb
|
|
194
202
|
- app/services/action_agent/sandbox_orchestrator.rb
|
|
203
|
+
- app/services/action_agent/scenario_evaluation_runner.rb
|
|
195
204
|
- app/services/action_agent/session_recording_service.rb
|
|
196
205
|
- app/services/action_agent/tool_discovery.rb
|
|
197
206
|
- app/views/action_agent/dashboard/index.html.erb
|
|
@@ -211,7 +220,9 @@ files:
|
|
|
211
220
|
- lib/generators/action_agent/templates/action_agent.rb.erb
|
|
212
221
|
- lib/generators/action_agent/templates/add_agent_id_to_active_agent_telemetry_traces.rb.erb
|
|
213
222
|
- lib/generators/action_agent/templates/create_active_agent_dashboard_tables.rb.erb
|
|
223
|
+
- lib/generators/action_agent/templates/create_active_agent_evaluation_scenarios.rb.erb
|
|
214
224
|
- lib/generators/action_agent/templates/create_active_agent_telemetry_traces.rb.erb
|
|
225
|
+
- lib/tasks/action_agent.rake
|
|
215
226
|
homepage: https://activeagents.ai
|
|
216
227
|
licenses:
|
|
217
228
|
- MIT
|
|
@@ -220,6 +231,7 @@ metadata:
|
|
|
220
231
|
documentation_uri: https://docs.activeagents.ai/framework/self-hosted-observability
|
|
221
232
|
source_code_uri: https://github.com/activeagents/activeagent
|
|
222
233
|
rubygems_mfa_required: 'true'
|
|
234
|
+
post_install_message:
|
|
223
235
|
rdoc_options: []
|
|
224
236
|
require_paths:
|
|
225
237
|
- lib
|
|
@@ -227,14 +239,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
227
239
|
requirements:
|
|
228
240
|
- - ">="
|
|
229
241
|
- !ruby/object:Gem::Version
|
|
230
|
-
version: 3.
|
|
242
|
+
version: 3.2.0
|
|
231
243
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
232
244
|
requirements:
|
|
233
245
|
- - ">="
|
|
234
246
|
- !ruby/object:Gem::Version
|
|
235
247
|
version: '0'
|
|
236
248
|
requirements: []
|
|
237
|
-
rubygems_version:
|
|
249
|
+
rubygems_version: 3.5.22
|
|
250
|
+
signing_key:
|
|
238
251
|
specification_version: 4
|
|
239
252
|
summary: The Active Agent dashboard, as a mountable Rails engine
|
|
240
253
|
test_files: []
|