activeagent 1.1.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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +61 -0
  3. data/README.md +24 -14
  4. data/lib/active_agent/telemetry/configuration.rb +13 -9
  5. data/lib/active_agent/telemetry/instrumentation.rb +4 -0
  6. data/lib/active_agent/telemetry/tool_origin.rb +90 -0
  7. data/lib/active_agent/telemetry.rb +1 -0
  8. data/lib/active_agent/version.rb +1 -1
  9. data/lib/active_agent.rb +0 -5
  10. metadata +22 -32
  11. data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/api/traces_controller.rb +0 -138
  12. data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/application_controller.rb +0 -64
  13. data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/dashboard_controller.rb +0 -129
  14. data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/traces_controller.rb +0 -123
  15. data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/agent_execution_job.rb +0 -56
  16. data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/application_job.rb +0 -14
  17. data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/sandbox_cleanup_job.rb +0 -49
  18. data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/sandbox_provision_job.rb +0 -65
  19. data/lib/active_agent/dashboard/app/jobs/active_agent/process_telemetry_traces_job.rb +0 -86
  20. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent.rb +0 -256
  21. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_run.rb +0 -113
  22. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_template.rb +0 -208
  23. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_version.rb +0 -60
  24. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/application_record.rb +0 -46
  25. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/recording_action.rb +0 -125
  26. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/recording_snapshot.rb +0 -83
  27. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/sandbox_run.rb +0 -52
  28. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/sandbox_session.rb +0 -169
  29. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/session_recording.rb +0 -193
  30. data/lib/active_agent/dashboard/app/models/active_agent/telemetry_trace.rb +0 -214
  31. data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/_trace_detail.html.erb +0 -117
  32. data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/index.html.erb +0 -135
  33. data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/metrics.html.erb +0 -145
  34. data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/show.html.erb +0 -36
  35. data/lib/active_agent/dashboard/app/views/layouts/active_agent/dashboard/application.html.erb +0 -94
  36. data/lib/active_agent/dashboard/config/routes.rb +0 -19
  37. data/lib/active_agent/dashboard/engine.rb +0 -43
  38. data/lib/active_agent/dashboard.rb +0 -161
  39. data/lib/generators/active_agent/dashboard/install_generator.rb +0 -92
  40. data/lib/generators/active_agent/dashboard/templates/active_agent_dashboard.rb.erb +0 -67
  41. data/lib/generators/active_agent/dashboard/templates/create_active_agent_telemetry_traces.rb.erb +0 -46
@@ -1,129 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveAgent
4
- module Dashboard
5
- # Main dashboard controller showing overview metrics and recent activity.
6
- #
7
- class DashboardController < ApplicationController
8
- def index
9
- unless ActiveAgent::Dashboard.use_inertia && defined?(InertiaRails)
10
- # No ERB overview ships yet — the traces list is the dashboard.
11
- # Loading agent/run data here would also require the full
12
- # dashboard tables, which telemetry-only installs don't have.
13
- return redirect_to(traces_path)
14
- end
15
-
16
- @agents = fetch_agents.limit(10)
17
- @recent_runs = fetch_recent_runs.limit(10)
18
- @recent_traces = fetch_recent_traces.limit(10)
19
- @metrics = calculate_metrics
20
-
21
- render inertia: "Dashboard", props: {
22
- agents: serialize_agents(@agents),
23
- recentRuns: serialize_runs(@recent_runs),
24
- recentTraces: serialize_traces(@recent_traces),
25
- metrics: @metrics,
26
- user: current_user_props,
27
- account: current_account_props
28
- }
29
- end
30
-
31
- private
32
-
33
- def fetch_agents
34
- agents = Agent.order(updated_at: :desc)
35
- agents = agents.for_owner(current_owner) if current_owner
36
- agents
37
- end
38
-
39
- def fetch_recent_runs
40
- runs = AgentRun.includes(:agent).recent
41
- if current_owner && ActiveAgent::Dashboard.multi_tenant?
42
- runs = runs.joins(:agent).where(agents: { account_id: current_owner.id })
43
- end
44
- runs
45
- end
46
-
47
- def fetch_recent_traces
48
- traces = ActiveAgent::Dashboard.trace_model.recent
49
- traces = traces.for_account(current_owner) if current_owner && ActiveAgent::Dashboard.multi_tenant?
50
- traces
51
- end
52
-
53
- def calculate_metrics
54
- traces_24h = ActiveAgent::Dashboard.trace_model.where("created_at > ?", 24.hours.ago)
55
- runs_24h = AgentRun.where("created_at > ?", 24.hours.ago)
56
-
57
- if current_owner && ActiveAgent::Dashboard.multi_tenant?
58
- traces_24h = traces_24h.for_account(current_owner)
59
- runs_24h = runs_24h.joins(:agent).where(agents: { account_id: current_owner.id })
60
- end
61
-
62
- {
63
- total_agents: fetch_agents.count,
64
- active_agents: fetch_agents.active_agents.count,
65
- total_runs_24h: runs_24h.count,
66
- successful_runs_24h: runs_24h.successful.count,
67
- failed_runs_24h: runs_24h.failed_runs.count,
68
- total_traces_24h: traces_24h.count,
69
- total_tokens_24h: traces_24h.sum(:total_input_tokens).to_i + traces_24h.sum(:total_output_tokens).to_i,
70
- avg_duration_ms: runs_24h.where.not(duration_ms: nil).average(:duration_ms)&.round || 0
71
- }
72
- end
73
-
74
- def serialize_agents(agents)
75
- agents.map do |agent|
76
- {
77
- id: agent.id,
78
- name: agent.name,
79
- slug: agent.slug,
80
- description: agent.description,
81
- provider: agent.provider,
82
- model: agent.model,
83
- status: agent.status,
84
- presetType: agent.preset_type,
85
- versionCount: agent.version_count,
86
- updatedAt: agent.updated_at.iso8601
87
- }
88
- end
89
- end
90
-
91
- def serialize_runs(runs)
92
- runs.map(&:summary)
93
- end
94
-
95
- def serialize_traces(traces)
96
- traces.map do |trace|
97
- {
98
- id: trace.id,
99
- traceId: trace.trace_id,
100
- displayName: trace.display_name,
101
- status: trace.status,
102
- duration: trace.formatted_duration,
103
- tokens: trace.formatted_tokens,
104
- timestamp: trace.timestamp&.iso8601
105
- }
106
- end
107
- end
108
-
109
- def current_user_props
110
- return nil unless current_user
111
-
112
- {
113
- id: current_user.id,
114
- name: current_user.try(:display_name) || current_user.try(:name) || current_user.try(:email),
115
- email: current_user.try(:email)
116
- }
117
- end
118
-
119
- def current_account_props
120
- return nil unless current_owner && ActiveAgent::Dashboard.multi_tenant?
121
-
122
- {
123
- id: current_owner.id,
124
- name: current_owner.try(:name)
125
- }
126
- end
127
- end
128
- end
129
- end
@@ -1,123 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveAgent
4
- module Dashboard
5
- # Controller for viewing telemetry traces.
6
- #
7
- # Provides:
8
- # - List view with filtering and pagination
9
- # - Detail view with span timeline
10
- # - Metrics overview
11
- # - Live updates via Turbo Streams
12
- class TracesController < ApplicationController
13
- def index
14
- @traces = fetch_traces
15
- @metrics = calculate_metrics
16
-
17
- respond_to do |format|
18
- format.html
19
- # turbo-rails is optional; responding to an unregistered MIME
20
- # type raises in host apps without it.
21
- format.turbo_stream if turbo_stream_available?
22
- end
23
- end
24
-
25
- def show
26
- @trace = scoped_traces.find(params[:id])
27
- end
28
-
29
- def metrics
30
- @metrics = calculate_metrics
31
- @agent_stats = agent_statistics
32
- @time_series = time_series_data
33
-
34
- respond_to do |format|
35
- format.html
36
- format.turbo_stream if turbo_stream_available?
37
- end
38
- end
39
-
40
- private
41
-
42
- def turbo_stream_available?
43
- Mime::Type.lookup_by_extension(:turbo_stream).present?
44
- end
45
-
46
- # All queries honor the configured trace model and, in multi-tenant
47
- # mode, the current owner's account (for_account no-ops otherwise).
48
- #
49
- # In multi-tenant mode an unresolvable owner returns nothing rather
50
- # than falling through: current_owner degrades to current_user when
51
- # current_account_method is unset, which would scope by a user id
52
- # against account_id — another tenant's traces.
53
- def scoped_traces
54
- model = ActiveAgent::Dashboard.trace_model
55
- return model.none if ActiveAgent::Dashboard.multi_tenant? && current_owner.nil?
56
-
57
- model.for_account(current_owner)
58
- end
59
-
60
- def fetch_traces
61
- traces = scoped_traces.recent
62
-
63
- traces = traces.for_agent(params[:agent]) if params[:agent].present?
64
- traces = traces.with_errors if params[:status] == "error"
65
- traces = traces.for_service(params[:service]) if params[:service].present?
66
-
67
- if params[:start_date].present? && params[:end_date].present?
68
- traces = traces.for_date_range(
69
- Time.parse(params[:start_date]),
70
- Time.parse(params[:end_date])
71
- )
72
- end
73
-
74
- traces.limit(params[:limit] || 50)
75
- end
76
-
77
- def calculate_metrics
78
- traces = scoped_traces.where(
79
- "created_at > ?", 24.hours.ago
80
- )
81
-
82
- {
83
- total_traces: traces.count,
84
- total_tokens: traces.sum(:total_input_tokens) + traces.sum(:total_output_tokens),
85
- avg_duration_ms: traces.average(:total_duration_ms)&.round(2) || 0,
86
- error_rate: calculate_error_rate(traces),
87
- unique_agents: traces.distinct.count(:agent_class)
88
- }
89
- end
90
-
91
- def calculate_error_rate(traces)
92
- total = traces.count
93
- return 0.0 if total.zero?
94
-
95
- errors = traces.with_errors.count
96
- ((errors.to_f / total) * 100).round(2)
97
- end
98
-
99
- def agent_statistics
100
- scoped_traces
101
- .where("created_at > ?", 24.hours.ago)
102
- .group(:agent_class)
103
- .select(
104
- "agent_class",
105
- "COUNT(*) as trace_count",
106
- "SUM(total_input_tokens + total_output_tokens) as total_tokens",
107
- "AVG(total_duration_ms) as avg_duration",
108
- "SUM(CASE WHEN status = 'ERROR' THEN 1 ELSE 0 END) as error_count"
109
- )
110
- end
111
-
112
- def time_series_data
113
- scoped_traces
114
- .where("created_at > ?", 1.hour.ago)
115
- .group_by_minute(:created_at)
116
- .count
117
- rescue NoMethodError
118
- # Fallback if groupdate gem not available
119
- {}
120
- end
121
- end
122
- end
123
- end
@@ -1,56 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveAgent
4
- module Dashboard
5
- # Background job for executing agent runs.
6
- #
7
- # Handles the actual execution of an agent with the given input,
8
- # updating the AgentRun record with results.
9
- #
10
- class AgentExecutionJob < ApplicationJob
11
- queue_as :default
12
-
13
- def perform(agent_run_id)
14
- run = AgentRun.find(agent_run_id)
15
- return if run.finished?
16
-
17
- run.update!(status: :running, started_at: Time.current)
18
-
19
- begin
20
- agent = run.agent
21
- result = execute_agent(agent, run.input_prompt, **(run.input_params || {}))
22
-
23
- run.update!(
24
- output: result[:output],
25
- output_metadata: result[:metadata],
26
- status: :complete,
27
- completed_at: Time.current,
28
- duration_ms: ((Time.current - run.started_at) * 1000).to_i,
29
- input_tokens: result.dig(:usage, :input_tokens),
30
- output_tokens: result.dig(:usage, :output_tokens),
31
- total_tokens: result.dig(:usage, :total_tokens)
32
- )
33
- rescue => e
34
- run.update!(
35
- status: :failed,
36
- completed_at: Time.current,
37
- error_message: e.message,
38
- error_backtrace: e.backtrace&.first(10)&.join("\n")
39
- )
40
- end
41
- end
42
-
43
- private
44
-
45
- def execute_agent(agent, input_prompt, **params)
46
- # TODO: Build and execute actual ActiveAgent::Base subclass
47
- # For now, return mock data
48
- {
49
- output: "Executed: #{input_prompt}",
50
- metadata: { provider: agent.provider, model: agent.model },
51
- usage: { input_tokens: 100, output_tokens: 200, total_tokens: 300 }
52
- }
53
- end
54
- end
55
- end
56
- end
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveAgent
4
- module Dashboard
5
- # Base class for all Dashboard engine jobs.
6
- class ApplicationJob < ActiveJob::Base
7
- # Retry failed jobs
8
- retry_on StandardError, wait: :polynomially_longer, attempts: 3
9
-
10
- # Discard jobs for records that no longer exist
11
- discard_on ActiveRecord::RecordNotFound
12
- end
13
- end
14
- end
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveAgent
4
- module Dashboard
5
- # Background job for cleaning up sandbox environments.
6
- #
7
- # Removes containers, Cloud Run jobs, or other resources
8
- # when a sandbox session expires.
9
- #
10
- class SandboxCleanupJob < ApplicationJob
11
- queue_as :default
12
-
13
- def perform(sandbox_session_id)
14
- session = SandboxSession.find_by(id: sandbox_session_id)
15
- return unless session
16
-
17
- cleanup_sandbox(session)
18
- end
19
-
20
- private
21
-
22
- def cleanup_sandbox(session)
23
- case ActiveAgent::Dashboard.sandbox_service
24
- when :cloud_run
25
- cleanup_cloud_run(session)
26
- when :kubernetes
27
- cleanup_kubernetes(session)
28
- else
29
- cleanup_local(session)
30
- end
31
- end
32
-
33
- def cleanup_local(session)
34
- # Local mode: Nothing to clean up
35
- Rails.logger.info "[ActiveAgent::Dashboard] Cleaned up local sandbox: #{session.session_id}"
36
- end
37
-
38
- def cleanup_cloud_run(session)
39
- # TODO: Implement Cloud Run cleanup
40
- Rails.logger.info "[ActiveAgent::Dashboard] Would clean up Cloud Run job: #{session.cloud_run_job_id}"
41
- end
42
-
43
- def cleanup_kubernetes(session)
44
- # TODO: Implement Kubernetes cleanup
45
- Rails.logger.info "[ActiveAgent::Dashboard] Would clean up Kubernetes pod: #{session.cloud_run_job_id}"
46
- end
47
- end
48
- end
49
- end
@@ -1,65 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveAgent
4
- module Dashboard
5
- # Background job for provisioning sandbox environments.
6
- #
7
- # Creates isolated execution environments (Docker, Cloud Run, etc.)
8
- # for running agents with tools.
9
- #
10
- class SandboxProvisionJob < ApplicationJob
11
- queue_as :default
12
-
13
- def perform(sandbox_session_id)
14
- session = SandboxSession.find(sandbox_session_id)
15
- return unless session.provisioning?
16
-
17
- begin
18
- result = provision_sandbox(session)
19
-
20
- session.mark_ready!(
21
- sandbox_url: result[:url],
22
- sandbox_job_id: result[:job_id]
23
- )
24
- rescue => e
25
- session.update!(
26
- status: :failed,
27
- error_message: e.message
28
- )
29
- end
30
- end
31
-
32
- private
33
-
34
- def provision_sandbox(session)
35
- case ActiveAgent::Dashboard.sandbox_service
36
- when :cloud_run
37
- provision_cloud_run(session)
38
- when :kubernetes
39
- provision_kubernetes(session)
40
- else
41
- provision_local(session)
42
- end
43
- end
44
-
45
- def provision_local(session)
46
- # Local mode: No actual provisioning needed
47
- # The sandbox runs in the same process or via Docker
48
- {
49
- url: "http://localhost:#{3000 + session.id}",
50
- job_id: "local-#{session.session_id}"
51
- }
52
- end
53
-
54
- def provision_cloud_run(session)
55
- # TODO: Implement Cloud Run provisioning
56
- raise NotImplementedError, "Cloud Run provisioning not yet implemented in engine"
57
- end
58
-
59
- def provision_kubernetes(session)
60
- # TODO: Implement Kubernetes provisioning
61
- raise NotImplementedError, "Kubernetes provisioning not yet implemented in engine"
62
- end
63
- end
64
- end
65
- end
@@ -1,86 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveAgent
4
- # Processes telemetry traces received from ActiveAgent clients.
5
- #
6
- # This job handles the asynchronous processing of trace data to avoid
7
- # blocking the ingestion endpoint. It:
8
- # - Creates TelemetryTrace records for each trace
9
- # - Updates aggregate statistics
10
- # - Handles any errors gracefully
11
- #
12
- # @example Local mode
13
- # ActiveAgent::ProcessTelemetryTracesJob.perform_later(
14
- # traces: [...],
15
- # sdk_info: { name: "activeagent", version: "0.5.0" },
16
- # received_at: "2024-01-15T10:30:00Z"
17
- # )
18
- #
19
- # @example Multi-tenant mode
20
- # ActiveAgent::ProcessTelemetryTracesJob.perform_later(
21
- # account_id: 1,
22
- # traces: [...],
23
- # sdk_info: { name: "activeagent", version: "0.5.0" },
24
- # received_at: "2024-01-15T10:30:00Z"
25
- # )
26
- #
27
- class ProcessTelemetryTracesJob < ::ActiveJob::Base
28
- queue_as :default
29
-
30
- # Maximum traces to process in a single job to avoid memory issues
31
- MAX_TRACES_PER_JOB = 100
32
-
33
- def perform(account_id: nil, traces:, sdk_info:, received_at:)
34
- account = resolve_account(account_id)
35
-
36
- # In multi-tenant mode, require an account
37
- if ActiveAgent::Dashboard.multi_tenant? && account.nil?
38
- Rails.logger.warn("[ProcessTelemetryTracesJob] Skipping traces - no valid account")
39
- return
40
- end
41
-
42
- # Process in bounded slices; anything beyond the cap is re-enqueued
43
- # rather than silently dropped (the client already received 202).
44
- remainder = traces.drop(MAX_TRACES_PER_JOB)
45
- traces = traces.take(MAX_TRACES_PER_JOB)
46
- if remainder.any?
47
- self.class.perform_later(
48
- account_id: account_id, traces: remainder,
49
- sdk_info: sdk_info, received_at: received_at
50
- )
51
- end
52
-
53
- traces.each do |trace|
54
- process_trace(trace, sdk_info, account)
55
- rescue StandardError => e
56
- Rails.logger.error(
57
- "[ProcessTelemetryTracesJob] Failed to process trace #{trace['trace_id']}: " \
58
- "#{e.class} - #{e.message}"
59
- )
60
- end
61
- end
62
-
63
- private
64
-
65
- def resolve_account(account_id)
66
- return nil unless ActiveAgent::Dashboard.multi_tenant?
67
- return nil unless account_id
68
-
69
- account_class = ActiveAgent::Dashboard.account_class.constantize
70
- account_class.find_by(id: account_id)
71
- end
72
-
73
- def process_trace(trace, sdk_info, account)
74
- model = ActiveAgent::Dashboard.trace_model
75
-
76
- # Build uniqueness scope
77
- scope = model.where(trace_id: trace["trace_id"])
78
- scope = scope.where(account: account) if ActiveAgent::Dashboard.multi_tenant? && account
79
-
80
- # Skip if trace already exists (idempotency)
81
- return if scope.exists?
82
-
83
- model.create_from_payload(trace, sdk_info, account: account)
84
- end
85
- end
86
- end