<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Max Mealing</title>
    <description>The latest articles on DEV Community by Max Mealing (@maxbonnard).</description>
    <link>https://dev.to/maxbonnard</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4032337%2F41320bce-53bd-4722-9997-b15a2d6125a0.png</url>
      <title>DEV Community: Max Mealing</title>
      <link>https://dev.to/maxbonnard</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/maxbonnard"/>
    <language>en</language>
    <item>
      <title>What Is a Semantic Layer? Guide for Data Engineers</title>
      <dc:creator>Max Mealing</dc:creator>
      <pubDate>Thu, 16 Jul 2026 15:26:01 +0000</pubDate>
      <link>https://dev.to/maxbonnard/what-is-a-semantic-layer-a-practical-guide-for-data-engineers-3i1g</link>
      <guid>https://dev.to/maxbonnard/what-is-a-semantic-layer-a-practical-guide-for-data-engineers-3i1g</guid>
      <description>&lt;p&gt;Your data warehouse has a table called &lt;code&gt;orders&lt;/code&gt;. It has columns like &lt;code&gt;amount&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;, &lt;code&gt;created_at&lt;/code&gt;, and &lt;code&gt;customer_id&lt;/code&gt;. Now three people ask "What was Q1 revenue?"&lt;/p&gt;

&lt;p&gt;The analyst writes &lt;code&gt;SELECT SUM(amount) FROM orders WHERE created_at BETWEEN '2026-01-01' AND '2026-03-31'&lt;/code&gt;. The data engineer adds &lt;code&gt;WHERE status = 'completed'&lt;/code&gt;. Finance excludes refunds and trial conversions. Three queries, three numbers, one question. Nobody is wrong. They just defined "revenue" differently.&lt;/p&gt;

&lt;p&gt;Multiply this by every metric in your organization, every team that queries the warehouse, and every tool that displays a number.&lt;/p&gt;

&lt;p&gt;A semantic layer solves it by defining each metric once, in one place, and serving that definition to every consumer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a semantic layer?
&lt;/h2&gt;

&lt;p&gt;A semantic layer is a metadata layer between your data warehouse and every tool that queries it. It defines business metrics, maps them to SQL, and exposes them through APIs. Instead of every consumer writing its own query, they all reference the same definition.&lt;/p&gt;

&lt;p&gt;When someone asks for "revenue," the semantic layer knows that means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;'refunded'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;'trial'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That definition lives in one place. Dashboards, APIs, AI agents, and ad-hoc queries all use it. Change the definition once and every consumer gets the updated calculation. No detective work tracing a wrong number back to a stale query in a notebook somewhere.&lt;/p&gt;

&lt;p&gt;The core components of a semantic layer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Metrics (measures).&lt;/strong&gt; The numbers you aggregate: revenue, order count, average deal size. Each metric has a fixed SQL definition.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dimensions.&lt;/strong&gt; The columns you filter and group by: date, status, category, region. Dimensions define the axes of analysis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relationships (joins).&lt;/strong&gt; How tables connect: orders belong to customers, products belong to categories. Defined once, reused by every query.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access rules.&lt;/strong&gt; Who can see what. Row-level security, tenant isolation, role-based access. Enforced on every query, not bolted on per tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching rules.&lt;/strong&gt; Which rollups to pre-compute, how often to refresh, when to invalidate. Performance is part of the definition.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A brief history
&lt;/h2&gt;

&lt;p&gt;In the 1990s, &lt;strong&gt;Business Objects&lt;/strong&gt; introduced "universes," metadata layers that mapped business terms to database columns. Analysts queried "Revenue by Region" without knowing the underlying table structure. It worked, but only within Business Objects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SSAS (SQL Server Analysis Services)&lt;/strong&gt; cubes brought multidimensional modeling to the Microsoft stack. You defined measures and dimensions in a cube, and every Excel pivot table and SSRS report consumed the same definitions. Tightly coupled to the Microsoft ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Looker's LookML&lt;/strong&gt; (2012) moved semantic modeling into code. Metric definitions lived in version-controlled files, not a GUI. Data teams could review metric changes in pull requests. But LookML was (and is) proprietary to Looker, which is now proprietary to Google Cloud.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;dbt's MetricFlow&lt;/strong&gt; (2023) brought metric definitions into the dbt ecosystem. Define metrics alongside your transformations. Good idea, but MetricFlow defines metrics without serving them: no caching, no multi-tenancy, no API layer.&lt;/p&gt;

&lt;p&gt;The current generation of semantic layers (Cube, AtScale) are standalone infrastructure. They connect to any warehouse, serve any consumer, and run independently of your BI tool. The semantic layer is a layer in the stack, not a feature inside a product.&lt;/p&gt;

&lt;p&gt;What's driving this shift: the number of consumers has exploded. In 2015, a semantic layer served dashboards. In 2026, it also serves &lt;a href="https://bonnard.dev/glossary/mcp" rel="noopener noreferrer"&gt;AI agents via MCP&lt;/a&gt;. One definition needs to serve all of them, and the newest consumer is an agent that queries metrics and then wants to chart the result in the conversation. A BI-embedded semantic layer can't reach that consumer at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does it matter?
&lt;/h2&gt;

&lt;p&gt;Without a semantic layer, business logic lives in three places: your analysts' heads, scattered SQL files, and buried dashboard definitions. Each copy drifts over time. Someone updates the revenue definition in Looker but forgets the dbt model. A new analyst writes a fresh query from scratch and gets a number that doesn't match either.&lt;/p&gt;

&lt;p&gt;The cost isn't abstract:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inconsistent metrics.&lt;/strong&gt; Marketing reports 12% growth. Finance reports 8%. The board meeting stalls while someone debugs which number is right. This happens every quarter at companies without a shared metric layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Duplicated logic.&lt;/strong&gt; Every new dashboard, API endpoint, or report re-implements the same calculations. You end up with revenue defined in 14 places, each slightly different, each maintained by a different person. One team discovers a bug in their version and fixes it. The other 13 don't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Slow onboarding.&lt;/strong&gt; New data engineers spend weeks learning which queries are canonical and which are stale. "Ask Sarah, she knows which revenue query is the right one." Tribal knowledge doesn't scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The data team becomes a service desk.&lt;/strong&gt; Instead of building, they spend their time answering ad-hoc questions like "which dashboard has the right churn number?" A semantic layer makes the data self-serve. The team defines the metrics. Everyone else queries them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI agents make it worse.&lt;/strong&gt; An AI agent with warehouse access generates plausible SQL from column names. It doesn't know your business rules. Ask two different agents the same question and you'll get two different numbers. Neither matches finance. &lt;a href="https://bonnard.dev/glossary/text-to-sql" rel="noopener noreferrer"&gt;Text-to-SQL&lt;/a&gt; gives you speed without trust. A semantic layer gives you both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Types of semantic layers
&lt;/h2&gt;

&lt;p&gt;Not all semantic layers work the same way. The architecture matters because it determines what consumers you can serve, how fast queries run, and how much you control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Embedded semantic layers&lt;/strong&gt; live inside a BI tool. Looker's LookML, Tableau's semantic model, Power BI's DAX measures. They define metrics well, but only for that tool's consumers. Your Looker semantic layer doesn't help your React app or your AI agent. If you're in one BI tool and staying there, this can be enough. Most teams outgrow it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Virtual semantic layers&lt;/strong&gt; sit between the warehouse and consumers without moving data. AtScale is the primary example. Queries pass through the semantic layer, which translates them to the right SQL dialect and sends them to the warehouse. No data duplication. The tradeoff: every query hits the warehouse, so latency depends on warehouse performance. Virtual layers add caching to mitigate this, but it's an optimization, not the default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Materialized semantic layers&lt;/strong&gt; pre-compute rollups and store them separately. Cube's pre-aggregation engine is the best-known example. You define which combinations of measures and dimensions to pre-compute, and the semantic layer builds and maintains those materialized tables. Hot queries hit the cache (single-digit milliseconds). Cold queries fall through to the warehouse. This is how you get sub-second performance at scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hybrid semantic layers&lt;/strong&gt; combine virtual and materialized approaches. Most modern semantic layers work this way. Define pre-aggregations for your most-queried metrics. Let everything else pass through to the warehouse. You configure the tradeoff per metric based on query frequency and latency requirements.&lt;/p&gt;

&lt;p&gt;For most teams, hybrid is the right choice. You get the flexibility of virtual queries with the performance of materialized caching where it matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works in practice
&lt;/h2&gt;

&lt;p&gt;Modern semantic layers are defined in code (typically YAML), version-controlled in Git, and deployed as an API. Here's what that looks like end to end.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define your metrics
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;cubes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;orders&lt;/span&gt;
    &lt;span class="na"&gt;sql_table&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;public.orders&lt;/span&gt;
    &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;total_revenue&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CASE&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;WHEN&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;!=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'refunded'&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;AND&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;!=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'trial'&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;THEN&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ELSE&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;END"&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sum&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;order_count&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;count&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;average_order_value&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;amount&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;avg&lt;/span&gt;
    &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;status&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;status&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;category&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;category&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;created_at&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;created_at&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;time&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;customer_id&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;customer_id&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Measures&lt;/strong&gt; are the numbers you aggregate: revenue, count, average. &lt;strong&gt;Dimensions&lt;/strong&gt; are the columns you filter and group by: status, date, category. &lt;code&gt;total_revenue&lt;/code&gt; is now a governed definition, not a column someone interprets.&lt;/p&gt;

&lt;p&gt;You define joins between cubes to model relationships:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;cubes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;customers&lt;/span&gt;
    &lt;span class="na"&gt;sql_table&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;public.customers&lt;/span&gt;
    &lt;span class="na"&gt;joins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;orders&lt;/span&gt;
        &lt;span class="na"&gt;relationship&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;one_to_many&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{customers.id}&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{orders.customer_id}"&lt;/span&gt;
    &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;count&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;count&lt;/span&gt;
    &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;plan&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;plan&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;signed_up_at&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;created_at&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;time&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now any consumer can query "revenue by customer plan" without knowing how the tables join. The semantic layer handles the SQL generation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add caching with pre-aggregations
&lt;/h3&gt;

&lt;p&gt;Queries against raw warehouse tables are expensive, especially when AI agents are querying them frequently. A pre-aggregation caches rollups so consumers get sub-second responses instead of waiting for full table scans.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;pre_aggregations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;daily_revenue&lt;/span&gt;
        &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;total_revenue&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;order_count&lt;/span&gt;
        &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;status&lt;/span&gt;
        &lt;span class="na"&gt;time_dimension&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;created_at&lt;/span&gt;
        &lt;span class="na"&gt;granularity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;day&lt;/span&gt;
        &lt;span class="na"&gt;refresh_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;every&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1 hour&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The semantic layer rebuilds this rollup every hour. Hot queries hit the cache. Cold queries fall through to the warehouse. You configure the tradeoff per metric: high-traffic metrics get aggressive caching, low-traffic metrics query the warehouse directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define access control
&lt;/h3&gt;

&lt;p&gt;For B2B products serving multiple customers, every query needs to be scoped to the right tenant. A security context enforces this structurally, not through prompt engineering or middleware hacks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;security_context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tenant_filter&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{SECURITY_CONTEXT.tenant_id}&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;customer_id"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every query for a given tenant automatically includes this filter. It's not optional. The consumer can't skip it or override it. This is how RBAC works in a semantic layer: defined in the schema, enforced on every query. It doesn't matter if the consumer is a dashboard, an API call, or an AI agent. The access rules are the same.&lt;/p&gt;

&lt;h3&gt;
  
  
  Expose to consumers
&lt;/h3&gt;

&lt;p&gt;One set of definitions, multiple consumers. A BI dashboard queries via REST API. A backend service calls an SDK. An AI agent discovers and queries metrics over &lt;a href="https://bonnard.dev/glossary/mcp" rel="noopener noreferrer"&gt;MCP&lt;/a&gt;. They all hit the same definitions and get the same numbers.&lt;/p&gt;

&lt;p&gt;The agent is the consumer that changed the most. It discovers metrics, queries them, and shows the result in the conversation. The semantic layer is what keeps that answer the same one the dashboard shows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world use cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Customer-facing analytics in a B2B SaaS product
&lt;/h3&gt;

&lt;p&gt;You're building a SaaS product and your customers want to see their data. Usage metrics, billing summaries, performance dashboards. Without a semantic layer, you build custom API endpoints for each view, maintain the SQL yourself, and hope the numbers match what your internal dashboards show.&lt;/p&gt;

&lt;p&gt;With a semantic layer: define the metrics once with row-level security, and let customers query their data through your product's UI or through their own AI agents (via MCP). Multi-tenancy is structural. The same metric definition serves every customer with automatic data isolation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replacing internal dashboards nobody opens
&lt;/h3&gt;

&lt;p&gt;Your team built 47 Metabase dashboards. Three of them get used. The rest are stale, broken, or duplicated. The data team spends time maintaining dashboard infrastructure instead of defining the metrics that matter.&lt;/p&gt;

&lt;p&gt;A semantic layer shifts the model: the data team defines and governs metrics. Consumers choose their own surface. Some use dashboards. Some query the API. Some ask an AI agent. The data team's job is the definitions, not the delivery mechanism.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connecting AI agents to governed data
&lt;/h3&gt;

&lt;p&gt;Your engineering team wants to ship an AI-powered analytics feature. The first prototype uses text-to-SQL. It works in demos and breaks in production: inconsistent numbers, no tenant isolation, no audit trail. Better prompts do not fix this; a semantic layer does. The agent queries metric definitions instead of raw tables. Every answer traces back to a versioned, governed definition. Full walkthrough: &lt;a href="https://bonnard.dev/blog/connect-ai-agent-data-warehouse" rel="noopener noreferrer"&gt;How to Connect an AI Agent to Your Data Warehouse&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Semantic layer vs. dbt metrics
&lt;/h2&gt;

&lt;p&gt;They are complementary. The differences still matter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;dbt&lt;/strong&gt; defines transformations: how raw data becomes clean tables. It runs SQL models that create &lt;code&gt;dim_customers&lt;/code&gt;, &lt;code&gt;fct_orders&lt;/code&gt;, and other modeled tables in your warehouse. This is the "T" in ELT. dbt is excellent at this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;dbt's MetricFlow&lt;/strong&gt; adds metric definitions on top of those tables. You can define &lt;code&gt;revenue&lt;/code&gt; as a metric in your dbt project. This is useful for documentation and consistency within dbt's ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where dbt stops:&lt;/strong&gt; MetricFlow defines metrics but doesn't serve them to production consumers. There's no built-in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Caching or pre-aggregation.&lt;/strong&gt; Every query hits the warehouse.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-tenancy.&lt;/strong&gt; No per-tenant isolation for B2B use cases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API serving.&lt;/strong&gt; No REST API or SDK for applications to query.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI agent integration.&lt;/strong&gt; No MCP server or tool-use interface.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Row-level security.&lt;/strong&gt; Access control is warehouse-level, not metric-level.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A standalone semantic layer fills these gaps. It connects to the same warehouse where dbt outputs its modeled tables, defines metrics on top, and serves them with caching, multi-tenancy, and multiple APIs.&lt;/p&gt;

&lt;p&gt;The practical workflow: dbt transforms your raw data into clean tables on a schedule. The semantic layer defines business metrics on those tables and serves them to dashboards, applications, and AI agents in real time. Many semantic layers can import dbt models directly. You don't choose between them. You use both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Semantic layer tools compared
&lt;/h2&gt;

&lt;p&gt;Several tools occupy this space, each with different design priorities.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Strengths&lt;/th&gt;
&lt;th&gt;Limitations&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://cube.dev" rel="noopener noreferrer"&gt;Cube&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Open-source semantic layer engine&lt;/td&gt;
&lt;td&gt;Mature query engine, pre-aggregation, REST API, large community&lt;/td&gt;
&lt;td&gt;No built-in MCP, dashboards, or multi-tenant keys. You build the product layer yourself.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;dbt Semantic Layer / MetricFlow&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Metric definitions in dbt&lt;/td&gt;
&lt;td&gt;Tight dbt integration, SQL-native, good for documentation&lt;/td&gt;
&lt;td&gt;Defines metrics but doesn't serve them. No caching, no multi-tenancy, no agent support.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Looker (LookML)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;BI-embedded semantic layer&lt;/td&gt;
&lt;td&gt;Mature modeling language, strong governance, Google Cloud integration&lt;/td&gt;
&lt;td&gt;Expensive, enterprise-only, proprietary. Definitions locked to Looker's ecosystem. No MCP.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AtScale&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Virtual semantic layer&lt;/td&gt;
&lt;td&gt;Enterprise BI compatibility, no data movement, MDX/DAX support&lt;/td&gt;
&lt;td&gt;Enterprise pricing. Not agent-native. Complex deployment.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Power BI (DAX)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;BI-embedded semantic layer&lt;/td&gt;
&lt;td&gt;Deep Microsoft integration, familiar to analysts&lt;/td&gt;
&lt;td&gt;Locked to Power BI ecosystem. No API serving. No multi-tenancy for embedded use cases.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tableau (Semantic Model)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;BI-embedded semantic layer&lt;/td&gt;
&lt;td&gt;Visual modeling, strong visualization&lt;/td&gt;
&lt;td&gt;Definitions don't leave Tableau. No API access. Salesforce pricing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://bonnard.ai" rel="noopener noreferrer"&gt;Bonnard&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Governed BI inside AI clients, on top of your semantic layer&lt;/td&gt;
&lt;td&gt;Sits on dbt, Cube, MetricFlow, or a custom layer. Per-app RBAC, four tenancy models, per-call audit log. Delivered in Claude, ChatGPT, Gemini, Copilot.&lt;/td&gt;
&lt;td&gt;Not a semantic layer engine itself. Pre-launch, waitlist only.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The right choice depends on your primary consumer. If internal dashboards are the only output and you're already in Looker or Power BI, the embedded semantic layer may be sufficient. If you're serving multiple consumers (customer-facing products, AI agents, APIs, internal dashboards), you need a standalone semantic layer that isn't locked to one tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Warehouse-specific considerations
&lt;/h2&gt;

&lt;p&gt;Your semantic layer needs to work with your warehouse, and connecting is the minimum. What matters is how well it exploits each warehouse's strengths.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Snowflake.&lt;/strong&gt; Pre-aggregations work well with Snowflake's auto-suspend compute. The semantic layer can target specific virtual warehouses for different workloads: fast cache rebuilds on a small warehouse, heavy ad-hoc queries on a larger one. Snowflake's separation of storage and compute means pre-aggregation tables don't compete with production workloads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://bonnard.dev/integrations/bigquery" rel="noopener noreferrer"&gt;BigQuery&lt;/a&gt;.&lt;/strong&gt; BigQuery's slot-based pricing means pre-aggregations save real money. Every query scans data and costs money. Caching a daily revenue rollup avoids scanning terabytes of raw data on every request. The semantic layer's pre-aggregation layer can cut BigQuery costs by 10-100x for frequently queried metrics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Databricks.&lt;/strong&gt; Unity Catalog provides table-level governance. The semantic layer adds metric-level governance on top: defining what "revenue" means, not just who can access the &lt;code&gt;orders&lt;/code&gt; table. Databricks' Photon engine handles the heavy queries. The semantic layer caches the results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://bonnard.dev/integrations/postgres" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt; (including Supabase, Neon, and RDS).&lt;/strong&gt; Works well for smaller datasets and development. Most teams start with Postgres for prototyping their semantic layer and migrate to a cloud warehouse as data volume grows. Pre-aggregation is especially valuable here because Postgres doesn't have the raw compute power of cloud warehouses for large analytical queries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://bonnard.dev/integrations/duckdb" rel="noopener noreferrer"&gt;DuckDB&lt;/a&gt; (including MotherDuck).&lt;/strong&gt; In-process analytical database. Useful for local development and testing without a remote warehouse connection. Define your metrics, test locally against DuckDB, deploy to production against Snowflake or BigQuery. Same schema, different warehouses.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to evaluate a semantic layer
&lt;/h2&gt;

&lt;p&gt;If you're choosing a semantic layer for your stack:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who are your consumers?&lt;/strong&gt; This is the most important question. If it's only internal dashboards, an embedded semantic layer in your BI tool might be enough. If it's dashboards plus a customer-facing product plus AI agents, you need a standalone semantic layer with API serving.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does it support your warehouse?&lt;/strong&gt; Check for native support, which means more than a JDBC connection. Native support means the semantic layer generates optimized SQL for your specific warehouse dialect and exploits warehouse-specific features like Snowflake's clustering or BigQuery's partitioning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How does it handle caching?&lt;/strong&gt; Pre-aggregation support varies widely. Some tools don't cache at all (every query hits the warehouse). Some cache at the query level. The best cache at the metric level with configurable refresh schedules and automatic invalidation. If AI agents are querying your data, caching is not optional.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-tenancy.&lt;/strong&gt; If you're building a B2B product, every query needs tenant isolation. Look for structural multi-tenancy (security context in the schema) rather than middleware-level filtering. One missed filter is a data leak.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schema-as-code.&lt;/strong&gt; Metric definitions should live in version control. Changes should go through pull requests. Rollbacks should be &lt;code&gt;git revert&lt;/code&gt;. If you can't diff your metric definitions, you can't govern them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI agent support.&lt;/strong&gt; If AI agents are current or future consumers, look for &lt;a href="https://bonnard.dev/glossary/mcp" rel="noopener noreferrer"&gt;MCP&lt;/a&gt; support or a tool-use interface. An API is the minimum. MCP is the emerging standard for agent-to-tool communication and gives agents native discovery of available metrics.&lt;/p&gt;

&lt;h2&gt;
  
  
  When do you need a semantic layer?
&lt;/h2&gt;

&lt;p&gt;Not every team needs one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You need one when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple consumers query the same metrics (dashboards + API + agents)&lt;/li&gt;
&lt;li&gt;Different teams report different numbers for the same question&lt;/li&gt;
&lt;li&gt;You're shipping analytics to customers (B2B &lt;a href="https://bonnard.dev/glossary/embedded-analytics" rel="noopener noreferrer"&gt;embedded analytics&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;AI agents need access to your data with governance&lt;/li&gt;
&lt;li&gt;New analysts spend days learning which queries are canonical&lt;/li&gt;
&lt;li&gt;Your data team is stuck answering ad-hoc metric questions instead of building&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;You probably don't need one when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One analyst queries the warehouse with SQL and shares results in spreadsheets&lt;/li&gt;
&lt;li&gt;You have a single dashboard tool and no API consumers&lt;/li&gt;
&lt;li&gt;Your data model is small enough that everyone knows the canonical queries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The threshold is usually "more than one consumer for the same metrics." Once dashboards, APIs, and applications all need revenue numbers, a shared definition pays for itself immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Start with what your primary consumer needs. For internal dashboards only, your BI tool's built-in semantic layer may be enough. For a dbt-native workflow with documentation as the goal, start with MetricFlow. For multiple consumers including AI agents, use a standalone semantic layer like &lt;a href="https://cube.dev" rel="noopener noreferrer"&gt;Cube&lt;/a&gt; or &lt;a href="https://www.atscale.com" rel="noopener noreferrer"&gt;AtScale&lt;/a&gt;, with metrics defined once and served over an API.&lt;/p&gt;

&lt;p&gt;When AI agents are a consumer, the semantic layer is only half the job. Someone still has to decide who can query which metrics, keep tenants apart, and record what was asked. Bonnard handles that part: it sits on dbt, Cube, MetricFlow, or a custom layer and delivers governed BI inside Claude, ChatGPT, Gemini, and Copilot. Setup is four steps (connect your data, create an app, choose the audience, publish), with role-based access per app, four tenancy models, and a log of every call. Bonnard is pre-launch. &lt;a href="https://bonnard.dev/#waitlist" rel="noopener noreferrer"&gt;Join the waitlist&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is a semantic layer in simple terms?
&lt;/h3&gt;

&lt;p&gt;A semantic layer is a single place where you define what business terms like "revenue" or "active user" mean in terms of database columns and calculations. Every tool that needs that number references the same definition instead of writing its own query. One definition, every consumer, same answer.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between a semantic layer and a data warehouse?
&lt;/h3&gt;

&lt;p&gt;A data warehouse stores your data. A semantic layer defines what the data means. The warehouse has a column called &lt;code&gt;amount&lt;/code&gt;. The semantic layer defines that &lt;code&gt;total_revenue = SUM(amount) WHERE status != 'refunded'&lt;/code&gt;. They work together: the warehouse holds the data, the semantic layer holds the business logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is dbt a semantic layer?
&lt;/h3&gt;

&lt;p&gt;dbt is primarily a transformation tool that models raw data into clean tables. dbt's MetricFlow adds metric definition capabilities, which is one part of a semantic layer. But a full semantic layer also includes caching, multi-tenancy, access control, and API serving, which MetricFlow doesn't provide on its own. Most teams use dbt for transformations and a separate semantic layer for metric serving.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between a semantic layer and an OBT (One Big Table)?
&lt;/h3&gt;

&lt;p&gt;An OBT flattens everything into a single denormalized table. A semantic layer keeps your data normalized and defines metrics as calculations across tables. OBTs are simpler at first but break down at scale: they're expensive to rebuild, hard to govern, and create massive tables with redundant data. A semantic layer gives you the same query simplicity without the storage and maintenance cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between a semantic layer and a metrics layer?
&lt;/h3&gt;

&lt;p&gt;The terms are often used interchangeably, but a metrics layer is a subset of a semantic layer. A metrics layer defines measures and dimensions. A semantic layer adds relationships (joins), access control, caching, and API serving on top. dbt's MetricFlow is a metrics layer. Cube and AtScale are full semantic layers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need a semantic layer for AI agents?
&lt;/h3&gt;

&lt;p&gt;If AI agents query your data, a semantic layer is the difference between "plausible numbers" and "correct numbers." Without one, agents interpret column names and generate ad-hoc SQL. With one, agents query governed metric definitions and get the same answer every time. See &lt;a href="https://bonnard.dev/blog/what-is-agentic-semantic-layer" rel="noopener noreferrer"&gt;What Is an Agentic Semantic Layer?&lt;/a&gt; for the full breakdown.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does a semantic layer handle performance?
&lt;/h3&gt;

&lt;p&gt;Through pre-aggregation. The semantic layer pre-computes common rollups (daily revenue by status, weekly order count by category) and caches the results. Queries hit the cache instead of scanning raw tables. Hot queries resolve in single-digit milliseconds. The cache rebuilds on a configurable schedule with automatic invalidation when source data changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use a semantic layer with Snowflake, BigQuery, and other warehouses?
&lt;/h3&gt;

&lt;p&gt;Yes. Modern semantic layers are warehouse-agnostic. You configure the connection to your warehouse (Snowflake, &lt;a href="https://bonnard.dev/integrations/bigquery" rel="noopener noreferrer"&gt;BigQuery&lt;/a&gt;, Databricks, &lt;a href="https://bonnard.dev/integrations/postgres" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt; (including Supabase, Neon, and RDS), &lt;a href="https://bonnard.dev/integrations/redshift" rel="noopener noreferrer"&gt;Redshift&lt;/a&gt;, &lt;a href="https://bonnard.dev/integrations/duckdb" rel="noopener noreferrer"&gt;DuckDB&lt;/a&gt; (including MotherDuck)) and the semantic layer generates the appropriate SQL dialect. Swap warehouses without changing your metric definitions.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a universal semantic layer?
&lt;/h3&gt;

&lt;p&gt;A universal semantic layer serves every consumer from one set of definitions, regardless of what tool they use. Instead of defining metrics separately in Looker, Power BI, and your application code, you define them once and serve them everywhere. "Universal" means tool-agnostic: the semantic layer isn't embedded in any single BI product.&lt;/p&gt;

</description>
      <category>semanticlayer</category>
      <category>dataengineering</category>
      <category>database</category>
      <category>data</category>
    </item>
    <item>
      <title>Self-Service BI Is a Lie (Unless You Govern the Metrics)</title>
      <dc:creator>Max Mealing</dc:creator>
      <pubDate>Thu, 16 Jul 2026 15:25:55 +0000</pubDate>
      <link>https://dev.to/maxbonnard/self-service-bi-is-a-lie-unless-you-govern-the-metrics-2o6</link>
      <guid>https://dev.to/maxbonnard/self-service-bi-is-a-lie-unless-you-govern-the-metrics-2o6</guid>
      <description>&lt;p&gt;Self-service BI was supposed to free the data team. Give everyone a BI tool, teach them to drag and drop, and they'll answer their own questions. The data team can stop building dashboards and focus on infrastructure.&lt;/p&gt;

&lt;p&gt;That's not what happened.&lt;/p&gt;

&lt;p&gt;What happened: everyone builds their own dashboards with their own metric definitions. Marketing's "active users" counts monthly logins. Product's "active users" counts weekly feature usage. Finance's "active users" counts paying customers. Three dashboards, three numbers, one term. The data team now spends their time reconciling conflicting metrics instead of building.&lt;/p&gt;

&lt;p&gt;Self-service BI without governed metrics is just self-service chaos.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is self-service BI?
&lt;/h2&gt;

&lt;p&gt;Self-service BI means non-technical users can query data, build visualizations, and generate reports without help from the data team. The tools (Metabase, Looker, Power BI, Tableau, Superset) provide drag-and-drop interfaces, visual query builders, and template libraries.&lt;/p&gt;

&lt;p&gt;The promise: democratize data access. Anyone can answer their own questions.&lt;/p&gt;

&lt;p&gt;The reality: it works for simple questions ("how many orders this week?") and breaks for anything requiring business logic ("what's our net revenue retention?"). Users either define metrics incorrectly or ask the data team anyway. The data team becomes a help desk for the self-service tool instead of a help desk for SQL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where self-service BI goes wrong
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Metric sprawl
&lt;/h3&gt;

&lt;p&gt;Without a central definition, every self-service user creates their own version of key metrics. A company with 50 Metabase users might have 30 different "revenue" calculations saved across collections. Which one is right? The one that matches the board report. Which one matches the board report? Nobody knows without checking each one.&lt;/p&gt;

&lt;h3&gt;
  
  
  The "someone who knows" bottleneck
&lt;/h3&gt;

&lt;p&gt;True self-service requires understanding the data model. Which table has revenue? What does &lt;code&gt;status = 3&lt;/code&gt; mean? Is the &lt;code&gt;amount&lt;/code&gt; column in cents or dollars? Pre-tax or post-tax? Including shipping or excluding?&lt;/p&gt;

&lt;p&gt;Without this context, self-service users either guess (and get wrong answers) or ask the data team (and it's not self-service). The BI tool gives access to data. It doesn't give understanding of what the data means.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dashboard proliferation
&lt;/h3&gt;

&lt;p&gt;Every self-service user creates dashboards for their use case. The dashboard count grows from 10 to 500. Most are stale. Many are duplicates. Finding the right dashboard becomes its own research project. The data team periodically "cleans up," deleting abandoned dashboards and breaking someone's workflow each time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Open source BI tools and their limitations
&lt;/h3&gt;

&lt;p&gt;Open-source BI tools make self-service accessible without enterprise licensing. The main options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://bonnard.dev/vs-metabase" rel="noopener noreferrer"&gt;Metabase&lt;/a&gt;&lt;/strong&gt; (AGPL): Most popular. Visual query builder, drag-and-drop dashboards. No semantic layer. Multi-tenancy requires Enterprise license.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Superset&lt;/strong&gt; (Apache 2.0): SQL-native, powerful for technical teams. No semantic layer. Limited multi-tenancy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redash&lt;/strong&gt; (BSD): SQL editor focused. Simple, lightweight. No governance features. Maintenance mode since Databricks acquired it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lightdash&lt;/strong&gt; (MIT): dbt-native. Good for teams already using dbt. Growing community. No MCP or embedded SDK.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evidence&lt;/strong&gt; (Apache 2.0): Code-first reporting. Markdown + SQL. Good for scheduled reports. Not designed for embedded or multi-tenant use cases.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They share the same fundamental limitation: no &lt;a href="https://bonnard.dev/glossary/semantic-layer" rel="noopener noreferrer"&gt;semantic layer&lt;/a&gt;. Users query raw tables and define metrics ad-hoc. The open-source tools democratize access. They don't democratize understanding.&lt;/p&gt;

&lt;h2&gt;
  
  
  What real self-service looks like
&lt;/h2&gt;

&lt;p&gt;Real self-service means anyone can query data and get the right answer. Not "anyone can query data and get an answer." The difference is governance.&lt;/p&gt;

&lt;h3&gt;
  
  
  The data team defines metrics
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;cubes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;revenue&lt;/span&gt;
    &lt;span class="na"&gt;sql_table&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;analytics.revenue&lt;/span&gt;
    &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;total_revenue&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CASE&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;WHEN&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;!=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'refunded'&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;AND&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;!=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'trial'&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;THEN&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ELSE&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;END"&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sum&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Finance-approved&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;revenue&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;definition&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(excludes&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;refunds&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;trials)"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;net_revenue_retention&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;current_period_mrr&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;prior_period_mrr"&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;avg&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NRR&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;existing&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;customer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;cohorts"&lt;/span&gt;
    &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;plan&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;plan_name&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;region&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;region&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;period&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;period_date&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;time&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These definitions live in Git. Finance approves them in a pull request. When the definition changes, every consumer gets the update. There's one "revenue." Not 30.&lt;/p&gt;

&lt;h3&gt;
  
  
  Everyone queries the same definitions
&lt;/h3&gt;

&lt;p&gt;The product manager asks an AI agent: "What's revenue by plan this quarter?" The agent calls &lt;code&gt;explore_schema&lt;/code&gt;, finds &lt;code&gt;revenue.total_revenue&lt;/code&gt;, and queries it. Same definition finance approved.&lt;/p&gt;

&lt;p&gt;The customer success team views an &lt;a href="https://bonnard.dev/glossary/embedded-analytics" rel="noopener noreferrer"&gt;embedded analytics&lt;/a&gt; dashboard in the product. Same definition.&lt;/p&gt;

&lt;p&gt;The CEO checks a dashboard. Same definition.&lt;/p&gt;

&lt;p&gt;Nobody re-implements the metric. Nobody guesses which table to query. The data team defined it once. Every surface serves it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The data team builds, not debugs
&lt;/h3&gt;

&lt;p&gt;With governed metrics, the data team's job shifts from "answer ad-hoc questions" and "reconcile conflicting dashboards" to "define and maintain metric definitions." They add new metrics, refine existing ones, and optimize pre-aggregation strategies. The mechanical work (querying, formatting, presenting) is handled by the semantic layer and its consumers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-service BI tools compared
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Self-service model&lt;/th&gt;
&lt;th&gt;Governed metrics&lt;/th&gt;
&lt;th&gt;AI agent support&lt;/th&gt;
&lt;th&gt;Open source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Metabase&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Visual query builder&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (AGPL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Superset&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SQL + visual builder&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (Apache 2.0)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Redash&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SQL editor&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Power BI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Drag-and-drop&lt;/td&gt;
&lt;td&gt;DAX (PBI only)&lt;/td&gt;
&lt;td&gt;Copilot&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tableau&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Drag-and-drop&lt;/td&gt;
&lt;td&gt;Tableau model&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Looker&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;LookML + Explore&lt;/td&gt;
&lt;td&gt;LookML&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ThoughtSpot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Natural language search&lt;/td&gt;
&lt;td&gt;Proprietary&lt;/td&gt;
&lt;td&gt;Proprietary&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bonnard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Ask in Claude, ChatGPT, Gemini, or Copilot&lt;/td&gt;
&lt;td&gt;Via your semantic layer (dbt, Cube, MetricFlow, custom)&lt;/td&gt;
&lt;td&gt;Native&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The tools in the top half give users direct data access with no metric governance. The tools in the bottom half add governance but lock it to their ecosystem. The bottom row is a different kind of option: Bonnard delivers governed BI inside the AI client instead of in a BI tool of its own, on top of the semantic layer you already have.&lt;/p&gt;

&lt;h2&gt;
  
  
  When traditional self-service BI is enough
&lt;/h2&gt;

&lt;p&gt;Self-service BI works when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A small team (&amp;lt; 10 analysts) agrees on metric definitions informally&lt;/li&gt;
&lt;li&gt;One BI tool serves all consumers&lt;/li&gt;
&lt;li&gt;Internal use only (no customers seeing the numbers)&lt;/li&gt;
&lt;li&gt;The data model is simple enough that users understand it&lt;/li&gt;
&lt;li&gt;You have someone who maintains a "source of truth" dashboard that others copy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It breaks when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple teams define the same metric differently&lt;/li&gt;
&lt;li&gt;You need metrics in dashboards AND APIs AND AI agents AND embedded analytics&lt;/li&gt;
&lt;li&gt;Customers see the numbers (trust and consistency matter)&lt;/li&gt;
&lt;li&gt;The data team spends more time reconciling than building&lt;/li&gt;
&lt;li&gt;New hires take weeks to find the right dashboard&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Self-service for customers, inside the agent
&lt;/h2&gt;

&lt;p&gt;Governed metrics fix the consistency problem for internal self-service. There's a separate question for customer-facing self-service: where does the customer ask their question? Increasingly the answer is an AI agent. They want to ask about their data in Claude or ChatGPT and get a chart back.&lt;/p&gt;

&lt;p&gt;Bonnard covers that surface. It delivers governed BI inside Claude, ChatGPT, Gemini, and Copilot, on top of the semantic layer you already run (dbt, Cube, MetricFlow, or custom). You connect your data, create an app, choose the audience, and publish. Each app has its own role-based access, tenants are isolated by one of four models, and every call is logged, so a customer only sees their own numbers and you can show who saw what. Bonnard is pre-launch. &lt;a href="https://bonnard.dev/#waitlist" rel="noopener noreferrer"&gt;Join the waitlist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For background: &lt;a href="https://bonnard.dev/blog/what-is-a-semantic-layer" rel="noopener noreferrer"&gt;What Is a Semantic Layer?&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is self-service BI?
&lt;/h3&gt;

&lt;p&gt;Self-service BI is a BI model where non-technical users can query data, build visualizations, and generate reports without help from the data team. Tools like Metabase, Power BI, and Tableau provide drag-and-drop interfaces for this. The limitation: self-service without governed metrics leads to inconsistent numbers across the organization.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the best open-source BI tool?
&lt;/h3&gt;

&lt;p&gt;For internal dashboards: &lt;a href="https://bonnard.dev/vs-metabase" rel="noopener noreferrer"&gt;Metabase&lt;/a&gt; (most popular), Superset (more technical), or Redash (SQL-focused). For governed metrics served to multiple surfaces including AI agents: a semantic layer approach. Open-source BI tools give you self-service querying. A semantic layer gives you self-service with correct, governed answers.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between self-service BI and a semantic layer?
&lt;/h3&gt;

&lt;p&gt;Self-service BI gives users tools to query data. A semantic layer defines what the data means. They're complementary: a self-service BI tool with a semantic layer underneath gives users access to governed metrics. Without the semantic layer, self-service users define metrics ad-hoc and the numbers diverge.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I prevent metric chaos in self-service BI?
&lt;/h3&gt;

&lt;p&gt;Define each metric once in a &lt;a href="https://bonnard.dev/glossary/semantic-layer" rel="noopener noreferrer"&gt;semantic layer&lt;/a&gt;. Version the definitions in Git. Review changes in pull requests. Serve the same definitions to every consumer. When everyone queries the same governed definitions, there's nothing to reconcile.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I offer self-service BI for customers in 2026?
&lt;/h3&gt;

&lt;p&gt;Decide where customers ask their questions. A traditional embedded BI tool puts a dashboard in your product. The newer surface is the AI agent: customers ask in Claude or ChatGPT and get a chart back. For that surface, you need the same governance an embedded dashboard has: per-tenant isolation, role-based access, and a record of every query. The two surfaces are complementary, and both should read from the same metric definitions.&lt;/p&gt;

</description>
      <category>semanticlayer</category>
      <category>analytics</category>
      <category>data</category>
      <category>datavisualization</category>
    </item>
    <item>
      <title>Real-Time Analytics: When You Need It and When You Don't</title>
      <dc:creator>Max Mealing</dc:creator>
      <pubDate>Thu, 16 Jul 2026 15:25:52 +0000</pubDate>
      <link>https://dev.to/maxbonnard/real-time-analytics-when-you-need-it-and-when-you-dont-2pok</link>
      <guid>https://dev.to/maxbonnard/real-time-analytics-when-you-need-it-and-when-you-dont-2pok</guid>
      <description>&lt;p&gt;"We need real-time analytics" is one of the most common requests in data engineering. It's also one of the most misunderstood. When the VP of Sales says "real-time," they usually mean "faster than the dashboard that refreshes overnight." When the CTO says it, they might mean sub-second event streaming. The gap between those two definitions is a 6-month infrastructure project.&lt;/p&gt;

&lt;p&gt;Most teams don't need true real-time. They need fast enough. And "fast enough" is achievable with pre-aggregation caching at a fraction of the complexity and cost of a streaming architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is real-time analytics?
&lt;/h2&gt;

&lt;p&gt;Real-time analytics means querying data with minimal latency between when an event happens and when it's visible in your analytics. The spectrum:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Freshness&lt;/th&gt;
&lt;th&gt;Latency&lt;/th&gt;
&lt;th&gt;Architecture&lt;/th&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;True real-time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&amp;lt; 1 second&lt;/td&gt;
&lt;td&gt;Event streaming (Kafka, Flink)&lt;/td&gt;
&lt;td&gt;Fraud detection, stock trading, live monitoring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Near real-time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1-60 seconds&lt;/td&gt;
&lt;td&gt;Micro-batch or streaming&lt;/td&gt;
&lt;td&gt;Operational dashboards, alerting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Frequent refresh&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1-60 minutes&lt;/td&gt;
&lt;td&gt;Scheduled refresh + caching&lt;/td&gt;
&lt;td&gt;KPI dashboards, AI agent queries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Batch&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hours to daily&lt;/td&gt;
&lt;td&gt;Scheduled ETL&lt;/td&gt;
&lt;td&gt;Board reports, monthly summaries&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Most analytics use cases fall in the "frequent refresh" category. Revenue by region doesn't need sub-second freshness. Active users in the last hour doesn't need event streaming. A pre-aggregation cache that refreshes every 15 minutes covers 90% of what teams call "real-time."&lt;/p&gt;

&lt;h2&gt;
  
  
  When you actually need real-time
&lt;/h2&gt;

&lt;p&gt;True real-time analytics (sub-second latency from event to query result) is worth the infrastructure investment when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fraud detection.&lt;/strong&gt; Every second of delay is potential fraud that slips through.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live monitoring.&lt;/strong&gt; Server health, API error rates, active user counts for live products.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trading and pricing.&lt;/strong&gt; Financial instruments where stale data means wrong prices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live events.&lt;/strong&gt; Streaming metrics during a product launch, marketing campaign, or live broadcast.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're in one of these categories, you need an event streaming architecture: Kafka, Flink, Materialize, or similar. A semantic layer complements this by governing the metric definitions, but the freshness comes from the streaming pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you don't (and what to use instead)
&lt;/h2&gt;

&lt;p&gt;Most business analytics doesn't need sub-second freshness. It needs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fast query response&lt;/strong&gt; (milliseconds, not seconds)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reasonably fresh data&lt;/strong&gt; (minutes or hours, not days)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistent numbers&lt;/strong&gt; (same answer everywhere)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pre-aggregation gives you all three without a streaming architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  How pre-aggregation works
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://bonnard.dev/glossary/semantic-layer" rel="noopener noreferrer"&gt;semantic layer&lt;/a&gt; pre-computes your most common queries and caches the results. When a dashboard or AI agent asks for "revenue by region this month," the query hits the cache instead of scanning raw warehouse tables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;cubes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;orders&lt;/span&gt;
    &lt;span class="na"&gt;sql_table&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;public.orders&lt;/span&gt;
    &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;total_revenue&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CASE&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;WHEN&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;!=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'refunded'&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;THEN&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ELSE&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;END"&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sum&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;order_count&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;count&lt;/span&gt;
    &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;region&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;region&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;created_at&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;created_at&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;time&lt;/span&gt;

    &lt;span class="na"&gt;pre_aggregations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;revenue_by_region&lt;/span&gt;
        &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;total_revenue&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;order_count&lt;/span&gt;
        &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;region&lt;/span&gt;
        &lt;span class="na"&gt;time_dimension&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;created_at&lt;/span&gt;
        &lt;span class="na"&gt;granularity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;day&lt;/span&gt;
        &lt;span class="na"&gt;refresh_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;every&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;15 minutes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cache rebuilds every 15 minutes. Queries that match the pre-aggregation return in single-digit milliseconds. Queries that don't match fall through to the warehouse (seconds).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The result:&lt;/strong&gt; your dashboard loads in under a second. Your AI agent gets responses in milliseconds. Your data is at most 15 minutes old. For 90% of analytics use cases, this is indistinguishable from "real-time."&lt;/p&gt;

&lt;h3&gt;
  
  
  Different refresh rates for different metrics
&lt;/h3&gt;

&lt;p&gt;Not all metrics need the same freshness. Configure per rollup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;pre_aggregations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Operational: refresh every 5 minutes&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;active_users_live&lt;/span&gt;
        &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;active_users&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;time_dimension&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;last_seen_at&lt;/span&gt;
        &lt;span class="na"&gt;granularity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;minute&lt;/span&gt;
        &lt;span class="na"&gt;refresh_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;every&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5 minutes&lt;/span&gt;

      &lt;span class="c1"&gt;# Strategic: refresh every hour&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;daily_revenue&lt;/span&gt;
        &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;total_revenue&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;mrr&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;plan&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;region&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;time_dimension&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;created_at&lt;/span&gt;
        &lt;span class="na"&gt;granularity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;day&lt;/span&gt;
        &lt;span class="na"&gt;refresh_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;every&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1 hour&lt;/span&gt;

      &lt;span class="c1"&gt;# Historical: refresh daily&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;monthly_cohorts&lt;/span&gt;
        &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;retention_rate&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ltv&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;cohort_month&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;plan&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;time_dimension&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;created_at&lt;/span&gt;
        &lt;span class="na"&gt;granularity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;month&lt;/span&gt;
        &lt;span class="na"&gt;refresh_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;every&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;24 hours&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Operational metrics refresh every 5 minutes. Revenue refreshes hourly. Historical cohorts refresh daily. Each metric gets the freshness it needs without over-engineering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-time analytics for B2B products
&lt;/h2&gt;

&lt;p&gt;If you're shipping analytics to B2B customers, "real-time" has a different meaning. Your customers expect their dashboards to load fast and show recent data. They don't expect sub-second event streaming.&lt;/p&gt;

&lt;p&gt;Pre-aggregation handles this well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fast load times.&lt;/strong&gt; Cached queries return in milliseconds. Customer dashboards feel instant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-tenant caching.&lt;/strong&gt; The cache respects tenant isolation. Customer A's cached rollup doesn't include Customer B's data. Security context filters apply at cache build time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost control.&lt;/strong&gt; On usage-based warehouses (&lt;a href="https://bonnard.dev/integrations/bigquery" rel="noopener noreferrer"&gt;BigQuery&lt;/a&gt;, Snowflake), fewer raw queries means lower costs. Serving from cache is essentially free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI agent compatibility.&lt;/strong&gt; Agents make multiple queries per interaction. Pre-aggregation keeps response times low even with agent-scale query volumes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Real-time analytics architecture compared
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Freshness&lt;/th&gt;
&lt;th&gt;Query speed&lt;/th&gt;
&lt;th&gt;Complexity&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Event streaming&lt;/strong&gt; (Kafka + Flink)&lt;/td&gt;
&lt;td&gt;Sub-second&lt;/td&gt;
&lt;td&gt;Fast (materialized views)&lt;/td&gt;
&lt;td&gt;Very high&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Fraud, trading, live monitoring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Streaming DB&lt;/strong&gt; (Materialize, RisingWave)&lt;/td&gt;
&lt;td&gt;Seconds&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Medium-high&lt;/td&gt;
&lt;td&gt;Continuous queries, CDC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Warehouse + pre-aggregation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Minutes&lt;/td&gt;
&lt;td&gt;Milliseconds (cached)&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Low-medium&lt;/td&gt;
&lt;td&gt;Dashboards, AI agents, embedded analytics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Warehouse direct&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Minutes-hours (ETL dependent)&lt;/td&gt;
&lt;td&gt;Seconds&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Ad-hoc analysis, batch reports&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Spreadsheet&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;One-off analysis&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For most teams, the warehouse + pre-aggregation approach covers the use case. Add streaming only for the metrics that genuinely need sub-second freshness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Once your queries return fast enough, agents become a consumer, and the questions shift from freshness to governance: who can ask, what they can see, and what was asked. Bonnard handles that side. It sits on your semantic layer (dbt, Cube, MetricFlow, or custom) and delivers governed BI inside Claude, ChatGPT, Gemini, and Copilot, reading from whatever rollup or live table you point it at. Setup is four steps: connect your data, create an app, choose the audience, publish. Access is role-based per app, tenants are isolated by one of four models, and every call is logged. Bonnard is pre-launch. &lt;a href="https://bonnard.dev/#waitlist" rel="noopener noreferrer"&gt;Join the waitlist&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is real-time analytics?
&lt;/h3&gt;

&lt;p&gt;Real-time analytics means querying data with minimal delay between when events happen and when they appear in your analytics. True real-time (sub-second) requires event streaming. Near-real-time (minutes) is achievable with pre-aggregation caching on top of a data warehouse.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need real-time analytics?
&lt;/h3&gt;

&lt;p&gt;Most business analytics doesn't need sub-second freshness. If your use case is dashboards, AI agent queries, or &lt;a href="https://bonnard.dev/glossary/embedded-analytics" rel="noopener noreferrer"&gt;embedded analytics&lt;/a&gt;, a pre-aggregation cache refreshing every 5-60 minutes provides "real-time" performance at a fraction of the streaming complexity. True real-time is needed for fraud detection, live monitoring, and financial trading.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between real-time and batch analytics?
&lt;/h3&gt;

&lt;p&gt;Batch analytics processes data on a schedule (hourly, daily). Real-time analytics processes data continuously with minimal delay. Pre-aggregation caching sits in between: the cache rebuilds on a schedule (every N minutes), but queries against the cache return in milliseconds. It's batch refresh with real-time query performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does pre-aggregation make analytics feel real-time?
&lt;/h3&gt;

&lt;p&gt;Pre-aggregation pre-computes your most common queries and caches the results. When a user or AI agent queries "revenue by region," the response comes from the cache in single-digit milliseconds instead of scanning raw warehouse tables (which takes seconds). The data is minutes old, but the query feels instant.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is streaming analytics?
&lt;/h3&gt;

&lt;p&gt;Streaming analytics processes events as they arrive, maintaining continuously updated query results. Technologies include Kafka, Flink, Materialize, and RisingWave. It's the right choice for sub-second freshness requirements. For most business analytics, pre-aggregation caching provides comparable user experience at lower cost and complexity.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>database</category>
      <category>data</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>KPI Dashboards Are Broken. Here's What Replaces Them.</title>
      <dc:creator>Max Mealing</dc:creator>
      <pubDate>Thu, 16 Jul 2026 15:25:47 +0000</pubDate>
      <link>https://dev.to/maxbonnard/kpi-dashboards-are-broken-heres-what-replaces-them-1f6p</link>
      <guid>https://dev.to/maxbonnard/kpi-dashboards-are-broken-heres-what-replaces-them-1f6p</guid>
      <description>&lt;p&gt;Your company has a KPI dashboard. It was built six months ago by someone who has since moved teams. It shows revenue, churn, and a few product metrics. It loads slowly. The numbers don't match what finance reports. Nobody trusts it, but everyone screenshots it for the Monday standup.&lt;/p&gt;

&lt;p&gt;This is the state of KPI dashboards at most companies. Not because the tools are bad, but because the approach is wrong. A dashboard is a static view of a dynamic system. The moment someone builds it, it starts drifting from reality.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a KPI dashboard?
&lt;/h2&gt;

&lt;p&gt;A KPI (Key Performance Indicator) dashboard is a visual display of an organization's most important metrics. Revenue, customer count, churn rate, conversion rate, average order value, NPS. The metrics that tell you whether the business is healthy.&lt;/p&gt;

&lt;p&gt;Traditional KPI dashboards live in a BI tool: Power BI, Tableau, Looker, Metabase, Grafana. An analyst builds the dashboard, connects it to a data source, and shares a link. People visit the dashboard (or receive a scheduled screenshot) to check the numbers.&lt;/p&gt;

&lt;p&gt;The concept is sound. The execution breaks for predictable reasons.&lt;/p&gt;

&lt;h2&gt;
  
  
  KPI dashboard examples
&lt;/h2&gt;

&lt;p&gt;Before diving into what's broken, here's what teams typically build:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SaaS executive dashboard.&lt;/strong&gt; MRR, ARR, net revenue retention, churn rate, new customers this month, average contract value. Updated daily. Viewed by the CEO and board. The numbers must match the finance team's report exactly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Product analytics dashboard.&lt;/strong&gt; Daily active users, feature adoption rates, conversion funnel stages, time to value. Updated hourly. Viewed by product managers. Often the first dashboard built and the first to drift from reality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Customer health dashboard (B2B).&lt;/strong&gt; Per-customer usage metrics, support ticket volume, NPS, renewal risk score. Updated daily. Viewed by customer success. In embedded analytics use cases, the customer sees this too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Engineering operations dashboard.&lt;/strong&gt; API error rates, p95 latency, deployment frequency, uptime. Updated real-time. Viewed by engineering leads. Often built in Grafana rather than a BI tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sales pipeline dashboard.&lt;/strong&gt; Open deals by stage, weighted pipeline value, win rate, average deal cycle. Updated daily. Viewed by sales leadership. Usually lives in Salesforce or a connected BI tool.&lt;/p&gt;

&lt;p&gt;Each of these follows the same pattern: connect to a data source, write queries, build charts, share a link. The problems start when multiple dashboards need the same metric (revenue, active users, churn) and define it differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why KPI dashboards fail
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The metrics drift
&lt;/h3&gt;

&lt;p&gt;Revenue is defined in the dashboard as &lt;code&gt;SUM(amount) WHERE status = 'completed'&lt;/code&gt;. Finance defines it as &lt;code&gt;SUM(amount) WHERE status != 'refunded' AND type != 'trial'&lt;/code&gt;. The CFO looks at the dashboard, compares it to the finance report, and stops trusting the dashboard. This happens every quarter at companies without a shared metric layer.&lt;/p&gt;

&lt;p&gt;The root cause: business logic is defined in the dashboard, not in a governed layer. Every dashboard that shows revenue re-implements the calculation. Each implementation drifts independently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nobody opens them
&lt;/h3&gt;

&lt;p&gt;Dashboards that load in 8 seconds don't get used. Dashboards behind a BI tool login don't get used. Dashboards that require navigating to a separate tool don't get used. The data team builds 50 dashboards. Three get regular traffic. The rest are abandoned.&lt;/p&gt;

&lt;p&gt;The data team keeps building new ones because stakeholders keep asking. Each request means a new dashboard, a new query, a new maintenance burden. The team becomes a dashboard factory instead of building the metrics layer that would make dashboards unnecessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  They only serve one surface
&lt;/h3&gt;

&lt;p&gt;A KPI dashboard in Looker serves people who open Looker. What about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The product team that wants metrics in their React app?&lt;/li&gt;
&lt;li&gt;The customer success team that wants account health in Slack?&lt;/li&gt;
&lt;li&gt;The AI agent that needs to answer "how's revenue trending?"&lt;/li&gt;
&lt;li&gt;The B2B customer that wants their own usage dashboard inside your product?&lt;/li&gt;
&lt;li&gt;The executive who wants a weekly email summary?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these requires a different integration. The dashboard metric definitions don't transfer. You end up rebuilding the same KPIs in every tool, with each copy drifting independently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stale data, stale insight
&lt;/h3&gt;

&lt;p&gt;Most KPI dashboards refresh on a schedule: hourly, daily, sometimes manually. Between refreshes, the numbers are stale. For operational KPIs (active users right now, orders this hour, API error rate), stale data is useless data.&lt;/p&gt;

&lt;p&gt;Even with real-time refresh, the dashboard is a passive display. It shows numbers. It doesn't answer questions. "Revenue is down 12% this week" is visible on the dashboard. "Why?" requires a human to open the BI tool, write a query, drill into dimensions, and figure it out. An AI agent could answer that question in seconds, but the dashboard can't feed an agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The alternative: a KPI chart the agent renders on demand
&lt;/h2&gt;

&lt;p&gt;The static dashboard is a snapshot someone built once. The agent path is different: instead of opening a tool and reading a chart someone else laid out, you ask "how's revenue trending?" and the agent runs the query and draws the chart in the conversation.&lt;/p&gt;

&lt;p&gt;That last step (drawing the chart) is where most agent setups go wrong. The agent already has access to your data. What it lacks is a way to turn the rows it gets back into a chart the person can actually read, without improvising HTML that may not match the numbers.&lt;/p&gt;

&lt;h3&gt;
  
  
  How a KPI request flows
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Someone asks the agent "how's MRR trending this quarter?"&lt;/li&gt;
&lt;li&gt;The agent runs a governed query against your warehouse or semantic layer, scoped to what that person is allowed to see.&lt;/li&gt;
&lt;li&gt;The server builds the chart from the typed result: a line chart for MRR over time, a bar chart for revenue by plan, a funnel for a conversion sequence.&lt;/li&gt;
&lt;li&gt;Claude or ChatGPT renders it in the conversation over &lt;a href="https://bonnard.dev/glossary/mcp" rel="noopener noreferrer"&gt;MCP&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because the chart comes from the typed result rather than from the model, the same KPI query renders the same chart every time. Axes, number formatting, and gap-fill follow from the schema, not from a prompt.&lt;/p&gt;

&lt;p&gt;No dashboard to patch. The KPI gets drawn the moment someone asks, from whatever query answers the question.&lt;/p&gt;

&lt;h2&gt;
  
  
  KPI dashboard tools compared
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Metric governance&lt;/th&gt;
&lt;th&gt;Surfaces&lt;/th&gt;
&lt;th&gt;AI agent support&lt;/th&gt;
&lt;th&gt;Multi-tenant&lt;/th&gt;
&lt;th&gt;Performance&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Power BI dashboard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;DAX (locked to PBI)&lt;/td&gt;
&lt;td&gt;Power BI only&lt;/td&gt;
&lt;td&gt;Copilot (internal)&lt;/td&gt;
&lt;td&gt;Complex&lt;/td&gt;
&lt;td&gt;Import mode or DirectQuery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tableau dashboard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Semantic model (locked)&lt;/td&gt;
&lt;td&gt;Tableau only&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Enterprise&lt;/td&gt;
&lt;td&gt;Extract or live&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Looker dashboard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;LookML&lt;/td&gt;
&lt;td&gt;Looker + embed&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Enterprise&lt;/td&gt;
&lt;td&gt;PDTs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Metabase dashboard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Metabase + embed&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Enterprise only&lt;/td&gt;
&lt;td&gt;No caching&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Grafana dashboard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Grafana only&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;td&gt;Query-level&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Superset dashboard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Superset + embed&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;No caching&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bonnard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Your database or semantic layer (dbt, Cube, MetricFlow, custom)&lt;/td&gt;
&lt;td&gt;Claude, ChatGPT, Gemini, Copilot&lt;/td&gt;
&lt;td&gt;Native (MCP)&lt;/td&gt;
&lt;td&gt;Four isolation models, role-based per app&lt;/td&gt;
&lt;td&gt;Inherits your warehouse&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The traditional tools render KPIs inside the dashboard tool. Bonnard renders them as charts, tables, and dashboards inside the AI client, from governed query results, with every call logged.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to keep your KPI dashboard
&lt;/h2&gt;

&lt;p&gt;KPI dashboards aren't always wrong. They work when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One team, one tool, one audience&lt;/li&gt;
&lt;li&gt;The metrics are simple and stable&lt;/li&gt;
&lt;li&gt;Nobody else needs the same numbers in a different format&lt;/li&gt;
&lt;li&gt;You don't need AI agent access or embedded analytics&lt;/li&gt;
&lt;li&gt;The dashboard builder is still on the team and maintains it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They break when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple teams need the same KPIs in different tools&lt;/li&gt;
&lt;li&gt;Customers need to see their metrics inside your product&lt;/li&gt;
&lt;li&gt;AI agents need to query KPIs&lt;/li&gt;
&lt;li&gt;The dashboard shows different numbers than finance reports&lt;/li&gt;
&lt;li&gt;The data team spends more time maintaining dashboards than defining metrics&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where Bonnard fits
&lt;/h2&gt;

&lt;p&gt;Bonnard delivers KPIs as governed BI inside the AI clients a team already uses: Claude, ChatGPT, Gemini, Copilot. Connect PostgreSQL, BigQuery, Redshift, ClickHouse, DuckDB or MotherDuck, or MySQL or MariaDB, or sit it on a dbt, Cube, MetricFlow, or custom semantic layer. Create an app, choose the audience, publish. Someone asks for a KPI and the chart arrives in the conversation, scoped by their role and recorded in the audit log. Bonnard is pre-launch. &lt;a href="https://bonnard.dev/#waitlist" rel="noopener noreferrer"&gt;Join the waitlist&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is a KPI dashboard?
&lt;/h3&gt;

&lt;p&gt;A KPI dashboard is a visual display of key performance indicators: revenue, churn, active users, conversion rate, and other metrics that measure business health. Traditional KPI dashboards live in BI tools like Power BI, Tableau, Looker, or Grafana. Modern approaches define KPIs in a semantic layer and serve them across dashboards, AI agents, and embedded analytics simultaneously.&lt;/p&gt;

&lt;h3&gt;
  
  
  What KPIs should be on a dashboard?
&lt;/h3&gt;

&lt;p&gt;The KPIs depend on your business. Common B2B SaaS KPIs: MRR, ARR, churn rate, net revenue retention, active users, customer count, NPS, average contract value. The specific metrics matter less than the governance: every KPI should have one canonical definition that every consumer references.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the best KPI dashboard tool?
&lt;/h3&gt;

&lt;p&gt;For internal-only dashboards: Power BI (Microsoft ecosystem), Tableau (Salesforce ecosystem), Looker (Google Cloud), or Metabase (open source). For governed KPIs served to multiple surfaces including AI agents and &lt;a href="https://bonnard.dev/glossary/embedded-analytics" rel="noopener noreferrer"&gt;embedded analytics&lt;/a&gt;: a semantic layer approach. The right choice depends on how many consumers need the same metrics.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I make my KPI dashboard load faster?
&lt;/h3&gt;

&lt;p&gt;Most dashboard slowness comes from querying the warehouse on every render. Pre-aggregation caching pre-computes common KPI rollups. The dashboard queries the cache instead of the warehouse. Response times drop from seconds to single-digit milliseconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can AI agents query KPIs?
&lt;/h3&gt;

&lt;p&gt;With a semantic layer and MCP support, yes. AI agents discover available KPIs via &lt;code&gt;explore_schema&lt;/code&gt; and query them via &lt;code&gt;query&lt;/code&gt;. The agent gets governed, multi-tenant data scoped to their access level. Without a semantic layer, agents generate ad-hoc SQL and return inconsistent numbers. See &lt;a href="https://bonnard.dev/blog/what-is-agentic-semantic-layer" rel="noopener noreferrer"&gt;What Is an Agentic Semantic Layer?&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between a KPI dashboard and analytics dashboard?
&lt;/h3&gt;

&lt;p&gt;A KPI dashboard focuses on a small set of headline metrics that measure business health. An analytics dashboard provides broader exploration: filtering, drilling, and ad-hoc querying. In practice, both benefit from governed metric definitions. The KPI dashboard shows the numbers. The analytics dashboard lets you investigate why they changed.&lt;/p&gt;

&lt;h3&gt;
  
  
  How often should a KPI dashboard refresh?
&lt;/h3&gt;

&lt;p&gt;Depends on the KPI. Revenue and churn: daily or hourly. Active users and API metrics: real-time or near-real-time. Strategic KPIs (NPS, retention cohorts): weekly or monthly. With pre-aggregation, refresh frequency is configurable per metric. Set aggressive refresh for operational KPIs and relaxed refresh for strategic ones.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>datavisualization</category>
      <category>data</category>
      <category>semanticlayer</category>
    </item>
    <item>
      <title>How to Build Customer-Facing Analytics for B2B SaaS</title>
      <dc:creator>Max Mealing</dc:creator>
      <pubDate>Thu, 16 Jul 2026 15:25:42 +0000</pubDate>
      <link>https://dev.to/maxbonnard/how-to-build-customer-facing-analytics-for-b2b-saas-h91</link>
      <guid>https://dev.to/maxbonnard/how-to-build-customer-facing-analytics-for-b2b-saas-h91</guid>
      <description>&lt;p&gt;Your B2B customers want to see their data. Usage metrics, billing summaries, conversion funnels, performance dashboards. Every customer expects analytics inside your product. They shouldn't have to ask your support team for a CSV export.&lt;/p&gt;

&lt;p&gt;The question isn't whether to ship customer-facing analytics. It's how.&lt;/p&gt;

&lt;p&gt;Most teams start with one of two approaches. They embed a BI tool (Metabase, Looker, Power BI) and fight with multi-tenancy, iframe styling, and paid embedding licenses. Or they build custom charts from scratch and spend months maintaining SQL queries, API endpoints, and frontend components that nobody asked for.&lt;/p&gt;

&lt;p&gt;Both approaches burn engineering time on the wrong problem. You end up building analytics infrastructure instead of your product.&lt;/p&gt;

&lt;p&gt;And there's a surface most of these tools miss entirely: the AI agent. Customers increasingly want to ask questions about their data inside Claude or ChatGPT and get a chart back. That's a different problem from embedding a dashboard, and it's covered later in this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why embedded BI tools fall short
&lt;/h2&gt;

&lt;p&gt;Embedding a BI tool sounds fast. Drop in an iframe, connect to your database, ship it. In practice, the friction shows up quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-tenancy is an afterthought
&lt;/h3&gt;

&lt;p&gt;Most BI tools were built for internal teams, not B2B products serving hundreds of tenants. Multi-tenancy is either missing, manual, or gated behind an enterprise license.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bonnard.dev/vs-metabase" rel="noopener noreferrer"&gt;Metabase&lt;/a&gt; requires the Enterprise license ($500+/month) for row-level permissions and sandboxed embedding. The open-source version has basic embedding but no tenant isolation. You end up writing middleware to filter queries by tenant, which is exactly the custom infrastructure you were trying to avoid.&lt;/p&gt;

&lt;p&gt;Looker's embedded analytics requires an enterprise contract. Power BI Embedded uses capacity-based pricing that gets expensive at scale. Tableau's embedding story is Salesforce-priced.&lt;/p&gt;

&lt;p&gt;Even when multi-tenancy is available, it's usually dashboard-level or role-based, not per-query structural enforcement. You're trusting the BI tool to filter correctly on every query. One misconfiguration and Customer A sees Customer B's data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Styling and UX limitations
&lt;/h3&gt;

&lt;p&gt;An iframe is a foreign element in your product. It looks like a foreign element. Matching your brand's fonts, colors, spacing, and interaction patterns inside an embedded BI tool ranges from difficult to impossible. Your customers notice.&lt;/p&gt;

&lt;p&gt;White-label analytics means your customers shouldn't know they're using a third-party tool. Most embedded BI solutions make this hard. The ones that make it easy charge for it.&lt;/p&gt;

&lt;h3&gt;
  
  
  You're locked to dashboards
&lt;/h3&gt;

&lt;p&gt;Embedded BI gives you dashboards. That's one surface. But your customers might also want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An API to pull metrics into their own tools&lt;/li&gt;
&lt;li&gt;AI agents that answer questions about their data&lt;/li&gt;
&lt;li&gt;Scheduled email reports&lt;/li&gt;
&lt;li&gt;Webhook alerts when metrics cross thresholds&lt;/li&gt;
&lt;li&gt;CSV/Excel exports for their finance team&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these requires a different integration, often with different tools and different metric definitions. The dashboard shows one number. The API returns a different one. The export uses yet another query. The metrics drift.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why building from scratch is worse
&lt;/h2&gt;

&lt;p&gt;The alternative: skip the BI tool and build it yourself. Custom SQL queries, custom API endpoints, custom React charts.&lt;/p&gt;

&lt;p&gt;This works for the first dashboard. Then the second. By the tenth, you're maintaining a bespoke analytics platform. Every new metric means a new SQL query, a new API endpoint, a new frontend component, and a new set of tests. Your data engineers are writing API handlers instead of defining metrics. Your frontend engineers are debugging chart edge cases instead of building product features.&lt;/p&gt;

&lt;p&gt;The real cost isn't the initial build. It's the ongoing maintenance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Metric drift.&lt;/strong&gt; Revenue is calculated differently in the dashboard, the API, and the export. Nobody notices until a customer complains.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No caching layer.&lt;/strong&gt; Every API call hits the warehouse. Response times grow with data volume. Your customers experience slow dashboards at month-end when they need them most.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security is DIY.&lt;/strong&gt; You build tenant filtering yourself. You test it yourself. You hope you didn't miss an edge case.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No schema evolution.&lt;/strong&gt; Adding a new dimension means updating every query, endpoint, and component that touches the affected metric. A one-line change in business logic cascades into a multi-day project.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The teams on Reddit asking &lt;a href="https://www.reddit.com/r/analytics/comments/1pxjm1z/" rel="noopener noreferrer"&gt;"Is embedded analytics for SaaS actually worth it vs building your own charts?"&lt;/a&gt; are wrestling with exactly this tradeoff. There's no single right answer. The decision depends on how many surfaces you serve and whether your customers want charts in a dashboard, an API, or an AI agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI agent surface: analytics inside the client
&lt;/h2&gt;

&lt;p&gt;The sections above cover the two classic customer-facing analytics options: embed a BI tool or build dashboards from scratch. There's a third surface that neither covers well: the AI agent. When your customer asks a question in Claude or ChatGPT and wants a chart back, an embedded dashboard doesn't help.&lt;/p&gt;

&lt;p&gt;Serving this surface takes four things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A governed query path.&lt;/strong&gt; The agent asks a question; something turns it into a query against your database or semantic layer, with the metric definitions your finance team would recognise. Leaving the model to write arbitrary SQL gives you the same drift problem as building from scratch, only faster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-tenant isolation that follows the sign-in.&lt;/strong&gt; The customer authenticates with their identity provider, and that identity decides which rows a query can see. Row-level security, schema routing, a dataset per tenant, or one app per customer all work; what matters is that the isolation is structural rather than a filter you remember to add.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rendering in the host.&lt;/strong&gt; &lt;a href="https://bonnard.dev/glossary/mcp" rel="noopener noreferrer"&gt;MCP&lt;/a&gt; lets a server return a chart or table that the AI client renders in the conversation. The customer never leaves the chat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An audit log.&lt;/strong&gt; Every call recorded with who ran it, when, and what it touched, so you can answer "who saw which data" without reconstructing it from warehouse logs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of this is exotic. It is the same governance you would want behind an embedded dashboard, delivered through a different front door.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build vs. embed vs. governed BI in the agent
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Build from scratch&lt;/th&gt;
&lt;th&gt;Embed BI tool&lt;/th&gt;
&lt;th&gt;Governed BI in the agent (Bonnard)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Surface&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;In your product UI&lt;/td&gt;
&lt;td&gt;In your product UI&lt;/td&gt;
&lt;td&gt;Inside Claude, ChatGPT, Gemini, Copilot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Time to first chart&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2-4 months&lt;/td&gt;
&lt;td&gt;1-2 weeks&lt;/td&gt;
&lt;td&gt;Four setup steps, no engineering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;What renders the chart&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Your frontend code&lt;/td&gt;
&lt;td&gt;The BI tool&lt;/td&gt;
&lt;td&gt;The AI client&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Where the query runs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Your code&lt;/td&gt;
&lt;td&gt;The BI tool&lt;/td&gt;
&lt;td&gt;Bonnard, against your database or semantic layer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tenant isolation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;DIY&lt;/td&gt;
&lt;td&gt;Enterprise tier&lt;/td&gt;
&lt;td&gt;Four models, decided by the customer's sign-in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Maintenance burden&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High (you own everything)&lt;/td&gt;
&lt;td&gt;Medium (tool updates, iframes)&lt;/td&gt;
&lt;td&gt;Low (publish an app, choose the audience)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AI agent support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Build from scratch&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Native (MCP)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are not competing for the same slot. Embedded BI and a from-scratch build put charts in your product UI. Governed BI in the agent puts them inside the AI client. Many teams will do both.&lt;/p&gt;

&lt;h2&gt;
  
  
  What makes the AI agent surface different
&lt;/h2&gt;

&lt;p&gt;Embedded analytics tools (Metabase Embedded, Holistics, Explo, Luzmo, Reveal) give you dashboards inside your product. That's their scope. They're good at it. None of them render a chart inside an AI agent.&lt;/p&gt;

&lt;p&gt;The agent surface differs in two ways. The chart lives in the conversation: a customer asks a question in Claude or ChatGPT and the answer arrives as a chart or table there, with no tab switch. And the customer's identity does the scoping: the sign-in decides which tenant's rows a query can see, so per-customer isolation is a property of access rather than a filter added per dashboard.&lt;/p&gt;

&lt;p&gt;The tradeoff: embedded BI tools render charts in your product UI, which is where many customers expect them. If your customers want both a dashboard and an answer in the agent, use an embedded BI tool for the dashboard and a governed agent surface for the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Bonnard fits
&lt;/h2&gt;

&lt;p&gt;Bonnard serves the AI agent surface as governed BI inside the clients your customers already use: Claude, ChatGPT, Gemini, Copilot. Connect PostgreSQL, BigQuery, Redshift, ClickHouse, DuckDB or MotherDuck, or MySQL or MariaDB, or point it at a dbt, Cube, MetricFlow, or custom semantic layer. Create an app, choose the audience, publish. Access is role-based per app, tenant isolation comes in four models, and every call is logged with who ran it and what it touched. Bonnard is pre-launch. &lt;a href="https://bonnard.dev/#waitlist" rel="noopener noreferrer"&gt;Join the waitlist&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is customer-facing analytics?
&lt;/h3&gt;

&lt;p&gt;Customer-facing analytics is analytics embedded in your product for your customers to use. Instead of internal dashboards for your team, the analytics are exposed to end users: your B2B customers, their teams, and their tools. The key challenges are multi-tenancy (each customer sees only their data), performance (customers expect fast load times), and consistency (the numbers should match across every surface).&lt;/p&gt;

&lt;h3&gt;
  
  
  What is embedded analytics?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://bonnard.dev/glossary/embedded-analytics" rel="noopener noreferrer"&gt;Embedded analytics&lt;/a&gt; means integrating analytics capabilities directly into another application. This can be as simple as an iframe embedding a dashboard or as sophisticated as native React components querying a governed API. The term covers a range of approaches from basic chart embedding to full white-label analytics platforms.&lt;/p&gt;

&lt;h3&gt;
  
  
  How is this different from Metabase embedding?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://bonnard.dev/vs-metabase" rel="noopener noreferrer"&gt;Metabase&lt;/a&gt; offers embedded dashboards via iframe or full-app embedding. The open-source version has basic embedding but no tenant isolation. The Enterprise version ($500+/month) adds row-level permissions and sandboxed embedding. Metabase renders charts in your product UI. Bonnard is a different surface: governed charts and tables inside an AI client. They are not substitutes. Use Metabase for an in-product dashboard, and Bonnard for analytics in the agent.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is white-label analytics?
&lt;/h3&gt;

&lt;p&gt;White-label analytics means your customers see your brand, not a third-party tool's brand. No "Powered by Metabase" footer. No foreign-looking iframe. The analytics feel native to your product. Most embedded BI tools charge for white-labeling, and the harder it is to match your design, the more obvious the third-party tool becomes. This is a property of in-product embedding, not of charts rendered inside an AI agent.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is customer-facing analytics for AI agents?
&lt;/h3&gt;

&lt;p&gt;It is letting your customers ask questions about their data inside an AI agent (Claude, ChatGPT, Gemini, Copilot) and get a chart or table back in the conversation. The requirements are the same as in-product analytics, with one addition: the result has to render inside a host you do not control. Bonnard covers this surface as governed BI published into those clients.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can my customers get charts inside an AI agent?
&lt;/h3&gt;

&lt;p&gt;Yes. The agent connects to a governed data source over &lt;a href="https://bonnard.dev/glossary/mcp" rel="noopener noreferrer"&gt;MCP&lt;/a&gt;, runs the query against your database or semantic layer with the customer's access applied, and the host renders the chart in the conversation. Bonnard packages this as an app you publish to a chosen audience.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>saas</category>
      <category>data</category>
      <category>datavisualization</category>
    </item>
    <item>
      <title>How to Connect an AI Agent to Your Data Warehouse</title>
      <dc:creator>Max Mealing</dc:creator>
      <pubDate>Thu, 16 Jul 2026 15:25:39 +0000</pubDate>
      <link>https://dev.to/maxbonnard/how-to-connect-an-ai-agent-to-your-data-warehouse-ack</link>
      <guid>https://dev.to/maxbonnard/how-to-connect-an-ai-agent-to-your-data-warehouse-ack</guid>
      <description>&lt;p&gt;Most teams connecting AI agents to their data warehouse start with text-to-SQL. The agent generates SQL from natural language, runs it against the warehouse, and returns results. It works until it doesn't: hallucinated JOINs, inconsistent aggregations, no access control, no audit trail.&lt;/p&gt;

&lt;p&gt;There's a better approach. Define your business metrics in a semantic layer, expose them via &lt;a href="https://bonnard.dev/glossary/mcp" rel="noopener noreferrer"&gt;MCP&lt;/a&gt; (Model Context Protocol), and let any AI agent query governed definitions instead of raw tables. This tutorial shows how to set it up in under 30 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does text-to-SQL break in production?
&lt;/h2&gt;

&lt;p&gt;The agent sees column names but not business logic. It doesn't know that your company excludes refunds from revenue. It doesn't know that &lt;code&gt;status = 'completed'&lt;/code&gt; means something different in &lt;code&gt;orders&lt;/code&gt; than in &lt;code&gt;subscriptions&lt;/code&gt;. It doesn't know that marketing and finance defined "active user" differently three years ago and never reconciled.&lt;/p&gt;

&lt;p&gt;So the agent writes plausible SQL and returns plausible numbers. Ask the same question twice with different phrasing and you get different answers. Ask two different agents and you get two different numbers. Neither matches the number your finance team reports.&lt;/p&gt;

&lt;p&gt;Beyond consistency, there's no row-level security. No multi-tenancy. No audit trail showing which agent queried what, when, and for whom. In production, with real customers, that's a non-starter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bonnard.dev/glossary/text-to-sql" rel="noopener noreferrer"&gt;Text-to-SQL&lt;/a&gt; gives you speed. It doesn't give you trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the semantic layer approach?
&lt;/h2&gt;

&lt;p&gt;Instead of letting agents write arbitrary SQL, define your metrics once in YAML: cubes, measures, dimensions, access rules. Then expose those definitions via MCP so agents query governed metrics, not raw tables.&lt;/p&gt;

&lt;p&gt;The difference: every agent gets the same answer because the metric definition is fixed. &lt;code&gt;total_revenue&lt;/code&gt; isn't a column the agent interprets. It's a pre-defined calculation with agreed-upon filters and aggregations. When your finance team updates the revenue definition to exclude trial conversions, that change propagates to every consumer instantly. No agent retrained. No dashboard patched. One diff in your schema repo.&lt;/p&gt;

&lt;p&gt;This architecture also decouples the query interface from the warehouse dialect. Swap PostgreSQL for BigQuery and your agents don't notice. The semantic layer abstracts the SQL generation, so consumers stay stable while infrastructure evolves underneath.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Governance&lt;/th&gt;
&lt;th&gt;Consistency&lt;/th&gt;
&lt;th&gt;Multi-tenant&lt;/th&gt;
&lt;th&gt;Access Control&lt;/th&gt;
&lt;th&gt;Setup Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Direct SQL&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Varies by query&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text-to-SQL&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Varies by prompt&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Semantic Layer via MCP&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;td&gt;Guaranteed&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;td&gt;Row-level&lt;/td&gt;
&lt;td&gt;Under 30 min&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;a href="https://bonnard.dev/glossary/semantic-layer" rel="noopener noreferrer"&gt;semantic layer&lt;/a&gt; is the control plane between your warehouse and every consumer, whether that's a human analyst, a React component, or an AI agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Set up your semantic layer
&lt;/h2&gt;

&lt;p&gt;Stand up a semantic layer that connects to your warehouse and exposes metrics over MCP. Open-source engines like &lt;a href="https://cube.dev" rel="noopener noreferrer"&gt;Cube&lt;/a&gt; connect to &lt;a href="https://bonnard.dev/integrations/bigquery" rel="noopener noreferrer"&gt;BigQuery&lt;/a&gt;, Snowflake, &lt;a href="https://bonnard.dev/integrations/redshift" rel="noopener noreferrer"&gt;Redshift&lt;/a&gt;, Databricks, &lt;a href="https://bonnard.dev/integrations/postgres" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt; (including Supabase, Neon, and RDS), and &lt;a href="https://bonnard.dev/integrations/duckdb" rel="noopener noreferrer"&gt;DuckDB&lt;/a&gt; (including MotherDuck). You configure your warehouse connection in a config file or pass credentials via environment variables.&lt;/p&gt;

&lt;p&gt;If you want to explore without connecting your own warehouse, point the semantic layer at a sample PostgreSQL database with orders, customers, and products data so you can follow along with the rest of this tutorial. A few thousand rows across three tables is enough to test aggregations, filters, and multi-dimensional queries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Define your metrics
&lt;/h2&gt;

&lt;p&gt;Create a cube that maps to a table in your warehouse and defines the metrics your agents will query.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;cubes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;orders&lt;/span&gt;
    &lt;span class="na"&gt;sql_table&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;public.orders&lt;/span&gt;
    &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;total_revenue&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;amount&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sum&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;count&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;count&lt;/span&gt;
    &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;status&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;status&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;created_at&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;created_at&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;time&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Measures&lt;/strong&gt; are the numbers you aggregate: sums, counts, averages. &lt;strong&gt;Dimensions&lt;/strong&gt; are the columns you filter and group by: status, date, category. One definition. Every tool, dashboard, and AI agent that queries &lt;code&gt;total_revenue&lt;/code&gt; gets the same number.&lt;/p&gt;

&lt;p&gt;You can also define &lt;code&gt;pre_aggregations&lt;/code&gt; in the same file to cache expensive computations. For example, a daily rollup of &lt;code&gt;total_revenue&lt;/code&gt; by &lt;code&gt;status&lt;/code&gt; can cut query times from seconds to single-digit milliseconds. The semantic layer rebuilds these rollups on a configurable schedule and invalidates stale caches automatically.&lt;/p&gt;

&lt;p&gt;Schemas are version-controlled alongside your application code. Review metric changes in pull requests. Roll back a bad definition with &lt;code&gt;git revert&lt;/code&gt;. Your data contracts get the same CI/CD workflow as your product.&lt;/p&gt;

&lt;p&gt;To control what's exposed to specific consumers, define a view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;views&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;order_metrics&lt;/span&gt;
    &lt;span class="na"&gt;cubes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;join_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;orders&lt;/span&gt;
        &lt;span class="na"&gt;includes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;total_revenue&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;count&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;status&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;created_at&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Views act as a curated interface. Your agents see &lt;code&gt;order_metrics&lt;/code&gt; with four fields instead of navigating the full schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Deploy and connect your agent
&lt;/h2&gt;

&lt;p&gt;Deploy your schema so the semantic layer serves it over an MCP endpoint, then add the MCP server URL to your client's config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"semantic-layer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://your-semantic-layer.example.com/mcp"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste this into your MCP client's config file and restart. For Claude Desktop, that's &lt;code&gt;~/Library/Application Support/Claude/claude_desktop_config.json&lt;/code&gt;. For Cursor, it's &lt;code&gt;.cursor/mcp.json&lt;/code&gt; in your project root. Claude Code reads from &lt;code&gt;.mcp.json&lt;/code&gt; in your project directory.&lt;/p&gt;

&lt;p&gt;The MCP server handles tool discovery, schema introspection, and query execution over HTTP. Your agent sees available metrics the same way it sees any other MCP tool. No custom integration code required.&lt;/p&gt;

&lt;p&gt;For customer-facing use cases, scope each connection to a specific tenant's data with row-level security defined in the schema, so a customer's agent sees only that customer's rows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Query from your agent
&lt;/h2&gt;

&lt;p&gt;Once connected, your AI agent can discover and query your metrics using natural language. Behind the scenes, the agent calls MCP tools.&lt;/p&gt;

&lt;p&gt;Ask: "What's our total revenue this quarter?"&lt;/p&gt;

&lt;p&gt;The agent calls &lt;code&gt;explore_schema&lt;/code&gt; to discover available metrics, then calls &lt;code&gt;query&lt;/code&gt; with the right measures and time filters. The response comes back as structured data, not raw SQL results.&lt;/p&gt;

&lt;p&gt;Ask: "Break down order count by status for the last 30 days."&lt;/p&gt;

&lt;p&gt;Same flow. The agent uses &lt;code&gt;query&lt;/code&gt; with &lt;code&gt;count&lt;/code&gt; as the measure, &lt;code&gt;status&lt;/code&gt; as the dimension, and a date filter on &lt;code&gt;created_at&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A semantic layer typically exposes a handful of MCP tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;explore_schema&lt;/code&gt;&lt;/strong&gt;: Discover available cubes, measures, and dimensions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;query&lt;/code&gt;&lt;/strong&gt;: Fetch aggregated data using governed metric definitions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;sql_query&lt;/code&gt;&lt;/strong&gt;: Run queries for cases that need custom SQL (still governed by access controls)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;describe_field&lt;/code&gt;&lt;/strong&gt;: Get metadata about a specific measure or dimension&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every query runs through the semantic layer. The agent never touches raw tables. Read more about the architecture in our &lt;a href="https://bonnard.dev/agentic-analytics" rel="noopener noreferrer"&gt;agentic analytics&lt;/a&gt; guide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Charting the agent's results
&lt;/h2&gt;

&lt;p&gt;A governed query returns rows. The agent usually needs to show the user a chart, not a wall of numbers. Leaving the model to draw HTML puts the most important part of the answer in its hands to improvise, and the chart can disagree with the rows it came from.&lt;/p&gt;

&lt;p&gt;The fix is to have the server return the chart. MCP lets a server hand back a chart or table built from the query result, and the client renders it in the conversation. The chart comes from the data, not from tokens the model invents. Same data, same chart, every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  From raw SQL to governed metrics
&lt;/h2&gt;

&lt;p&gt;You started with a warehouse and an AI agent that writes its own SQL. Now you have governed metrics defined in YAML, exposed via MCP, queryable from any AI tool your team or customers use. Setup took under 30 minutes.&lt;/p&gt;

&lt;p&gt;The shift from raw SQL to governed metrics pays off immediately: consistent numbers across every consumer, row-level access control per tenant, and a full audit trail for every query. As your team adds more agents and surfaces, the semantic layer scales with you. No duplicate logic. No drift between dashboards and AI answers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Bonnard fits
&lt;/h2&gt;

&lt;p&gt;Bonnard is the managed version of this setup: governed BI delivered inside Claude, ChatGPT, Gemini, Copilot, and the other clients a team already uses. Connect PostgreSQL, BigQuery, Redshift, ClickHouse, DuckDB or MotherDuck, or MySQL or MariaDB, or sit it on the dbt, Cube, MetricFlow, or custom semantic layer you just built. Create an app, choose who can see it, publish. Role-based access per app, four tenancy models, and a per-call audit log come with it. Bonnard is pre-launch. &lt;a href="https://bonnard.dev/#waitlist" rel="noopener noreferrer"&gt;Join the waitlist&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://bonnard.dev/glossary/mcp" rel="noopener noreferrer"&gt;What is MCP?&lt;/a&gt; -- the protocol that powers agent-to-data connections&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://bonnard.dev/agentic-analytics" rel="noopener noreferrer"&gt;Agentic analytics&lt;/a&gt; -- the architecture behind governed agent access to data&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Do I need to retrain my AI model?
&lt;/h3&gt;

&lt;p&gt;No. A semantic layer with MCP support works with any MCP-compatible AI agent out of the box. Your agent discovers available metrics through the MCP protocol at runtime. There is no fine-tuning, prompt engineering, or model modification required.&lt;/p&gt;

&lt;h3&gt;
  
  
  What data warehouses does this work with?
&lt;/h3&gt;

&lt;p&gt;A semantic layer connects to the warehouses its engine supports; Cube, for example, covers BigQuery, Snowflake, Redshift, Databricks, PostgreSQL (including Supabase, Neon, and RDS), and DuckDB (including MotherDuck). Bonnard connects to PostgreSQL, BigQuery, Redshift, ClickHouse, DuckDB and MotherDuck, and MySQL and MariaDB, directly or through a dbt, Cube, MetricFlow, or custom semantic layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  How is this different from text-to-SQL?
&lt;/h3&gt;

&lt;p&gt;Text-to-SQL lets an AI agent generate arbitrary SQL from natural language. It has no governance, no consistency guarantees, and no access control. A semantic layer defines metrics once in YAML and exposes them as governed APIs. Every consumer gets the same answer because the calculation is fixed, not interpreted per query.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I let the agent show charts?
&lt;/h3&gt;

&lt;p&gt;Return the chart from the server rather than asking the model to draw one. With &lt;a href="https://bonnard.dev/glossary/mcp" rel="noopener noreferrer"&gt;MCP&lt;/a&gt;, a server can hand the client a chart or table built from the query result, and Claude or ChatGPT renders it in the conversation. Bonnard does this for every published app, so the chart matches the rows every time.&lt;/p&gt;

&lt;h3&gt;
  
  
  How long does setup take?
&lt;/h3&gt;

&lt;p&gt;Under 30 minutes for most teams. Stand up a semantic layer, connect your warehouse, define a few metrics in YAML, and deploy it over MCP. The tutorial above walks through each step with working code examples.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>aiagents</category>
      <category>database</category>
      <category>data</category>
    </item>
    <item>
      <title>Best Embedded Analytics Tools for SaaS in 2026</title>
      <dc:creator>Max Mealing</dc:creator>
      <pubDate>Thu, 16 Jul 2026 15:25:36 +0000</pubDate>
      <link>https://dev.to/maxbonnard/best-embedded-analytics-tools-for-saas-in-2026-5d0o</link>
      <guid>https://dev.to/maxbonnard/best-embedded-analytics-tools-for-saas-in-2026-5d0o</guid>
      <description>&lt;p&gt;If you're building a B2B SaaS product and need to ship analytics to your customers, you're evaluating embedded analytics tools. The market has more options than it needs. This guide cuts through the noise.&lt;/p&gt;

&lt;p&gt;We compare 10 tools across the dimensions that matter for B2B: multi-tenancy, customization, AI agent support, pricing model, and how many surfaces each tool serves beyond dashboards.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to look for in embedded analytics
&lt;/h2&gt;

&lt;p&gt;Before comparing tools, know what matters for your use case:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-tenancy.&lt;/strong&gt; Every customer sees only their data. This needs to be structural (enforced at the query layer), not bolted on (middleware filters that might miss an edge case). One data leak and your customers' trust is gone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Customization.&lt;/strong&gt; The analytics should look like part of your product. Iframe embedding limits this. Native SDK components give you full control over styling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Metric governance.&lt;/strong&gt; Every chart should show the same number as your API. If the dashboard says $45K and the API returns $43K, your customer notices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance.&lt;/strong&gt; Dashboards that take 8 seconds to load don't get used. Caching matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing model.&lt;/strong&gt; Per-user pricing kills B2B use cases. If you have 1,000 customers each with 10 users, per-seat licensing makes embedded analytics unaffordable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI agent support.&lt;/strong&gt; Increasingly, customers want to connect AI tools to their data. None of the traditional embedded analytics tools support this. It's a differentiator.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 10 tools compared
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Multi-tenancy&lt;/th&gt;
&lt;th&gt;AI/Agent&lt;/th&gt;
&lt;th&gt;Pricing&lt;/th&gt;
&lt;th&gt;Open source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://bonnard.dev/vs-metabase" rel="noopener noreferrer"&gt;Metabase&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;BI tool with embedding&lt;/td&gt;
&lt;td&gt;Enterprise only ($500+/mo)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Free OSS / $500+ Pro&lt;/td&gt;
&lt;td&gt;Yes (AGPL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Holistics&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Embedded BI platform&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Per-user&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Explo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Embedded analytics SDK&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Usage-based&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Luzmo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Embedded analytics SDK&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Usage-based&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reveal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Embedded analytics SDK&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Per-app&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GoodData&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Enterprise embedded BI&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Usage-based&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://bonnard.dev/vs-looker" rel="noopener noreferrer"&gt;Looker&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Enterprise BI with embedding&lt;/td&gt;
&lt;td&gt;Enterprise&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Enterprise pricing&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://bonnard.dev/vs-power-bi" rel="noopener noreferrer"&gt;Power BI Embedded&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Enterprise BI with embedding&lt;/td&gt;
&lt;td&gt;Complex setup&lt;/td&gt;
&lt;td&gt;Copilot (internal)&lt;/td&gt;
&lt;td&gt;Capacity-based&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://bonnard.dev/vs-tableau" rel="noopener noreferrer"&gt;Tableau Embedded&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Enterprise BI with embedding&lt;/td&gt;
&lt;td&gt;Enterprise&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Per-user + Server&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://bonnard.ai" rel="noopener noreferrer"&gt;Bonnard&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Governed BI inside AI clients (not embedded BI)&lt;/td&gt;
&lt;td&gt;Per-app RBAC, four tenancy models&lt;/td&gt;
&lt;td&gt;Native (Claude, ChatGPT, Gemini, Copilot)&lt;/td&gt;
&lt;td&gt;Pre-launch, waitlist&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Tool-by-tool breakdown
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Metabase
&lt;/h3&gt;

&lt;p&gt;The most popular open-source BI tool. Great for internal dashboards. Embedding requires Pro ($500+/mo) for static embeds or Enterprise for interactive. Multi-tenancy (sandboxing) is Enterprise only. No semantic layer, no AI agent support.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Internal dashboards, MVPs, teams that want free self-hosted BI.&lt;br&gt;
&lt;strong&gt;Not great for:&lt;/strong&gt; Customer-facing B2B analytics at scale. Multi-tenancy and embedding costs add up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Holistics
&lt;/h3&gt;

&lt;p&gt;Self-service embedded BI aimed at product teams. Built-in multi-tenancy with row-level security. Strong data modeling layer. Drag-and-drop dashboard builder with embedding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Teams that want a managed embedded BI platform with a modeling layer.&lt;br&gt;
&lt;strong&gt;Not great for:&lt;/strong&gt; AI agent use cases, custom React components, or teams that want open-source infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Explo
&lt;/h3&gt;

&lt;p&gt;SDK-first embedded analytics. React and JavaScript components for building dashboards in your product. Built-in multi-tenancy. Good API for programmatic control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Product teams that want prebuilt dashboard components with customization.&lt;br&gt;
&lt;strong&gt;Not great for:&lt;/strong&gt; Teams that need governed metrics across multiple surfaces beyond dashboards. No semantic layer, no AI agent support.&lt;/p&gt;

&lt;h3&gt;
  
  
  Luzmo
&lt;/h3&gt;

&lt;p&gt;Embedded analytics with a focus on developer experience. SDK components, API access, and a dashboard builder. Some AI capabilities for insight generation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Small to mid-size SaaS products that want quick embedded dashboards.&lt;br&gt;
&lt;strong&gt;Not great for:&lt;/strong&gt; Enterprise multi-tenancy, AI agent integration, or teams that need metrics governance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reveal
&lt;/h3&gt;

&lt;p&gt;Embedded analytics SDK targeting .NET and Java applications. Rich visualization library. Built-in multi-tenancy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; .NET and Java shops building enterprise applications.&lt;br&gt;
&lt;strong&gt;Not great for:&lt;/strong&gt; React/TypeScript teams, AI agent use cases, or modern data stack.&lt;/p&gt;

&lt;h3&gt;
  
  
  GoodData
&lt;/h3&gt;

&lt;p&gt;Enterprise embedded analytics platform. Strong multi-tenancy, API access, and metric definitions ("metrics as code" via MAQL). Partial open-source.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Large SaaS companies with enterprise analytics requirements.&lt;br&gt;
&lt;strong&gt;Not great for:&lt;/strong&gt; Smaller teams, open-source-first teams, or AI agent integration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Looker
&lt;/h3&gt;

&lt;p&gt;Google Cloud's BI platform. LookML provides a strong semantic layer, but it's locked to Looker's ecosystem. Enterprise pricing. Embedding requires Looker Embed API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Google Cloud teams with enterprise budgets that need a mature semantic layer.&lt;br&gt;
&lt;strong&gt;Not great for:&lt;/strong&gt; Teams outside the Google ecosystem, budget-conscious products, or AI agent use cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Power BI Embedded
&lt;/h3&gt;

&lt;p&gt;Microsoft's embedded BI. Capacity-based pricing (SKUs). Deep Azure integration. Complex multi-tenancy setup requiring Azure AD.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Microsoft shops with Azure infrastructure.&lt;br&gt;
&lt;strong&gt;Not great for:&lt;/strong&gt; Non-Microsoft teams, simple multi-tenancy, or AI agent access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tableau Embedded
&lt;/h3&gt;

&lt;p&gt;Salesforce's visualization platform. Rich charting. Embedding requires Tableau Server/Cloud plus custom auth. Per-seat pricing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Organizations deeply invested in Tableau with budget for per-seat licensing.&lt;br&gt;
&lt;strong&gt;Not great for:&lt;/strong&gt; B2B products with many end users (per-seat cost is prohibitive). No AI agent support.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bonnard
&lt;/h3&gt;

&lt;p&gt;Not an embedded BI tool. Bonnard delivers governed BI inside the AI clients your customers already use: Claude, ChatGPT, Gemini, Copilot. It sits on your semantic layer (dbt, Cube, MetricFlow, or custom) and connects to PostgreSQL, BigQuery, Redshift, ClickHouse, DuckDB and MotherDuck, MySQL and MariaDB. You connect your data, create an app, choose the audience, and publish. Each app has role-based access, tenants are isolated by one of four models, and every call is logged with who ran it and what it touched. Pre-launch. &lt;a href="https://bonnard.dev/#waitlist" rel="noopener noreferrer"&gt;Join the waitlist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Teams whose customers ask about their data in an AI client and want governed answers there, without building a dashboard.&lt;br&gt;
&lt;strong&gt;Not great for:&lt;/strong&gt; Teams that want in-product dashboards for non-technical users. This is BI inside the agent, not an embedded dashboard tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision framework
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Choose dashboard embedding (Metabase, Holistics, Explo, Luzmo, Reveal)&lt;/strong&gt; if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dashboards are the only analytics surface you need&lt;/li&gt;
&lt;li&gt;You want a prebuilt UI your customers interact with&lt;/li&gt;
&lt;li&gt;AI agent access is not a requirement&lt;/li&gt;
&lt;li&gt;You don't need the same metrics in APIs and SDKs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Choose enterprise BI embedding (Looker, Power BI, Tableau)&lt;/strong&gt; if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're already in that vendor's ecosystem&lt;/li&gt;
&lt;li&gt;Enterprise pricing fits your model&lt;/li&gt;
&lt;li&gt;Internal + embedded analytics in one tool matters&lt;/li&gt;
&lt;li&gt;You have dedicated BI administrators&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Choose BI inside the AI client (Bonnard)&lt;/strong&gt; if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your customers already work in Claude, ChatGPT, Gemini, or Copilot and want answers there&lt;/li&gt;
&lt;li&gt;You need per-tenant isolation, role-based access, and an audit log on every query&lt;/li&gt;
&lt;li&gt;You already have a semantic layer (dbt, Cube, MetricFlow, or custom) and want it served, not rebuilt&lt;/li&gt;
&lt;li&gt;This is adjacent to embedded BI, not a replacement for it&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the best free embedded analytics tool?
&lt;/h3&gt;

&lt;p&gt;Metabase (AGPL) is the most popular free option for dashboards. Superset (Apache 2.0) is free for SQL-native dashboards. "Free" depends on what you need: basic charting, full embedded BI, or governed answers inside an AI client.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is embedded analytics?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://bonnard.dev/glossary/embedded-analytics" rel="noopener noreferrer"&gt;Embedded analytics&lt;/a&gt; is analytics integrated directly into your product for end users. Instead of linking customers to a separate BI tool, charts and metrics appear inside your application. Ranges from basic iframe embedding to native SDK components with governed metrics.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much does embedded analytics cost for SaaS?
&lt;/h3&gt;

&lt;p&gt;Free (Metabase OSS) to enterprise pricing (Looker, Tableau). Mid-range tools (Explo, Holistics, Luzmo) charge per-user or usage-based, typically $500-5,000/mo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which embedded analytics tool has the best multi-tenancy?
&lt;/h3&gt;

&lt;p&gt;Tools with structural multi-tenancy (enforced at the query layer): Explo, Holistics. Tools with configuration-based multi-tenancy: Metabase Enterprise (sandboxing), Power BI (Azure AD + RLS). Tools with no built-in multi-tenancy: Metabase OSS, Superset, Redash.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do any embedded analytics tools support AI agents?
&lt;/h3&gt;

&lt;p&gt;The traditional embedded analytics tools render charts in your product, not inside an AI agent. Some enterprise tools (Power BI Copilot, Luzmo, GoodData) have limited AI features but none render interactive charts inside an MCP host. BI inside the agent is a different category: Bonnard delivers governed charts, tables, and reports inside Claude, ChatGPT, Gemini, and Copilot, on top of your semantic layer, with per-app access control and a log of every call.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>saas</category>
      <category>data</category>
      <category>datavisualization</category>
    </item>
    <item>
      <title>Analytics API: How to Serve Governed Metrics to Any Consumer</title>
      <dc:creator>Max Mealing</dc:creator>
      <pubDate>Thu, 16 Jul 2026 15:25:33 +0000</pubDate>
      <link>https://dev.to/maxbonnard/analytics-api-how-to-serve-governed-metrics-to-any-consumer-1ko4</link>
      <guid>https://dev.to/maxbonnard/analytics-api-how-to-serve-governed-metrics-to-any-consumer-1ko4</guid>
      <description>&lt;p&gt;You have metrics. Revenue, active users, churn, usage per customer. They live in your data warehouse. Now you need to serve them to multiple consumers: your product's frontend, your customers' integrations, an AI agent, a scheduled report generator.&lt;/p&gt;

&lt;p&gt;The first instinct is custom API endpoints. One endpoint for revenue by region. Another for churn by plan. Another for the customer dashboard. Each endpoint has its own SQL query, its own response format, its own maintenance burden. By the twentieth endpoint, you're running a bespoke analytics service.&lt;/p&gt;

&lt;p&gt;An analytics API backed by a &lt;a href="https://bonnard.dev/glossary/semantic-layer" rel="noopener noreferrer"&gt;semantic layer&lt;/a&gt; gives you one query interface that serves every consumer. Define metrics once. Query them from anywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is an analytics API?
&lt;/h2&gt;

&lt;p&gt;An analytics API is a programmatic interface for querying metrics. Instead of connecting to a database and writing SQL, consumers call an API endpoint with the metrics they want and get structured results back.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://analytics.example.com/v1/query &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer bon_pk_..."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "measures": ["orders.total_revenue", "orders.order_count"],
    "dimensions": ["orders.region"],
    "timeDimensions": [{
      "dimension": "orders.created_at",
      "granularity": "month",
      "dateRange": ["2026-01-01", "2026-03-31"]
    }],
    "orderBy": { "orders.total_revenue": "desc" }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response is structured JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"orders.region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EMEA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"orders.total_revenue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;142000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"orders.order_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;340&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"orders.created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-01-01"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"orders.region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"APAC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"orders.total_revenue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;98000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"orders.order_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;220&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"orders.created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-01-01"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every consumer uses the same interface. The dashboard frontend, the customer's API integration, the scheduled report, and the AI agent all query the same endpoint with the same metric definitions. The analytics API handles query generation, execution, caching, and access control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just build API endpoints?
&lt;/h2&gt;

&lt;p&gt;Custom endpoints work at small scale. They break at medium scale:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Metric drift.&lt;/strong&gt; &lt;code&gt;/api/revenue&lt;/code&gt; and &lt;code&gt;/api/dashboard/revenue&lt;/code&gt; both return "revenue" but use different SQL queries. One gets updated. The other doesn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;N+1 endpoint problem.&lt;/strong&gt; Every new metric or dimension combination is a new endpoint. Revenue by region. Revenue by plan. Revenue by region AND plan. Revenue by region AND plan AND month. The combinatorial explosion is real.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No caching strategy.&lt;/strong&gt; Each endpoint hits the warehouse on every request. Response times grow with data volume. You end up building a caching layer per endpoint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No access control.&lt;/strong&gt; Each endpoint implements its own auth. Customer A's key works on all endpoints or none. Per-tenant, per-metric access control is custom code.&lt;/p&gt;

&lt;p&gt;A shared query interface handles all of this. But there's a gap that shows up once AI agents become consumers: an agent that fetches rows still has to present them. A wall of JSON tells the user nothing. Agents need to chart query results, not just fetch them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Charting the results an agent fetches
&lt;/h2&gt;

&lt;p&gt;Fetching rows is half the job. The other half is rendering them so a person can read the answer. An analytics API that serves agents needs a way to return the picture as well as the numbers.&lt;/p&gt;

&lt;h3&gt;
  
  
  How the flow works
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;The agent calls the query interface with measures, dimensions, and filters.&lt;/li&gt;
&lt;li&gt;The API runs the governed query against your warehouse and returns typed rows.&lt;/li&gt;
&lt;li&gt;The server builds the chart from the typed result: line, bar, or area for trends, scatter for distributions, funnel or waterfall for staged data, pie for shares, or a table when a table is the right answer.&lt;/li&gt;
&lt;li&gt;The host renders it inside Claude or ChatGPT over &lt;a href="https://bonnard.dev/glossary/mcp" rel="noopener noreferrer"&gt;MCP&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Axes, formatting, and gap-fill are decided from the typed schema, so the same query produces the same chart every time. The model never draws the chart, so the picture cannot disagree with the rows.&lt;/p&gt;

&lt;p&gt;One query interface for the numbers. One rendering path for the picture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Analytics API vs alternatives
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Metric governance&lt;/th&gt;
&lt;th&gt;Multi-tenant&lt;/th&gt;
&lt;th&gt;Caching&lt;/th&gt;
&lt;th&gt;Maintenance&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Custom REST endpoints&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None (per-endpoint SQL)&lt;/td&gt;
&lt;td&gt;DIY&lt;/td&gt;
&lt;td&gt;DIY&lt;/td&gt;
&lt;td&gt;High (per endpoint)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GraphQL API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Schema-level&lt;/td&gt;
&lt;td&gt;DIY&lt;/td&gt;
&lt;td&gt;Per-resolver&lt;/td&gt;
&lt;td&gt;Medium-high&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Direct warehouse access&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Low (but dangerous)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;BI tool API&lt;/strong&gt; (Looker, Metabase)&lt;/td&gt;
&lt;td&gt;Tool-specific&lt;/td&gt;
&lt;td&gt;Tool-specific&lt;/td&gt;
&lt;td&gt;Tool-specific&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bonnard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Your database or semantic layer (dbt, Cube, MetricFlow, custom)&lt;/td&gt;
&lt;td&gt;Four isolation models, role-based per app&lt;/td&gt;
&lt;td&gt;Inherits your warehouse&lt;/td&gt;
&lt;td&gt;Low (connect data, create an app, choose the audience, publish). Charts and tables render in Claude, ChatGPT, Gemini, and Copilot from governed query results&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  When you need an analytics API
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;You need one when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple consumers query the same metrics (frontend + backend + AI agents)&lt;/li&gt;
&lt;li&gt;Customers integrate with your data via API&lt;/li&gt;
&lt;li&gt;You're building &lt;a href="https://bonnard.dev/glossary/embedded-analytics" rel="noopener noreferrer"&gt;embedded analytics&lt;/a&gt; with a frontend SDK&lt;/li&gt;
&lt;li&gt;AI agents need programmatic access to governed data&lt;/li&gt;
&lt;li&gt;Your data team is tired of building custom endpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;You don't need one when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A single dashboard covers all use cases&lt;/li&gt;
&lt;li&gt;Nobody queries your metrics programmatically&lt;/li&gt;
&lt;li&gt;The data model is so simple that direct SQL is fine&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where Bonnard fits
&lt;/h2&gt;

&lt;p&gt;Bonnard is the managed form of this: governed BI inside the AI clients your consumers already use, rather than an endpoint you host. Connect PostgreSQL, BigQuery, Redshift, ClickHouse, DuckDB or MotherDuck, or MySQL or MariaDB, or sit it on a dbt, Cube, MetricFlow, or custom semantic layer. Create an app, choose the audience, publish. Role-based access per app, four tenancy models, and a per-call audit log come with it. Bonnard is pre-launch. &lt;a href="https://bonnard.dev/#waitlist" rel="noopener noreferrer"&gt;Join the waitlist&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is an analytics API?
&lt;/h3&gt;

&lt;p&gt;An analytics API is a programmatic interface for querying business metrics. Instead of writing SQL against a database, consumers call an API with the measures, dimensions, and filters they want. The API handles query generation, caching, and access control.&lt;/p&gt;

&lt;h3&gt;
  
  
  How is an analytics API different from a REST API?
&lt;/h3&gt;

&lt;p&gt;A generic REST API exposes resources (users, orders, invoices). An analytics API exposes metrics (revenue, churn rate, active users) with aggregation, filtering, and time dimensions built in. The query interface is designed for analytical questions, not CRUD operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need an analytics API for AI agents?
&lt;/h3&gt;

&lt;p&gt;If AI agents query your data, an analytics API is the governed path. The alternative is giving agents direct database access (dangerous) or text-to-SQL (inconsistent). An analytics API backed by a semantic layer gives agents governed access to metric definitions. See &lt;a href="https://bonnard.dev/blog/what-is-agentic-semantic-layer" rel="noopener noreferrer"&gt;What Is an Agentic Semantic Layer?&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about GraphQL for analytics?
&lt;/h3&gt;

&lt;p&gt;GraphQL works for analytics APIs but you end up building the aggregation, caching, and access control layer yourself. A semantic layer provides these out of the box with a purpose-built query interface for analytical workloads.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>api</category>
      <category>data</category>
      <category>semanticlayer</category>
    </item>
    <item>
      <title>AI Reporting: How to Automate Reports Without Losing Trust</title>
      <dc:creator>Max Mealing</dc:creator>
      <pubDate>Thu, 16 Jul 2026 15:25:31 +0000</pubDate>
      <link>https://dev.to/maxbonnard/ai-reporting-how-to-automate-reports-without-losing-trust-1ehb</link>
      <guid>https://dev.to/maxbonnard/ai-reporting-how-to-automate-reports-without-losing-trust-1ehb</guid>
      <description>&lt;p&gt;Your data team spends 40% of their time building reports. Weekly revenue summaries. Monthly board decks. Quarterly business reviews. Customer-facing usage reports. Each one requires pulling data, checking the numbers match last month's methodology, formatting it, and sending it out.&lt;/p&gt;

&lt;p&gt;AI reporting tools promise to automate this. Ask for a report in natural language and get charts, tables, and summaries in seconds. The problem: the AI generates the queries from scratch each time. This month's revenue calculation might differ from last month's. The board deck numbers might not match the customer report. Automation without governance creates reports nobody trusts.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is AI reporting?
&lt;/h2&gt;

&lt;p&gt;AI reporting uses large language models to automate parts of the reporting workflow: querying data, generating visualizations, writing narrative summaries, and scheduling delivery. Instead of an analyst manually pulling numbers and building slides, the AI handles the mechanical work.&lt;/p&gt;

&lt;p&gt;The tools range from simple (ChatGPT generating a summary from a CSV) to sophisticated (enterprise BI platforms with AI-powered report builders). What they share: the AI interprets your data on every run. There's no guarantee that "revenue" means the same thing in this week's report as it did last week.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why AI-generated reports break trust
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Inconsistent methodology
&lt;/h3&gt;

&lt;p&gt;The AI generates SQL from your schema context. This week it calculates revenue as &lt;code&gt;SUM(amount)&lt;/code&gt;. Next week, with slightly different prompt context, it adds a &lt;code&gt;WHERE status = 'completed'&lt;/code&gt; filter. The CEO compares the two reports and asks why revenue dropped 8%. It didn't drop. The calculation changed. Nobody noticed because the SQL is generated on the fly.&lt;/p&gt;

&lt;h3&gt;
  
  
  No audit trail
&lt;/h3&gt;

&lt;p&gt;When the CFO asks "where did this number come from?", the answer should be a versioned metric definition, not "an LLM wrote some SQL." Automated reports need the same auditability as manual ones. If you can't reproduce a number from a specific report, the report is useless for compliance, board materials, or customer-facing delivery.&lt;/p&gt;

&lt;h3&gt;
  
  
  Metric drift across reports
&lt;/h3&gt;

&lt;p&gt;The weekly ops report, the monthly board deck, and the quarterly customer report all show "revenue." Without a shared definition, each report calculates it independently. The numbers diverge. Stakeholders compare reports and lose confidence. The data team gets pulled into reconciliation instead of building.&lt;/p&gt;

&lt;h2&gt;
  
  
  The governed approach to AI reporting
&lt;/h2&gt;

&lt;p&gt;The fix isn't avoiding AI. It's separating what AI is good at (generating natural language summaries, choosing visualizations, scheduling delivery) from what it's bad at (defining business metrics).&lt;/p&gt;

&lt;h3&gt;
  
  
  Define metrics once
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;cubes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;revenue_metrics&lt;/span&gt;
    &lt;span class="na"&gt;sql_table&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;analytics.monthly_revenue&lt;/span&gt;
    &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;total_revenue&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CASE&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;WHEN&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;!=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'refunded'&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;AND&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;!=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'trial'&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;THEN&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ELSE&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;END"&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sum&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Finance-approved&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;revenue&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(excludes&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;refunds&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;trials)"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mrr&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;monthly_amount&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sum&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Monthly&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;recurring&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;revenue&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;active&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;subscriptions"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;net_revenue_retention&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;current_mrr&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;previous_mrr"&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;avg&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NRR:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;current&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;period&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;MRR&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;prior&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;period&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;MRR&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;same&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;cohort"&lt;/span&gt;
    &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;plan&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;plan_name&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;region&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;region&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;period&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;period_date&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;time&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every report, whether generated by AI or built manually, references these definitions. The methodology is fixed. This month's "revenue" uses the same calculation as last month's.&lt;/p&gt;

&lt;h3&gt;
  
  
  Let AI handle presentation, not calculation
&lt;/h3&gt;

&lt;p&gt;The AI's job shifts from "calculate revenue" to "present the governed revenue number." It queries the &lt;a href="https://bonnard.dev/glossary/semantic-layer" rel="noopener noreferrer"&gt;semantic layer&lt;/a&gt;, gets a trustworthy number, and wraps it in a narrative: "Revenue grew 12% QoQ, driven primarily by Enterprise plan expansion in EMEA."&lt;/p&gt;

&lt;p&gt;This is a better division of labor. The data team defines what's true. The AI makes it readable.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI report generators: what's available
&lt;/h2&gt;

&lt;p&gt;AI report generators automate the mechanical parts of building reports. Here's what exists and where each approach fits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;General-purpose LLMs as report generators.&lt;/strong&gt; Upload a CSV to ChatGPT or Claude and ask for an analysis. Good for one-off reports. No governance, no scheduling, no multi-tenancy. The report is as good as the prompt. Ask the same question next month and you might get a different methodology.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dedicated AI reporting tools.&lt;/strong&gt; Narrative BI, Rath, and similar tools generate automated dashboards and written insights from your data. They connect to your warehouse and produce reports on a schedule. The limitation: they define metrics at report generation time, not in a governed layer. Different reports can calculate the same metric differently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BI tools with AI features.&lt;/strong&gt; Power BI Copilot, Tableau AI, Domo AI. The AI helps you build reports inside the BI tool. Better than general-purpose LLMs because the data stays in your warehouse. Limited to whatever the BI tool supports.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Semantic layer + AI.&lt;/strong&gt; Define metrics once in a governed layer. Use AI to generate narrative summaries, choose visualizations, and schedule delivery. The AI handles presentation. The semantic layer handles correctness. This is the approach that produces trustworthy automated reports.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI reporting tools compared
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Governed metrics&lt;/th&gt;
&lt;th&gt;Multi-tenant reports&lt;/th&gt;
&lt;th&gt;Audit trail&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ChatGPT / Claude&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Upload data, ask for analysis&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Power BI Copilot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AI in Power BI&lt;/td&gt;
&lt;td&gt;DAX measures (PBI only)&lt;/td&gt;
&lt;td&gt;Complex&lt;/td&gt;
&lt;td&gt;Within PBI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tableau AI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AI in Tableau&lt;/td&gt;
&lt;td&gt;Tableau model only&lt;/td&gt;
&lt;td&gt;Enterprise&lt;/td&gt;
&lt;td&gt;Within Tableau&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Domo AI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AI in Domo platform&lt;/td&gt;
&lt;td&gt;Within Domo&lt;/td&gt;
&lt;td&gt;Within Domo&lt;/td&gt;
&lt;td&gt;Within Domo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Automated BI tools&lt;/strong&gt; (Narrative BI, Rath)&lt;/td&gt;
&lt;td&gt;AI-generated dashboards&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bonnard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Governed BI inside Claude, ChatGPT, Gemini, Copilot&lt;/td&gt;
&lt;td&gt;Yes (dbt, Cube, MetricFlow, or custom)&lt;/td&gt;
&lt;td&gt;Per-app RBAC, four tenancy models&lt;/td&gt;
&lt;td&gt;Every call logged&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  When to use AI reporting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use AI reporting with a semantic layer when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reports go to the board, customers, or regulators (trust matters)&lt;/li&gt;
&lt;li&gt;Multiple reports use the same metrics (consistency matters)&lt;/li&gt;
&lt;li&gt;You're automating B2B customer reports with per-tenant data&lt;/li&gt;
&lt;li&gt;The data team is spending too much time on report generation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Use raw AI reporting tools when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One-off ad-hoc analysis for internal consumption&lt;/li&gt;
&lt;li&gt;Exploring a new dataset where methodology isn't established&lt;/li&gt;
&lt;li&gt;Speed matters more than consistency&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Bonnard takes the governed approach and delivers it inside the AI clients a team already uses: Claude, ChatGPT, Gemini, Copilot. It sits on your semantic layer (dbt, Cube, MetricFlow, or a custom one), so every report queries the same metric definitions. Setup is four steps: connect your data, create an app, choose the audience, publish. Access is role-based per app, tenants are isolated by one of four models, and every call is logged with who ran it and what it touched. Bonnard is pre-launch. &lt;a href="https://bonnard.dev/#waitlist" rel="noopener noreferrer"&gt;Join the waitlist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For background: &lt;a href="https://bonnard.dev/blog/what-is-a-semantic-layer" rel="noopener noreferrer"&gt;What Is a Semantic Layer?&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is AI reporting?
&lt;/h3&gt;

&lt;p&gt;AI reporting uses large language models to automate report generation: querying data, creating visualizations, writing summaries, and scheduling delivery. The AI handles the mechanical work that analysts currently do manually. The risk: without governed metric definitions, the AI generates different calculations on each run.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can AI replace my reporting team?
&lt;/h3&gt;

&lt;p&gt;No. AI can automate the mechanical parts (pulling data, formatting charts, writing summaries). It can't define what metrics should mean, decide which numbers matter for a specific audience, or judge whether an insight is actionable. The best approach: data teams define governed metrics, AI automates the delivery.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is automated reporting?
&lt;/h3&gt;

&lt;p&gt;Automated reporting is any system that generates reports without manual intervention. This includes scheduled dashboard refreshes, programmatic report generation via API, and AI-generated narrative summaries. The key question isn't whether to automate, but whether the automated reports use governed metric definitions.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I make AI reports trustworthy?
&lt;/h3&gt;

&lt;p&gt;Separate metric definition from report generation. Define each metric once in a &lt;a href="https://bonnard.dev/glossary/semantic-layer" rel="noopener noreferrer"&gt;semantic layer&lt;/a&gt; with a fixed calculation. Let the AI query those definitions instead of generating SQL. Every report references the same versioned definitions. When someone questions a number, you point to a Git commit, not "the AI decided."&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the best approach to AI reporting for SaaS?
&lt;/h3&gt;

&lt;p&gt;Govern the numbers first so methodology stays fixed across runs. Then pick the surface where the report lands. If your team or your customers already work in Claude or ChatGPT, delivering the report there beats another dashboard tab, as long as the numbers come from governed definitions and every run is logged.&lt;/p&gt;

</description>
      <category>data</category>
      <category>aiagents</category>
      <category>analytics</category>
      <category>semanticlayer</category>
    </item>
    <item>
      <title>AI Data Analysis: Why Governed Metrics Beat Raw SQL</title>
      <dc:creator>Max Mealing</dc:creator>
      <pubDate>Thu, 16 Jul 2026 15:25:28 +0000</pubDate>
      <link>https://dev.to/maxbonnard/ai-data-analysis-why-governed-metrics-beat-raw-sql-generation-19ji</link>
      <guid>https://dev.to/maxbonnard/ai-data-analysis-why-governed-metrics-beat-raw-sql-generation-19ji</guid>
      <description>&lt;p&gt;AI data analysis tools are everywhere. Upload a spreadsheet to Julius AI and get a chart. Ask ChatGPT's Data Analyst to find trends. Connect Databricks AI/BI to your warehouse and let it generate queries. The pitch: anyone can analyze data without writing SQL.&lt;/p&gt;

&lt;p&gt;The pitch works for one-off exploration. It breaks when you need trustworthy numbers at scale: consistent results across tools, governed access per customer, and an audit trail for every answer. That's where most AI analytics tools stop and where a semantic layer starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is AI data analysis?
&lt;/h2&gt;

&lt;p&gt;AI data analysis uses large language models and machine learning to automate parts of the data analysis workflow: generating queries, identifying patterns, creating visualizations, and summarizing findings in natural language. Instead of writing SQL by hand, you describe what you want and the AI produces it.&lt;/p&gt;

&lt;p&gt;The tools fall into three categories:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spreadsheet AI.&lt;/strong&gt; Upload a CSV and ask questions. Julius AI, Formula Bot, ChatGPT Data Analyst. Good for ad-hoc exploration of small datasets. No governance, no multi-tenancy, no audit trail. Your data leaves your infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BI tool AI.&lt;/strong&gt; Built into an existing BI platform. Tableau's AI analytics, Power BI Copilot, Domo AI, ThoughtSpot's natural language search. Better than spreadsheet tools because they query your warehouse. Still limited by the BI tool's ecosystem and licensing model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Text-to-SQL agents.&lt;/strong&gt; An LLM generates SQL against your warehouse schema. DataGPT, custom GPT agents, Databricks AI/BI. Most flexible, most dangerous. The agent writes arbitrary SQL. No guardrails on what it queries or how it calculates metrics.&lt;/p&gt;

&lt;p&gt;All three share the same fundamental problem: the AI interprets your data on every query. There's no shared definition of what "revenue" means. Different tools, different phrasings, different numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The accuracy problem nobody talks about
&lt;/h2&gt;

&lt;p&gt;AI data analysis demos are impressive. The tool generates a chart from a natural language question in seconds. But run the same question through two different tools and compare the numbers. They don't match.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why?&lt;/strong&gt; Because each tool independently interprets your schema. "Revenue" might be &lt;code&gt;SUM(amount)&lt;/code&gt; in one tool and &lt;code&gt;SUM(total_price) WHERE status = 'completed'&lt;/code&gt; in another. Both are plausible. Neither knows that your finance team excludes refunds and trial conversions.&lt;/p&gt;

&lt;p&gt;This isn't a model quality issue. GPT-4, Claude, and Gemini all exhibit this behavior. The model does what it's designed to do: generate plausible SQL from schema context. But "plausible" and "correct" are different things.&lt;/p&gt;

&lt;p&gt;The accuracy problem compounds at scale:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One analyst, one question, one tool.&lt;/strong&gt; Accuracy is "good enough." You eyeball the result and know if it's in the right ballpark.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple analysts, same question, different tools.&lt;/strong&gt; The numbers diverge. Debates about "which dashboard is right" start.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customers querying their own data.&lt;/strong&gt; You can't tell customers "the number is approximately right." They expect precision. One wrong number and trust is gone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI agents querying autonomously.&lt;/strong&gt; No human in the loop to sanity-check. Wrong numbers propagate through workflows without anyone noticing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://bonnard.dev/glossary/text-to-sql" rel="noopener noreferrer"&gt;Text-to-SQL&lt;/a&gt; gives you speed. It doesn't give you trust. For production analytics, you need both.&lt;/p&gt;

&lt;h2&gt;
  
  
  What makes AI analytics production-grade?
&lt;/h2&gt;

&lt;p&gt;The gap between a demo and a production system is five things. Most AI data analysis tools have one or two. None of the spreadsheet or text-to-SQL tools have all five.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Governed metric definitions
&lt;/h3&gt;

&lt;p&gt;Every metric has one definition. "Revenue" is &lt;code&gt;SUM(amount) WHERE status != 'refunded' AND type != 'trial'&lt;/code&gt;. This definition is fixed, versioned in Git, and referenced by every consumer. The AI doesn't interpret what revenue means. It queries the governed definition.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;cubes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;orders&lt;/span&gt;
    &lt;span class="na"&gt;sql_table&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;public.orders&lt;/span&gt;
    &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;total_revenue&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CASE&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;WHEN&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;!=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'refunded'&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;AND&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;!=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'trial'&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;THEN&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ELSE&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;END"&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sum&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Total&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;revenue&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;excluding&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;refunds&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;trials"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;order_count&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;count&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;average_order_value&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;amount&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;avg&lt;/span&gt;
    &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;status&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;status&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;category&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;category&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;created_at&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;created_at&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;time&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a &lt;a href="https://bonnard.dev/glossary/semantic-layer" rel="noopener noreferrer"&gt;semantic layer&lt;/a&gt;: a metadata layer between your warehouse and every consumer. The AI agent calls &lt;code&gt;explore_schema&lt;/code&gt; to discover available metrics, then &lt;code&gt;query&lt;/code&gt; to fetch governed data. It never generates SQL from scratch.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Consistency across every surface
&lt;/h3&gt;

&lt;p&gt;The same metric definition serves dashboards, React components in your product, REST APIs, AI agents, and markdown reports. Ask for "revenue" from any surface and you get the same number. Not because the AI happened to generate the same SQL, but because the definition is fixed.&lt;/p&gt;

&lt;p&gt;This matters for AI specifically because AI tools generate different SQL on every call. Even the same tool, same question, different session can produce different queries. Governed definitions eliminate this variance.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Multi-tenant access control
&lt;/h3&gt;

&lt;p&gt;For B2B products, Customer A's AI agent should never see Customer B's data. This can't be a prompt instruction ("only query data for customer A"). It needs to be structural: enforced on every query regardless of what the AI does.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;security_context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tenant_filter&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{SECURITY_CONTEXT.tenant_id}&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;customer_id"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every query through a tenant's key automatically includes this filter. The AI agent can't bypass it. See RBAC for more on how this works.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Performance at AI scale
&lt;/h3&gt;

&lt;p&gt;AI agents make more queries than humans. A human asks one question. An agent might make 10 queries to answer it: exploring the schema, trying different dimensions, following up on anomalies. Multiply by hundreds of customers' agents querying simultaneously.&lt;/p&gt;

&lt;p&gt;Pre-aggregation caching pre-computes common rollups. Hot queries return in single-digit milliseconds. Without it, AI-scale query volumes overwhelm most warehouses.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Audit trail
&lt;/h3&gt;

&lt;p&gt;When the number is wrong, you need to trace it. With a semantic layer, every result traces back to a versioned metric definition in Git. You can point to the exact commit that defined the calculation, see when it changed, and who approved the change. With raw text-to-SQL, the trail is "an LLM generated some SQL" with no reproducibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI data analysis tools compared
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Governed metrics&lt;/th&gt;
&lt;th&gt;Multi-tenant&lt;/th&gt;
&lt;th&gt;Audit trail&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Julius AI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Spreadsheet upload + LLM&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Ad-hoc exploration of CSVs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ChatGPT Data Analyst&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;File upload + code interpreter&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;One-off analysis, prototyping&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Databricks AI/BI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Text-to-SQL + Unity Catalog&lt;/td&gt;
&lt;td&gt;Partial (table-level)&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Teams already on Databricks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Power BI Copilot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Natural language in Power BI&lt;/td&gt;
&lt;td&gt;DAX measures (locked to PBI)&lt;/td&gt;
&lt;td&gt;Complex setup&lt;/td&gt;
&lt;td&gt;Within PBI&lt;/td&gt;
&lt;td&gt;Microsoft ecosystem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tableau AI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Natural language in Tableau&lt;/td&gt;
&lt;td&gt;Tableau semantic model&lt;/td&gt;
&lt;td&gt;Enterprise&lt;/td&gt;
&lt;td&gt;Within Tableau&lt;/td&gt;
&lt;td&gt;Salesforce ecosystem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ThoughtSpot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Natural language BI&lt;/td&gt;
&lt;td&gt;Proprietary semantic layer&lt;/td&gt;
&lt;td&gt;Enterprise&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Enterprise AI-powered BI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DataGPT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Conversational analytics agent&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Autonomous data analysis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Domo AI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;BI platform with AI features&lt;/td&gt;
&lt;td&gt;Within Domo&lt;/td&gt;
&lt;td&gt;Within Domo&lt;/td&gt;
&lt;td&gt;Within Domo&lt;/td&gt;
&lt;td&gt;End-to-end data platform&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bonnard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Governed BI published into Claude, ChatGPT, Gemini, Copilot&lt;/td&gt;
&lt;td&gt;Yes (your database or semantic layer)&lt;/td&gt;
&lt;td&gt;Yes (four isolation models)&lt;/td&gt;
&lt;td&gt;Yes (per call)&lt;/td&gt;
&lt;td&gt;SaaS teams serving agents and customers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The tools in the top half are AI-first but governance-second. The ones in the middle (enterprise BI) have governance but lock it to their ecosystem. The bottom row is a different kind of option: Bonnard does not replace your warehouse or semantic layer. It publishes governed metrics into the AI client, with access and audit applied per call.&lt;/p&gt;

&lt;h2&gt;
  
  
  When each approach makes sense
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use spreadsheet AI (Julius, ChatGPT)&lt;/strong&gt; when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're exploring a dataset for the first time&lt;/li&gt;
&lt;li&gt;Nobody else needs to reproduce the result&lt;/li&gt;
&lt;li&gt;The data isn't sensitive&lt;/li&gt;
&lt;li&gt;You want speed over accuracy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Use BI tool AI (Power BI Copilot, Tableau AI)&lt;/strong&gt; when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're already in that BI ecosystem&lt;/li&gt;
&lt;li&gt;Internal teams are the primary consumer&lt;/li&gt;
&lt;li&gt;You don't need to serve AI agents or embed in products&lt;/li&gt;
&lt;li&gt;Enterprise licensing fits your budget&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Use a semantic layer&lt;/strong&gt; when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple consumers need the same metrics (dashboards + API + agents)&lt;/li&gt;
&lt;li&gt;Customers see the results (B2B &lt;a href="https://bonnard.dev/glossary/embedded-analytics" rel="noopener noreferrer"&gt;embedded analytics&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;AI agents query data autonomously&lt;/li&gt;
&lt;li&gt;You need auditability (which definition produced this number?)&lt;/li&gt;
&lt;li&gt;Metrics must be consistent across every tool and surface&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting the chart back: charts inside the agent
&lt;/h2&gt;

&lt;p&gt;Governing the numbers is one half of AI data analysis. The other half is what the customer actually sees. When someone asks a question in Claude or ChatGPT, they often want a chart, not a table of rows. That's a separate surface from the governance layer.&lt;/p&gt;

&lt;p&gt;The failure mode here is letting the model draw the chart. It writes HTML or picks values from memory, and the picture drifts from the rows. The better pattern is for the server to return the chart built from the typed query result, and for the host to render it in the conversation over &lt;a href="https://bonnard.dev/glossary/mcp" rel="noopener noreferrer"&gt;MCP&lt;/a&gt;. The same result then produces the same chart every time, and the agent gets a compact summary and a typed schema it can reason about rather than a blob of pixels.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shift from AI-generated SQL to AI-queried metrics
&lt;/h2&gt;

&lt;p&gt;The first generation of AI analytics gave agents raw warehouse access and hoped for the best. It worked in demos. It failed in production because the agent interprets business logic on every query.&lt;/p&gt;

&lt;p&gt;The next generation separates the concerns. Data teams define governed metrics in a &lt;a href="https://bonnard.dev/glossary/semantic-layer" rel="noopener noreferrer"&gt;semantic layer&lt;/a&gt;. AI agents query those definitions instead of raw tables. The data team's expertise is encoded in the schema. The AI's role shifts from "guess what revenue means" to "pick the right governed metric and present the result."&lt;/p&gt;

&lt;p&gt;This is a better division of labor. The data team defines what's true. The AI handles the interface. Trustworthy numbers with a natural language frontend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Bonnard fits
&lt;/h2&gt;

&lt;p&gt;Bonnard is governed BI inside the AI clients your team and customers already use: Claude, ChatGPT, Gemini, Copilot. Connect PostgreSQL, BigQuery, Redshift, ClickHouse, DuckDB or MotherDuck, or MySQL or MariaDB, or sit it on a dbt, Cube, MetricFlow, or custom semantic layer. Create an app, choose the audience, publish; charts and tables come back in the conversation, built from the query result. Access is role-based per app, tenant isolation has four models, and every call is logged. Bonnard is pre-launch. &lt;a href="https://bonnard.dev/#waitlist" rel="noopener noreferrer"&gt;Join the waitlist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For background: &lt;a href="https://bonnard.dev/blog/what-is-a-semantic-layer" rel="noopener noreferrer"&gt;What Is a Semantic Layer?&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is AI data analysis?
&lt;/h3&gt;

&lt;p&gt;AI data analysis uses large language models and machine learning to automate data querying, pattern detection, and insight generation. Instead of writing SQL manually, you ask questions in natural language and the AI produces queries, charts, and summaries. Tools range from spreadsheet analyzers (Julius AI) to enterprise BI platforms with AI features (Power BI Copilot, Tableau AI).&lt;/p&gt;

&lt;h3&gt;
  
  
  Is AI data analysis accurate?
&lt;/h3&gt;

&lt;p&gt;For ad-hoc exploration, accuracy is "good enough." For production analytics where customers see the numbers, accuracy varies per query because the AI generates SQL from scratch each time. Two tools generating SQL for "revenue" may produce different numbers because they interpret the calculation differently. A semantic layer solves this by defining each metric once and letting AI query the definition instead of generating its own.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the best AI tool for data analysis?
&lt;/h3&gt;

&lt;p&gt;It depends on the use case. For one-off CSV exploration: Julius AI or ChatGPT Data Analyst. For enterprise BI: Power BI Copilot or Tableau AI. For governed metrics served to AI agents and B2B customers: a semantic layer approach. See the comparison table above.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the best approach to AI data analysis for SaaS?
&lt;/h3&gt;

&lt;p&gt;For a SaaS product, two things matter: trustworthy numbers and where the customer sees the result. Govern the numbers with a semantic layer so the AI queries fixed definitions instead of guessing. Then decide the surface. If your customers work in Claude, ChatGPT, Gemini, or Copilot, publish the governed metrics into those clients so the chart arrives in the conversation. Bonnard does this as a published app with role-based access and a per-call audit log.&lt;/p&gt;

&lt;h3&gt;
  
  
  How is AI analytics different from traditional BI?
&lt;/h3&gt;

&lt;p&gt;Traditional BI requires analysts to write queries and build dashboards. AI analytics automates the query generation. The risk: AI-generated queries are inconsistent. Traditional BI with a semantic layer gives you consistent definitions. AI analytics with a semantic layer gives you both: consistent definitions with a natural language interface.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can AI replace data analysts?
&lt;/h3&gt;

&lt;p&gt;No. AI can generate queries and create visualizations faster than humans. It can't define what metrics should mean, decide which questions to ask, or judge whether an insight is actionable. Data analysts bring domain knowledge and judgment. AI handles the mechanical parts. A semantic layer encodes the analyst's expertise so AI can query it reliably.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between AI analytics and text-to-SQL?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://bonnard.dev/glossary/text-to-sql" rel="noopener noreferrer"&gt;Text-to-SQL&lt;/a&gt; is a technique where an LLM generates SQL from natural language. AI analytics is broader: it includes text-to-SQL plus pattern detection, anomaly identification, forecasting, and visualization generation. Both share the same governance gap: the AI interprets your data on every query. A semantic layer addresses this for both.&lt;/p&gt;

&lt;h3&gt;
  
  
  What warehouses work with AI data analysis?
&lt;/h3&gt;

&lt;p&gt;Most AI analytics tools work with common warehouses: Snowflake, &lt;a href="https://bonnard.dev/integrations/bigquery" rel="noopener noreferrer"&gt;BigQuery&lt;/a&gt;, Databricks, &lt;a href="https://bonnard.dev/integrations/postgres" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt; (including Supabase, Neon, and RDS), &lt;a href="https://bonnard.dev/integrations/redshift" rel="noopener noreferrer"&gt;Redshift&lt;/a&gt;, and &lt;a href="https://bonnard.dev/integrations/duckdb" rel="noopener noreferrer"&gt;DuckDB&lt;/a&gt; (including MotherDuck). The semantic layer generates the appropriate SQL dialect for each warehouse.&lt;/p&gt;

</description>
      <category>data</category>
      <category>sql</category>
      <category>aiagents</category>
      <category>semanticlayer</category>
    </item>
    <item>
      <title>What Is an Agentic Semantic Layer?</title>
      <dc:creator>Max Mealing</dc:creator>
      <pubDate>Thu, 16 Jul 2026 14:53:15 +0000</pubDate>
      <link>https://dev.to/maxbonnard/what-is-an-agentic-semantic-layer-4hbj</link>
      <guid>https://dev.to/maxbonnard/what-is-an-agentic-semantic-layer-4hbj</guid>
      <description>&lt;p&gt;An agentic semantic layer is a metadata layer between AI agents and a data warehouse that defines business metrics, enforces access control, and exposes governed query interfaces. Instead of writing raw SQL, agents query metric definitions through protocols like &lt;a href="https://bonnard.dev/glossary/mcp" rel="noopener noreferrer"&gt;MCP&lt;/a&gt; (Model Context Protocol) or REST APIs. Every agent gets the same answer because the business logic is defined once, not interpreted per query.&lt;/p&gt;

&lt;p&gt;Traditional &lt;a href="https://bonnard.dev/glossary/semantic-layer" rel="noopener noreferrer"&gt;semantic layers&lt;/a&gt; were built for BI dashboards and human analysts. An agentic semantic layer is built for programmatic consumers: LLMs, AI agents, SDKs, and applications. The interface, security model, and deployment patterns are different.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with AI analytics today
&lt;/h2&gt;

&lt;p&gt;AI agents are becoming a primary interface to data. Executives ask Claude for quarterly numbers. Product managers ask Cursor for usage metrics. Customer success teams ask chatbots for account health scores. The agent is the new dashboard.&lt;/p&gt;

&lt;p&gt;But most data infrastructure wasn't built for this. Two problems dominate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Text-to-SQL breaks in production
&lt;/h3&gt;

&lt;p&gt;The default approach is to give an agent access to your warehouse and let it write SQL. It works in demos; in production, &lt;a href="https://bonnard.dev/glossary/text-to-sql" rel="noopener noreferrer"&gt;text-to-SQL&lt;/a&gt; produces inconsistent answers, enforces no access control, and leaves no audit trail. &lt;a href="https://bonnard.dev/blog/why-ai-agents-need-semantic-layer" rel="noopener noreferrer"&gt;Why Your AI Agents Need a Semantic Layer&lt;/a&gt; walks through each failure mode.&lt;/p&gt;

&lt;h3&gt;
  
  
  Legacy semantic layers weren't built for agents
&lt;/h3&gt;

&lt;p&gt;Traditional semantic layers solve the consistency problem for dashboards. Looker's LookML, Tableau's semantic model, Power BI's DAX measures. They define metrics once and serve them to BI consumers.&lt;/p&gt;

&lt;p&gt;But they were designed for a world where a human browses a catalog, selects metrics, and views a dashboard. That interaction model doesn't translate to AI agents.&lt;/p&gt;

&lt;p&gt;Agents don't browse catalogs. They call tools at runtime. They need programmatic discovery, querying, and access control, over a standardized protocol rather than a proprietary query language embedded in a BI tool.&lt;/p&gt;

&lt;p&gt;A BI-embedded semantic layer can't serve an MCP client. It can't scope each query to a tenant. It can't serve an AI agent, an embedded chart, and a dashboard from the same definitions.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it differs from a traditional semantic layer
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Traditional semantic layer&lt;/th&gt;
&lt;th&gt;Agentic semantic layer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Primary consumer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;BI dashboards, analysts&lt;/td&gt;
&lt;td&gt;AI agents, LLMs, applications&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Interface&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SQL, proprietary query language (LookML, DAX)&lt;/td&gt;
&lt;td&gt;MCP, REST API, SDK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Discovery&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Human browses a catalog in a GUI&lt;/td&gt;
&lt;td&gt;Agent calls &lt;code&gt;explore_schema&lt;/code&gt; at runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multi-tenancy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Often manual or absent&lt;/td&gt;
&lt;td&gt;Per-query enforcement, tenant scope carried by the caller's identity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Access control&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Dashboard-level or role-based&lt;/td&gt;
&lt;td&gt;Row-level, per-consumer, structural&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Query volume&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Dozens of dashboard refreshes per hour&lt;/td&gt;
&lt;td&gt;Hundreds of agent queries per minute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Caching&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Dashboard-level refresh&lt;/td&gt;
&lt;td&gt;Pre-aggregation with automatic invalidation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deployment&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GUI-configured, click-ops&lt;/td&gt;
&lt;td&gt;YAML in Git, CLI-driven, CI/CD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Schema management&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GUI editor or proprietary file format&lt;/td&gt;
&lt;td&gt;Version-controlled code, PR reviews&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;An agentic semantic layer treats programmatic access as the primary use case, so MCP support, multi-tenant keys, and row-level security are built in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core capabilities
&lt;/h2&gt;

&lt;p&gt;An agentic semantic layer needs five capabilities to work in production. Missing any one of them and you'll end up rebuilding it later.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Machine-readable metric definitions
&lt;/h3&gt;

&lt;p&gt;Metrics defined in YAML or a similar declarative format that both humans and machines can read.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;cubes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;orders&lt;/span&gt;
    &lt;span class="na"&gt;sql_table&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;public.orders&lt;/span&gt;
    &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;total_revenue&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CASE&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;WHEN&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;!=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'refunded'&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;AND&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;!=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'trial'&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;THEN&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ELSE&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;END"&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sum&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Total&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;revenue&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;excluding&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;refunds&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;trials"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;order_count&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;count&lt;/span&gt;
    &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;status&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;status&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;category&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;category&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;created_at&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;created_at&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;time&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;total_revenue&lt;/code&gt; is now a governed definition with a description the agent can read. Deploy this schema and any MCP-compatible agent (Claude, Cursor, or any client supporting the protocol) can discover and query these metrics at runtime.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;description&lt;/code&gt; field matters more than it looks. When an agent calls &lt;code&gt;explore_schema&lt;/code&gt;, descriptions are what it uses to decide which metric answers the user's question. Good descriptions are the difference between an agent that picks the right metric and one that guesses.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Programmatic discovery and querying
&lt;/h3&gt;

&lt;p&gt;Agents need a standardized way to find out what metrics exist and query them. This is what &lt;a href="https://bonnard.dev/glossary/mcp" rel="noopener noreferrer"&gt;MCP&lt;/a&gt; (Model Context Protocol) provides.&lt;/p&gt;

&lt;p&gt;The agent calls &lt;code&gt;explore_schema&lt;/code&gt; to see available cubes, measures, and dimensions. It reads descriptions to understand what each metric represents. Then it calls &lt;code&gt;query&lt;/code&gt; with the right measures, dimensions, and filters. It never writes SQL. The semantic layer handles query generation, execution, caching, and access control.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent: explore_schema → "orders cube has total_revenue, order_count, status, category, created_at"
Agent: query(measures: [total_revenue], dimensions: [status], filters: [{created_at: last 90 days}])
Semantic layer: generates SQL, executes against warehouse, returns structured result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent selects from governed definitions, and the semantic layer's query engine writes the SQL for your warehouse dialect.&lt;/p&gt;

&lt;p&gt;Five tools make this work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;explore_schema&lt;/code&gt;&lt;/strong&gt;: Discover available cubes, measures, and dimensions with descriptions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;query&lt;/code&gt;&lt;/strong&gt;: Fetch aggregated data using governed metric definitions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;sql_query&lt;/code&gt;&lt;/strong&gt;: Run queries for edge cases that need custom SQL (still governed by access controls)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;describe_field&lt;/code&gt;&lt;/strong&gt;: Get detailed metadata about a specific measure or dimension&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A render tool&lt;/strong&gt;: Return a chart or table in the conversation from the query result, so the model does not draw it from memory&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Structural multi-tenancy
&lt;/h3&gt;

&lt;p&gt;If you're building a B2B product, every agent query needs to be scoped to a specific tenant. It needs to be structural: impossible to bypass regardless of what the agent does.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;security_context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tenant_filter&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{SECURITY_CONTEXT.tenant_id}&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;customer_id"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every query for a given tenant automatically includes this filter. The consumer can't skip it. The agent can't override it. The caller's identity (an OAuth token, an API key, or a JWT) carries the tenant context, and every query is scoped from it.&lt;/p&gt;

&lt;p&gt;This is how you ship AI-powered analytics to B2B customers without building a custom access control layer. The semantic layer handles isolation. You handle the product.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Pre-aggregation for agent-scale query volume
&lt;/h3&gt;

&lt;p&gt;AI agents make more queries than humans. A human refreshes a dashboard once. An agent might make 10 queries to answer one question: exploring the schema, trying different dimensions, following up on anomalies.&lt;/p&gt;

&lt;p&gt;Pre-aggregation handles this. Define which metric combinations to pre-compute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;pre_aggregations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;daily_revenue&lt;/span&gt;
        &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;total_revenue&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;order_count&lt;/span&gt;
        &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;status&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;category&lt;/span&gt;
        &lt;span class="na"&gt;time_dimension&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;created_at&lt;/span&gt;
        &lt;span class="na"&gt;granularity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;day&lt;/span&gt;
        &lt;span class="na"&gt;refresh_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;every&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1 hour&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The semantic layer builds and maintains these rollups. Hot queries hit the cache (single-digit milliseconds). Cold queries fall through to the warehouse. Without this, agent workloads can overwhelm your warehouse, especially when multiple customers' agents are querying simultaneously.&lt;/p&gt;

&lt;p&gt;At agent scale, pre-aggregation is a requirement.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Schema-as-code with version control
&lt;/h3&gt;

&lt;p&gt;Metric definitions should live in Git. Changes should go through pull requests. Rollbacks should be &lt;code&gt;git revert&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is how you maintain trust in an agentic system. When someone asks why the revenue number changed, the answer is a Git commit. When a definition is wrong, you revert it and every consumer gets the corrected version.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git diff HEAD~1 schema/orders.yml  &lt;span class="c"&gt;# see what changed&lt;/span&gt;
git revert abc123                   &lt;span class="c"&gt;# undo a bad metric definition&lt;/span&gt;
&lt;span class="c"&gt;# redeploy your semantic layer, and every consumer gets the fix&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Who needs an agentic semantic layer?
&lt;/h2&gt;

&lt;p&gt;Different teams interact with the agentic semantic layer differently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data engineers and analytics engineers&lt;/strong&gt; define the metrics. They write the YAML, review changes in PRs. The agentic semantic layer gives them a single place to define business logic instead of maintaining it across dashboards, notebooks, and custom API endpoints.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Engineering leads and product teams&lt;/strong&gt; consume the metrics. They connect AI agents via MCP, build features on the REST API, and let agents chart query results in the conversation. The semantic layer means they don't need the data team to build a custom endpoint for every new feature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data leaders&lt;/strong&gt; govern the metrics. They ensure definitions are correct, access controls are appropriate, and audit trails are maintained. The semantic layer centralizes governance instead of distributing it across tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Customers (in B2B products)&lt;/strong&gt; query their own data. Through embedded dashboards in your product, through AI agents scoped to their tenant, or through APIs. The semantic layer ensures they see only their data, with the same metric definitions your internal teams use.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it fits the modern data stack
&lt;/h2&gt;

&lt;p&gt;An agentic semantic layer doesn't replace your existing infrastructure. It sits on top of it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Data Sources] → [Ingestion (Fivetran, Airbyte)] → [Warehouse (Snowflake, BigQuery)]
                                                            ↓
                                                    [dbt transformations]
                                                            ↓
                                                  [Agentic Semantic Layer]
                                                    ↙    ↓    ↘
                                            [MCP Agents] [Embedded charts] [API]
                                            [Dashboards]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your ingestion pipeline feeds raw data into the warehouse. dbt transforms it into clean tables. The agentic semantic layer defines business metrics on those tables and serves them to every consumer through the appropriate interface.&lt;/p&gt;

&lt;p&gt;The semantic layer connects to your warehouse (Snowflake, &lt;a href="https://bonnard.dev/integrations/bigquery" rel="noopener noreferrer"&gt;BigQuery&lt;/a&gt;, Databricks, &lt;a href="https://bonnard.dev/integrations/postgres" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt; (including Supabase, Neon, and RDS), &lt;a href="https://bonnard.dev/integrations/redshift" rel="noopener noreferrer"&gt;Redshift&lt;/a&gt;, &lt;a href="https://bonnard.dev/integrations/duckdb" rel="noopener noreferrer"&gt;DuckDB&lt;/a&gt; (including MotherDuck)) and generates the appropriate SQL dialect. Swap warehouses without changing your metric definitions or consumer integrations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agentic semantic layer tools compared
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Agent support&lt;/th&gt;
&lt;th&gt;Multi-tenancy&lt;/th&gt;
&lt;th&gt;Pre-aggregation&lt;/th&gt;
&lt;th&gt;Open source&lt;/th&gt;
&lt;th&gt;Primary use case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://cube.dev" rel="noopener noreferrer"&gt;Cube&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;REST API (no MCP)&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Yes (mature)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Headless BI, API-first analytics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;dbt MetricFlow&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Metric documentation in dbt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Looker (LookML)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Enterprise dashboard BI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AtScale&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Enterprise&lt;/td&gt;
&lt;td&gt;Virtual caching&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Enterprise BI compatibility&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ThoughtSpot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Proprietary ("Spotter")&lt;/td&gt;
&lt;td&gt;Enterprise&lt;/td&gt;
&lt;td&gt;Proprietary&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;AI-powered BI platform&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://bonnard.ai" rel="noopener noreferrer"&gt;Bonnard&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Native (Claude, ChatGPT, Gemini, Copilot)&lt;/td&gt;
&lt;td&gt;Per-app RBAC, four tenancy models&lt;/td&gt;
&lt;td&gt;Via your semantic layer&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Governed BI inside AI clients, on dbt, Cube, MetricFlow, or custom&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The right choice depends on whether AI agents are your primary consumer or a secondary integration. If agents are an afterthought, most semantic layers can be retrofitted with API access. If agents are the primary interface, you need a layer designed for programmatic consumers from the ground up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;An agentic semantic layer gives the agent governed metrics to query. What is left is everything around the query: who is allowed to ask, which tenant's data they see, and what they get back. Bonnard covers that. It sits on dbt, Cube, MetricFlow, or a custom layer and delivers governed BI inside Claude, ChatGPT, Gemini, and Copilot. Setup is four steps: connect your data, create an app, choose the audience, publish. Each app has role-based access, tenants are isolated by one of four models, and every call is logged. Bonnard is pre-launch. &lt;a href="https://bonnard.dev/#waitlist" rel="noopener noreferrer"&gt;Join the waitlist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For a broader look at semantic layers beyond the agentic use case, see &lt;a href="https://bonnard.dev/blog/what-is-a-semantic-layer" rel="noopener noreferrer"&gt;What Is a Semantic Layer?&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is a semantic layer in simple terms?
&lt;/h3&gt;

&lt;p&gt;A semantic layer is a translation layer between raw data and the people or tools that query it. It defines what business terms like "revenue" or "active user" mean in terms of actual database columns and calculations. Instead of every consumer writing its own SQL, they all reference the same definition. See &lt;a href="https://bonnard.dev/blog/what-is-a-semantic-layer" rel="noopener noreferrer"&gt;What Is a Semantic Layer?&lt;/a&gt; for the full guide.&lt;/p&gt;

&lt;h3&gt;
  
  
  What makes a semantic layer "agentic"?
&lt;/h3&gt;

&lt;p&gt;Three things: programmatic discovery (agents find metrics via API, not a GUI catalog), programmatic querying (agents call tools, not SQL), and structural multi-tenancy (access control enforced per query, not per dashboard). A traditional semantic layer might have an API, but an agentic one is designed for agents as the primary consumer.&lt;/p&gt;

&lt;h3&gt;
  
  
  How is an agentic semantic layer different from RAG?
&lt;/h3&gt;

&lt;p&gt;RAG (Retrieval-Augmented Generation) feeds unstructured documents to an LLM for context. An agentic semantic layer provides structured metric definitions for data queries. RAG answers "What does our refund policy say?" A semantic layer answers "What was Q1 revenue?" They solve different problems and are often used together: RAG for knowledge, semantic layer for data.&lt;/p&gt;

&lt;h3&gt;
  
  
  How is an agentic semantic layer different from text-to-SQL?
&lt;/h3&gt;

&lt;p&gt;Text-to-SQL lets an agent generate arbitrary SQL from natural language. The agent interprets column names and guesses business logic. An agentic semantic layer defines metrics once and lets agents query those definitions. Text-to-SQL produces plausible answers; a semantic layer produces governed, auditable ones. See &lt;a href="https://bonnard.dev/glossary/text-to-sql" rel="noopener noreferrer"&gt;text-to-SQL&lt;/a&gt; for more on why the raw approach breaks in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need an agentic semantic layer if I use dbt?
&lt;/h3&gt;

&lt;p&gt;dbt defines transformations: how raw data becomes clean tables. An agentic semantic layer defines metrics: what "revenue" means on top of those tables, and serves them to agents with caching and access control. They're complementary. dbt gets data into shape. The semantic layer defines the business logic on top and serves it. Most agentic semantic layers can import dbt models directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is MCP and why does it matter?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://bonnard.dev/glossary/mcp" rel="noopener noreferrer"&gt;MCP&lt;/a&gt; (Model Context Protocol) is a standard protocol for connecting AI agents to external tools and data sources. It defines how agents discover available tools, call them, and receive results. For an agentic semantic layer, MCP is the interface that lets any compatible agent (Claude, Cursor, Claude Code, and others) discover and query your metrics without custom integration code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can any AI model use an agentic semantic layer?
&lt;/h3&gt;

&lt;p&gt;Yes. A semantic layer with MCP support works with any MCP-compatible client. For other agents, REST APIs and SDKs provide model-agnostic access. The semantic layer sits between the agent and the warehouse, not inside the model. It doesn't matter whether the agent runs Claude, GPT, Gemini, or an open-source model.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the performance impact?
&lt;/h3&gt;

&lt;p&gt;With pre-aggregation, queries get faster. The semantic layer caches rollups so agents query pre-computed results instead of running full aggregations on every request. Hot queries resolve in single-digit milliseconds. Without pre-aggregation, agent-scale query volumes (hundreds of queries per minute across multiple tenants) will overwhelm most warehouses.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does an agentic semantic layer handle hallucinations?
&lt;/h3&gt;

&lt;p&gt;It eliminates the primary source of data hallucinations: ad-hoc SQL generation. The agent never writes SQL. It selects from governed metric definitions and the semantic layer generates correct SQL. The agent can still hallucinate its interpretation of the results (that's a model problem), but the underlying data is always correct and traceable to a versioned definition.&lt;/p&gt;

</description>
      <category>semanticlayer</category>
      <category>data</category>
      <category>goldlayer</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Why Your AI Agents Need a Semantic Layer</title>
      <dc:creator>Max Mealing</dc:creator>
      <pubDate>Thu, 16 Jul 2026 14:51:44 +0000</pubDate>
      <link>https://dev.to/maxbonnard/why-your-ai-agents-need-a-semantic-layer-3l9e</link>
      <guid>https://dev.to/maxbonnard/why-your-ai-agents-need-a-semantic-layer-3l9e</guid>
      <description>&lt;p&gt;Give an AI agent access to your data warehouse and it will write SQL. It might even write good SQL. But ask two agents the same revenue question and you'll get two different numbers. Neither matches finance's report. Text-to-SQL gives agents access to data without any understanding of it.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://bonnard.dev/glossary/semantic-layer" rel="noopener noreferrer"&gt;semantic layer&lt;/a&gt; for AI agents fixes this by putting a governed metrics layer between the agent and the warehouse. The agent queries defined metrics instead of raw tables. Every consumer, whether it's an AI agent, a dashboard, or an API, gets the same answer because the calculation is fixed, not interpreted per query.&lt;/p&gt;

&lt;p&gt;Semantic layers have existed in BI for decades. AI agents make the problem worse and the solution more urgent.&lt;/p&gt;

&lt;h2&gt;
  
  
  What goes wrong without a semantic layer?
&lt;/h2&gt;

&lt;p&gt;Three failure modes show up in production. All of them trace back to the same root cause: the agent interprets business logic on every query instead of referencing a shared definition.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inconsistent answers
&lt;/h3&gt;

&lt;p&gt;Ask Claude "What was Q1 revenue?" through &lt;a href="https://bonnard.dev/glossary/text-to-sql" rel="noopener noreferrer"&gt;text-to-SQL&lt;/a&gt;. It writes a query summing &lt;code&gt;amount&lt;/code&gt; from &lt;code&gt;orders&lt;/code&gt; where &lt;code&gt;created_at&lt;/code&gt; is between January 1 and March 31. Reasonable.&lt;/p&gt;

&lt;p&gt;Ask GPT the same question. It filters on &lt;code&gt;status = 'completed'&lt;/code&gt; first, then sums. Different number. Also reasonable.&lt;/p&gt;

&lt;p&gt;Neither agent knows that your finance team excludes refunds and trial conversions from revenue. That logic lives in a Notion doc that got updated six months ago. The agents don't have access to it. They generate plausible SQL from column names and return plausible numbers that don't match anything your team reports.&lt;/p&gt;

&lt;p&gt;This isn't a model quality problem. It's a context problem. The agent sees schema, not semantics.&lt;/p&gt;

&lt;h3&gt;
  
  
  No access control
&lt;/h3&gt;

&lt;p&gt;You're building a B2B product. Customer A asks your agent a question. The agent writes SQL against the warehouse. Nothing in the text-to-SQL pipeline enforces that Customer A only sees Customer A's data.&lt;/p&gt;

&lt;p&gt;You can patch this. Add tenant filters to prompts. Write middleware that rewrites queries. Build a validation layer. Each patch is a new surface for bugs. One missed filter and you've got a data leak in production.&lt;/p&gt;

&lt;p&gt;RBAC needs to be structural. It should be impossible for an agent to return data outside its authorized scope, regardless of what SQL it generates.&lt;/p&gt;

&lt;h3&gt;
  
  
  No audit trail
&lt;/h3&gt;

&lt;p&gt;When your CFO asks "where did this number come from?", the answer is "an LLM generated some SQL." There is no metric definition to trace, the prompt context has moved on, and correctness was never defined.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bonnard.dev/glossary/metric-governance" rel="noopener noreferrer"&gt;Metric governance&lt;/a&gt; requires a fixed, versioned definition that every consumer references. Without it, every query is an ad hoc interpretation of raw data.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a semantic layer for AI agents?
&lt;/h2&gt;

&lt;p&gt;A semantic layer is a metadata layer between your data warehouse and every data consumer. It defines business metrics, relationships between tables, and access rules in one place. Consumers query metric definitions instead of raw tables.&lt;/p&gt;

&lt;p&gt;For AI agents specifically, the semantic layer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Translates natural language to governed queries.&lt;/strong&gt; The agent asks for "revenue." The semantic layer knows that means &lt;code&gt;SUM(amount) WHERE status != 'refunded' AND type != 'trial'&lt;/code&gt;. The agent never writes this SQL itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enforces multi-tenancy and access control.&lt;/strong&gt; Every query runs through row-level security rules defined in the schema. The agent physically cannot return unauthorized data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provides an API, not a database connection.&lt;/strong&gt; The agent calls a query endpoint or &lt;a href="https://bonnard.dev/glossary/mcp" rel="noopener noreferrer"&gt;MCP&lt;/a&gt; tool, not a database driver. The semantic layer generates the SQL, executes it, and returns structured results.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The term "agentic semantic layer" describes a semantic layer built for this use case: agent-native interfaces (MCP, tool-use APIs), multi-tenant by default, designed for programmatic access rather than human-driven BI.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works: metrics as code
&lt;/h2&gt;

&lt;p&gt;Define your metrics in YAML. Version them in Git. Review changes in pull requests. This is the same workflow your engineering team uses for application code, applied to data definitions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;cubes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;orders&lt;/span&gt;
    &lt;span class="na"&gt;sql_table&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;public.orders&lt;/span&gt;
    &lt;span class="na"&gt;measures&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;total_revenue&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CASE&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;WHEN&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;!=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'refunded'&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;AND&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;!=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'trial'&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;THEN&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ELSE&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;END"&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sum&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;order_count&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;count&lt;/span&gt;
    &lt;span class="na"&gt;dimensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;status&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;status&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;created_at&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;created_at&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;time&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;customer_id&lt;/span&gt;
        &lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;customer_id&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;total_revenue&lt;/code&gt; isn't a column. It's a calculation with your business rules baked in. When finance decides to exclude a new edge case, one diff updates the definition for every consumer. No agent retrained. No dashboard patched.&lt;/p&gt;

&lt;p&gt;Expose this to AI agents over MCP and the agent discovers available metrics at runtime. It calls a discovery tool to see what metrics exist, then a query tool to fetch data. It never generates SQL or interprets column names.&lt;/p&gt;

&lt;p&gt;The next step is what the agent shows the user. A governed query returns rows; the user usually wants a chart or a table in the conversation. That rendering should come from the query result, not from tokens the model invents, so the same data produces the same chart every time.&lt;/p&gt;

&lt;p&gt;For the full setup walkthrough, see &lt;a href="https://bonnard.dev/blog/connect-ai-agent-data-warehouse" rel="noopener noreferrer"&gt;How to Connect an AI Agent to Your Data Warehouse&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agentic semantic layer vs traditional BI semantic layer
&lt;/h2&gt;

&lt;p&gt;Not every semantic layer works well with AI agents. Most were built for BI tools and retrofitted. The difference matters in production.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;Traditional BI semantic layer&lt;/th&gt;
&lt;th&gt;Agentic semantic layer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Primary consumer&lt;/td&gt;
&lt;td&gt;Dashboards, analysts&lt;/td&gt;
&lt;td&gt;AI agents, LLMs, applications&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interface&lt;/td&gt;
&lt;td&gt;SQL or proprietary query language&lt;/td&gt;
&lt;td&gt;MCP, REST API, SDK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-tenancy&lt;/td&gt;
&lt;td&gt;Afterthought or manual&lt;/td&gt;
&lt;td&gt;Built-in, per-query enforcement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Access control&lt;/td&gt;
&lt;td&gt;Dashboard-level&lt;/td&gt;
&lt;td&gt;Row-level, per-consumer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Discovery&lt;/td&gt;
&lt;td&gt;Human browses catalog&lt;/td&gt;
&lt;td&gt;Agent calls &lt;code&gt;explore_schema&lt;/code&gt; at runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Caching&lt;/td&gt;
&lt;td&gt;Cube-level&lt;/td&gt;
&lt;td&gt;Pre-aggregation with automatic invalidation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment&lt;/td&gt;
&lt;td&gt;UI-driven&lt;/td&gt;
&lt;td&gt;CLI, Git, CI/CD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schema management&lt;/td&gt;
&lt;td&gt;GUI editor&lt;/td&gt;
&lt;td&gt;YAML in version control&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;An agentic semantic layer treats programmatic access as the default, so MCP support, multi-tenant keys, and row-level security are built in.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to look for when evaluating
&lt;/h2&gt;

&lt;p&gt;If you're choosing a semantic layer for AI agent use cases:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP or tool-use support.&lt;/strong&gt; Your agents need a standardized way to discover metrics and query them. &lt;a href="https://bonnard.dev/glossary/mcp" rel="noopener noreferrer"&gt;MCP&lt;/a&gt; (Model Context Protocol) is the emerging standard. Without it, you're writing custom integration code for every agent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-tenancy.&lt;/strong&gt; If you're building a B2B product, every agent query needs to be scoped to a specific tenant. This should be structural.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Row-level security.&lt;/strong&gt; Beyond tenant scoping, you need fine-grained access control. Marketing agents see marketing data. Finance agents see finance data. Defined in the schema, enforced on every query.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pre-aggregation.&lt;/strong&gt; AI agents make more queries than humans. Sub-second response times require cached rollups. Look for configurable pre-aggregation with automatic cache invalidation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Warehouse coverage.&lt;/strong&gt; Your semantic layer needs to support your warehouse. Engines like Cube typically cover Snowflake, BigQuery, Databricks, PostgreSQL (including Supabase, Neon, and RDS), DuckDB (including MotherDuck), and Redshift. Bonnard connects to PostgreSQL, BigQuery, Redshift, ClickHouse, DuckDB, and MySQL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schema-as-code.&lt;/strong&gt; Metric definitions should live in version control. Changes should go through pull requests. Rollbacks should be &lt;code&gt;git revert&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Several tools in this space: &lt;a href="https://cube.dev" rel="noopener noreferrer"&gt;Cube&lt;/a&gt; pioneered the open-source semantic layer. &lt;a href="https://www.atscale.com" rel="noopener noreferrer"&gt;AtScale&lt;/a&gt; and &lt;a href="https://www.getdbt.com" rel="noopener noreferrer"&gt;dbt&lt;/a&gt; offer semantic layer capabilities for different stacks. The right choice depends on your stack, your use case, and whether you need the semantic layer to serve AI agents as its primary consumer or as a secondary integration.&lt;/p&gt;

&lt;p&gt;Bonnard sits on top of that layer, whichever one you pick (dbt, Cube, MetricFlow, or custom), and delivers governed BI inside Claude, ChatGPT, Gemini, and Copilot. Setup is four steps: connect your data, create an app, choose the audience, publish. Access is role-based per app, tenants are isolated by one of four models, and every call is logged. Bonnard is pre-launch. &lt;a href="https://bonnard.dev/#waitlist" rel="noopener noreferrer"&gt;Join the waitlist&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this is heading
&lt;/h2&gt;

&lt;p&gt;The a16z team &lt;a href="https://a16z.com/your-data-agents-need-context/" rel="noopener noreferrer"&gt;wrote recently&lt;/a&gt; that data agents are "essentially useless without the right context." The teams shipping &lt;a href="https://bonnard.dev/agentic-analytics" rel="noopener noreferrer"&gt;agentic analytics&lt;/a&gt; today defined their metrics before connecting their agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Do I need a semantic layer if I already use dbt?
&lt;/h3&gt;

&lt;p&gt;dbt defines transformations: how raw data becomes clean tables. A semantic layer defines metrics: how clean tables become business numbers. They're complementary. dbt gets your data into the right shape. The semantic layer defines what "revenue" means on top of that shape. Many semantic layers can import dbt models directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the difference between a semantic layer and RAG?
&lt;/h3&gt;

&lt;p&gt;RAG (Retrieval-Augmented Generation) feeds documents to an LLM for context. A semantic layer feeds governed metric definitions to an agent for data queries. RAG is for unstructured knowledge ("What does our refund policy say?"). A semantic layer is for structured data ("What was Q1 revenue?"). You likely need both, but they solve different problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use a semantic layer with Claude, GPT, and open-source models?
&lt;/h3&gt;

&lt;p&gt;Yes. A semantic layer with MCP support works with any MCP-compatible client: Claude Desktop, Cursor, Claude Code, and others. For non-MCP agents, most semantic layers expose REST APIs or SDKs. The semantic layer is model-agnostic because it sits between the agent and the warehouse, not inside the model.&lt;/p&gt;

&lt;h3&gt;
  
  
  How is this different from giving agents read-only database access?
&lt;/h3&gt;

&lt;p&gt;Read-only access prevents writes but doesn't prevent incorrect reads. The agent still interprets column names, guesses JOIN conditions, and invents filter logic. A semantic layer replaces interpretation with definition. The agent queries &lt;code&gt;total_revenue&lt;/code&gt; and gets the exact calculation your finance team agreed on, every time.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the performance impact of adding a semantic layer?
&lt;/h3&gt;

&lt;p&gt;With pre-aggregation, queries typically get faster. The semantic layer caches rollups so agents query pre-computed results instead of running full aggregations on every request. Cold queries hit the warehouse directly. Hot queries resolve in single-digit milliseconds.&lt;/p&gt;

</description>
      <category>semanticlayer</category>
      <category>mcp</category>
      <category>data</category>
      <category>dataengineering</category>
    </item>
  </channel>
</rss>
