Dashboard Agent V1 — chat, reports, Investigate, Watch - #4418
Draft
kathiekiwi wants to merge 260 commits into
Draft
Dashboard Agent V1 — chat, reports, Investigate, Watch#4418kathiekiwi wants to merge 260 commits into
kathiekiwi wants to merge 260 commits into
Conversation
…counters entryOrderKey returns a string built with BigInt math so ordering stays correct at real epoch magnitudes. Odometer keys are namespaced by definition name. The consumer reports null lag for a missing consumer group instead of 0, and empty gauge values parse as NaN rather than 0.
…ng order keys The wait-time quantile materialized view now excludes wait_ms = 0 rows so it matches the count aggregation. order_key accepts a string or a number. Migration comments no longer contain semicolons that split the migration into invalid statements.
…rride The queues list tolerates a metrics query failure by rendering without metrics and logging a warning. UsageSparkline renders its total override even when every bucket is zero. The queue detail page returns 404 and its loader skips the metrics query when the feature flag is off. The seed script validates bucket size and only writes ClickHouse against a local host.
A bucket-led ORDER BY DESC combined with fillGaps emitted an ascending WITH FILL (positive step, ascending bounds), which produces invalid or empty fills. Skip the gap-fill rewrite for descending orders and let the plain descending query stand. Adds a DESC fillGaps test.
Packs the stream sequence with a 1e6 factor (was 1e5) so up to 1M entries per millisecond per shard fit before a seq could spill into the next millisecond's range, far above what a single Redis stream can produce. ms*1e6 stays within UInt64. Also fixes the webapp mapping test that still expected a numeric order_key after the switch to a BigInt-derived string.
The queues list and queue detail pages now use the shared TimeFilter (any preset period or a custom date range) and everything on the page follows it: header tiles, per queue metric columns, charts, and stats. The custom period buttons, hand rolled chart cards, and duplicated metric fetch loops are replaced by the ChartCard and Chart primitives, UsageSparkline, and a shared useMetricResourceQuery hook. The ClickHouse list queries take an explicit end bound so fixed ranges query only their window.
Queries using deltaSumTimestampMerge failed with an unknown function error, which broke the queue detail stats and the started counts on the built in Queues dashboard.
The queues list header tiles now render the same line chart, grid, and tooltip as the rest of the metrics charts instead of a row sparkline, with the headline value in the tile header. The env saturation tile draws the environment concurrency limit and burst limit as labeled reference lines. Chart tooltips keep a gap between the series label and the value, and the shared line chart gains showDots and referenceLines options.
Adds an Allocation tab to the Queues page (behind the queue metrics UI flag): overview cards, a burst-aware capacity bar showing each queue allocation and its live usage in a distinct color, an inline-editable limits table with per-queue locks, load-weighted auto-balance, and a review dialog that bulk-applies limits as overrides through the existing concurrency system. The queue list now defaults to Busiest ordering (with Backlog and Name options). ClickHouse ranks queues by activity over the last 15 minutes and returns just the requested page of names, so the cost per page is one small aggregate regardless of environment size; idle queues follow in name order and any failure falls back to name ordering. The classic page keeps plain name order.
The fallback WHERE injection only targeted the top-level SELECT, so a query shaped as an outer aggregation over a FROM subquery failed to compile: the time column only exists inside the subquery. Descend into the subquery so the fallback lands next to the table reference.
Adds two rollups fed from the raw landing table: a per-queue 5-minute tier and an environment-level 1-minute tier (gauges plus TDigest wait quantiles). Ranking now reads the 5m tier and returns the page and the ranked total in one windowed query instead of two scans. The 5m materialized view reads raw rather than cascading off the 10s table: deltaSumTimestamp states hold a single first/last segment, so merging states in an MV's hash-ordered GROUP BY double-counts bridging spans. For the same reason the env tier carries no counter columns, and env-wide counter totals must group by queue before summing.
The built-in queues dashboard's enqueued vs started chart merged counter states across queues, which mixes unrelated cumulative counters and returns wrong totals; it now merges per queue and sums outside. Env header tiles and saturation charts read the environment rollup, so their cost no longer scales with queue count, and coarse-bucket ranges are served from the 5m rollup automatically. Queue list ranking runs as one query, time bounds are aligned to the bucket grid, and repeated auto-refresh reads share ClickHouse query-cache entries.
… rollup The env rollup's win comes from dropping the queue dimension, not from coarser buckets: row count is queue-independent (~8640/day/env), so full 10-second granularity stays cheap at any range. Env header tiles and saturation charts now resolve short-range detail exactly like the per-queue charts, and the current-value tiles read the latest 10-second bucket instead of a minute-wide one.
The simulator's --reset only cleared the raw and 10s tables, leaving stale rows in the 5m and env rollups. It also force-merges the rollups after seeding so current-value widgets read cleanly.
Counter events now emit per queue and op odometer readings with a seeded zero baseline, matching the production emitter, so throughput and started counts reconstruct from simulated data instead of reading zero. Scenario switches prune the previous scenario's queues, a --project flag seeds each scenario into its own project for side-by-side design review, and a new many-queues scenario covers pagination and relevance ranking with one runaway queue, a busy head, a bursty middle, and a sparse tail. Adds --help.
A --usage flag stages plausible running counts in the local run-queue Redis for the seeded queues, so the list's Running column and the Allocation tab's usage bars have data without the run engine. Staged state is reconciled on every run: present with --usage, cleared without. Local Redis hosts only.
The tail query's exclusion list overwrote the search's name filter via object spread, so searching while sorted by activity showed unrelated queues past the ranked head. Combine the conditions with AND instead.
…ot ready Without a readiness guard, every fire-and-forget emit during a metrics Redis outage queued a command in ioredis's in-memory offline queue until rejection. Metrics are loss-tolerant by design, so drop instead; waitUntilReady() lets embedders await the initial connect.
The allocation view keeps manual limit edits, the review dialog, and bulk apply. The one-shot auto-balance button is removed (and the row locks whose only purpose was protecting queues from it); a policy-driven approach can replace it if rebalancing returns.
deltaSumTimestamp states are kept per queue, and merging them across queues silently returns wrong totals, on the dashboard and the public query API alike. Columns can now declare a mergeGroupKey, and the compiler rejects queries that merge such a column without grouping by that key or pinning it to a single value. The error names the column, explains the failure, and includes a corrected example query.
…e calls Short parameter lists on quantilesMerge and quantilesTDigestMerge do execute (the state layout is parameter independent, verified on both ClickHouse versions we run), but they rely on undocumented leniency and make the result-array indexes mean different quantiles per call site. Every merge now uses the stored four-quantile list with indexes re-pointed accordingly; returned values are unchanged.
The consumer retries a failed batch with the same insert deduplication token, and deduplicate_blocks_in_dependent_materialized_views re-runs the materialized views on a source-deduplicated insert, relying on each target table's dedup window to drop the duplicate. Only the raw table had one, so retries appended extra copies into every aggregate tier, silently inflating sums and quantiles. All three targets now set non_replicated_deduplication_window, with a regression test inserting the same batch three times.
… cluster slots, and stream caps Counter readings for names past the per-env cardinality cap are dropped instead of merging unrelated odometers under the overflow label (gauges still flow). The odometer key now shares the stream's shard hash tag so the INCR plus XADD script stays in one Cluster slot. The counter stream cap defaults lower when the stream shares the run-queue Redis. The per-bucket counter boundary undercount is documented on the delta columns.
The poll only ran while the panel was closed, so a wake in any chat other than the visible one announced nothing until the panel closed.
The toast still shows for every wake; the dot only counts wakes the user isn't already looking at.
…e orders Investigate before Watch
…e-Pause, secondary Investigate there
…t queries A chart block's query runs after the turn, so the model never sees its error — a camelCase column produced a permanently broken chart.
…the prompt's example
A card opened by a turn that died, or opened for a later turn that never came (a wake's narration does this), sat in_progress forever — a spinner on the card and an Investigating marker in History. A new sweep on the existing dashboard-agent cron settles anything untouched for 30 minutes to inconclusive, with the same wording the turn-level settle uses, guarded on the row still being in_progress so a live turn always wins.
A chart block's TRQL query used to run only in the panel, after the turn, so a bad query left a broken chart the model never learned about. render_view now runs each chart query through the query API first and fails by name with the query error, so the model fixes it in the same turn. The rows are discarded — the panel stays the runner. Skipped when the turn has no delegated token or the validation request itself fails.
Markdown renderers won't link an unknown scheme, so a cited trigger:// target rendered dead. Prose links now rewrite through the panel's resolver; while unresolved they degrade to their plain label.
…s at something worth monitoring
…headline is an unresolved recurring error
Contributor
Observability mapAs of 18/100 over 422 measured of 437 entry points (base 18, no change) What this PR changed
FIX FIRST
AUDIT 3 of 49 sensitive mutations record an actor. 46 without one. What the score is made ofReport only, nothing here gates the merge. The rules and their reasons: internal-packages/observability-map/README.md. |
The Badge primitive's small variant paints a blue tinted chip on system themes, which overrode the severity/confidence tones — Degraded and Medium confidence rendered blue instead of amber.
New "actions" view block: a row of 1-3 buttons the model may emit. A watch action opens the watch configuration card pre-filled; ask sends the labelled question as the user's next message; a navigate target that doesn't parse is dropped at render time, as on chart actions.
…ger button Agent logo instead of the indigo bubble, the button's own surface (charcoal on dark, white on light) and softened green border.
Third ToastUI variant: success's layout with the agent glyph and the Ask Trigger border. The wake toast drops its Callout composition for it.
…allery section id The wake toast moved to the standard toast's agent status, leaving the Callout variant with no consumer.
The root already mounts one; a fired toast rendered in both and the two copies stacked.
Sonner stamps data-theme="light" (its default) on the toast list; since the theme system remaps tokens by that attribute, every custom toast rendered light regardless of the page's theme.
…ays the side panel
…ast surface matches Ask Trigger on dark themes A wake read on screen before the next poll never toasted. The toast list is now recent deliveries (15 min, id-deduped client-side); the dot still counts unread only.
The wake seeded the card and said it had started looking, then nothing ran it — the findings were left to a turn only the user could start. The watcher now reports a delivered consented wake to the webapp, which mints the same delegated user-actor token a turn gets and sends a `watch.investigate` action into the chat; the agent conducts a real investigating turn on that card and delivers the findings as its own message. Best-effort throughout: nothing here can retry or invalidate the wake.
Chats belong to (organization, user); several queries enforced only the user, so a user's own chat from another org could be opened, renamed, pinned or appended to through a different org's route.
Crossing orgs re-renders the layout without remounting, so the previous org's open chat and history lingered in the panel.
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.
An AI assistant in a side panel on every dashboard page, behind the dashboard-agent feature flag. It reads runs, errors, queues, deploys and health through the public API (read-only, delegated user token), answers with rich cards, and can keep watching things after the conversation ends.
What's inside
@internal/dashboard-agent-contracts(trigger:// URI grammar, intents, watch specs, block envelope), investigations + watches tables, head-start reliability fix, eval sample-rate gate.get_reportrenders the deterministic health report as a card (metric grid, sparklines, Next steps button row); stale telemetry is flagged and never trusted for advice.db:seed:agent-examples, with--heartbeat/--degrade/--recoverfor demos).How to review
GUIDEBOOK.md — 10-minute local setup and a hands-on walkthrough of every case. Component gallery at
/storybook/agent-ui.Notes
canAccessDashboardAgent; no behavior change with the flag off.--heartbeatmode is a review-stand crutch and will be removed before merge.