What a Fabric Data Agent Is and Why It Matters#
A Fabric data agent is a standalone, highly configurable artifact that queries across OneLake and semantic models and can be invoked by external orchestrators — which is precisely what distinguishes it from a Fabric Copilot. Copilots are preconfigured, non-customizable, and scoped to in-product tasks like generating notebook or warehouse code; a data agent is a purpose-built, promotable asset you shape yourself [S1].
That distinction matters because it changes what the artifact is for. A Copilot helps you write code faster inside an existing Fabric item. A data agent is closer to a virtual analyst: Microsoft describes it as connected to Fabric data sources, answering natural-language questions for a specific domain, grounded in semantic models and ontologies, and publishable out to Microsoft 365, Foundry, Copilot Studio, and custom apps [S2]. In other words, it's built to be embedded somewhere a business user already is, not just used inside the Fabric portal.
That embeddability used to mean publishing through Microsoft's own surfaces. It now means something broader: as of the data agent public API, the whole lifecycle of a data agent — creating it, wiring up sources, setting instructions, publishing it — can be driven entirely from code, outside the Fabric portal, from a developer's own tools and pipelines [S3]. That is the headline architectural shift covered in this update, and it's woven through the sections below rather than treated as a footnote.
Inside a larger multi-agent application architecture, a data agent plays a specific, narrow role: the conversational analytics component, connecting to governed OneLake data through lakehouses, warehouses, semantic models, and KQL databases [S1]. It is not the orchestrator, and it is not the only agent in the system — it is the piece that turns "what were our sales in Q3" into a governed query and a structured answer.
A data agent is a standalone, highly configurable artifact... which distinguishes it from Fabric Copilots that are preconfigured, non-customizable, and scoped to in-product tasks [S1].
For architects, the practical upside is scope: a single data agent can combine up to five data sources in any mix of lakehouses, warehouses, KQL databases, Power BI semantic models, ontologies, and Microsoft Graph, including tables exposed only through OneLake shortcuts and queried without copying data [S1] [S5]. That lets one conversational surface span a real, heterogeneous data estate instead of being locked to a single engine.
The Public API: Agents Embeddable in External Tools and Pipelines#
The most consequential change to this capability is that the data agent's management surface is now a public API. Code outside Fabric can programmatically create and manage data agent artifacts, add and configure data sources, set agent and data-source instructions and example queries, and update or publish agents — all without going through the Fabric portal UI [S3].
This is a management-plane surface, and it's worth being precise about what that means: the API and its SDK cover lifecycle operations — creating, configuring, updating, publishing. Once a data agent is published, querying it at runtime happens through the separate MCP endpoint, which remains the surface tools, applications, and other agent experiences call to actually ask it questions [S3].
Public API and SDK = build/configure/publish (management plane). MCP endpoint = ask it questions after it's published (runtime plane) [S3].
The API reuses Fabric's existing plumbing rather than inventing a new one: it uses the same authentication and request patterns as the rest of the Fabric public API that already covers workspaces and items, so it drops into existing Fabric automation without a separate auth model to learn [S3]. Reference documentation is split into two parts that mirror the lifecycle — a Data Agent Items API for managing agent lifecycle, and a separate Staging API for editing data sources and example queries before publish [S3].
A minimal outside-Fabric workflow looks like this: authenticate with an Azure credential (for example AzureCliCredential), create a data agent scoped to a target workspace ID via create_data_agent, attach a data source and agent instructions via update_settings and add_staging_datasource, then call publish_staging to publish it [S3].
# Illustrative -- mirrors the create -> configure -> publish sequence described in <sup id="cite-3"><a href="#src-3" class="cite">[S3]</a></sup>
from azure.identity import AzureCliCredential
# SDK import path illustrative; see the Data Agent Items API / Staging API reference for exact package names
from fabric_data_agent_mgmt import DataAgentClient
credential = AzureCliCredential()
client = DataAgentClient(credential=credential)
agent = client.create_data_agent(workspace_id="<workspace-guid>", display_name="SalesQA")
client.update_settings(agent_id=agent.id, instructions="Prefer the semantic model for QoQ questions.")
client.add_staging_datasource(agent_id=agent.id, datasource_id="<warehouse-guid>")
client.publish_staging(agent_id=agent.id)
Inference: the exact SDK import path and class names above are illustrative scaffolding around the verified create/configure/publish call sequence in [S3], not a literal code sample reproduced from the source -- always confirm current package/method names against the Data Agent Items API and Staging API reference before shipping this.
Microsoft names four concrete use cases this API unlocks: provisioning the same data agent shape repeatably across many workspaces, building internal tools or portals that manage agents without opening Fabric, wiring agent provisioning into existing CI/CD pipelines, and ISVs embedding data agents as part of their own product rather than as a side experience inside Fabric [S3]. That last one is the biggest shift in posture -- a data agent is no longer only a thing your organization uses internally; it's a component an independent software vendor can package into a product they ship to customers.
The API is designed to complement, not replace, the ALM story Fabric already had. Git integration and deployment pipelines still track and promote agent configuration as files at the workspace level; the public API adds a programmatic layer on top for creating, updating, and publishing agents directly from code [S3]. And the underlying Python SDK itself became more portable in the same update: previously usable only from inside a Fabric notebook, it now also runs on the general Fabric public REST API, so it can execute from a developer's local machine, a CI/CD pipeline, a container, or a backend service entirely outside Fabric [S3].
Best practice: keep Git/deployment pipelines as the config source of truth, use the API for orchestration#
The rule: treat the public API as the automation layer that drives promotion, not a second, competing place where an agent's instructions live.
The why: Git integration and deployment pipelines already track and promote agent configuration as files at the workspace level -- that's the durable, reviewable source of truth. The public API's job is to call create/update/publish operations against that configuration from code, the same way a CI/CD pipeline calls kubectl apply against a manifest rather than storing cluster state itself [S3].
# Wrong: hand-editing agent instructions directly via ad hoc API calls,
# with no record of what changed or why
client.update_settings(agent_id=agent.id, instructions="<new text, not in git>")
# Right: instructions live in a git-tracked file; the pipeline step
# reads that file and pushes it via the API as part of a reviewed deploy
instructions_text = open("agents/SalesQA/instructions.md").read()
client.update_settings(agent_id=agent.id, instructions=instructions_text)
client.publish_staging(agent_id=agent.id)
Core Concepts: Routing, Not a Single Text-to-SQL Engine#
The most important thing to understand about a data agent is that it is not one model doing text-to-SQL. It is a router. After identifying the relevant data source for a question, the agent hands off to a source-specific generator: NL2SQL for a lakehouse or warehouse, NL2DAX for a Power BI semantic model, NL2KQL for a KQL database, or a Microsoft Graph query for organizational data [S1].
That routing step is why a single agent can sensibly front five different kinds of sources at once -- each source keeps its native query language, and the agent's job is picking the right generator for the question rather than translating everything through one lowest-common-denominator query surface. The routing decision itself isn't based on a single signal: it factors in each source's description, schema, and example queries together [S4].
A data agent actually exposes four distinct configuration surfaces that shape this behavior: agent-level instructions, data-source-level instructions, data source descriptions, and data-source example queries [S4]. Agent-level instructions sit above any single source and are meant to state the agent's overall objective, set a priority order across attached sources, define key terminology or acronyms, and specify response-formatting expectations [S4]. Data-source-level instructions apply only after the agent has already routed a question to a specific source, holding source-specific guidance like relevant tables, columns, relationships, and query logic for common or complex questions [S4]. A data source description is a separate, high-level summary of what a source contains and what kinds of questions it can answer -- one of the inputs the agent uses when deciding which source is relevant [S4]. And example queries function as a few-shot mechanism: once a source is selected for a question, its tool automatically looks up the most relevant example queries and passes the top three into the query-generation step [S4].
Microsoft ships recommended templates for the first two surfaces, and they're not the same shape. The agent-level instructions template is organized into Objective, Data sources, Key terminology, Response guidelines, and Handling common topics [S4]. The data-source-instructions template is organized differently -- General knowledge, Table descriptions, and When asked about -- reflecting that it's answering a narrower question once routing has already happened [S4].
Eventhouse KQL databases get a specific mention in how the generator behaves in practice: they're queried in place with no data movement, NL2KQL can reuse existing KQL user-defined functions, and configurators are advised to scope agents to the most relevant tables and encourage time filters to keep responses fast over high-volume event data [S1]. That's a configuration best practice worth internalizing before you point an agent at a large Eventhouse table: without scoping and time-filter guidance, a natural-language question over a high-volume event stream has no default row-limiting behavior baked into the question itself.
A preview capability extends what can be exposed from a KQL database beyond raw tables: Eventhouse KQL user-defined functions, materialized views, and shortcut tables can also be data agent sources, per community coverage of the feature [S7].
Best practice: scope Eventhouse-backed agents deliberately#
The rule: when a data source is a large KQL database, configure the agent's instructions to name the relevant tables explicitly and steer users (or the agent's own query generation) toward time-bounded questions.
The why: Eventhouse tables are the one source type in this list where "ask anything" scales badly -- a broad natural-language question over an unscoped, high-volume event table has no natural row limit until KQL syntax adds one, and NL2KQL generation quality benefits from the agent already knowing which tables and functions are in play [S1].
# Weak configuration -- agent instructions leave source scope implicit
Instructions: "Answer questions about telemetry events."
# Better configuration -- explicit table scope and a time-filter nudge
Instructions: >
Answer questions about telemetry using the RawEvents and DailyRollup tables only.
When a user does not specify a time range, ask a clarifying question or default
to the last 24 hours before generating a KQL query.
Inference: the instruction-block phrasing above is illustrative configuration text, not a literal Fabric UI field reproduced from a source -- the underlying guidance (scope to relevant tables, encourage time filters) is grounded in [S1].
Security: Caller Identity, Not Agent Identity#
The security model is the single most load-bearing design decision in this capability, and it is simple to state: the agent enforces read-only, least-privilege access by executing under the requesting user's own credentials and permissions, so each query only ever reaches data that user is already authorized to view [S1]. There is no separate, more-privileged "agent service account" quietly widening access -- the agent's reach is always a subset of the asking user's own. That caller-identity model extends to schema discovery itself: when answering a question, the agent first fetches the target source's schema using the requesting user's own credentials, so schema visibility is scoped to what that user can already see before a query is even generated [S5].
That has a specific, sometimes counterintuitive implication for Power BI semantic models: only Read permission on the model is required for the agent to retrieve schema and run queries against it. Build permission is not needed -- though Row-Level Security and Column-Level Security still apply in full [S1]. Architects used to Build being the bar for programmatic semantic-model access should treat this as a deliberate loosening, not an oversight: it lowers the permission a user needs to converse with a model through an agent, while RLS/CLS keep the actual data boundary intact. The same pattern holds for sharing: consumers who only query a shared data agent backed by Power BI semantic models need Read permission on those models and no workspace-level access at all -- Write is required only to modify the model or use Prep for AI [S5].
Read is sufficient, Build is not required -- but RLS and CLS are never bypassed, because the agent is always executing as the calling user [S1].
Authentication for the agent's own reasoning engine is handled for you: no customer-managed Azure OpenAI key or access token is required to use a data agent, because Fabric brokers a Microsoft-managed Azure OpenAI Assistant and handles that authentication internally [S5]. That has a specific interaction with workspace network controls worth flagging: when Workspace outbound access protection is enabled, a data agent's calls to external data sources are governed by the workspace's data connection rules, but the Microsoft-managed Azure OpenAI service the agent relies on is exempt from that protection [S5].
A newer authentication option changes who can be the "requesting user" in the first place. Preview: per a tier-6 source, a June 2026 Fabric update introduced a service-principal authentication option for data agents, letting automated callers authenticate without an interactive user sign-in [S9] -- a prerequisite for the CI/CD and ISV-embedding use cases the public API targets, since a pipeline or an embedded product integration isn't a human clicking through a sign-in prompt. (Hedge: single tier-6 source; treat the specific "June 2026" dating as unverified until corroborated by official documentation.)
New: Sensitivity-Label-Aware Behavior#
A separate, orthogonal governance dimension has emerged for agents built on top of data agents: sensitivity labels as a guidance signal, distinct from the permission checks already described above. An AI skill or agent can read an item's sensitivity label and use it to decide how appropriate a given piece of data is for the current question -- not just whether the user is technically allowed to see it [S6].
The risk this addresses is concrete. Without label-aware guidance, a report-analysis skill treats all attached reports equally, which can cause it to blend public and confidential content into a single answer to a general question -- mixing contexts the organization intended to keep separate, even when every source involved is one the user is individually permitted to view [S6].
Label-unaware blending is a real-answer-quality risk, not just a compliance one: a general question can surface confidential detail folded into what reads as a routine summary, because nothing in the agent's configuration told it to treat sources differently by sensitivity [S6].
A worked pattern from the source defines label-based rules per tier: General-labeled reports get full analysis with charts and data points; Confidential reports return only a summary to authorized users, with access logged; Highly Confidential reports are restricted to cleared executives and produce only high-level briefings, with all access tracked [S6]. It's important to be precise about what this pattern does and doesn't do: label-based response rules do not expand or restrict what a user can access. A user still sees only the reports they're already permitted to see -- the agent changes how much detail it surfaces from those permitted sources based on label tier, not who gets in the door [S6].
| Label tier | What the agent returns | Access logging |
|---|---|---|
| General | Full analysis, charts, data points | Standard |
| Confidential | Summary only, to authorized users | Logged |
| Highly Confidential | High-level briefing only, cleared execs | All access tracked |
(table derived from the per-tier pattern in [S6])
The recommended adoption sequence starts narrower than "turn this on everywhere": first ensure the data a skill depends on is consistently labeled, since unlabeled data gives the skill no signal to act on; then define per-label handling rules; then apply the rules within one narrowly scoped skill before expanding further [S6].
Best practice: label data before you configure label-aware behavior, not after#
The rule: don't write label-tier response rules into an agent's instructions until the underlying sources actually carry consistent sensitivity labels.
The why: unlabeled data provides the skill with no signal to act on at all -- a rule that says "summarize Confidential-labeled reports" is a no-op against a source with no label applied, and it will fail silently rather than erroring, which is worse than not writing the rule [S6].
# Wrong: adding label-tier rules to agent instructions before labeling is in place
Instructions: >
If the report is labeled Highly Confidential, return only a high-level briefing.
# ...but no reports in the attached lakehouse actually carry that label yet -- no-op.
# Right: label first, then add the rule, then verify on a small scope
# Step 1: apply/verify sensitivity labels on the source reports (Purview / Information Protection)
# Step 2: add the instruction once labels are confirmed present
# Step 3: pilot on one narrowly scoped skill before expanding
Governance: A Four-Layer Precedence Model#
Layered on top of caller-identity execution is an explicit governance hierarchy. Agent behavior is governed by four layers, from highest to lowest precedence: tenant-wide organizational policy, workspace/role-based governance settings, developer-supplied instructions and example queries, and finally end-user prompts. Higher layers always override lower ones [S1].
That ordering is worth internalizing because it tells you where to intervene when an agent misbehaves. If an agent is answering something it shouldn't, the fix belongs at the tenant-policy or workspace-governance layer, not as a patch to the developer's instruction text -- a user's prompt can never override policy set above it, and neither can a developer's own instructions.
| Layer | Precedence | Who sets it |
|---|---|---|
| Organizational policy | Highest | Tenant admins |
| Workspace/role-based governance | 2nd | Workspace/capacity admins |
| Developer instructions & example queries | 3rd | Agent builders |
| End-user prompts | Lowest | The person asking |
(table derived from the precedence ordering in [S1])
Provisioning itself sits behind its own gate before any of this matters: creating or evaluating a data agent requires a paid F2-or-higher Fabric capacity, or a Power BI Premium P1-or-higher capacity with Fabric enabled [S5], and separately, an admin must enable cross-geo processing and cross-geo storing for AI at the tenant level before a data agent can run at all -- a distinct switch from simply having capacity [S5].
Ecosystem Integrations: Where a Published Agent Can Live#
Once published, a data agent's reach extends well beyond the Fabric chat surface, and the public API accelerates that further by making programmatic embedding a first-class path rather than an edge case.
(Hedge: the specifics in this section, unless otherwise cited to S1-S6, come from tier-6, non-Microsoft tutorial sites rather than official documentation -- treat them as directionally useful but unverified until corroborated by a tier-1/tier-2 source.)
A published data agent exposes a callable API endpoint identified by a workspace ID and an artifact ID -- the mechanism external orchestrators use to invoke it outside the Fabric chat UI, per a tier-6 tutorial source [S8]. Microsoft Foundry integrates with a data agent through a named tool, reportedly MicrosoftFabricAgentTool, and per the same post this requires the Foundry project and the Fabric workspace to be deployed in the same tenant [S8]. Client SDK support for calling a published agent's endpoint is reported in three languages -- Python, TypeScript, and JavaScript [S8].
Copilot Studio and Microsoft Teams are two more named integration points: per the same tier-6 source, Copilot Studio consumes a data agent as a registered plugin and handles the conversational layer itself, while Teams access can go either directly through Fabric's own chat surface or indirectly via a Copilot Studio integration [S8].
Preview: per a separate tier-6 source, an observability integration with Microsoft Foundry adds per-request tracing and latency monitoring for data agent calls [S9]. Also preview, from the same source: a Code Interpreter capability that lets a data agent execute Python for tasks like forecasting or statistical analysis, and a "Creator Agent" feature that assists a human builder by AI-generating initial configuration for SQL and Eventhouse data sources [S9].
The Foundry/Copilot Studio/Teams/SDK-language specifics above all trace to tier-6 tutorial sites rather than Microsoft documentation. They read as plausible extensions of the public API story and are included because they materially describe where an agent becomes embeddable, but they should be treated as unverified detail pending a tier-1/tier-2 source, not load-bearing architecture facts.
Community coverage broadens this further, describing the same decision independently: data agents get consumed outside native Fabric/Copilot surfaces by embedding them in custom applications via service-principal authentication, publishing them through Copilot Studio into Microsoft Teams, or routing between multiple agents from Azure AI Foundry using function calling -- treated by that community source as the standard "consumption path" decision beyond the Data Agent API, MCP, or GraphQL [S7].
A related but distinct capability sits alongside the data agent in Fabric's IQ surface: the operations agent. Per a comparison from a tier-6 source, the two differ by data-source scope and by mode -- a data agent can query lakehouses, warehouses, KQL databases, and Power BI semantic models on demand, per question, while the operations agent is restricted to Eventhouse/KQL sources and runs continuously rather than per-question [S10]. (Hedge: this comparison is sourced from a single tier-6 tutorial article; treat the specific framing as directional rather than an authoritative product boundary.)
How It Works / Best Practices#
Build multi-source agents around the five-source ceiling, not around it being unlimited. The rule: plan an agent's source list up front rather than adding sources ad hoc. The why: a single data agent can combine up to five data sources across lakehouses, warehouses, KQL databases, Power BI semantic models, ontologies, and Microsoft Graph -- five is the working ceiling for one agent's scope, not a soft suggestion [S1].
# Wrong: treating source attachment as unlimited, adding a 6th source
# and discovering the configuration surface won't accommodate it
Agent "SalesQA" sources: Lakehouse_Sales, Warehouse_Finance, KQL_Telemetry,
SemanticModel_Exec, Graph_Org, Ontology_Product # 6 -- over the line
# Right: design the domain boundary so one agent's five sources
# cover a coherent question space, and split into a second agent otherwise
Agent "SalesQA" sources: Lakehouse_Sales, Warehouse_Finance, KQL_Telemetry,
SemanticModel_Exec, Graph_Org # 5 -- at the ceiling
Agent "ProductQA" sources: Ontology_Product, ... # separate agent
Grant Read, not Build, for Power BI sources -- deliberately. The rule: when scoping permissions for a semantic model a data agent will query, Read is the correct and sufficient grant. The why: Build is explicitly not required, and over-granting Build gives the requesting user editing rights they don't need just to converse with the model through the agent [S1].
# Wrong: granting Build "to be safe" for a user who only needs conversational Q&A
Workspace role: Contributor (implies Build on the semantic model)
# Right: grant Read only -- sufficient for the agent to retrieve schema and query,
# RLS/CLS still enforced per user
Workspace role: Viewer (Read on the semantic model)
Keep unstructured content out of an agent's source list. The rule: only expose tabular, queryable sources to a data agent, and ingest or table-ize anything else first. The why: the agent only ever generates read queries -- SQL, DAX, or KQL -- and cannot directly query unstructured files such as PDF, DOCX, or TXT, or standalone lakehouse files, unless those files are first ingested or exposed as tables [S1].
# Wrong: pointing an agent at a lakehouse Files/ folder full of PDFs,
# expecting it to answer questions from their contents
Agent source: Lakehouse (Files/reports/*.pdf) # not queryable by the agent
# Right: ingest/parse the PDFs into a structured table first,
# then point the agent at the table
Pipeline: PDF -> extraction -> Delta table "Tables/report_extracts"
Agent source: Lakehouse (Tables/report_extracts) # queryable
Write agent-level and data-source-level instructions as two separate jobs, not one blob. The rule: use the agent-level template (Objective, Data sources, Key terminology, Response guidelines, Handling common topics) for cross-source guidance, and the data-source template (General knowledge, Table descriptions, When asked about) for guidance that only matters once a specific source has already been chosen [S4]. The why: data-source-level instructions apply only after routing has happened, so cross-cutting guidance (like which source to prefer for a given question type) belongs at the agent level or it will never be evaluated before the routing decision is made [S4].
# Agent-level instructions (evaluated before routing)
Objective: Answer finance and org questions for the FinanceQA team.
Data sources: Prefer SemanticModel_Quarterly for QoQ comparisons; use Graph_Org
for "who owns" questions.
Key terminology: "QoQ" = quarter-over-quarter; "cost center" = GL segment 4.
# Data-source-level instructions (evaluated only after this source is chosen)
General knowledge: Warehouse_Finance uses a star schema with FactRevenue at
monthly grain.
Table descriptions: DimCostCenter maps GL segment 4 codes to display names.
When asked about: quarterly totals, always join through DimDate.FiscalQuarter.
What goes wrong#
Asking for more rows than the cap allows, repeatedly, does not help. Agent responses are capped at a maximum of 25 rows and 25 columns. Because prior chat turns can influence later answers, even a follow-up question in the same session asking for "all rows for the year" is still limited to 25 rows [S1]. A tier-6 source frames this the same way in stronger terms: the 25x25 cap is a deliberate design choice for conversational use, not a temporary limitation, and it explicitly recommends against relying on a data agent for heavy analytical workloads [S8]. Treat this as a hard ceiling on what a data agent can be used for -- it is a conversational summarization surface, not a bulk-export mechanism, and no amount of prompt rephrasing lifts the cap. The same tier-6 source also notes a per-source example-query cap of up to 100 stored examples per attached data source -- a separate limit from the five-source and 25x25 caps [S8]. (Hedge: both figures in this sentence are from a single non-Microsoft tutorial source; treat as directional pending tier-1 confirmation.)
Splitting a data agent's capacity from its source's capacity, across regions, breaks it outright. A data agent cannot execute queries when its own workspace capacity and its data source's workspace capacity are in different regions -- a lakehouse in one region paired with a data agent capacity in another region will simply fail [S1]. This is a placement decision to get right at provisioning time, not something to discover after users start reporting errors.
Region mismatch between a data agent's capacity and its data source's capacity is a hard failure, not a performance penalty [S1].
Superficially relabeling a bronze dataset as Gold silently breaks NL2SQL agents. A community write-up coins the term "GINO" (Gold in Name Only) for a data-modeling antipattern where a dataset is called Gold-layer but still contains multi-grain fact tables, unnecessarily normalized structures, and outer joins that a human analyst can navigate around but a natural-language agent cannot [S7]. (Hedge: this is a community/tier-4 observation, not an official Microsoft antipattern designation -- but the underlying mechanism, that NL2SQL generation degrades on ambiguous multi-grain joins, is consistent with how the routing-and-generation model in this article works.) The proposed fix is simplifying toward Kimball-style star schemas before pointing an agent at the data [S7].
Thin agent instructions produce silently wrong answers, not obvious errors. Multiple independent community write-ups converge on the same conclusion: agent instructions and data-model documentation are the biggest lever for answer accuracy. One side-by-side community demo showed a minimally-instructed agent silently returning wrong answers on complex SCD2/bridge-table queries, while an identically-sourced agent with full instructions answered correctly [S7]. (Hedge: single community source, tier 4 -- directionally consistent with the official few-shot/instruction mechanism described above, but not independently verified by Microsoft.)
Internals#
Architecture & design#
Structurally, a data agent sits between a caller (a person in Microsoft 365, Foundry, Copilot Studio, a custom app, or an external orchestrator in a multi-agent system -- now including code driving the public management API) and a set of up to five governed Fabric data sources [S1] [S2] [S3]. It does not hold its own copy of data and does not maintain a persistent, elevated identity of its own -- every query it issues carries the requesting user's credentials and permissions forward, so the agent's effective access is always bounded by what that user could already see [S1].
The publish model reinforces that a data agent is a versioned artifact, not a single mutable object: publishing creates two independent versions, an editable draft and a separately shareable published copy, so an author can keep refining the draft without disrupting colleagues already querying the published version [S5]. That two-version model is also what the public API's publish_staging call operates against -- staging changes accumulate against the draft, and publishing promotes them [S3].
Above the routing-and-execution core sits the four-layer governance stack -- organizational policy, workspace/role governance, developer instructions, end-user prompts, applied in strict precedence order [S1]. Sensitivity labels add an orthogonal layer alongside this stack rather than replacing any part of it: they inform how much detail an agent surfaces from sources the caller is already permitted to see, while the four-layer precedence stack governs permission and policy boundaries themselves [S6]. This is best understood as a compile-time-to-run-time layering: policy and workspace settings are effectively fixed constraints an agent operates within, developer instructions and label-tier rules shape how it behaves inside those constraints, and only the end-user's actual prompt varies turn to turn.
Within Fabric's broader IQ/Copilot surface, the data agent is the piece Microsoft positions as the general-purpose, embeddable virtual analyst -- grounded in semantic models and ontologies and publishable across Microsoft 365, Foundry, Copilot Studio, and custom apps -- as distinct from a Copilot, which stays fixed to a single in-product task [S2]. Architecturally, that makes the data agent the reusable, cross-surface component, while Copilots remain point solutions embedded in one authoring experience. The public API extends that reusability one step further: the same agent shape can now be provisioned repeatably across many workspaces from code, or embedded by an ISV as part of a packaged product rather than remaining a side experience inside Fabric [S3].
How it works internally#
Query execution follows a two-stage path: source identification, then generator dispatch. First the agent determines which of its configured sources (up to five) is relevant to the incoming question, using each source's description, schema, and example queries together as routing signal [S4]. Then it routes to a source-specific generator -- NL2SQL for lakehouse/warehouse, NL2DAX for Power BI semantic models, NL2KQL for KQL databases, or a Microsoft Graph query for organizational data [S1]. Each generator produces a read-only query in the native language of its target engine; there is no intermediate universal query representation described in the verified material -- the routing decision happens before generation, not after.
The prompt sent to the underlying Azure OpenAI Assistant is assembled from three inputs: the user's natural-language question, the fetched schema metadata (retrieved under the caller's own credentials), and any creator-supplied instructions or example queries -- before the assistant selects a query-generation tool [S5]. Example queries are matched as a few-shot mechanism: when a source is selected, its tool automatically looks up the most relevant example queries and passes the top three into the query-generation step, with only syntactically valid, schema-matching examples ever used; Power BI semantic models don't support this example-query mechanism at all [S5].
For Power BI semantic model sources specifically, the internal permission check is narrower than the Fabric norm: retrieving schema and running a query both succeed on Read permission alone, with Build permission not required, while Row-Level and Column-Level Security are still evaluated per query [S1]. That means the security enforcement point for semantic-model sources is RLS/CLS evaluation at query time under the caller's identity, not an object-permission gate at Build level.
For Eventhouse (KQL database) sources, execution happens in place with no data movement into or out of the agent, and NL2KQL-generated queries can invoke existing KQL user-defined functions already defined on the database [S1] -- meaning query logic already encoded as a UDF is reusable by the agent's generated queries rather than being reimplemented in natural-language-derived KQL each time. A preview capability extends this further, letting materialized views and shortcut tables serve as sources alongside raw tables and UDFs [S7].
Every generated query, regardless of source type, is constrained to be read-only: the agent only ever produces SQL, DAX, or KQL read statements, never create, update, or delete operations [S1]. This is enforced as a property of what the agent generates, not as a permission the calling user happens to lack -- even a user with write access to the underlying source cannot get the agent to issue a write on their behalf, because the agent's query-generation layer does not produce write statements at all.
Cross-tenant querying follows the same caller-identity model with one addition: when a workspace shares OneLake data from another tenant via external data sharing, a data agent can query that shared data through the shortcut created at share acceptance, with the consumer tenant's governance policies applying and no additional authentication configuration needed [S5].
Preview: per a tier-6 source, a Code Interpreter capability lets a data agent execute Python for tasks like forecasting or statistical analysis in addition to generating SQL/DAX/KQL queries -- extending the generator-dispatch model above with a fifth kind of tool the assistant can select, beyond the four query generators already described [S9]. (Hedge: single tier-6 source; treat as a documented-preview claim pending tier-1 corroboration.)
Performance characteristics#
Most of what would sit here -- measured query latency, NL2SQL/NL2DAX/NL2KQL generation throughput, or capacity consumption under concurrent load -- remains outside verified Microsoft grounding, and one accuracy figure that surfaced in the newly ingested material is explicitly hedged rather than treated as a hard benchmark: a tier-6 source describes a preview NL2SQL runtime update as improving query-generation accuracy by roughly 20% in what the post calls Microsoft's own internal benchmarks [S9]. (Hedge: this is a single non-Microsoft tutorial site's paraphrase of an unnamed internal benchmark, with no publicly linked methodology, dataset, or Microsoft-published number behind it -- do not treat "roughly 20%" as a citable, reproducible figure. It is included here only because it is the one performance-adjacent data point in the newly ingested claims, and it is flagged as unverified rather than omitted.)
Separately, and with a materially higher evidence bar, a community benchmark effort is worth noting for what it demonstrates about evaluation methodology rather than as a product performance number: a community-built, 72-question multilingual benchmark evaluated with the evaluate_data_agent SDK function initially showed weaker accuracy, but a row-level audit found many failures traced to faulty ground truth and ambiguous phrasing in the benchmark itself, not agent mistakes. After fixing the benchmark and tightening agent instructions, measured accuracy rose to about 97.2% [S7]. A related community finding sharpens the caution around evaluation numbers generally: substituting a stricter custom critic prompt in the same SDK's evaluation step materially changed the measured results without any change to the agent or benchmark under test [S7] -- meaning an accuracy percentage from this SDK is only as meaningful as the critic prompt used to produce it, official or custom.
Coming soon -- verified, Microsoft-published latency/throughput numbers for query execution, generation time by generator type, or capacity consumption under concurrent agent load aren't in the knowledge base yet. It needs an L4/L5 source such as a Microsoft engineering blog or benchmark writeup on Fabric data agent query latency or capacity consumption at scale. Tracked in content/queue.md.
Programmatic Evaluation#
Beyond the public management API, Fabric ships a second programmatic surface specifically for testing agent quality before rollout. Preview: programmatic evaluation of a data agent is delivered through a separate pip-installable package, fabric-data-agent-sdk, run from a Fabric notebook rather than the chat UI [S11]. It shares the same capacity and tenant prerequisites as building a data agent in the first place -- a paid F2+ or P1+ capacity with Fabric enabled, plus the tenant's cross-geo processing/storing for AI setting turned on [S11].
A ground-truth evaluation set is deliberately simple: a two-column table of question and expected_answer pairs, supplied either as an in-notebook pandas DataFrame or loaded from a CSV file with those exact column names [S11]. The evaluate_data_agent function runs that set against a named agent, targeting either its production or sandbox stage, and returns a unique evaluation_id while writing results into a summary table plus a matching _steps table holding detailed reasoning and execution traces [S11].
# Illustrative shape of the evaluation workflow described in <sup id="cite-11"><a href="#src-11" class="cite">[S11]</a></sup>
import pandas as pd
from fabric_data_agent_sdk import evaluate_data_agent, get_evaluation_summary, get_evaluation_details
ground_truth = pd.DataFrame({
"question": ["What was Q3 revenue?", "Who owns cost center 4100?"],
"expected_answer": ["$4.2M", "J. Alvarez"],
})
evaluation_id = evaluate_data_agent(
data_agent_name="FinanceQA",
stage="production",
evaluation_data=ground_truth,
)
summary = get_evaluation_summary(evaluation_id) # totals, true/false/unclear, accuracy
failures = get_evaluation_details(evaluation_id, only_failures=True)
Inference: parameter names above are illustrative, shaped around the documented function behavior in [S11] rather than copied verbatim from a code sample.
Two read-back functions cover the results: get_evaluation_summary returns aggregated metrics -- total questions evaluated, counts of true/false/unclear results, and overall accuracy [S11]; get_evaluation_details returns row-level results -- question, expected answer, actual answer, a true/false/unclear evaluation_result, and a thread_url -- and can be filtered to only failing or unclear rows [S11]. That thread_url is scoped for privacy: it's only accessible to the user who ran that particular evaluation, not to other collaborators [S11].
Whether an actual answer counts as a match is judged by an LLM using a built-in critic prompt by default, and that prompt can be fully replaced via a critic_prompt parameter that must include {query}, {expected_answer}, and {actual_answer} placeholders [S11]. A custom critic prompt is useful for loosening or tightening match criteria, and for handling cases where expected and actual answers differ in formatting but are semantically equivalent, or involve domain-specific judgment [S11] -- a capability the community benchmark finding above shows can materially swing measured results, so treat the critic prompt as part of what you're testing, not a neutral judge.
Best practice: audit your evaluation set before concluding the agent is wrong#
The rule: when evaluate_data_agent reports low accuracy, inspect the row-level failures with get_evaluation_details(..., only_failures=True) before assuming the agent's query generation is at fault.
The why: a documented community case found many apparent failures traced back to faulty ground truth, ambiguous phrasing, and inconsistent casing in the evaluation set itself, not agent mistakes -- and a separate case found the critic prompt's strictness could swing results independent of any agent change [S7].
# Wrong: seeing 60% accuracy and immediately rewriting agent instructions
evaluate_data_agent(...) -> accuracy: 0.60
# -> jump straight to editing agent-level instructions
# Right: inspect failing rows first, separate ground-truth issues from real agent errors
failures = get_evaluation_details(evaluation_id, only_failures=True)
# review each row: is expected_answer actually correct and unambiguously phrased?
# only then decide whether the fix belongs in the eval set or the agent config
For troubleshooting outside the evaluation workflow, a Diagnostics button exports a full snapshot of a data agent's configuration and execution steps -- including data source settings, applied instructions, and example queries used -- for troubleshooting or for engaging Microsoft Support [S11].
Worked Example#
Consider a finance team that wants a natural-language Q&A surface over quarterly results, provisioned and promoted entirely through the public API rather than the portal, so it can be stamped out consistently across a dozen regional workspaces.
1. Scope the agent to a coherent set of sources, at or under the five-source ceiling. The team attaches a Warehouse holding transactional finance data, a Power BI semantic model holding the published quarterly reporting model, and Microsoft Graph for org-chart-aware questions like "who owns this cost center" -- three sources, comfortably under the five-source limit [S1]:
Agent "FinanceQA" sources:
- Warehouse_Finance (NL2SQL)
- SemanticModel_Quarterly (NL2DAX)
- Graph_Org (Microsoft Graph query)
2. Provision it from a CI/CD pipeline using the public API, not by hand in each workspace. A pipeline step authenticates with a service principal, calls create_data_agent against each regional workspace ID, and pushes the same git-tracked instructions and data-source configuration to every one -- the exact repeatable-provisioning use case named for this API [S3] [S9]:
for workspace_id in regional_workspace_ids:
create_data_agent(workspace_id, display_name="FinanceQA")
update_settings(agent_id, instructions=git_tracked_instructions_text)
add_staging_datasource(agent_id, "Warehouse_Finance")
add_staging_datasource(agent_id, "SemanticModel_Quarterly")
add_staging_datasource(agent_id, "Graph_Org")
publish_staging(agent_id)
3. Grant Read, not Build, on the semantic model. Business users who will chat with the agent get Viewer access to the Power BI workspace -- sufficient for the agent to retrieve schema and run DAX queries against SemanticModel_Quarterly on their behalf, with RLS on the model still filtering results to each user's authorized cost centers [S1].
4. Layer sensitivity labels on top of the standard governance stack. The finance workspace's reports already carry General/Confidential/Highly Confidential labels; the team follows the recommended adoption sequence -- confirm labels are consistently applied, then add a rule so Highly Confidential reports produce only high-level briefings for cleared executives, piloted on this one agent before extending the pattern elsewhere [S6].
5. Layer the four-layer governance stack above the agent's own instructions. A tenant-wide organizational policy already restricts which workspaces can host data agents; a workspace-level governance setting restricts the finance workspace's agent to read-only warehouse access; the developer's instructions add domain guidance ("prefer the semantic model for anything involving quarter-over-quarter comparisons"); and end-user prompts sit at the bottom, unable to override any of the above [S1]:
Precedence (highest to lowest):
1. Org policy: "Data agents may only be published from approved workspaces."
2. Workspace policy: "This workspace's agents are read-only against Warehouse_Finance."
3. Developer instr.: "Prefer SemanticModel_Quarterly for QoQ comparison questions."
4. End-user prompt: "Show me QoQ revenue by region."
6. Set expectations around output limits before rollout. The team documents for end users that any answer table is capped at 25 rows by 25 columns, and that asking the same question a different way in a follow-up turn will not lift that cap [S1] -- a design choice a tier-6 source frames explicitly as "not for heavy analytical workloads" [S8] -- so the agent is positioned internally as a summarization and drill-down aid, not a report-export tool.
7. Verify capacity placement before going live. Both Warehouse_Finance and the Fabric capacity hosting the data agent are confirmed to sit in the same region; had they been split across regions, the agent would simply fail to execute queries against the warehouse [S1].
8. Evaluate before promoting to production. Before flipping the pipeline's target stage from sandbox to production, the team runs evaluate_data_agent against a ground-truth question set, reviews any failing rows with get_evaluation_details, and only then calls publish_staging against the production stage [S11].
9. Once published, hand the runtime endpoint -- not the management API -- to consuming applications. The finance workspace's own analysts continue querying through Fabric chat, while a separate internal reporting portal calls the agent's MCP endpoint to embed the same Q&A capability, keeping the two surfaces cleanly separated per the management-plane/runtime-plane distinction described above [S3].