actionagent 1.5.2 → 1.6.2

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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/builds/action_agent.css +1 -1
  3. data/app/assets/builds/action_agent.js +54 -54
  4. data/app/controllers/action_agent/api/agent_runs_controller.rb +2 -2
  5. data/app/controllers/action_agent/api/agents_controller.rb +47 -10
  6. data/app/controllers/action_agent/api/analytics_controller.rb +1 -1
  7. data/app/controllers/action_agent/api/base_controller.rb +37 -0
  8. data/app/controllers/action_agent/api/interactions_controller.rb +3 -3
  9. data/app/controllers/action_agent/api/mcp_controller.rb +115 -4
  10. data/app/controllers/action_agent/api/sandboxes_controller.rb +6 -1
  11. data/app/controllers/action_agent/api/session_recordings_controller.rb +24 -7
  12. data/app/jobs/action_agent/agent_execution_job.rb +4 -1
  13. data/app/models/action_agent/agent.rb +88 -7
  14. data/app/models/action_agent/agent_run.rb +63 -0
  15. data/app/models/action_agent/agent_version.rb +10 -0
  16. data/app/models/action_agent/evaluation.rb +11 -0
  17. data/app/models/action_agent/evaluation_run.rb +4 -0
  18. data/app/models/action_agent/telemetry_trace.rb +28 -1
  19. data/app/services/action_agent/agent_execution_service.rb +28 -2
  20. data/app/services/action_agent/agent_registrar.rb +23 -1
  21. data/app/services/action_agent/agent_release.rb +61 -0
  22. data/app/services/action_agent/agent_tool_roster.rb +295 -0
  23. data/app/services/action_agent/agent_toolbox.rb +12 -3
  24. data/app/services/action_agent/evaluation_runner_service.rb +11 -9
  25. data/app/services/action_agent/scenario_evaluation_runner.rb +17 -1
  26. data/config/routes.rb +6 -0
  27. data/lib/action_agent/version.rb +1 -1
  28. data/lib/action_agent.rb +66 -4
  29. data/lib/generators/action_agent/install_generator.rb +6 -0
  30. data/lib/generators/action_agent/templates/action_agent.rb.erb +8 -0
  31. data/lib/generators/action_agent/templates/add_agent_releases.rb.erb +50 -0
  32. data/lib/tasks/action_agent.rake +33 -0
  33. metadata +5 -2
@@ -468,15 +468,17 @@ module ActionAgent
468
468
  end
469
469
 
470
470
  def judge_class
471
- provider = judge_provider
472
- model = @evaluation.judge_model.presence
473
- options = {}
474
- options[:model] = model if model
475
- options.merge!(owner_provider_options(provider))
476
-
477
- @judge_class ||= Class.new(ActiveAgent::Base) do
478
- define_singleton_method(:name) { "EvaluationJudgeAgent" }
479
- generate_with provider, **options
471
+ @judge_class ||= begin
472
+ provider = judge_provider
473
+ model = @evaluation.judge_model.presence
474
+ options = {}
475
+ options[:model] = model if model
476
+ options.merge!(owner_provider_options(provider))
477
+
478
+ Class.new(ActiveAgent::Base) do
479
+ define_singleton_method(:name) { "EvaluationJudgeAgent" }
480
+ generate_with provider, **options
481
+ end
480
482
  end
481
483
  end
482
484
 
@@ -147,7 +147,8 @@ module ActionAgent
147
147
  agent_run = @evaluation.agent.test_execute(
148
148
  scenario.prompt,
149
149
  model_override: spec.model,
150
- provider_override: spec.provider
150
+ provider_override: spec.provider,
151
+ actor: replay_actor
151
152
  )
152
153
 
153
154
  Evals::Replay.new(
@@ -162,6 +163,21 @@ module ActionAgent
162
163
  )
163
164
  end
164
165
 
166
+ # The caller a replay runs on behalf of: the evaluation's owner, when the
167
+ # install owns agents per user. A run with no caller reads, through any
168
+ # host scope, as "no access" — every tool answers empty and the suite
169
+ # grades an agent that never saw a row — so the person the evaluation
170
+ # belongs to is the right default, as the key's owner is over MCP. An
171
+ # account is who is billed, not who is allowed (see Api::BaseController
172
+ # #agent_actor), so a multi-tenant install replays unattributed unless a
173
+ # host adapter (ActionAgent.scenario_evaluation_adapter_resolver) runs
174
+ # the suite itself.
175
+ def replay_actor
176
+ return nil if ActionAgent.multi_tenant? || ActionAgent.user_class.blank?
177
+
178
+ owner
179
+ end
180
+
165
181
  # Each tool call the run made, rebuilt from the run's progress events
166
182
  # (AgentRun#append_event pairs a "started" event with its "done"/"error"
167
183
  # by eid). Falls back to the bare names in the run's metadata for a run
data/config/routes.rb CHANGED
@@ -38,6 +38,9 @@ ActionAgent::Engine.routes.draw do
38
38
  post :duplicate
39
39
  get :export
40
40
  get :analytics
41
+ # The Tools tab's roster: offerable tools and MCP services, each
42
+ # with what the window recorded for it.
43
+ get :tool_roster
41
44
  # The runner's conversation picker: this agent's persisted contexts,
42
45
  # and a fresh one to pin a first message to.
43
46
  get :conversations
@@ -158,6 +161,9 @@ ActionAgent::Engine.routes.draw do
158
161
  # dashboard API key rather than a session, so it sits outside the api
159
162
  # namespace's session-authenticated controllers.
160
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? }
161
167
 
162
168
  # MCP Streamable HTTP (2025-03-26): a client MAY open the server-to-client
163
169
  # SSE stream with GET, and ends a session with DELETE. This facade offers
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionAgent
4
- VERSION = "1.5.2"
4
+ VERSION = "1.6.2"
5
5
  end
data/lib/action_agent.rb CHANGED
@@ -145,6 +145,26 @@ module ActionAgent
145
145
  # @return [Proc, nil]
146
146
  attr_accessor :current_user_resolver
147
147
 
148
+ # Resolves the caller an agent run executes on behalf of — what reaches
149
+ # +ActiveAgent::Base#current_user+, a SchemaTools +scope+ block, and any
150
+ # authorization gem an agent calls from its callbacks.
151
+ #
152
+ # Called with the controller, so the same seam covers a browser session
153
+ # and an MCP request. Whatever it returns is passed through untouched:
154
+ # the engine never interprets an actor, and never widens one.
155
+ #
156
+ # config.agent_actor_resolver = ->(controller) { controller.current_user }
157
+ #
158
+ # Unset means the dashboard's signed-in user, and, for the MCP endpoint,
159
+ # the API key's owner — the identity that authenticated the call. A host
160
+ # whose keys are issued per end user overrides this to return that user.
161
+ #
162
+ # Returning nil runs the agent unattributed, which a correctly written
163
+ # host scope reads as "no access". That is the safe direction, and it is
164
+ # why this is never defaulted to something more privileged.
165
+ # @return [Proc, nil]
166
+ attr_accessor :agent_actor_resolver
167
+
148
168
  # Resolves the current tenant from the controller. See
149
169
  # current_user_resolver.
150
170
  # @return [Proc, nil]
@@ -348,9 +368,21 @@ module ActionAgent
348
368
  # @return [Array<Class, String>, nil]
349
369
  attr_accessor :schema_tools
350
370
 
371
+ # Whether the MCP facade (POST <mount>/mcp) offers the host's schema tools
372
+ # directly — find_<records>, count_<records>, get_<record> — beside the
373
+ # run_<slug> agent tools. Each call runs as the key's caller, through the
374
+ # host's own scope, exactly as it would inside an agent run. On by
375
+ # default: the host declared the tools; set it to false to keep them
376
+ # reachable only through agents.
377
+ # @return [Boolean]
378
+ attr_accessor :mcp_schema_tools
379
+
351
380
  # Directory scanned for SchemaTools subclasses when {#schema_tools} is
352
381
  # unset. Relative to the host's root. Set to nil to disable discovery and
353
- # require an explicit declaration.
382
+ # require an explicit declaration. Classes built at runtime with
383
+ # +ActiveAgent::SchemaTools.define+ are discovered alongside the files
384
+ # whatever this is set to; only an explicit {#schema_tools} list excludes
385
+ # them.
354
386
  # @return [String, nil]
355
387
  attr_accessor :schema_tools_path
356
388
 
@@ -368,6 +400,13 @@ module ActionAgent
368
400
  @multi_tenant == true
369
401
  end
370
402
 
403
+ # Whether the MCP facade serves the host's schema tools directly.
404
+ #
405
+ # @return [Boolean]
406
+ def mcp_schema_tools?
407
+ @mcp_schema_tools != false
408
+ end
409
+
371
410
  # Returns whether agent execution is permitted.
372
411
  #
373
412
  # @return [Boolean]
@@ -519,8 +558,10 @@ module ActionAgent
519
558
  @sign_out_path = nil
520
559
  @sign_in_path = nil
521
560
  @mcp_catalog = []
561
+ @agent_actor_resolver = nil
522
562
  @schema_tools = nil
523
563
  @schema_tools_path = "app/agent_tools"
564
+ @mcp_schema_tools = nil
524
565
  end
525
566
 
526
567
  # Host-declared schema tool classes, resolved from names and filtered to
@@ -550,14 +591,31 @@ module ActionAgent
550
591
  schema_tool_classes.flat_map(&:tool_names).map(&:to_s)
551
592
  end
552
593
 
553
- # SchemaTools subclasses defined under {#schema_tools_path}.
594
+ # SchemaTools classes on offer when {#schema_tools} is unset: the files
595
+ # under {#schema_tools_path}, and every runtime definition in
596
+ # +ActiveAgent::SchemaTools.registry+.
554
597
  #
555
598
  # The files are loaded before reading +descendants+: in development nothing
556
599
  # has referenced those constants yet, so the list would otherwise be empty
557
600
  # at boot and fill in only once something happened to touch them.
601
+ # Runtime-built classes are read from the registry, never from
602
+ # +descendants+, where every class ever built stays until collected and a
603
+ # superseded definition would be offered beside its replacement. A runtime
604
+ # definition for a model also supersedes a file for that model: it is the
605
+ # more recent intent.
558
606
  # @return [Array<Class>]
559
607
  def discovered_schema_tools
560
- return [] if @schema_tools_path.blank? || !defined?(ActiveAgent::SchemaTools)
608
+ return [] unless defined?(ActiveAgent::SchemaTools)
609
+
610
+ from_registry = ActiveAgent::SchemaTools.respond_to?(:registry) ? ActiveAgent::SchemaTools.registry.values : []
611
+ (from_registry + file_defined_schema_tools).uniq { |klass| klass.model.name }
612
+ end
613
+
614
+ # The named SchemaTools subclasses under {#schema_tools_path}; none when
615
+ # the path is unset or the directory does not exist.
616
+ # @return [Array<Class>]
617
+ def file_defined_schema_tools
618
+ return [] if @schema_tools_path.blank?
561
619
  return [] unless defined?(Rails) && Rails.respond_to?(:root) && Rails.root
562
620
 
563
621
  root = Rails.root.join(@schema_tools_path)
@@ -569,7 +627,11 @@ module ActionAgent
569
627
  warn "[ActionAgent] could not load #{path}: #{e.class} - #{e.message}"
570
628
  end
571
629
 
572
- ActiveAgent::SchemaTools.descendants.select { |klass| klass.name.present? }
630
+ ActiveAgent::SchemaTools.descendants.select { |klass| klass.name.present? && !runtime_schema_tools?(klass) }
631
+ end
632
+
633
+ def runtime_schema_tools?(klass)
634
+ klass.respond_to?(:runtime?) && klass.runtime?
573
635
  end
574
636
 
575
637
  # The schema tool class that generated +name+, or nil.
@@ -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
@@ -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,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: actionagent
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.2
4
+ version: 1.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Bowen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-11 00:00:00.000000000 Z
11
+ date: 2026-09-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activeagent
@@ -193,7 +193,9 @@ files:
193
193
  - app/serializers/action_agent/trace_interaction_serializer.rb
194
194
  - app/services/action_agent/agent_execution_service.rb
195
195
  - app/services/action_agent/agent_registrar.rb
196
+ - app/services/action_agent/agent_release.rb
196
197
  - app/services/action_agent/agent_scorecard.rb
198
+ - app/services/action_agent/agent_tool_roster.rb
197
199
  - app/services/action_agent/agent_toolbox.rb
198
200
  - app/services/action_agent/dashboard_assistant_service.rb
199
201
  - app/services/action_agent/evaluation_evidence.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