Make the dashboard engine (local dev console) work out of the box#344
Merged
Conversation
The dashboard engine shipped in 1.0.3 was not usable in host apps without
manual workarounds. This makes it work out of the box:
- Engine.find_root now returns the dashboard directory (defined before
isolate_namespace materializes config), so app/{models,controllers,jobs,
views} and config/routes.rb land on host apps' load paths; the engine is
required eagerly with Rails since lazily-autoloaded engines miss
initializer collection
- Routes trimmed to shipped controllers (traces, metrics, api/traces
ingest matching LOCAL_ENDPOINT_PATH); removed routes to never-shipped
controllers that 500'd; engine root renders traces#index and
dashboard#index redirects in ERB mode instead of rendering a missing view
- Reporter local_storage mode: stringify symbol-keyed tracer payloads
(previously every locally-stored trace failed validation silently) and
honor ActiveAgent::Dashboard.trace_model overrides
- create_from_payload: count child spans as token source of truth —
instrumentation mirrors llm tokens onto the root span and totals were
exactly doubled
- Trace detail waterfall: compute relative span offsets from start_time
(start_time_relative_ms was never emitted; all bars rendered at 0), fix
clamp crash when a span starts at 100%
- turbo_stream responses gated on the MIME type being registered
(turbo-rails is optional; without it every dashboard page raised)
- Layout used an undefined active_agent_dashboard route proxy; engine
views use engine-local helpers
- Add Agent.for_owner scope (referenced by DashboardController, never
defined); cap synchronous ingest at 100 traces/request like the async job
- Docs: docs/framework/dashboard.md self-hosting guide (auth is nil by
default — documented loudly), README section, CHANGELOG; dashboard
engine test suite (test/dashboard/) run against the dummy app
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014h5Yw7jE62UyPPAF1h7Mr5
- Fail closed in production: the dashboard now returns 403 with setup instructions when authentication_method is unconfigured, instead of serving prompts/outputs/backtraces to the internet by default - ProcessTelemetryTracesJob re-enqueues traces beyond the 100-per-job cap instead of silently dropping them after the client got 202 - Remove for_owner scope added earlier on Dashboard::Agent — the shared ApplicationRecord base already defines it (more defensively); the new scope shadowed it - Installer post-install message no longer references the nonexistent active_agent:dashboard:seed task - Mark capture_bodies/redact_attributes as reserved (documented but not yet consumed) so the config surface is honest - CHANGELOG: place Unreleased under the Keep-a-Changelog preamble Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014h5Yw7jE62UyPPAF1h7Mr5
Product decision: the core framework stays free, but production observability and monitoring are the hosted platform's paid product (free workspaces get a low-volume trial). The engine keeps working exactly as before — this reframes README, CHANGELOG, and docs/framework/dashboard.md from "free self-hosted production observability" to "dev console while you build, platform for production", and the parity table now states the intended use of each. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014h5Yw7jE62UyPPAF1h7Mr5
…enerators Three gaps surfaced by building an example app against this branch: - Instrumented traces couldn't be correlated with anything: the tracer always generated its own trace_id, while the generation parameters (and e.g. solid_agent's persisted generations) carry prompt_options[:trace_id]. Instrumentation now mints/reuses prompt_options[:trace_id] and passes it to Telemetry.trace; the tracer honors a caller-supplied trace_id and generates one only as fallback. - Reporter#flush was fire-and-forget: the batch went to a detached thread nothing joined, so short-lived processes (rails runner, jobs, rolling restarts) silently dropped traces. flush now waits for the send to complete, shutdown joins in-flight send threads, and the railtie installs an at_exit shutdown when telemetry is enabled. - Dashboard install generator migrations used t.jsonb, which raises on SQLite — Rails' default database and the primary dev-console audience. Templates now use t.json (works on both SQLite and Postgres). New test/dashboard/telemetry_correlation_test.rb covers: caller-supplied trace ids, generated fallback ids, deterministic flush (no sleeps), shutdown flushing the buffer, and an instrumented generation storing its trace under the prompt_options UUID (dashed-UUID format proves the id flowed from prompt_options rather than the tracer's hex generator). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014h5Yw7jE62UyPPAF1h7Mr5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The dashboard engine that shipped in 1.0.3 is the local dev console counterpart to the activeagents.ai cloud platform, but it was undocumented and not actually usable in a host app: its classes never landed on autoload paths,
local_storagetelemetry silently persisted nothing, most engine routes pointed at controllers that don't exist, token totals were exactly doubled, and every dashboard page 500'd in apps without turbo-rails. The activeagents.ai platform (which runs this engine multi-tenant — see activeagents/activeagents#90) needed manualrequireworkarounds and app-side patches for all of this.This PR makes the engine work out of the box for v2 and the cloud launch. All fixes were verified two ways: a new engine test suite against the gem's dummy app, and the full activeagents.ai platform test suite (166 runs) run against this branch with every workaround removed.
What
Engine boot (the big one)
Engine.find_rootnow returns the dashboard directory, and is defined beforeisolate_namespace(which materializesconfigviaroutes, freezing the root). Previously onlyEngine.rootwas overridden, which Rails never consults for load paths — soActiveAgent::TelemetryTrace,ProcessTelemetryTracesJob, the ingest controller, views, and the engine's routes file were all invisible to host apps.lib/active_agent.rb). It was previously behindautoload :Dashboard, and an engine defined after the host app collects railtie initializers never registers its paths.Routes / controllers
api/v1/*) pointed at controllers that don't exist in the gem and produced 500s; the models remain for host apps that use them. A route↔controller consistency test now guards this.POST /api/traces, matchingTelemetry::Configuration::LOCAL_ENDPOINT_PATH(the routes previously declaredapi/v1while the controller lives atApi::TracesControllerand the config pointed atapi/traces— three different answers).dashboard#indexredirects in ERB mode instead of rendering a view that doesn't exist (and no longer queries Agent/AgentRun tables that telemetry-only installs don't have). Added theAgent.for_ownerscopeDashboardControllerreferenced but nothing defined.Telemetry correctness
local_storagemode:Tracer#build_trace_payloademits symbol keys,create_from_payloadreads string keys — every locally stored trace failedtrace_idpresence validation inside a rescued block, i.e. the flagship local mode persisted nothing. The reporter now deep-stringifies and honorsActiveAgent::Dashboard.trace_modeloverrides instead of hardcoding the class.create_from_payloadsummed all spans, so every trace reported exactly 2× tokens. Child spans are now the source of truth when they carry token data (root-only traces still count the root).ProcessTelemetryTracesJob::MAX_TRACES_PER_JOB.Views
start_time(the template readstart_time_relative_ms, which no emitter produces — every bar rendered at 0ms). Also fixes aclampcrash for spans starting at 100%.turbo_streamresponses are gated on the MIME type being registered — turbo-rails is not a dependency, and without it every dashboard page raised.active_agent_dashboard.route proxy (the mount helper isactive_agent); engine views now use engine-local helpers.Docs & tests
docs/framework/dashboard.md: install, what you get, authentication (none by default — stated loudly), remote ingestion, multi-tenant mode, and the dev-console ↔ cloud relationship table. README gains a Dev Console & Observability section. CHANGELOG entry under Unreleased.test/dashboard/suite (16 tests): payload normalization/dedup, engine paths + autoload, route↔controller consistency, page rendering,LOCAL_ENDPOINT_PATHingest round-trip, and symbol-keyed reporter persistence — run against the dummy app (now mounts the engine).Verification
test/dashboard/: 16 runs, 0 failures/errors (rails8 gemfile)OPENAI_API_KEY-style credential errors in this environment, not regressions)Notes for reviewers
requireworkarounds and its app-side token-dedup override.🤖 Generated with Claude Code
https://claude.ai/code/session_014h5Yw7jE62UyPPAF1h7Mr5
Generated by Claude Code