actionagent 0.0.0 → 1.2.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/LICENSE +21 -0
- data/README.md +103 -0
- data/app/assets/builds/action_agent.css +2 -0
- data/app/assets/builds/action_agent.js +163 -0
- data/app/controllers/action_agent/api/agent_runs_controller.rb +128 -0
- data/app/controllers/action_agent/api/agents_controller.rb +411 -0
- data/app/controllers/action_agent/api/analytics_controller.rb +94 -0
- data/app/controllers/action_agent/api/api_keys_controller.rb +45 -0
- data/app/controllers/action_agent/api/base_controller.rb +112 -0
- data/app/controllers/action_agent/api/evaluations_controller.rb +148 -0
- data/app/controllers/action_agent/api/instance_tiers_controller.rb +106 -0
- data/app/controllers/action_agent/api/interactions_controller.rb +197 -0
- data/app/controllers/action_agent/api/mcp_controller.rb +218 -0
- data/app/controllers/action_agent/api/mcp_servers_controller.rb +156 -0
- data/app/controllers/action_agent/api/metrics_controller.rb +178 -0
- data/app/controllers/action_agent/api/provider_keys_controller.rb +52 -0
- data/app/controllers/action_agent/api/provider_models_controller.rb +119 -0
- data/app/controllers/action_agent/api/sandboxes_controller.rb +224 -0
- data/app/controllers/action_agent/api/session_recordings_controller.rb +372 -0
- data/app/controllers/action_agent/api/templates_controller.rb +94 -0
- data/app/controllers/action_agent/api/tools_controller.rb +58 -0
- data/app/controllers/action_agent/api/trace_reports_controller.rb +68 -0
- data/app/controllers/action_agent/api/traces_controller.rb +136 -0
- data/app/controllers/action_agent/application_controller.rb +105 -0
- data/app/controllers/action_agent/dashboard_controller.rb +104 -0
- data/app/controllers/action_agent/traces_controller.rb +121 -0
- data/app/jobs/action_agent/agent_execution_job.rb +52 -0
- data/app/jobs/action_agent/application_job.rb +12 -0
- data/app/jobs/action_agent/process_telemetry_traces_job.rb +86 -0
- data/app/jobs/action_agent/sandbox_cleanup_job.rb +42 -0
- data/app/jobs/action_agent/sandbox_provision_job.rb +56 -0
- data/app/jobs/action_agent/sandbox_run_job.rb +285 -0
- data/app/jobs/action_agent/trace_retention_job.rb +57 -0
- data/app/models/action_agent/agent.rb +343 -0
- data/app/models/action_agent/agent_context.rb +129 -0
- data/app/models/action_agent/agent_generation.rb +48 -0
- data/app/models/action_agent/agent_memory.rb +50 -0
- data/app/models/action_agent/agent_memory_entry.rb +14 -0
- data/app/models/action_agent/agent_message.rb +43 -0
- data/app/models/action_agent/agent_run.rb +151 -0
- data/app/models/action_agent/agent_template.rb +182 -0
- data/app/models/action_agent/agent_version.rb +48 -0
- data/app/models/action_agent/api_key.rb +53 -0
- data/app/models/action_agent/application_record.rb +27 -0
- data/app/models/action_agent/evaluation.rb +80 -0
- data/app/models/action_agent/evaluation_run.rb +20 -0
- data/app/models/action_agent/model_pricing.rb +80 -0
- data/app/models/action_agent/provider_key.rb +60 -0
- data/app/models/action_agent/recording_action.rb +119 -0
- data/app/models/action_agent/recording_snapshot.rb +88 -0
- data/app/models/action_agent/sandbox_instance_tier.rb +368 -0
- data/app/models/action_agent/sandbox_run.rb +45 -0
- data/app/models/action_agent/sandbox_session.rb +160 -0
- data/app/models/action_agent/session_recording.rb +178 -0
- data/app/models/action_agent/telemetry_trace.rb +357 -0
- data/app/models/concerns/action_agent/adapter_aware.rb +50 -0
- data/app/models/concerns/action_agent/ownable.rb +86 -0
- data/app/models/concerns/action_agent/session_recordable.rb +91 -0
- data/app/queries/action_agent/agent_executions.rb +201 -0
- data/app/serializers/action_agent/agent_message_serializer.rb +23 -0
- data/app/serializers/action_agent/interaction_preview.rb +22 -0
- data/app/serializers/action_agent/telemetry_trace_serializer.rb +122 -0
- data/app/serializers/action_agent/trace_interaction_serializer.rb +246 -0
- data/app/services/action_agent/agent_execution_service.rb +572 -0
- data/app/services/action_agent/agent_registrar.rb +197 -0
- data/app/services/action_agent/agent_scorecard.rb +191 -0
- data/app/services/action_agent/agent_toolbox.rb +504 -0
- data/app/services/action_agent/evaluation_runner_service.rb +481 -0
- data/app/services/action_agent/mcp_catalog.rb +247 -0
- data/app/services/action_agent/mcp_recording_middleware.rb +241 -0
- data/app/services/action_agent/mock_sandbox_backend.rb +52 -0
- data/app/services/action_agent/playwright_mcp_client.rb +148 -0
- data/app/services/action_agent/sandbox_orchestrator.rb +242 -0
- data/app/services/action_agent/session_recording_service.rb +228 -0
- data/app/services/action_agent/tool_discovery.rb +617 -0
- data/app/views/action_agent/dashboard/index.html.erb +5 -0
- data/app/views/action_agent/traces/_trace_detail.html.erb +117 -0
- data/app/views/action_agent/traces/index.html.erb +135 -0
- data/app/views/action_agent/traces/metrics.html.erb +145 -0
- data/app/views/action_agent/traces/show.html.erb +36 -0
- data/app/views/layouts/action_agent/application.html.erb +94 -0
- data/app/views/layouts/action_agent/react.html.erb +19 -0
- data/config/routes.rb +144 -0
- data/lib/action_agent/compatibility.rb +49 -0
- data/lib/action_agent/engine.rb +51 -0
- data/lib/action_agent/version.rb +5 -0
- data/lib/action_agent.rb +388 -0
- data/lib/actionagent.rb +6 -0
- data/lib/generators/action_agent/install_generator.rb +137 -0
- data/lib/generators/action_agent/templates/action_agent.rb.erb +82 -0
- data/lib/generators/action_agent/templates/add_agent_id_to_active_agent_telemetry_traces.rb.erb +24 -0
- data/lib/generators/action_agent/templates/create_active_agent_dashboard_tables.rb.erb +319 -0
- data/lib/generators/action_agent/templates/create_active_agent_telemetry_traces.rb.erb +58 -0
- metadata +209 -12
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "rails/generators/active_record"
|
|
5
|
+
|
|
6
|
+
module ActionAgent
|
|
7
|
+
# Generator for installing the ActiveAgent Dashboard.
|
|
8
|
+
#
|
|
9
|
+
# @example Run the generator
|
|
10
|
+
# rails generate action_agent:install
|
|
11
|
+
#
|
|
12
|
+
# This will:
|
|
13
|
+
# - Create the telemetry_traces table migration
|
|
14
|
+
# - Add mount directive to routes
|
|
15
|
+
# - Create initializer for dashboard configuration
|
|
16
|
+
#
|
|
17
|
+
class InstallGenerator < Rails::Generators::Base
|
|
18
|
+
include ActiveRecord::Generators::Migration
|
|
19
|
+
|
|
20
|
+
source_root File.expand_path("templates", __dir__)
|
|
21
|
+
|
|
22
|
+
desc "Installs the ActiveAgent Dashboard with telemetry storage"
|
|
23
|
+
|
|
24
|
+
class_option :multi_tenant, type: :boolean, default: false,
|
|
25
|
+
desc: "Scope traces to an Account (adds account_id to the migration)"
|
|
26
|
+
|
|
27
|
+
class_option :skip_migrations, type: :boolean, default: false,
|
|
28
|
+
desc: "Skip copying the migrations"
|
|
29
|
+
|
|
30
|
+
class_option :traces_only, type: :boolean, default: false,
|
|
31
|
+
desc: "Install trace ingestion alone, without the agent/run/evaluation tables"
|
|
32
|
+
|
|
33
|
+
class_option :skip_routes, type: :boolean, default: false,
|
|
34
|
+
desc: "Skip adding the engine mount to routes.rb"
|
|
35
|
+
|
|
36
|
+
def copy_migrations
|
|
37
|
+
return if options[:skip_migrations]
|
|
38
|
+
|
|
39
|
+
# An app that installed the dashboard when it shipped inside activeagent
|
|
40
|
+
# already has this migration. Re-emitting it aborts the whole generator
|
|
41
|
+
# on a duplicate name, so upgrade that install in place instead: the
|
|
42
|
+
# table exists but predates agent attribution, and only agent_id is
|
|
43
|
+
# missing.
|
|
44
|
+
if existing_migration?("create_active_agent_telemetry_traces")
|
|
45
|
+
say_status :skip, "create_active_agent_telemetry_traces already exists", :yellow
|
|
46
|
+
|
|
47
|
+
unless existing_migration?("add_agent_id_to_active_agent_telemetry_traces")
|
|
48
|
+
migration_template(
|
|
49
|
+
"add_agent_id_to_active_agent_telemetry_traces.rb.erb",
|
|
50
|
+
"db/migrate/add_agent_id_to_active_agent_telemetry_traces.rb"
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
else
|
|
54
|
+
migration_template(
|
|
55
|
+
"create_active_agent_telemetry_traces.rb.erb",
|
|
56
|
+
"db/migrate/create_active_agent_telemetry_traces.rb"
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# The rest of the dashboard — agents, runs, versions, conversations,
|
|
61
|
+
# evaluations, sandboxes, recordings, keys. Skippable for an app that
|
|
62
|
+
# only wants to be a trace sink.
|
|
63
|
+
return if options[:traces_only]
|
|
64
|
+
|
|
65
|
+
if existing_migration?("create_active_agent_dashboard_tables")
|
|
66
|
+
say_status :skip, "create_active_agent_dashboard_tables already exists", :yellow
|
|
67
|
+
return
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
migration_template(
|
|
71
|
+
"create_active_agent_dashboard_tables.rb.erb",
|
|
72
|
+
"db/migrate/create_active_agent_dashboard_tables.rb"
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def add_route
|
|
77
|
+
return if options[:skip_routes]
|
|
78
|
+
|
|
79
|
+
# Rails' `route` action is only idempotent on an exact string match,
|
|
80
|
+
# so an app installed before the default path changed would get a
|
|
81
|
+
# second mount — and two unnamed mounts of the same engine raise
|
|
82
|
+
# "Invalid route name, already in use: 'active_agent'" at boot.
|
|
83
|
+
routes_file = File.join(destination_root, "config/routes.rb")
|
|
84
|
+
if File.exist?(routes_file) && File.read(routes_file).include?("ActionAgent::Engine")
|
|
85
|
+
say_status :skip, "engine already mounted in config/routes.rb", :yellow
|
|
86
|
+
return
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
route 'mount ActionAgent::Engine => "/activeagents"'
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def create_initializer
|
|
93
|
+
template(
|
|
94
|
+
"action_agent.rb.erb",
|
|
95
|
+
"config/initializers/action_agent.rb"
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def show_readme
|
|
100
|
+
say "\n"
|
|
101
|
+
say "ActiveAgent Dashboard installed successfully!", :green
|
|
102
|
+
say "\n"
|
|
103
|
+
say "Next steps:"
|
|
104
|
+
say " 1. Run migrations: rails db:migrate"
|
|
105
|
+
say " 2. Configure telemetry in config/active_agent.yml:"
|
|
106
|
+
say " telemetry:"
|
|
107
|
+
say " enabled: true"
|
|
108
|
+
say " local_storage: true"
|
|
109
|
+
say " 3. Visit /activeagents to view the dashboard"
|
|
110
|
+
unless options[:traces_only]
|
|
111
|
+
say "\n"
|
|
112
|
+
say "The dashboard stores API keys and provider credentials encrypted."
|
|
113
|
+
say "Run `rails db:encryption:init` and add the keys to your credentials"
|
|
114
|
+
say "before creating any."
|
|
115
|
+
end
|
|
116
|
+
say "\n"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
def migration_version
|
|
122
|
+
"[#{ActiveRecord::Migration.current_version}]"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Whether db/migrate already carries a migration with this name. Checked
|
|
126
|
+
# against the filesystem rather than the database so the generator still
|
|
127
|
+
# works before the database exists.
|
|
128
|
+
def existing_migration?(name)
|
|
129
|
+
Dir.glob(File.join(destination_root.to_s, "db", "migrate", "*_#{name}.rb")).any?
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Consumed by the migration template.
|
|
133
|
+
def multi_tenant?
|
|
134
|
+
options[:multi_tenant]
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# ActiveAgent Dashboard Configuration
|
|
4
|
+
#
|
|
5
|
+
# This file configures the ActiveAgent telemetry dashboard.
|
|
6
|
+
# The dashboard is mounted at /activeagents by default (see config/routes.rb).
|
|
7
|
+
|
|
8
|
+
ActionAgent.configure do |config|
|
|
9
|
+
# ==========================================================================
|
|
10
|
+
# Authentication (required in production)
|
|
11
|
+
# ==========================================================================
|
|
12
|
+
#
|
|
13
|
+
# Provide a lambda that receives the controller and performs
|
|
14
|
+
# authentication. Return false or raise to deny access. Without one, the
|
|
15
|
+
# dashboard serves a 403 in production — traces contain prompts, outputs
|
|
16
|
+
# and error messages.
|
|
17
|
+
#
|
|
18
|
+
# Basic auth:
|
|
19
|
+
# config.authentication_method = ->(controller) {
|
|
20
|
+
# controller.authenticate_or_request_with_http_basic do |username, password|
|
|
21
|
+
# username == "admin" && password == Rails.application.credentials.dashboard_password
|
|
22
|
+
# end
|
|
23
|
+
# }
|
|
24
|
+
#
|
|
25
|
+
# Devise:
|
|
26
|
+
# config.authentication_method = ->(controller) {
|
|
27
|
+
# controller.authenticate_admin!
|
|
28
|
+
# }
|
|
29
|
+
#
|
|
30
|
+
# No authentication (development only!):
|
|
31
|
+
# config.authentication_method = nil
|
|
32
|
+
#
|
|
33
|
+
config.authentication_method = nil
|
|
34
|
+
|
|
35
|
+
# ==========================================================================
|
|
36
|
+
# Ingest API authentication
|
|
37
|
+
# ==========================================================================
|
|
38
|
+
#
|
|
39
|
+
# Bearer token other apps must send when posting traces to the mounted
|
|
40
|
+
# ingest endpoint (/activeagents/api/traces). Leave unset only when the
|
|
41
|
+
# mount is not reachable beyond your own machine.
|
|
42
|
+
#
|
|
43
|
+
# config.ingest_api_key = Rails.application.credentials.dig(:active_agent, :ingest_api_key)
|
|
44
|
+
|
|
45
|
+
# ==========================================================================
|
|
46
|
+
# Multi-tenant mode (SaaS platforms only)
|
|
47
|
+
# ==========================================================================
|
|
48
|
+
#
|
|
49
|
+
# Scopes traces per account and authenticates ingest with per-account
|
|
50
|
+
# keys. Most self-hosted installs should leave this off.
|
|
51
|
+
#
|
|
52
|
+
# config.multi_tenant = true
|
|
53
|
+
# config.account_class = "Account"
|
|
54
|
+
# config.user_class = "User"
|
|
55
|
+
#
|
|
56
|
+
# How the engine finds the signed-in user and their account. These have to
|
|
57
|
+
# be resolvers rather than method names: the dashboard's controllers are
|
|
58
|
+
# their own base class, so your app's `current_user` helper is not on them.
|
|
59
|
+
# Read the session (or your auth library) directly — a resolver that calls
|
|
60
|
+
# back into `current_user` reaches the engine's own accessor, not yours.
|
|
61
|
+
#
|
|
62
|
+
# config.current_user_resolver = ->(controller) {
|
|
63
|
+
# User.find_by(id: controller.session[:user_id])
|
|
64
|
+
# }
|
|
65
|
+
# config.current_account_resolver = ->(controller) {
|
|
66
|
+
# controller.send(:current_user)&.account
|
|
67
|
+
# }
|
|
68
|
+
#
|
|
69
|
+
# With an owner model configured, an owner that does not resolve sees
|
|
70
|
+
# nothing rather than everything. An empty dashboard for a signed-in user
|
|
71
|
+
# means the resolver above returned nil.
|
|
72
|
+
|
|
73
|
+
# ==========================================================================
|
|
74
|
+
# UI
|
|
75
|
+
# ==========================================================================
|
|
76
|
+
#
|
|
77
|
+
# Custom layout for dashboard views (the default layout loads Tailwind,
|
|
78
|
+
# Turbo and Stimulus from CDNs — override for CSP-strict or air-gapped
|
|
79
|
+
# environments).
|
|
80
|
+
#
|
|
81
|
+
# config.layout = "application"
|
|
82
|
+
end
|
data/lib/generators/action_agent/templates/add_agent_id_to_active_agent_telemetry_traces.rb.erb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Tops up a telemetry_traces table created before traces carried an agent
|
|
4
|
+
# attribution. Emitted instead of the create-table migration when the install
|
|
5
|
+
# generator finds one already in db/migrate.
|
|
6
|
+
#
|
|
7
|
+
# Without agent_id, AgentRegistrar cannot attribute an ingested trace to a
|
|
8
|
+
# dashboard agent: auto-registration silently does nothing, and the dashboard
|
|
9
|
+
# home page fails as soon as the first agent exists.
|
|
10
|
+
class AddAgentIdToActiveAgentTelemetryTraces < ActiveRecord::Migration<%= migration_version %>
|
|
11
|
+
def up
|
|
12
|
+
return unless table_exists?(:active_agent_telemetry_traces)
|
|
13
|
+
return if column_exists?(:active_agent_telemetry_traces, :agent_id)
|
|
14
|
+
|
|
15
|
+
add_column :active_agent_telemetry_traces, :agent_id, :bigint
|
|
16
|
+
add_index :active_agent_telemetry_traces, :agent_id
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def down
|
|
20
|
+
return unless column_exists?(:active_agent_telemetry_traces, :agent_id)
|
|
21
|
+
|
|
22
|
+
remove_column :active_agent_telemetry_traces, :agent_id
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Creates every table the ActiveAgent Dashboard reads and writes, apart from
|
|
4
|
+
# active_agent_telemetry_traces (which ships separately so an app can install
|
|
5
|
+
# trace ingestion alone).
|
|
6
|
+
#
|
|
7
|
+
# Table names follow ActionAgent.table_name_prefix, so an app that
|
|
8
|
+
# has deliberately set the prefix to "" gets unprefixed tables and one that
|
|
9
|
+
# leaves the default gets active_agent_*.
|
|
10
|
+
#
|
|
11
|
+
# JSON columns are jsonb on PostgreSQL and json elsewhere: the dashboard runs
|
|
12
|
+
# on SQLite and MySQL, it just loses jsonb containment (see
|
|
13
|
+
# ActionAgent::Agent.with_tool, which falls back to a LIKE).
|
|
14
|
+
class CreateActiveAgentDashboardTables < ActiveRecord::Migration<%= migration_version %>
|
|
15
|
+
def change
|
|
16
|
+
prefix = ActionAgent.table_name_prefix
|
|
17
|
+
|
|
18
|
+
create_table "#{prefix}agents" do |t|
|
|
19
|
+
t.string :name, null: false
|
|
20
|
+
t.string :slug, null: false
|
|
21
|
+
t.text :description
|
|
22
|
+
t.string :provider, default: "openai"
|
|
23
|
+
t.string :model, default: "gpt-4o-mini"
|
|
24
|
+
t.text :instructions
|
|
25
|
+
t.string :preset_type
|
|
26
|
+
t.string :agent_class_name
|
|
27
|
+
t.string :action_name
|
|
28
|
+
t.integer :status, default: 0, null: false
|
|
29
|
+
t.column :appearance, json_type, default: {}
|
|
30
|
+
t.column :instruction_sets, json_type, default: []
|
|
31
|
+
t.column :tools, json_type, default: []
|
|
32
|
+
t.column :mcp_servers, json_type, default: []
|
|
33
|
+
t.column :model_config, json_type, default: {}
|
|
34
|
+
t.column :response_format, json_type, default: {}
|
|
35
|
+
t.column :action_prompts, json_type, default: [], null: false
|
|
36
|
+
|
|
37
|
+
# Agents discovered from reported telemetry rather than authored here.
|
|
38
|
+
t.string :service_name
|
|
39
|
+
t.string :source
|
|
40
|
+
t.datetime :first_observed_at
|
|
41
|
+
t.datetime :last_observed_at
|
|
42
|
+
|
|
43
|
+
t.bigint :user_id
|
|
44
|
+
t.bigint :account_id
|
|
45
|
+
t.timestamps
|
|
46
|
+
t.index :user_id
|
|
47
|
+
t.index :account_id
|
|
48
|
+
t.index [ :user_id, :slug ], unique: true
|
|
49
|
+
t.index :status
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
create_table "#{prefix}agent_versions" do |t|
|
|
53
|
+
t.bigint :agent_id, null: false
|
|
54
|
+
t.integer :version_number, default: 1, null: false
|
|
55
|
+
t.column :configuration_snapshot, json_type, default: {}, null: false
|
|
56
|
+
t.string :change_summary
|
|
57
|
+
t.string :created_by
|
|
58
|
+
t.timestamps
|
|
59
|
+
t.index [ :agent_id, :version_number ], unique: true
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
create_table "#{prefix}agent_runs" do |t|
|
|
63
|
+
t.bigint :agent_id, null: false
|
|
64
|
+
t.string :trace_id
|
|
65
|
+
t.string :action_name
|
|
66
|
+
t.integer :status, default: 0, null: false
|
|
67
|
+
t.text :input_prompt
|
|
68
|
+
t.column :input_params, json_type, default: {}
|
|
69
|
+
t.text :output
|
|
70
|
+
t.column :output_metadata, json_type, default: {}
|
|
71
|
+
t.column :logs, json_type, default: []
|
|
72
|
+
t.text :error_message
|
|
73
|
+
t.text :error_backtrace
|
|
74
|
+
t.integer :input_tokens
|
|
75
|
+
t.integer :output_tokens
|
|
76
|
+
t.integer :total_tokens
|
|
77
|
+
t.integer :duration_ms
|
|
78
|
+
t.datetime :started_at
|
|
79
|
+
t.datetime :completed_at
|
|
80
|
+
t.timestamps
|
|
81
|
+
t.index :agent_id
|
|
82
|
+
t.index :trace_id
|
|
83
|
+
t.index :created_at
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
create_table "#{prefix}agent_templates" do |t|
|
|
87
|
+
t.string :name, null: false
|
|
88
|
+
t.string :slug, null: false
|
|
89
|
+
t.text :description
|
|
90
|
+
t.string :category
|
|
91
|
+
t.string :icon
|
|
92
|
+
t.string :provider, default: "openai"
|
|
93
|
+
t.string :model, default: "gpt-4o-mini"
|
|
94
|
+
t.text :instructions
|
|
95
|
+
t.string :preset_type
|
|
96
|
+
t.column :appearance, json_type, default: {}
|
|
97
|
+
t.column :instruction_sets, json_type, default: []
|
|
98
|
+
t.column :tools, json_type, default: []
|
|
99
|
+
t.column :mcp_servers, json_type, default: {}
|
|
100
|
+
t.column :model_config, json_type, default: {}
|
|
101
|
+
t.boolean :featured, default: false
|
|
102
|
+
t.boolean :free_tier, default: false
|
|
103
|
+
t.boolean :public, default: true
|
|
104
|
+
t.integer :usage_count, default: 0
|
|
105
|
+
t.timestamps
|
|
106
|
+
t.index :slug, unique: true
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Conversation persistence — the solid_agent schema, owned by the engine
|
|
110
|
+
# so the dashboard's Interactions view works without that gem installed.
|
|
111
|
+
create_table "#{prefix}agent_contexts" do |t|
|
|
112
|
+
t.string :agent_name, null: false
|
|
113
|
+
t.string :action_name, null: false
|
|
114
|
+
t.string :contextable_type
|
|
115
|
+
t.bigint :contextable_id
|
|
116
|
+
t.text :instructions
|
|
117
|
+
t.column :options, json_type, default: {}
|
|
118
|
+
t.string :trace_id
|
|
119
|
+
t.integer :total_input_tokens, default: 0
|
|
120
|
+
t.integer :total_output_tokens, default: 0
|
|
121
|
+
t.timestamps
|
|
122
|
+
t.index [ :contextable_type, :contextable_id ]
|
|
123
|
+
t.index :trace_id
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
create_table "#{prefix}agent_messages" do |t|
|
|
127
|
+
t.bigint :agent_context_id, null: false
|
|
128
|
+
t.string :role, null: false
|
|
129
|
+
t.text :content
|
|
130
|
+
t.string :content_checksum
|
|
131
|
+
t.string :tool_call_id
|
|
132
|
+
t.string :tool_name
|
|
133
|
+
t.column :tool_arguments, json_type, default: {}
|
|
134
|
+
t.column :tool_result, json_type
|
|
135
|
+
t.column :attachments, json_type, default: []
|
|
136
|
+
t.column :metadata, json_type, default: {}
|
|
137
|
+
t.column :provenance, json_type, default: {}
|
|
138
|
+
t.timestamps
|
|
139
|
+
t.index :agent_context_id
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
create_table "#{prefix}agent_generations" do |t|
|
|
143
|
+
t.bigint :agent_context_id, null: false
|
|
144
|
+
t.text :content
|
|
145
|
+
t.string :model
|
|
146
|
+
t.string :provider
|
|
147
|
+
t.string :finish_reason
|
|
148
|
+
t.integer :input_tokens, default: 0
|
|
149
|
+
t.integer :output_tokens, default: 0
|
|
150
|
+
t.integer :cached_tokens, default: 0
|
|
151
|
+
t.integer :reasoning_tokens, default: 0
|
|
152
|
+
t.float :duration_seconds
|
|
153
|
+
t.column :tool_calls, json_type, default: []
|
|
154
|
+
t.column :raw_response, json_type
|
|
155
|
+
t.column :provenance, json_type, default: {}
|
|
156
|
+
t.string :trace_id
|
|
157
|
+
t.timestamps
|
|
158
|
+
t.index :agent_context_id
|
|
159
|
+
t.index :trace_id
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
create_table "#{prefix}agent_memories" do |t|
|
|
163
|
+
t.string :memorable_type
|
|
164
|
+
t.bigint :memorable_id
|
|
165
|
+
t.string :scope, default: "default", null: false
|
|
166
|
+
t.timestamps
|
|
167
|
+
t.index [ :memorable_type, :memorable_id, :scope ], unique: true,
|
|
168
|
+
name: "index_active_agent_memories_on_memorable_and_scope"
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
create_table "#{prefix}agent_memory_entries" do |t|
|
|
172
|
+
t.bigint :agent_memory_id, null: false
|
|
173
|
+
t.text :content, null: false
|
|
174
|
+
t.string :source_agent
|
|
175
|
+
t.string :category
|
|
176
|
+
t.timestamps
|
|
177
|
+
t.index :agent_memory_id
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
create_table "#{prefix}evaluations" do |t|
|
|
181
|
+
t.bigint :agent_id, null: false
|
|
182
|
+
t.string :name, null: false
|
|
183
|
+
t.string :judge_kind, default: "rules", null: false
|
|
184
|
+
t.string :judge_model
|
|
185
|
+
t.integer :sample_size, default: 20, null: false
|
|
186
|
+
t.column :criteria, json_type, default: [], null: false
|
|
187
|
+
t.column :config, json_type, default: {}, null: false
|
|
188
|
+
t.timestamps
|
|
189
|
+
t.index [ :agent_id, :name ], unique: true
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
create_table "#{prefix}evaluation_runs" do |t|
|
|
193
|
+
t.bigint :evaluation_id, null: false
|
|
194
|
+
t.integer :status, default: 0, null: false
|
|
195
|
+
t.column :scores, json_type, default: {}
|
|
196
|
+
t.integer :samples_evaluated, default: 0
|
|
197
|
+
t.integer :samples_passed, default: 0
|
|
198
|
+
t.text :error_message
|
|
199
|
+
t.datetime :completed_at
|
|
200
|
+
t.timestamps
|
|
201
|
+
t.index :evaluation_id
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
create_table "#{prefix}sandbox_sessions" do |t|
|
|
205
|
+
t.string :session_id, null: false
|
|
206
|
+
t.bigint :agent_template_id
|
|
207
|
+
t.integer :status, default: 0
|
|
208
|
+
t.string :sandbox_type, default: "playwright_mcp"
|
|
209
|
+
t.string :cloud_run_job_id
|
|
210
|
+
t.string :cloud_run_url
|
|
211
|
+
t.column :runs, json_type, default: []
|
|
212
|
+
# MCP servers this session was started with, so the MCP Services view
|
|
213
|
+
# can show a launched server as running rather than offering to start
|
|
214
|
+
# a second copy.
|
|
215
|
+
t.column :mcp_servers, json_type, default: []
|
|
216
|
+
t.integer :runs_count, default: 0
|
|
217
|
+
t.integer :max_runs, default: 10
|
|
218
|
+
t.integer :timeout_seconds, default: 300
|
|
219
|
+
t.integer :total_duration_ms, default: 0
|
|
220
|
+
t.integer :total_tokens, default: 0
|
|
221
|
+
t.text :error_message
|
|
222
|
+
t.datetime :expires_at
|
|
223
|
+
t.datetime :last_activity_at
|
|
224
|
+
t.bigint :user_id
|
|
225
|
+
t.bigint :account_id
|
|
226
|
+
t.timestamps
|
|
227
|
+
t.index :session_id, unique: true
|
|
228
|
+
t.index :user_id
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
create_table "#{prefix}sandbox_runs" do |t|
|
|
232
|
+
t.bigint :sandbox_session_id
|
|
233
|
+
t.text :task, null: false
|
|
234
|
+
t.integer :status, default: 0, null: false
|
|
235
|
+
t.text :result
|
|
236
|
+
t.text :error
|
|
237
|
+
t.column :screenshots, json_type, default: []
|
|
238
|
+
t.integer :tokens_used, default: 0
|
|
239
|
+
t.integer :duration_ms
|
|
240
|
+
t.datetime :started_at
|
|
241
|
+
t.datetime :completed_at
|
|
242
|
+
t.timestamps
|
|
243
|
+
t.index :sandbox_session_id
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
create_table "#{prefix}session_recordings" do |t|
|
|
247
|
+
t.string :name
|
|
248
|
+
t.bigint :agent_run_id
|
|
249
|
+
t.bigint :sandbox_session_id
|
|
250
|
+
t.integer :status, default: 0, null: false
|
|
251
|
+
t.integer :action_count, default: 0
|
|
252
|
+
t.integer :duration_ms
|
|
253
|
+
t.column :metadata, json_type, default: {}
|
|
254
|
+
t.bigint :user_id
|
|
255
|
+
t.bigint :account_id
|
|
256
|
+
t.timestamps
|
|
257
|
+
t.index :agent_run_id
|
|
258
|
+
t.index :sandbox_session_id
|
|
259
|
+
t.index :user_id
|
|
260
|
+
t.index :account_id
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
create_table "#{prefix}recording_actions" do |t|
|
|
264
|
+
t.bigint :session_recording_id, null: false
|
|
265
|
+
t.integer :sequence, null: false
|
|
266
|
+
t.string :action_type, null: false
|
|
267
|
+
t.string :selector
|
|
268
|
+
t.text :value
|
|
269
|
+
t.integer :timestamp_ms, null: false
|
|
270
|
+
t.string :screenshot_key
|
|
271
|
+
t.string :dom_snapshot_key
|
|
272
|
+
t.column :metadata, json_type, default: {}
|
|
273
|
+
t.timestamps
|
|
274
|
+
t.index [ :session_recording_id, :sequence ], unique: true,
|
|
275
|
+
name: "index_active_agent_recording_actions_on_recording_and_sequence"
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
create_table "#{prefix}recording_snapshots" do |t|
|
|
279
|
+
t.bigint :session_recording_id, null: false
|
|
280
|
+
t.bigint :recording_action_id
|
|
281
|
+
t.string :snapshot_type, null: false
|
|
282
|
+
t.string :storage_key, null: false
|
|
283
|
+
t.integer :width
|
|
284
|
+
t.integer :height
|
|
285
|
+
t.integer :file_size_bytes
|
|
286
|
+
t.timestamps
|
|
287
|
+
t.index :session_recording_id
|
|
288
|
+
t.index :recording_action_id
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
create_table "#{prefix}api_keys" do |t|
|
|
292
|
+
t.string :name, null: false
|
|
293
|
+
t.string :token, null: false
|
|
294
|
+
t.string :token_prefix, null: false
|
|
295
|
+
t.datetime :last_used_at
|
|
296
|
+
t.bigint :user_id
|
|
297
|
+
t.bigint :account_id
|
|
298
|
+
t.timestamps
|
|
299
|
+
t.index :token, unique: true
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
create_table "#{prefix}provider_keys" do |t|
|
|
303
|
+
t.string :provider, null: false
|
|
304
|
+
t.string :credential, null: false
|
|
305
|
+
t.bigint :user_id
|
|
306
|
+
t.bigint :account_id
|
|
307
|
+
t.timestamps
|
|
308
|
+
t.index [ :account_id, :provider ]
|
|
309
|
+
t.index [ :user_id, :provider ]
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
private
|
|
314
|
+
|
|
315
|
+
# jsonb where the adapter has it, json where it doesn't.
|
|
316
|
+
def json_type
|
|
317
|
+
@json_type ||= connection.adapter_name.to_s.downcase.include?("postgres") ? :jsonb : :json
|
|
318
|
+
end
|
|
319
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateActiveAgentTelemetryTraces < ActiveRecord::Migration<%= migration_version %>
|
|
4
|
+
def change
|
|
5
|
+
create_table :active_agent_telemetry_traces do |t|
|
|
6
|
+
<% if multi_tenant? -%>
|
|
7
|
+
# Multi-tenant mode: every query is scoped through `for_account`, and
|
|
8
|
+
# ingest authenticates a per-account key. The foreign key requires an
|
|
9
|
+
# existing `accounts` table — on PostgreSQL/MySQL this migration must
|
|
10
|
+
# run after the one that creates it (drop `foreign_key: true` if your
|
|
11
|
+
# tenant table is named differently).
|
|
12
|
+
t.references :account, null: false, foreign_key: true
|
|
13
|
+
<% end -%>
|
|
14
|
+
t.string :trace_id, null: false
|
|
15
|
+
t.string :service_name
|
|
16
|
+
t.string :environment
|
|
17
|
+
t.datetime :timestamp, index: true
|
|
18
|
+
t.column :spans, json_type, default: []
|
|
19
|
+
t.column :resource_attributes, json_type, default: {}
|
|
20
|
+
t.column :sdk_info, json_type, default: {}
|
|
21
|
+
t.decimal :total_duration_ms, precision: 12, scale: 3
|
|
22
|
+
t.integer :total_input_tokens, default: 0
|
|
23
|
+
t.integer :total_output_tokens, default: 0
|
|
24
|
+
t.integer :total_thinking_tokens, default: 0
|
|
25
|
+
t.string :status
|
|
26
|
+
t.string :agent_class, index: true
|
|
27
|
+
t.string :agent_action
|
|
28
|
+
# Set on ingest when the trace can be attributed to a dashboard agent;
|
|
29
|
+
# nullable because a trace must still land when it cannot be.
|
|
30
|
+
t.bigint :agent_id, index: true
|
|
31
|
+
t.text :error_message
|
|
32
|
+
|
|
33
|
+
t.timestamps
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
<% if multi_tenant? -%>
|
|
37
|
+
# Ingest dedupes on trace_id (per account when multi-tenant); the unique
|
|
38
|
+
# index makes that dedupe race-proof rather than check-then-insert.
|
|
39
|
+
add_index :active_agent_telemetry_traces, [:account_id, :trace_id], unique: true
|
|
40
|
+
add_index :active_agent_telemetry_traces, [:account_id, :timestamp]
|
|
41
|
+
<% else -%>
|
|
42
|
+
add_index :active_agent_telemetry_traces, :trace_id, unique: true
|
|
43
|
+
<% end -%>
|
|
44
|
+
add_index :active_agent_telemetry_traces, :status
|
|
45
|
+
add_index :active_agent_telemetry_traces, [:agent_class, :agent_action]
|
|
46
|
+
add_index :active_agent_telemetry_traces, [:service_name, :environment]
|
|
47
|
+
add_index :active_agent_telemetry_traces, :created_at
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
# jsonb where the adapter has it, json where it doesn't. The dashboard
|
|
53
|
+
# traverses spans in SQL on PostgreSQL, and jsonb_array_elements rejects a
|
|
54
|
+
# json argument, so the column type and the query have to agree.
|
|
55
|
+
def json_type
|
|
56
|
+
@json_type ||= connection.adapter_name.to_s.downcase.include?("postgres") ? :jsonb : :json
|
|
57
|
+
end
|
|
58
|
+
end
|