actionagent 1.6.0 → 1.6.3
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/app/assets/builds/action_agent.js +45 -45
- data/app/controllers/action_agent/api/agent_runs_controller.rb +2 -2
- data/app/controllers/action_agent/api/agents_controller.rb +8 -5
- data/app/controllers/action_agent/api/analytics_controller.rb +1 -1
- data/app/controllers/action_agent/api/base_controller.rb +20 -0
- data/app/controllers/action_agent/api/interactions_controller.rb +3 -3
- data/app/controllers/action_agent/api/sandboxes_controller.rb +6 -1
- data/app/controllers/action_agent/api/session_recordings_controller.rb +24 -7
- data/app/models/action_agent/agent.rb +60 -0
- data/app/models/action_agent/agent_run.rb +4 -0
- data/app/models/action_agent/agent_version.rb +10 -0
- data/app/models/action_agent/evaluation.rb +19 -2
- data/app/models/action_agent/evaluation_run.rb +4 -0
- data/app/models/action_agent/telemetry_trace.rb +28 -1
- data/app/services/action_agent/agent_execution_service.rb +63 -12
- data/app/services/action_agent/agent_registrar.rb +23 -1
- data/app/services/action_agent/agent_release.rb +61 -0
- data/app/services/action_agent/agent_tool_roster.rb +47 -21
- data/app/services/action_agent/evaluation_runner_service.rb +12 -12
- data/app/services/action_agent/scenario_evaluation_runner.rb +16 -1
- data/config/routes.rb +3 -0
- data/lib/action_agent/version.rb +1 -1
- data/lib/generators/action_agent/install_generator.rb +6 -0
- data/lib/generators/action_agent/templates/action_agent.rb.erb +8 -0
- data/lib/generators/action_agent/templates/add_agent_releases.rb.erb +50 -0
- data/lib/tasks/action_agent.rake +33 -0
- metadata +8 -3
|
@@ -8,11 +8,19 @@ module ActionAgent
|
|
|
8
8
|
# Three groups, one row vocabulary — the same one the Tools and MCP
|
|
9
9
|
# Services pages use, so a roster reads the way the observability views do:
|
|
10
10
|
#
|
|
11
|
-
# * **Agent-defined** —
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
11
|
+
# * **Agent-defined** — the tools this agent's own generations offered
|
|
12
|
+
# (ToolDiscovery's +agent+ origin), plus every schema tool the host
|
|
13
|
+
# declares, on or off. Two kinds share the group. A *schema tool* — one
|
|
14
|
+
# a host ActiveAgent::SchemaTools class generates — is offered only while
|
|
15
|
+
# +agent.tools+ names it: that is the reading AgentToolbox takes at
|
|
16
|
+
# generation time, and the evaluation runner, dashboard runs and the MCP
|
|
17
|
+
# facade all follow it. So it is selected here, and switchable, and every
|
|
18
|
+
# one has a row because any agent may enable any of them
|
|
19
|
+
# (Agent.available_tools) and a tool switched off has to keep its row to
|
|
20
|
+
# be switched back on. A tool the agent class declares *in code* is
|
|
21
|
+
# offered by the class itself; the dashboard reports it and cannot switch
|
|
22
|
+
# it, so its row is read-only — a checkbox that cannot add or remove the
|
|
23
|
+
# tool is a control that changes nothing.
|
|
16
24
|
# * **Dashboard** — Agent::AVAILABLE_TOOLS, the capabilities the builder
|
|
17
25
|
# offers every agent. These are the roster: +agent.tools+ is what
|
|
18
26
|
# AgentToolbox turns into function schemas at generation time.
|
|
@@ -20,8 +28,9 @@ module ActionAgent
|
|
|
20
28
|
# agent enables, which is where they are edited.
|
|
21
29
|
#
|
|
22
30
|
# Enablement reads the agent's own configuration: +tools+ for the dashboard
|
|
23
|
-
# capabilities, +mcp_servers+ for services and their
|
|
24
|
-
# (an entry with no +tools+ key offers everything
|
|
31
|
+
# capabilities and the schema tools, +mcp_servers+ for services and their
|
|
32
|
+
# per-server allow-lists (an entry with no +tools+ key offers everything
|
|
33
|
+
# the server serves).
|
|
25
34
|
class AgentToolRoster
|
|
26
35
|
AGENT_DEFINED = "agent_defined"
|
|
27
36
|
DASHBOARD = "dashboard"
|
|
@@ -158,23 +167,40 @@ module ActionAgent
|
|
|
158
167
|
agent_defined_rows + dashboard_rows
|
|
159
168
|
end
|
|
160
169
|
|
|
161
|
-
#
|
|
162
|
-
#
|
|
170
|
+
# Agent-defined tools: whatever this agent's generations offered that is
|
|
171
|
+
# neither an MCP tool nor one of the dashboard's own, plus every schema
|
|
172
|
+
# tool the host declares, whether or not the window saw it.
|
|
163
173
|
def agent_defined_rows
|
|
164
|
-
detected
|
|
174
|
+
observed = detected
|
|
165
175
|
.select { |tool| tool[:origin] == ToolDiscovery::ORIGIN_AGENT }
|
|
166
176
|
.reject { |tool| dashboard_function_names.include?(tool[:name]) }
|
|
167
|
-
.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
177
|
+
.index_by { |tool| tool[:name] }
|
|
178
|
+
|
|
179
|
+
(observed.keys + ActionAgent.schema_tool_names).uniq.map do |name|
|
|
180
|
+
tool = observed[name]
|
|
181
|
+
schema = schema_tool?(name)
|
|
182
|
+
|
|
183
|
+
usage_row(tool).merge(
|
|
184
|
+
key: name,
|
|
185
|
+
name: name,
|
|
186
|
+
source: AGENT_DEFINED,
|
|
187
|
+
description: tool&.dig(:description) || schema_tool_description(name),
|
|
188
|
+
# A schema tool is offered only while the roster names it — the
|
|
189
|
+
# window may still show it being called, but that is history. A
|
|
190
|
+
# tool the agent class declares in code is offered by the class.
|
|
191
|
+
enabled: schema ? saved_tools.include?(name) : true,
|
|
192
|
+
editable: schema
|
|
193
|
+
)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def schema_tool?(name)
|
|
198
|
+
@schema_tool ||= Hash.new { |cache, key| cache[key] = ActionAgent.schema_tool_class_for(key).present? }
|
|
199
|
+
@schema_tool[name]
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def schema_tool_description(name)
|
|
203
|
+
AgentToolbox.schema_tool_definitions(name).first&.dig(:description)
|
|
178
204
|
end
|
|
179
205
|
|
|
180
206
|
def dashboard_rows
|
|
@@ -149,9 +149,7 @@ module ActionAgent
|
|
|
149
149
|
end
|
|
150
150
|
|
|
151
151
|
def sample_generations(model: nil)
|
|
152
|
-
scope =
|
|
153
|
-
.joins(:agent_context)
|
|
154
|
-
.where(AgentContext.table_name => { contextable: @evaluation.agent })
|
|
152
|
+
scope = @evaluation.agent.generations
|
|
155
153
|
scope = scope.where(model: model) if model
|
|
156
154
|
scope.order(created_at: :desc).limit(@evaluation.sample_size).to_a
|
|
157
155
|
end
|
|
@@ -468,15 +466,17 @@ module ActionAgent
|
|
|
468
466
|
end
|
|
469
467
|
|
|
470
468
|
def judge_class
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
469
|
+
@judge_class ||= begin
|
|
470
|
+
provider = judge_provider
|
|
471
|
+
model = @evaluation.judge_model.presence
|
|
472
|
+
options = {}
|
|
473
|
+
options[:model] = model if model
|
|
474
|
+
options.merge!(owner_provider_options(provider))
|
|
475
|
+
|
|
476
|
+
Class.new(ActiveAgent::Base) do
|
|
477
|
+
define_singleton_method(:name) { "EvaluationJudgeAgent" }
|
|
478
|
+
generate_with provider, **options
|
|
479
|
+
end
|
|
480
480
|
end
|
|
481
481
|
end
|
|
482
482
|
|
|
@@ -64,7 +64,16 @@ module ActionAgent
|
|
|
64
64
|
report = if adapter
|
|
65
65
|
raise ArgumentError, "scenario evaluation adapter must be callable" unless adapter.respond_to?(:call)
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
# The default path meters each replay itself (see #replay). An adapter
|
|
68
|
+
# runs the agent in the host's own runtime, out of reach of that call,
|
|
69
|
+
# so the execution is counted here as each result lands — one per
|
|
70
|
+
# scenario x model, the same unit. Otherwise a host that adapts the
|
|
71
|
+
# replay is silently unmetered unless it remembers to meter itself.
|
|
72
|
+
metered = lambda do |result|
|
|
73
|
+
ActionAgent.record_usage(owner, :execution)
|
|
74
|
+
on_result.call(result)
|
|
75
|
+
end
|
|
76
|
+
adapter.call(evaluation: @evaluation, owner: owner, scenarios: tasks, models: specs, on_result: metered)
|
|
68
77
|
else
|
|
69
78
|
ensure_judge_defined_kpis! if @evaluation.judge_defined?
|
|
70
79
|
default_report(tasks, specs, on_result)
|
|
@@ -122,6 +131,12 @@ module ActionAgent
|
|
|
122
131
|
def model_specs
|
|
123
132
|
names = Array(@selection[:models]).presence || @evaluation.compare_models
|
|
124
133
|
specs = Evals::ModelSpec.parse_all(names, default_provider: @evaluation.agent.provider, providers: Agent::PROVIDERS + %w[mock])
|
|
134
|
+
# parse_all resolves a bare name against `providers:` but passes through a
|
|
135
|
+
# `provider/model` whose provider is not in that list, so the run would
|
|
136
|
+
# otherwise reach the replay with a provider nothing can serve.
|
|
137
|
+
unsupported = specs.map(&:provider).uniq - (Agent::PROVIDERS + %w[mock])
|
|
138
|
+
raise ArgumentError, "unsupported model provider: #{unsupported.to_sentence}" if unsupported.any?
|
|
139
|
+
|
|
125
140
|
return specs if specs.any?
|
|
126
141
|
|
|
127
142
|
[ Evals::ModelSpec.new(label: @evaluation.agent.model, provider: @evaluation.agent.provider, model: @evaluation.agent.model) ]
|
data/config/routes.rb
CHANGED
|
@@ -161,6 +161,9 @@ ActionAgent::Engine.routes.draw do
|
|
|
161
161
|
# dashboard API key rather than a session, so it sits outside the api
|
|
162
162
|
# namespace's session-authenticated controllers.
|
|
163
163
|
post "mcp", to: "api/mcp#create"
|
|
164
|
+
match "mcp", to: ->(_env) { [ 405, { "Allow" => "POST" }, [] ] },
|
|
165
|
+
via: [ :get, :delete ],
|
|
166
|
+
constraints: ->(request) { request.delete? || !request.format.html? }
|
|
164
167
|
|
|
165
168
|
# MCP Streamable HTTP (2025-03-26): a client MAY open the server-to-client
|
|
166
169
|
# SSE stream with GET, and ends a session with DELETE. This facade offers
|
data/lib/action_agent/version.rb
CHANGED
|
@@ -57,6 +57,12 @@ module ActionAgent
|
|
|
57
57
|
)
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
+
# Agent releases arrived after both tables shipped; the migration guards
|
|
61
|
+
# every column, so it is emitted for any install that lacks it.
|
|
62
|
+
unless existing_migration?("add_agent_releases")
|
|
63
|
+
migration_template("add_agent_releases.rb.erb", "db/migrate/add_agent_releases.rb")
|
|
64
|
+
end
|
|
65
|
+
|
|
60
66
|
# The rest of the dashboard — agents, runs, versions, conversations,
|
|
61
67
|
# evaluations, sandboxes, recordings, keys. Skippable for an app that
|
|
62
68
|
# only wants to be a trace sink.
|
|
@@ -31,6 +31,14 @@ ActionAgent.configure do |config|
|
|
|
31
31
|
# config.authentication_method = nil
|
|
32
32
|
#
|
|
33
33
|
config.authentication_method = nil
|
|
34
|
+
#
|
|
35
|
+
# Where a browser is sent when it asks for a dashboard page without a valid
|
|
36
|
+
# session; unset, it gets a minimal session-expired page. API clients get a
|
|
37
|
+
# bare 401 either way. The sign-out path is what the header's "Sign out"
|
|
38
|
+
# item POSTs to (with _method=delete); unset, the item is not shown.
|
|
39
|
+
#
|
|
40
|
+
# config.sign_in_path = "/sign_in"
|
|
41
|
+
# config.sign_out_path = "/sign_out"
|
|
34
42
|
|
|
35
43
|
# ==========================================================================
|
|
36
44
|
# Ingest API authentication
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Agent releases: a version cut from the agent's code carries the digest of
|
|
4
|
+
# what the model was given and the deploy it shipped in, and every trace,
|
|
5
|
+
# run and evaluation run records the version it ran under. Emitted for an
|
|
6
|
+
# install whose dashboard tables predate releases; the create-table
|
|
7
|
+
# migration carries the same columns for a fresh install.
|
|
8
|
+
#
|
|
9
|
+
# Each step is guarded so the migration is safe to re-run against a database
|
|
10
|
+
# that already has some of these columns.
|
|
11
|
+
class AddAgentReleases < ActiveRecord::Migration<%= migration_version %>
|
|
12
|
+
def up
|
|
13
|
+
add_column_unless_exists :active_agent_agents, :release_digest, :string
|
|
14
|
+
|
|
15
|
+
add_column_unless_exists :active_agent_agent_versions, :release_digest, :string
|
|
16
|
+
add_column_unless_exists :active_agent_agent_versions, :revision, :string
|
|
17
|
+
add_index_unless_exists :active_agent_agent_versions, [ :agent_id, :release_digest ]
|
|
18
|
+
|
|
19
|
+
%i[active_agent_telemetry_traces active_agent_agent_runs active_agent_evaluation_runs].each do |table|
|
|
20
|
+
add_column_unless_exists table, :agent_version_id, :bigint
|
|
21
|
+
add_index_unless_exists table, :agent_version_id
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def down
|
|
26
|
+
remove_column :active_agent_agents, :release_digest if column_exists?(:active_agent_agents, :release_digest)
|
|
27
|
+
remove_column :active_agent_agent_versions, :release_digest if column_exists?(:active_agent_agent_versions, :release_digest)
|
|
28
|
+
remove_column :active_agent_agent_versions, :revision if column_exists?(:active_agent_agent_versions, :revision)
|
|
29
|
+
|
|
30
|
+
%i[active_agent_telemetry_traces active_agent_agent_runs active_agent_evaluation_runs].each do |table|
|
|
31
|
+
remove_column table, :agent_version_id if column_exists?(table, :agent_version_id)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def add_column_unless_exists(table, column, type)
|
|
38
|
+
return unless table_exists?(table)
|
|
39
|
+
return if column_exists?(table, column)
|
|
40
|
+
|
|
41
|
+
add_column table, column, type
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def add_index_unless_exists(table, columns)
|
|
45
|
+
return unless table_exists?(table)
|
|
46
|
+
return if index_exists?(table, columns)
|
|
47
|
+
|
|
48
|
+
add_index table, columns
|
|
49
|
+
end
|
|
50
|
+
end
|
data/lib/tasks/action_agent.rake
CHANGED
|
@@ -7,3 +7,36 @@ namespace :action_agent do
|
|
|
7
7
|
puts "Agent template library: #{ActionAgent::AgentTemplate.count} templates"
|
|
8
8
|
end
|
|
9
9
|
end
|
|
10
|
+
|
|
11
|
+
namespace :action_agent do
|
|
12
|
+
namespace :agents do
|
|
13
|
+
desc "Cut a dashboard version for every agent whose code changed (run on deploy; REVISION=<git sha> to label it)"
|
|
14
|
+
task :release, [ :revision ] => :environment do |_task, args|
|
|
15
|
+
Rails.application.eager_load!
|
|
16
|
+
result = ActionAgent::AgentRelease.call(revision: args[:revision] || ENV["REVISION"], released_by: ENV["RELEASED_BY"])
|
|
17
|
+
|
|
18
|
+
puts "Release #{result.revision.presence || '(no revision)'}"
|
|
19
|
+
result.rows.each do |row|
|
|
20
|
+
if row.skipped
|
|
21
|
+
puts format(" %-28s skipped — %s", row.agent.name, row.skipped)
|
|
22
|
+
elsif row.cut
|
|
23
|
+
puts format(" %-28s v%-3d cut %s", row.agent.name, row.version.version_number, row.version.change_summary)
|
|
24
|
+
else
|
|
25
|
+
puts format(" %-28s v%-3d unchanged (%s)", row.agent.name, row.version.version_number, row.version.release_digest)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
puts "#{result.cut.size} cut, #{result.rows.size - result.cut.size - result.skipped.size} unchanged, #{result.skipped.size} skipped"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
desc "List each agent's current version and release"
|
|
32
|
+
task versions: :environment do
|
|
33
|
+
ActionAgent::Agent.order(:name).find_each do |agent|
|
|
34
|
+
version = agent.latest_version
|
|
35
|
+
release = agent.latest_release
|
|
36
|
+
puts format("%-28s v%-3s %s%s", agent.name, version&.version_number || "-",
|
|
37
|
+
release ? "release #{release.release_digest}" : "no release",
|
|
38
|
+
release&.revision.present? ? " · #{release.revision}" : "")
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
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.6.
|
|
4
|
+
version: 1.6.3
|
|
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-18 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: activeagent
|
|
@@ -192,6 +193,7 @@ files:
|
|
|
192
193
|
- app/serializers/action_agent/trace_interaction_serializer.rb
|
|
193
194
|
- app/services/action_agent/agent_execution_service.rb
|
|
194
195
|
- app/services/action_agent/agent_registrar.rb
|
|
196
|
+
- app/services/action_agent/agent_release.rb
|
|
195
197
|
- app/services/action_agent/agent_scorecard.rb
|
|
196
198
|
- app/services/action_agent/agent_tool_roster.rb
|
|
197
199
|
- app/services/action_agent/agent_toolbox.rb
|
|
@@ -226,6 +228,7 @@ files:
|
|
|
226
228
|
- lib/generators/action_agent/install_generator.rb
|
|
227
229
|
- lib/generators/action_agent/templates/action_agent.rb.erb
|
|
228
230
|
- lib/generators/action_agent/templates/add_agent_id_to_active_agent_telemetry_traces.rb.erb
|
|
231
|
+
- lib/generators/action_agent/templates/add_agent_releases.rb.erb
|
|
229
232
|
- lib/generators/action_agent/templates/create_active_agent_dashboard_tables.rb.erb
|
|
230
233
|
- lib/generators/action_agent/templates/create_active_agent_evaluation_scenarios.rb.erb
|
|
231
234
|
- lib/generators/action_agent/templates/create_active_agent_telemetry_traces.rb.erb
|
|
@@ -238,6 +241,7 @@ metadata:
|
|
|
238
241
|
documentation_uri: https://docs.activeagents.ai/framework/self-hosted-observability
|
|
239
242
|
source_code_uri: https://github.com/activeagents/activeagent
|
|
240
243
|
rubygems_mfa_required: 'true'
|
|
244
|
+
post_install_message:
|
|
241
245
|
rdoc_options: []
|
|
242
246
|
require_paths:
|
|
243
247
|
- lib
|
|
@@ -252,7 +256,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
252
256
|
- !ruby/object:Gem::Version
|
|
253
257
|
version: '0'
|
|
254
258
|
requirements: []
|
|
255
|
-
rubygems_version: 3.
|
|
259
|
+
rubygems_version: 3.5.22
|
|
260
|
+
signing_key:
|
|
256
261
|
specification_version: 4
|
|
257
262
|
summary: The Active Agent dashboard, as a mountable Rails engine
|
|
258
263
|
test_files: []
|