Developer Experience Challenge – DAX Dependency Graph

The Fabric Semantic Link Developer Experience Challenge gave me a concrete reason to build something I had been meaning to build for a while. The result is a Fabric notebook that extracts every DAX measure from a Power BI semantic model, maps measure-to-measure dependencies into a directed graph, and produces a ranked complexity audit with risk ratings. This post is about what it does, how it works, and why the approach is useful beyond the challenge context.

The problem it solves

If you have worked on a Power BI semantic model that has grown organically over a few years, you already know the problem. Measures reference other measures. Those measures reference other measures. Nobody wrote it down. The person who built the original [Gross Margin Adj YTD] has since moved on, and the name was self-explanatory at the time.

Power BI Desktop shows you a measure’s DAX expression when you click on it. It does not show you which other measures that expression depends on, how deep the dependency chain goes, or whether any measure sits in a circular reference loop that the engine has been quietly working around. Tabular Editor helps, but it still requires manual navigation. There is no built-in view that answers “what are the ten most complex measures in this model, and which ones does everything else depend on?”

That is what this notebook answers.

Getting the measures out

The notebook uses sempy.fabric.list_measures() from Semantic Link to pull every measure from the target model in a single call. It returns a pandas DataFrame with measure name, parent table, DAX expression, visibility, and description per row.

measures_df = fabric.list_measures(dataset=DATASET_NAME, workspace=WORKSPACE_NAME)

Under the hood, Semantic Link connects via the Tabular Object Model (TOM) over the XMLA endpoint. Fabric handles authentication from the notebook’s identity. Two config values is all the setup needed:

WORKSPACE_NAME = None                      # None = current workspace
DATASET_NAME   = "YourSemanticModelName"

Then Run All.

Parsing measure references: three passes

The interesting part is working out which measures each expression actually references. DAX uses square bracket notation for both measures and columns: [Total Revenue] is a measure reference, Sales[Amount] is a column reference. The parser has to distinguish them correctly.

It does this in three passes:

  1. Strip string literals ("...") to avoid false positives. A FORMAT call like FORMAT([Date], "Total Revenue") would otherwise incorrectly register a dependency on [Total Revenue].
  2. Strip single-line and multi-line comments (// and /* */).
  3. Extract all [Name] patterns where the opening bracket is not preceded by a word character, digit, or apostrophe. That lookbehind excludes table-qualified references like Sales[Amount] and 'My Table'[Column].

The extracted names are then cross-referenced against the full set of known measure names. Anything that is not a measure name is discarded.

pattern = r"(?<![a-zA-Z0-9_'])\[([^\]]+)\]"
matches = re.findall(pattern, cleaned)
return list({m for m in matches if m in measure_names})

This correctly handles the common edge cases in real models. The one known limitation: measures referenced via SELECTEDMEASURE() or through a disconnected table SWITCH pattern cannot be resolved statically. If your model uses those patterns heavily, some dependencies will be missing from the graph.

Building the graph

Once the parser has run on every expression, the dependencies go into a NetworkX directed graph. Each measure is a node. An edge A -> B means “A’s DAX expression references measure B” — A depends on B.

The graph direction is important. It lets the tool compute:

  • In-degree (fan-in): how many measures depend on this one. High fan-in means “hub” measure. Breaking it cascades everywhere.
  • Out-degree (fan-out): how many measures this one calls. High fan-out means complex composition.
  • Longest path from any node: the transitive dependency depth.
  • Cycles: circular reference chains.

From those two properties alone, the next three analyses fall out naturally.

Five analyses

Dead measures. In-degree of zero means no other measure references this one. It might be a top-level report measure used directly in a visual, or it might be genuinely unused. The notebook flags all of them; cross-referencing with report usage is a follow-up step.

Root measures. Out-degree of zero means no dependencies on other measures. These are the foundation: the SUM(Sales[Amount]) base calculations that everything else builds on. Errors in root measures propagate silently through every measure above them.

Circular references. The notebook runs Johnson’s algorithm via nx.simple_cycles() to find every elementary cycle in the graph. In a well-designed model the result is: “No circular dependencies detected.” When it is not, the full chain is printed — A -> B -> C -> A — so you know exactly what to untangle.

Complexity scoring. Each measure gets a weighted composite score across six dimensions:

DimensionWeightRationale
CALCULATE / CALCULATETABLE count3Context transitions are the primary source of subtle DAX bugs
Max parenthesis nesting depth1Readability proxy
Branching (IF / SWITCH)2Code path count
Filter functions (FILTER, ALL, ALLEXCEPT, etc.)2Filter-context manipulation
Dependency depth (longest downstream path)2Transitive error amplification
Fan-out (direct measure references)1Composition width

The weights are the part most open to debate. I gave CALCULATE the highest weight because context-transition confusion is, in my experience, where DAX models accumulate the most invisible risk. The depth and branching weights reflect that those properties make a measure harder to verify than to write.

Visual dependency DAG. The notebook renders the graph with matplotlib. Node color encodes complexity score on a green-to-red scale. Node size encodes in-degree, so hub measures are physically larger. An optional pyvis interactive version renders inline in the notebook via an iframe: zoomable, draggable, with hover tooltips showing measure name, table, and score.

The audit report

The final step consolidates everything into a single DataFrame sorted by risk rating, then by descending complexity score within each tier.

Risk logic:

  • Critical: participates in any circular reference
  • High: complexity score ≥ 20
  • Medium: score between 10 and 19
  • Low: everything else

The summary banner above the table looks like this:

============================================================
  DAX DEPENDENCY GRAPH - AUDIT SUMMARY
============================================================
  Total measures:            147
  Dependency edges:          312
  Unreferenced measures:     38
  Root measures (no deps):   22
  Circular references:       0
  High complexity (>=20):    11
  Medium complexity (10-19): 29
============================================================

That is the starting point for any refactoring conversation. Eleven measures scoring High is a concrete prioritisation signal: start there, not with the 38 unreferenced ones.

Limitations worth knowing

The parser only resolves measure-to-measure dependencies. Column-level lineage is out of scope. Calculation groups are not modeled as nodes, so models that use them heavily will have gaps. Cross-model references (composite model / DirectQuery to external datasets) are not in scope either.

The “unreferenced” flag does not mean unused. It means not referenced by other measures. A measure used directly in 15 report visuals will still show as unreferenced in this graph, because the tool has no report-level visibility. That cross-reference is worth doing separately with sempy.fabric.list_reports() if you are planning to delete anything.

Getting the notebook

The notebook is on GitHub: github.com/vestergaardj/DDG-DAX-Dependency-Graph. Upload it to any Fabric workspace, set the two configuration values in the Configuration cell, and run all. Everything else is automatic.

It requires semantic-link-sempy, networkx, and matplotlib, all of which come pre-installed in Fabric. pyvis is optional; the static graph still renders without it.

Are you still the air traffic controller?

In February 2025 I wrote about building an event-driven ETL system in Microsoft Fabric. The metaphor was air traffic control: notebooks as flights, Azure Service Bus as the control tower, the Bronze/Silver/Gold medallion layers as the runway sequence. The whole system existed because Fabric has core-based execution limits that throttle how many Spark jobs run simultaneously on a given capacity SKU.

The post was about working around a constraint. You could not just fire all your notebooks at once. You needed something to manage the queue.

More than a year on, it is worth being honest about what held up and what has changed.

The original architecture, briefly

Three components:

Azure Service Bus acted as the message queue. When a source system finished loading raw data, it dropped a message onto the bus. Each message represented one flight waiting for clearance.

A capacity monitor notebook ran on a short schedule. It checked how many notebooks were currently executing and compared that count against the available core capacity. If capacity was available, it pulled the next message from the queue and triggered the appropriate notebook via the Fabric REST API.

The processing notebooks were standard Bronze, Silver, and Gold Spark notebooks. They ran as normal Fabric notebooks with no awareness of the orchestration layer above them. On completion, they acknowledged the Service Bus message.

The deliberate design choice was to keep the notebooks clean and put the complexity in the orchestration layer. A notebook should not need to know whether it is being called by a scheduled job, a pipeline, or a service bus monitor. That separation held up well.

What has changed in Fabric

Two relevant changes since February 2025:

Native job queueing

Fabric now queues Spark jobs automatically when capacity is exhausted rather than rejecting them outright. Jobs queue in FIFO order and wait up to 24 hours before expiring. The platform starts them automatically as capacity becomes available.

There is a hard constraint that limits how far this goes: the queue depth is bounded by the SKU’s CU allocation. It is not unlimited. A sudden burst of 100+ notebooks would exceed both the concurrent execution limit and the queue depth, and the excess jobs would be rejected rather than queued — the same failure mode as before native queueing existed.

So native FIFO queueing helps if your workload arrives gradually. It does not change the original problem if your trigger pattern involves large simultaneous batches. The Service Bus buffer sits outside Fabric and has no queue depth constraint. That distinction is why the architecture is still relevant.

Job-level bursting controls

Fabric capacities support bursting at up to 3x the nominal CU allocation. You can now disable bursting for specific Spark jobs, giving finer-grained control over which jobs are allowed to consume burst headroom. Useful for ring-fencing critical workloads. This is an additive improvement to the platform regardless of which orchestration approach you use.

What held up

The decoupled architecture held up. Keeping orchestration logic out of the processing notebooks made them easier to test, modify, and redeploy independently. A notebook that does not know how it was triggered is easier to reason about than one that contains scheduling and queueing logic alongside its data transformation. Nothing about that changed.

Azure Service Bus held up as a reliable messaging backbone. At-least-once delivery, dead-letter queues, message peek-lock, and configurable time-to-live are production-grade features. There were no reliability issues with the messaging layer over the period.

The Bronze, Silver, Gold medallion structure held up. That is sound data architecture independent of the orchestration tool above it.

What I would do differently today

Not much, given the workload pattern. The original system was designed for burst scenarios, and the burst scenario has not changed. The native queue depth limit tied to CU allocation means there is still no Fabric-native replacement for an external buffer that absorbs an unbounded number of incoming messages and feeds them into Fabric at a controlled rate.

The one thing I would reconsider is the capacity monitor notebook’s polling loop. It runs on a short schedule and checks active job counts before pulling the next message. That works, but it adds latency and a scheduling dependency. Whether the Fabric REST API now exposes enough observability to build a leaner version of that loop is worth investigating — but that is an implementation detail, not a reason to replace the architecture.

The honest production reality

The system is still running. A working production system with understood failure modes and people who know how to debug it has a high replacement threshold. The architecture from February 2025 has not needed replacing because the problem it solved has not stopped existing.

The question I get asked: is there a more native way to do this now? The answer is no, not for the burst-buffering problem specifically. Fabric pipelines handle sequential orchestration well. Native FIFO queueing handles gradual workloads within its depth limit. Neither absorbs an unbounded burst and controls submission into a capacity-constrained runtime. Service Bus still does that job, and it still does it well.

The air traffic controller is still in the tower. The runway is a little wider than it was, but the traffic has grown too.

W-Order Just Dropped in Microsoft Fabric

I was poking around in the Lakehouse settings after the latest Fabric update rolled out last night, and noticed something I have not seen documented anywhere yet.

Buried in the optimization section of the Lakehouse table properties, there is a new toggle: W-Order.

If you have been following along with V-Order since Fabric went GA, you know it already does a solid job of optimizing Delta tables for read performance. W-Order is apparently the next generation. The acronym, according to the tooltip, stands for Wavelet-Optimized Recursive Delta Encoding Rewrite. It claims to apply wavelet decomposition to the Parquet column chunks and recursively re-encode them into what the UI calls “spectral micro-partitions” based on historical query access patterns.

I have no idea what half of that means. But I had to test it. Obviously.


Where to Find It

Open your Lakehouse in the Fabric portal. Navigate to Table properties on any Delta table, scroll down past the standard V-Order settings, and you should see the new section sitting right below it.

Flip the toggle, confirm the dialog, and the table enters what the UI calls a “spectral rewrite” phase. On my test table (around 48 million rows, partitioned by month), this took about four minutes. A small progress indicator shows up next to the table name in the explorer while it runs.

Spectral rewrite in progress, indicated by the spinner next to the table name

The Numbers

I ran the same aggregation query on the table before and after enabling W-Order. Same capacity, same time of day, same query, three runs each.

RunBefore (seconds)After (seconds)
114.30.34
213.80.33
314.10.34
Same query, same capacity, same data. Three consecutive runs.

That is roughly a 42x improvement. On a simple GROUP BY with a SUM. I nearly spilled my coffee.

0.34 seconds on 48 million rows. I had to run it again to believe it.

A Few Things to Note

  • Always check your runtime version first, the feature requires the April 2026 update
  • Premium or F64+ capacity is required. The toggle simply does not show up on lower SKUs
  • Reoptimization consumes CU credits, so keep an eye on your capacity metrics while the spectral rewrite runs
  • It only appears on Delta tables, not on shortcuts or mirrored tables
  • Latency on tables with heavy concurrent write loads is unknown, so proceed with some caution there

Microsoft has not published any documentation for this yet as far as I can tell. The feature might still be rolling out, so if you do not see the toggle, give it a day or two. The latest runtime update notes are here.

Go check your Lakehouse settings. And if something about this whole thing feels off, maybe take a second look at today’s date before you reorganize your entire data estate.

Azure Databricks at FabCon 2026: What Got Announced and What It Actually Means

FabCon 2026 in Atlanta last week was bigger in many ways. For the first time, the Microsoft Fabric Community Conference ran alongside SQLCon, which meant two large communities shared the same convention center, the same coffee queues and the general atmosphere of too-many-sessions-not-enough-sleep that defines any conference worth attending.

Databricks showed up with several announcements. Some of them are incremental. A few are worth more than a passing glance, depending on where you sit on the data and BI stack.

One thing that still catches people off guard: Azure Databricks has been a first-party Azure service since 2017. Not a partner product, not a third-party integration. A first-party Azure service, alongside Power BI, Excel, Teams, Azure OpenAI, Copilot Studio and the Power Platform. When Microsoft talks about a unified data and AI platform on Azure, Databricks is part of the architecture. The announcements this week make that more visible.

Here is what they shipped, and what I think it means in practice.


Lakeflow Connect Free Tier: 100 Million Records a Day, at No Cost

Bad pipelines are one of the constants of BI work. The data arrives late, the connectors are fragile, someone is maintaining a web of custom scripts because there was never time to do it properly, and the people building reports spend half their week cleaning up after problems they did not cause.

Databricks announced a Lakeflow Connect Free Tier, and the headline number is worth taking seriously: 100 million records per workspace per day, at no charge. That is 100 free DBUs per day, included with every workspace, before standard Lakeflow Connect pricing applies.

What it connects to out of the box:

  • Databases: SQL Server, Oracle, Teradata, PostgreSQL, MySQL, Snowflake, Redshift, Synapse, BigQuery
  • SaaS applications: Dynamics 365, Salesforce, ServiceNow, Workday, Google Analytics

For databases, the ingestion runs on Change Data Capture, which means it reads the transaction log incrementally rather than scanning full tables. Data lands in Delta format on Azure Data Lake Storage. Unity Catalog governance applies from the moment the first record arrives, so access control and lineage are not something to sort out later.

Databricks quotes 25x faster pipeline builds and 83% ETL cost reduction. I would take vendor benchmarks with the usual scepticism, but the direction is clear: the intent is to make data ingestion a problem you configure rather than one you maintain. For a BI team currently paying for third-party Dynamics or Salesforce connectors, or running CSV exports on a schedule, this is worth a practical test.


Lakebase Is Generally Available: A Postgres Database Inside the Lakehouse

This one sits closer to architecture and engineering than it does to daily BI work, but it changes some assumptions that are worth understanding.

Azure Databricks Lakebase is now generally available in 14 Azure regions. It is a managed, serverless Postgres service that runs inside your lakehouse, on the same storage as your Delta tables.

The problem it addresses is one data architects have been working around for years: operational data and analytical data have historically lived on separate platforms, connected by pipelines that were always someone’s responsibility and frequently nobody’s priority. Lakebase puts an operational database directly in the same governed environment as the rest of the data platform.

Key characteristics:

  • Full Postgres compatibility, with support for extensions including pgvector and PostGIS
  • Compute and storage separated, with scale-to-zero and sub-second startup
  • Branching and instant restore for development and testing workflows
  • High availability with automatic failover across availability zones

The use cases Databricks highlights: transactional analytics on operational data, AI agent state management, and customer personalization and feature serving. For data engineers building pipelines that feed AI applications, Lakebase removes the need to run a separate operational database outside the Databricks platform just to give an agent somewhere to write state.

It is available to test today in 14 regions. If you have been looking for a Postgres layer that sits inside the lakehouse without architectural compromise, now is a reasonable time to look at the documentation.


The Excel Add-in Is in Public Preview: Governed Lakehouse Data in the Tool Most People Actually Use

This is the announcement that will get the most immediate attention from analysts and business users, and probably the one that causes the most internal conversations about data governance.

The Azure Databricks Excel Add-in is in public preview. It connects Excel directly to Unity Catalog tables and Metric Views. From inside Excel, you can browse the catalog, build pivot tables from governed semantic definitions, and filter and analyze data without writing SQL. It works on Excel for Windows, macOS and the web.

The problem it addresses is one every BI developer and governance specialist knows well: business users need data in Excel. So someone exports a CSV. Or the business user pulls their own export. Within 24 hours there are four versions of the file in four different places, none of them current, all of them cited in separate meetings. The analyst who originally produced them has no idea which version is being used.

The add-in replaces that pattern with a live connection to the same tables that power your Power BI reports and your analytics models. The data is current. The access rules in Unity Catalog apply here too, so a user who cannot query a table in Databricks cannot query it through the add-in either.

For analysts who work primarily in Excel, this is a genuine change in how a typical Tuesday works. For governance teams, it removes a whole class of ungoverned data copy that currently exists because there was no better option.


Genie Gets More Capable: Agent Mode, Genie Code and Databricks One

Genie is Databricks’ conversational analytics experience: you ask a data question in plain language and get back a chart, a table or a narrative answer. Databricks reported this week that 98% of Databricks SQL warehouse customers are using AI/BI, with monthly active Genie users up more than 300% year-over-year. The numbers are moving fast enough to suggest this has passed the experimental phase.

Three updates this week.

Genie Agent Mode

Standard Genie answers one question at a time. Genie Agent Mode takes a more complex business question, builds a research plan, runs multiple queries, tests intermediate results, refines its approach and then delivers a complete answer with supporting tables, charts and narrative context.

The difference becomes concrete quickly. Standard Genie handles: “What were total sales in Q3?” Genie Agent Mode handles: “Revenue in the Southeast dropped in Q3. Why did that happen, and what does the pattern suggest for Q4?” That is not a single query. It is an investigation, and Agent Mode runs it without someone having to direct every step.

For analytics managers sitting on a queue of complex ad hoc requests that only a senior analyst can currently answer, this is the update worth spending time with.

Genie Code

Genie Code is aimed at data practitioners, not end users. It is an agentic development assistant that runs inside Databricks notebooks, SQL editors and Lakeflow pipelines.

The distinction from a general-purpose AI coding assistant is that Genie Code understands your data context through Unity Catalog. It knows your tables, your lineage, your governance policies and your business semantics. With that, it can build pipelines and dashboards from natural language prompts, debug Lakeflow failures, generate queries grounded in your actual schema, and handle routine operational monitoring.

For senior BI developers and data engineers who spend part of every week on repetitive work that requires knowing the platform well, having an assistant that actually knows prod.gold.customer_activity is a different experience from hitting tab on a general-purpose tool that has never seen your schema.

Databricks One and Databricks One Mobile

Databricks One now includes a unified multi-agent chat experience powered by Genie. Business users can ask questions across the full data estate without needing to know which Genie space to route to. When a question goes beyond what existing spaces can answer, Databricks One can bring in additional agents to investigate. AI/BI dashboards and Databricks Apps are surfaced in the same interface.

Databricks One Mobile brings this to iOS and Android: Genie, dashboards and apps from a phone. Business users can ask data questions without being at a desk.


Genie in Microsoft Teams: Data Answers Where the Decisions Actually Happen

For organizations already using Microsoft 365, this is probably the most immediately deployable announcement.

You can now connect Genie to Microsoft Teams via Copilot Studio. The setup connects a Genie space to a Teams agent through the Copilot Studio connector, which handles the API and MCP logic. Once connected, users can ask data questions directly in a Teams conversation and get answers backed by your lakehouse data.

The part that makes this credible to security teams and BI leaders: every conversation runs through OAuth, authenticated against the user’s own identity. If a user does not have SELECT access to a table in Unity Catalog, Genie will not surface that data in Teams. The access model you already manage in Unity Catalog carries through to every Teams conversation.

For data governance managers who have spent years explaining why pasting screenshots of reports into Teams messages is not the same as having a governed answer, this changes the practical alternative. The question gets answered where it was asked, with the right access controls applied, and nothing leaves the governed environment.

For business users, it means getting a trusted data answer without leaving the tool they already have open.


What I Am Taking Away From This Week

The pattern across all of these announcements is one I have been watching build for a couple of years. Operational data, analytical data and AI have historically lived on separate platforms, and the work connecting them got called integration. That work is expensive, slow, and usually the first thing cut when a project runs over budget.

What Databricks is building is a single platform where all of it sits together, governed by Unity Catalog, accessible from Excel, Teams, a notebook, a mobile app or a SQL query. Whether the individual pieces fit together as neatly in production as they do in the announcement demos is something I will be watching as they move from preview toward GA.

If you were at FabCon this week, the Databricks session was Thursday March 19th in room C302 and should be available on demand if you missed it.

The next major Databricks gathering is Data + AI Summit, June 15 to 18, 2026, in San Francisco. 25,000 attendees, 800+ sessions, and the most complete view of where the platform is heading. Worth putting on the calendar.


What caught your attention this week at FabCon? Drop a comment below. I would like to hear what people are actually planning to test.

Exploring Fabric Ontology

Note: The Fabric Ontology is currently in preview as part of the Fabric IQ workload. Features and behaviour may change before general availability.

I have been spending a little time with the Microsoft Fabric data agent documentation lately, and one pattern keeps showing up, and it is not just in the official guidance but in community posts from people who have actually tried to deploy these things: the demo runs beautifully. The AI answers questions in plain English, leadership gets excited, the pilot gets approved. Then it hits production. Real users send real questions. The answers start drifting. Numbers that should match do not. The same question returns different results on different days. Trust evaporates faster than it was built.

And almost every time, the root cause is the same thing: the semantic foundation was not solid enough before anyone pointed an agent at it.

That is exactly the problem the Fabric Ontology is designed to address. It is the piece I think most teams will underestimate right up until the moment they need it.


Why the Data Agent Gets It Wrong

Generative AI is genuinely good at working with language and meaning. What it cannot do is fill in documentation that was never written.

Most enterprise databases were built for systems, not for consumption. Column names follow technical conventions an engineer settled on years ago. Business logic lives in a stored procedure nobody has touched since SQL Server 2014. Which customer table is the authoritative customer table? Documented nowhere. The abbreviation cust_rev_ytd_adj was obvious to the person who named it. To everyone else, including an AI agent, it is a puzzle.

When you connect an agent to that data and ask it to answer business questions, you are asking it to decode a language it was never given a dictionary for. It is not going to find meaning that was never documented. Someone has to build that foundation deliberately, before the agent gets anywhere near it.

This is not a new problem. It is the same problem that made undocumented semantic models painful for analysts, made onboarding new BI developers slow, and made “what does ‘active customer’ mean?” a recurring meeting agenda item. The AI just made it impossible to paper over.


What the Fabric Ontology Actually Is

The Fabric Ontology operates above the table and column level, at the concept level, the level where business people actually think and where agreements actually need to live.

Three building blocks:

Entity types are the real-world objects your business runs on. Customer, Order, Product, Shipment, Store. Defined once, with a stable name, description, and identifiers. Not four slightly different customer tables with different primary keys depending on which source system populated them first.

Properties are named, typed facts about an entity. Instead of a column called cust_rev_ytd_adj, you publish a Customer property called Adjusted Year-to-Date Revenue with a declared unit, a data type, and a binding to the underlying source column. Something a new analyst can understand without asking someone who remembers the original intent. Something an AI agent can reason about without guessing.

Relationships are explicit, directional, typed links between entities with cardinality rules. Customer places Order. Order contains Product. Shipment originates from Plant. Made reusable and visible, rather than buried in join logic spread across three different pipelines and a Power BI measure that no one wants to open.

Those concept definitions then bind to your actual data in OneLake: lakehouse tables, Eventhouse streams, Power BI semantic models. The data bindings handle schema drift, enforce data quality checks, and track provenance at the concept layer.

The result: a shared vocabulary that both people and AI agents can reason over. When an agent is grounded in a well-defined ontology, it is not reverse-engineering meaning from raw tables. It is working from a context that someone owns and maintains.


The Ontology Graph: Relationships as Queryable Data

The Fabric Ontology also builds an ontology graph from your data bindings and relationship definitions: a queryable instance graph where entity instances are nodes and relationships are edges, each carrying metadata and data source lineage, refreshed on a schedule.

For anyone who has spent time making implicit relationships explicit and queryable, this is worth understanding. Context that previously only existed as join logic, tracing which customers are tied to which orders and which products trace back to which suppliers, becomes something you can traverse, analyze, and govern. Path finding, centrality analysis, community detection: graph algorithms applied to your actual business data.

On top of that sits a Natural Language to Ontology (NL2Ontology) query layer that converts plain-language questions into structured queries across your bound sources, routing automatically to GQL for graph queries or KQL for Eventhouse. Not a best-effort guess at what a column might mean. Consistent answers that follow the definitions you published in your ontology.


Three Things That Actually Matter Before You Build the Agent

I have not shipped a production data agent grounded in a Fabric Ontology end-to-end yet. The feature is still in preview and I am still working through it. But the guidance is consistent enough across documentation and early community experience that I think these three things are worth naming before you start.

Build the semantic foundation first

This is the step that gets rushed. The agent is only as reliable as the context it has to work with. If your semantic model has undocumented measures, ambiguous column names, and definitions that three different teams would answer three different ways, an ontology built on top of that inherits all of it.

Before connecting an agent to your data:

  • Audit your semantic model. Are measure names self-explanatory? Are the terms your business uses defined anywhere?
  • Generate a Fabric Ontology from your semantic model as a starting point. Fabric can auto-generate one to give you something concrete to refine, rather than starting from a blank canvas.
  • Write descriptions for the columns and measures that currently only make sense to the person who created them.
  • Resolve the definitions that lack consensus before rollout, not after. “What counts as an active customer?” is not a technical question. It is a business alignment question. It needs an answer before the agent encounters it at 9am from a business stakeholder.

Keep each agent’s scope narrow

The temptation is to build one agent that answers everything. It almost always underperforms. The more data an agent has to reason over per question, the harder it is for it to return consistent answers.

A sales agent. An inventory agent. A finance agent. Each one is easier to configure, easier to test, and easier for the people who rely on it to trust, because the scope is legible and the owner is clear.

Start with one domain. The one where trust matters most and the semantic definitions are clearest. Do it properly. Let that one earn credibility before expanding.

Write the instructions like you are briefing a smart new colleague

Data agents are probabilistic: they use statistical reasoning to determine the most likely answer. Business users expect deterministic behavior, meaning the same question should return the same answer every time. Detailed agent instructions are the primary lever for closing that gap.

Think of it as the standing brief you would write for a new analyst on their first day: here is what matters, here is how we define things, here is what belongs out of scope, and here is what to do when a question is ambiguous.

For your most critical business questions, Fabric data agents support sample questions with pre-defined SQL, DAX, or KQL behind them, removing the probabilistic element entirely for those specific scenarios. Use it. Treat the instructions as a living document and update them as you learn how people in your organization actually phrase questions.


Where I Am On This

The hard part of building reliable AI over enterprise data is not the model. It is the semantic gap between raw data structures and the meaning business users expect the model to already know. The Fabric Ontology looks like the most direct thing Microsoft has shipped to address that gap at the platform level. That is what makes it worth paying attention to, even while it is in preview.

I am still early in exploring this and plan to dig further as it moves toward GA. If you have already started building with it, whether you found a workflow that clicked, hit a wall, or worked around something unexpected, I would genuinely like to hear about it in the comments.