THE MEDIA CUSTOMS LAUNCH CONTROL Google Cloud Agent Builder Powered by Grafana
Home Agent Studio
Grafana resources

What this system keeps in Grafana

Every dashboard the crew builds, every panel on it, every metric series and log stream it writes, the alert rules that wake the Remediator, and how each write actually travels. Read from the definitions the Publisher provisions from, not from a list somebody typed.

9 dashboards 40 panels 9 metric series 3 log streams 3 alert rules 8 write operations

The stack one folder, two datastores
WhatWhereHolds
Grafana Cloud https://dreamystairs2355.grafana.net Folder Customs uid customs, which every dashboard and all three alert rules are provisioned into.
Mimir grafanacloud-prom the four metric series above, pushed over OTLP
via OTLP HTTP, not the API
Loki grafanacloud-logs the three line kinds above
via the Loki push API
Public views 2 shared customs-overview, customs-timeline
Grafana Cloud answers every embed attempt with an enforcing frame-ancestors policy, so panels inside this console are server-rendered PNGs unless the viewer below is deployed.
Embeddable viewer deployed A grafana-oss instance holding no data of its own: its Loki and Mimir datasources read the same stores. Where it is running, the console frames live panels instead of rendering pictures of them.
Dashboards 9 boards · 40 panels
Customs: Findings Every finding the adjudicators wrote, as raw log lines and as a table. The line body is the finding JSON, so rule, class, severity, citation and the sourced flag are all queryable without a join back to the run store. customs-findings 2
PanelKindReadsQuery
Finding stream logs Loki {app="customs", kind!="observation", asset=~"$asset"}
Findings table Loki {app="customs", kind!="observation", asset=~"$asset"} | json severity="severity", sourced="sourced", citation="citation_ref", rationale="rationale", status="status", t_start="t_start"

Seen in Not embedded: the raw finding stream, for reading in Grafana itself

The grid Squares, not bars: the same observation stream the lanes dashboard draws, as a status history -- one square per dimension per interval, coloured by worst severity. Built to sit under the console's own grid, whose axes carry the scene stills and the taxonomy icons Grafana cannot draw. customs-grid 1
PanelKindReadsQuery
Occurrences per scene status-history Loki max by (dimension) (max_over_time({app="customs", kind=~"observation|watched", asset="$asset", dimension=~"$dim"} | json | run_id="$run" | unwrap max_severity [$__interval]))

Seen in Timeline tab, as the scene-by-dimension grid the console draws its own axes around

Customs: Campaign History Across assets and time. Which assets tripped which rules, and how often, over the last thirty days. customs-history 3
PanelKindReadsQuery
Findings per asset, rolling 24 hours timeseries Loki sum by (asset) (count_over_time({app="customs", kind!="observation", asset=~".+"}[1d]))
Every finding, newest first table Loki {app="customs", kind!="observation"} | json severity="severity", sourced="sourced", citation="citation_ref", rationale="rationale", status="status", t_start="t_start"
The read path text panel text

Seen in Archive, one live panel for the whole instance

Customs: Intelligence Every clearance this instance has performed, read across runs rather than one at a time. customs-insight 17
PanelKindReadsQuery
Every commercial, at its worst moment bargauge Loki sort_desc(max by (asset) (max_over_time({app="customs", kind="finding"} | json | unwrap severity [$__range])))
Findings stat Loki sum(count_over_time({app="customs", kind="finding"}[$__range]))
Commercials judged stat Loki count(sum by (asset) (count_over_time({app="customs", kind="observation"}[$__range])))
Markets exercised stat Loki count(sum by (market) (count_over_time({app="customs", kind="finding"}[$__range])))
Rules tripped stat Loki count(sum by (rule_id) (count_over_time({app="customs", kind="finding"}[$__range])))
Frames observed stat Loki sum(count_over_time({app="customs", kind="observation"}[$__range]))
Adjudications stat Loki sum(count_over_time({app="customs", kind="verdict"}[$__range]))
What gets objected to, every run barchart Loki sort_desc(sum by (dimension) (count_over_time({app="customs", kind="finding"}[$__range])))
Which markets object most barchart Loki sort_desc(sum by (market) (count_over_time({app="customs", kind="finding"}[$__range])))
Dimension against market: where the friction is table Loki sum by (dimension, market) (count_over_time({app="customs", kind="finding"}[$__range]))
How severe, across every finding histogram Loki sum by (rule_id, market, asset) (max_over_time({app="customs", kind="finding"} | json | unwrap severity [$__range]))
Worst severity per market, against the blocking line bargauge Loki sort_desc(max by (market) (max_over_time({app="customs", kind="finding"} | json | unwrap severity [$__range])))
Market status over time, from Mimir state-timeline Mimir max by (market) (customs_market_status)
Objections per day, by class timeseries Loki sum by (klass) (count_over_time({app="customs", kind="finding"}[1d]))
Adjudications: triggered against cleared barchart Loki sort_desc(sum by (verdict) (count_over_time({app="customs", kind="verdict"}[$__range])))
The rules that fire everywhere table Loki topk(15, sum by (rule_id, klass, market) (count_over_time({app="customs", kind="finding"}[$__range])))
The finding stream itself logs Loki {app="customs", kind="finding"} | json

Seen in Intelligence tab: the whole board in one frame, plus its dimension and market rankings as solo panels with the console's own icon axis down the side

Where the problems are Every observation dimension across a run's own mapped clock. Coloured by the worst severity any market attached to it, so a flat lane is a category that was looked at and cleared. customs-lanes 1
PanelKindReadsQuery
Every category the analyst can see, across the film's own clock state-timeline Loki max by (dimension) (max_over_time({app="customs", kind="observation", asset="$asset"} | json | run_id="$run" | unwrap max_severity [$__interval]))

Seen in Launch board's lane strip, and every archive card's squares (rendered to PNG when the viewer is not deployed)

Customs: Market Detail One market, in full: its current clearance status, which rules are holding it, every finding with its citation, and what the finding classes mean. customs-market 4
PanelKindReadsQuery
Clearance status stat Mimir customs_market_status{asset=~"$asset", market="$market"}
Rules holding this market stat Mimir max by (rule_id) (customs_blocking{asset=~"$asset", market="$market"})
Findings in this market table Loki {app="customs", kind!="observation", asset=~"$asset", market="$market"} | json severity="severity", sourced="sourced", citation="citation_ref", rationale="rationale", status="status", t_start="t_start"
Class semantics and the guard text panel text

Seen in Not embedded: one market in full, for reading in Grafana itself

Customs: Clearance Overview Is this ad cleared to air, and where is it not. One tile per market, read from the current clock series customs_market_status. public customs-overview 5
PanelKindReadsQuery
Market clearance status stat Mimir customs_market_status{asset=~"$asset"}
Blocking findings by market bargauge Mimir count by (market) (customs_blocking{asset=~"$asset"})
Markets cleared stat Mimir count(customs_market_status{asset=~"$asset"} == 0)
Markets blocked stat Mimir count(customs_market_status{asset=~"$asset"} == 2)
Stage errors (a clearance tool that silently skips a shot is worse than one that admits it) stat Mimir sum(customs_stage_error{asset=~"$asset"})

Seen in Launch board, as a rendered panel and a live embed; public link on every board · open the public view

Customs: Remediation What was changed and what it closed. Findings and their remediations as Grafana annotations, plus the blocking metric falling away as fixes land. customs-remediation 3
PanelKindReadsQuery
Findings and remediations annolist panel text
How a remediation closes the loop text panel text
Blocking severity as fixes land timeseries Mimir max by (market, rule_id) (customs_blocking{asset=~"$asset"})

Seen in Not embedded: what was changed and what it closed, with the annotations on it

Customs: Timeline The centrepiece. A commercial is a time series and the timecode is the x-axis: video second n is written at wall clock t0 + n, so a panel pinned to [t0, t0 + duration] reads as the ad's own timecode. public customs-timeline 4
PanelKindReadsQuery
Risk by market across the timecode state-timeline Mimir max by (market) (last_over_time(customs_risk{asset=~"$asset"}[$__interval]))
Risk by observation dimension state-timeline Mimir max by (dimension) (last_over_time(customs_risk{asset=~"$asset", dimension!="none"}[$__interval]))
The same risk, second by second timeseries Mimir max by (market) (last_over_time(customs_risk{asset=~"$asset"}[$__interval]))
Reading this page text panel text

Seen in Launch board and the run's Timeline tab; public link · open the public view

Metric series, in Mimir pushed over OTLP on two different clocks

A commercial is a time series and the timecode is the x axis, so one of these is written on the film's own clock: video second n lands at wall second t0+n. The other three are stamped at the real clock, because an alert rule's evaluation window has to mean what it says. Alerting reads only the current-clock three; the timecode axis reads only the mapped one.

SeriesClockLabelsRead by
customs_risk mapped asset, market, dimension Timeline dashboard, the lane strip, the grid
one sample isthe worst severity in force at video second n, one sample per second of film, written at t0+n so the panel's x axis IS the timecode
customs_market_status current asset, market Clearance overview, and the alert that watches for a market going down
one sample is0 cleared, 1 at risk, 2 blocked: one market's answer right now
customs_blocking current asset, market, rule_id Overview, Market detail, Remediation, and the blocking alert
one sample isthe severity of one open, sourced, blocking finding; it drops to zero when a remediation is verified, which is what resolves the alert
customs_stage_error current asset, stage Overview, and the stage-error alert
one sample ishow many times one stage failed on this asset: a tool that silently skips a shot is worse than one that admits it
customs_collateral_drift current asset, market, rule_id, method the change record in the cutting room, and the mission feed
one sample isPSNR in dB inside an edited span, with the boxes the edit was about painted out of both files: how much of the shot changed that nobody asked to change. A patch leaves this high; an Omni rewrite re-renders the shot and leaves it far lower, which is the honest cost of a method that can fix a scene a patch cannot reach
gen_ai_client_token_usage current gen_ai.system, gen_ai.request.model, gen_ai.operation.name, gen_ai.token.type nothing in these dashboards yet: it is there for Grafana Cloud's AI Observability, and for the question 'what did this run cost in tokens'
one sample istokens in and out of one Gemini call, labelled by the job it was doing: analyst, adjudicator, citation. OpenTelemetry's GenAI conventions, written through the same OTLP path as everything else here
gen_ai_client_operation_duration_seconds current gen_ai.system, gen_ai.request.model, gen_ai.operation.name the same
one sample ishow long one model call took, by job
customs_spend_eur_total current the budget alert
one sample iswhat today's generative fixes have cost, in euro, written after every charge
customs_budget_remaining_eur current the budget alert, which pauses automatic remediation
one sample iswhat is left of the day's allowance: a staircase down, and the series the third alert rule watches so the loop that spends can be told to stop
Log streams, in Loki all three on the film's own clock
StreamLabelsOne line is
kind="finding" app, asset, market, klass, rule_id, dimension, kind the whole finding as JSON: rule, statute, citation, severity, the window it covers and whether the guard blocked it
kind="observation" app, asset, dimension, flagged, kind what the analyst saw at that second, whether any market minded, and the rules that fired if one did
kind="verdict" app, asset, market, dimension, verdict, kind every market's answer about every observation, including the noes: a finding says France objected, a verdict says Germany looked at the same frame and did not
Annotations
WrittenTagsNote
one per finding customs, asset, market, rule_id, id drawn across the span the finding covers, on the run's mapped clock
one per remediation customs, asset, market, rule_id, id, resolved written when a fix lands and the verifier confirms it
Alerting this is the loop, not a notification

An alert firing in Grafana is what starts a fix. The rule evaluates every 30 seconds in group customs, routes on team=customs to the contact point customs-webhook, and that contact point is a webhook pointed back at this service. The Remediator wakes on the POST. When the fix is verified the series drops to zero and Grafana resolves its own alert.

RuleFires onForInstance per
customs_blocking_finding
customs-blocking-finding
max by (asset, market, rule_id) (customs_blocking) >= 70
Blocking finding: asset {{ $labels.asset }} in market {{ $labels.market }} trips rule {{ $labels.rule_id }} at severity {{ $value }}
0s asset, market, rule_id
customs_market_at_risk
customs-market-at-risk
max by (asset, market) (customs_market_status) >= 1
Market not cleared: asset {{ $labels.asset }} in market {{ $labels.market }} is at {{ $value }} (1 at risk, 2 blocked)
0s asset, market
customs_budget_low
customs-budget-low
min(customs_budget_remaining_eur) <= 9.0
Generation budget down to {{ $value }} EUR: automatic remediation paused for the rest of the day
0s
How every write travels MCP where the tool exists, REST where it does not

The crew provisions its own folder, dashboards and alert rules at runtime through the official mcp-grafana server. Where release 1.1.0 has no write tool for an operation, it goes over the provisioning API instead and says so: an honest inventory beats a flattering one.

OperationMCP toolOver REST
ensure_folder create_folder POST /api/folders
ensure_dashboards update_dashboard POST /api/dashboards/db
ensure_alert_rules alerting_manage_rules GET|POST|PUT /api/v1/provisioning/alert-rules
the rule group's 30s evaluation interval always goes over REST (PUT /api/v1/provisioning/folder/{uid}/rule-groups/customs): no MCP tool in 1.1.0 sets it
ensure_contact_point no tool in 1.1.0 GET|POST|PUT /api/v1/provisioning/contact-points
alerting_manage_routing is read-only in 1.1.0
ensure_notification_policy no tool in 1.1.0 GET|PUT /api/v1/provisioning/policies
alerting_manage_routing is read-only in 1.1.0
enable_public no tool in 1.1.0 POST|GET|PATCH /api/dashboards/uid/{uid}/public-dashboards
no public-dashboard tool exists in mcp-grafana 1.1.0
render_png get_panel_image GET /render/d-solo/{uid}
query_history query_loki_logs GET /api/datasources/proxy/uid/grafanacloud-logs/loki/api/v1/query_range
How it reads back a system that only writes could have written to a file
CallWhat it doesUsed for
prom_range / prom_window / prom_instant PromQL against Mimir through the datasource proxy the board's own sparklines and the market room's lanes when the app draws them itself
loki_lines / loki_instant LogQL against Loki through the datasource proxy the archive's history, and the agent's `query` tool
query_history one rule's history for one brand, over thirty days 'has this brand tripped this rule before?', asked by the agent and shown in the market room
render_png server-side panel render at /render/d-solo/{uid} every full-width Grafana panel in the console, because Grafana Cloud refuses to be framed
embed_url builds the windowed URL of a panel or dashboard the links out, and the iframes when the viewer is deployed
What's next Every row above is written by a real run, not a fixture. Open one and watch its dashboards, its alert and its fix land in this same Grafana stack. See it live on a run →