<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <title>FluentLake</title>
  <subtitle>Field notes from the lakehouse.</subtitle>
  <link href="https://fluentlake.com/feed.xml" rel="self" />
  <link href="https://fluentlake.com/" />
  <updated>2026-06-05T00:00:00Z</updated>
  <id>https://fluentlake.com/</id>
  <author>
    <name>FluentLake</name>
  </author>
  <entry>
    <title>Welcome to FluentLake</title>
    <link href="https://fluentlake.com/posts/welcome-to-fluentlake/" />
    <updated>2026-05-29T00:00:00Z</updated>
    <id>https://fluentlake.com/posts/welcome-to-fluentlake/</id>
    <content type="html">&lt;p&gt;I&#39;ve been meaning to write things down for a long time. Not polished think-pieces, just the working notes I usually keep in scratch files and then lose. This is the place I&#39;m going to keep them instead.&lt;/p&gt;
&lt;p&gt;I work in data and analytics, mostly in the Microsoft world: &lt;strong&gt;Microsoft Fabric&lt;/strong&gt;, lakehouses, Azure, Synapse, and a great deal of SQL. A lot of what I do is moving data from where it is to where it can answer a question, and making that path reliable enough that someone else can trust the answer. That work is full of small decisions that never make it into the documentation, and those small decisions are exactly what I want to capture here.&lt;/p&gt;
&lt;h2&gt;What this blog is&lt;/h2&gt;
&lt;p&gt;FluentLake is field notes from the lakehouse. The emphasis is on &lt;em&gt;notes&lt;/em&gt;: short, specific, and written close to the moment I learned something. I&#39;d rather publish one honest paragraph about a query plan than a tidy listicle I don&#39;t believe.&lt;/p&gt;
&lt;p&gt;A few things you can expect:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Lakehouse architecture&lt;/strong&gt;, and the medallion pattern in particular. Bronze, silver, gold is easy to draw and harder to live with. I want to write about the parts that bite.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Azure and Synapse trade-offs&lt;/strong&gt;, with the reasoning attached. The conclusion is usually the least interesting part; the &amp;quot;why&amp;quot; is what transfers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SQL worth keeping.&lt;/strong&gt; Queries that earned their place, and the tuning stories behind them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI-assisted engineering, told straight.&lt;/strong&gt; I build with Claude Code, Copilot CLI, and ChatGPT as part of the toolchain now. Some of it is genuinely transformative and some of it is overhyped, and I&#39;d like to be honest about which is which.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What this blog isn&#39;t&lt;/h2&gt;
&lt;p&gt;It isn&#39;t a product. There&#39;s no course at the end, no newsletter wall, no growth funnel. If a post is useful to you, wonderful. If it&#39;s wrong, please tell me, because the fastest way I&#39;ve found to learn something is to publish a confident mistake and get corrected.&lt;/p&gt;
&lt;p&gt;It also isn&#39;t speaking for anyone but me. Nothing here is the official position of an employer or a client. These are my notes, my opinions, and my errors.&lt;/p&gt;
&lt;h2&gt;On writing in the open&lt;/h2&gt;
&lt;p&gt;The honest reason I&#39;m doing this in public is that it makes me think more carefully. It&#39;s easy to half-understand something when it only lives in your head. The moment you have to explain it to a stranger, the gaps show up. Writing here is partly a forcing function: if I can&#39;t explain why I reached for a serverless SQL pool instead of a Fabric warehouse, I probably don&#39;t understand the choice well enough yet.&lt;/p&gt;
&lt;p&gt;So that&#39;s the plan. Notes from the lakehouse, written while I&#39;m still figuring it out, kept somewhere I won&#39;t lose them.&lt;/p&gt;
&lt;p&gt;If you want to follow along, the &lt;a href=&quot;https://fluentlake.com/feed.xml&quot;&gt;RSS feed&lt;/a&gt; is the best way. Thanks for reading the first one.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Synapse is retiring trusted-services access on 1 August 2026, and it may mean a new workspace</title>
    <link href="https://fluentlake.com/posts/synapse-trusted-services-retirement-2026/" />
    <updated>2026-05-30T00:00:00Z</updated>
    <id>https://fluentlake.com/posts/synapse-trusted-services-retirement-2026/</id>
    <content type="html">&lt;p&gt;Microsoft recently sent a notice that is easy to skim past and expensive to ignore. It announces that the trusted-services function in Azure Synapse Analytics is being retired, with a firm date of 1 August 2026. The language is dry and the impact is large, so I want to translate it into what it actually means and why it deserves attention now rather than closer to the deadline.&lt;/p&gt;
&lt;h2&gt;What is being retired, and the date&lt;/h2&gt;
&lt;p&gt;Today many Synapse workspaces reach an Azure Storage account or an Azure Key Vault using a managed identity together with a firewall exception. That exception is the trusted-services allowance, and it permits the Synapse service to reach the storage or Key Vault firewall because Microsoft treats it as a first-party service. On 1 August 2026, that allowance is removed.&lt;/p&gt;
&lt;p&gt;The replacement is private networking. Instead of a firewall exception, your workspace reaches Storage and Key Vault through a managed virtual network using managed private endpoints. This is a stronger security model. The hard part is the work required to get there.&lt;/p&gt;
&lt;p&gt;The change does not degrade gracefully. If no action is taken, the paths that depended on the trusted-services exception will stop working, and a Synapse workspace that cannot read its storage or its secrets cannot operate. This is a deadline that interrupts production workloads, not an optional cleanup.&lt;/p&gt;
&lt;h2&gt;Why this is the typical installation, not an edge case&lt;/h2&gt;
&lt;p&gt;It is natural to assume that only heavily hardened environments are affected. In practice, the opposite is true. The at-risk configuration is the ordinary result of following the installation guidance carefully.&lt;/p&gt;
&lt;p&gt;When you stand up Synapse Link for Dataverse, Microsoft&#39;s guidance directs you to set the landing storage account firewall to Selected networks. That is the appropriate next step after the wizard completes, because the storage account should not remain open to the public internet. Trusted Azure services are then allowed through so that Synapse can continue to reach the account. That combination, a restricted firewall together with the trusted-services exception, is precisely the pattern being retired.&lt;/p&gt;
&lt;p&gt;The population at risk is therefore not the teams that did something unusual. It is the majority that followed the security-conscious install. A fully open storage account, which is technically unaffected, typically appears only on demonstration tenants or in environments where the firewall was never tightened. If you followed the secure setup, you are more likely to be affected, not less.&lt;/p&gt;
&lt;h2&gt;The create-time-only trap&lt;/h2&gt;
&lt;p&gt;The managed virtual network is the workspace property that lets you use managed private endpoints. That property can only be enabled when the workspace is created. Microsoft does not provide a way to enable it on a workspace that already exists.&lt;/p&gt;
&lt;p&gt;As a result, if your workspace was not created with the managed virtual network enabled, this is not a setting you can change in the portal. The practical remediation is to deploy a new Synapse workspace with the managed virtual network enabled from the outset and migrate your work onto it.&lt;/p&gt;
&lt;p&gt;It is also worth remembering that every Synapse workspace has an associated primary storage account, an ADLS Gen2 account that holds its saved queries, pipelines, and related artifacts. The workspace and its storage are closely tied, which is part of why repointing an existing workspace is not straightforward.&lt;/p&gt;
&lt;h2&gt;How to tell if you are affected&lt;/h2&gt;
&lt;p&gt;This assessment is something you can run yourself today, at no cost, in a few minutes, and it establishes whether you are affected.&lt;/p&gt;
&lt;p&gt;First, identify the relevant storage account. This is the landing storage account for your Synapse Link, found in the Power Apps maker portal under Azure Synapse Link for Dataverse, then the link entry, then Details. Copy the value shown under Storage Account.&lt;/p&gt;
&lt;p&gt;With the correct account name, run the following Azure CLI query. Reader access on the storage account is sufficient for it to return cleanly.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;az storage account show &lt;span class=&quot;token parameter variable&quot;&gt;--name&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;landingStorageAccount&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--query&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;{publicNetworkAccess:publicNetworkAccess, defaultAction:networkRuleSet.defaultAction, bypass:networkRuleSet.bypass, ipRules:length(networkRuleSet.ipRules), privateEndpoints:length(privateEndpointConnections)}&quot;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-o&lt;/span&gt; table&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Interpret the result against three cases.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you see &lt;code&gt;defaultAction: Deny&lt;/code&gt; together with &lt;code&gt;bypass: AzureServices&lt;/code&gt; and &lt;code&gt;privateEndpoints: 0&lt;/code&gt;, and the paired workspace has no managed virtual network, then you are at risk and a rebuild is required before 1 August 2026. You confirm the managed virtual network status on the workspace Overview page in the Azure portal.&lt;/li&gt;
&lt;li&gt;If you see &lt;code&gt;defaultAction: Allow&lt;/code&gt;, then the firewall is not load-bearing. There is no trusted-services bypass to retire, so this specific change does not affect you.&lt;/li&gt;
&lt;li&gt;If you see a private endpoint already in place and the workspace already has a managed virtual network, then you have already migrated, and there is nothing to do here.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The middle case is worth a closer look. If your storage account accepts public traffic from all networks, the retirement does not affect you on 1 August 2026, but the only reason it does not is that the account is currently open to all networks, which is a risk worth addressing in its own right. Once you properly restrict the firewall to selected networks, you return to the at-risk pattern. A fully open account defers the deadline; it does not remove the underlying work. I would still plan a move to the private-networking model on a measured timeline, so the account can be locked down properly soon after.&lt;/p&gt;
&lt;p&gt;If you prefer to confirm this in the portal rather than the CLI, the at-risk setting lives on the storage account&#39;s Public network access page. Public network access is set to Enable, and the scope beneath it is set to Enable from selected networks. That is the firewall-on, trusted-services-allowed state, and it is the one that breaks on 1 August 2026.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fluentlake.com/assets/img/posts/synapse-storage-public-access-selected-networks.png&quot; alt=&quot;Storage account Public network access page in the Azure portal, set to Enable with the scope set to Enable from selected networks, which is the at-risk configuration&quot;&gt;&lt;/p&gt;
&lt;p&gt;This is also why the pattern is so common among Synapse Link users on D365. If you stood Synapse Link up with the enterprise policy and managed identity option, which lets Dynamics 365 write to the storage account directly while the firewall stays on, then &amp;quot;Enable from selected networks&amp;quot; is exactly the configuration you end up with. If you did not use the enterprise policy option, you had to leave the storage open to all networks instead, which drops you back into the wide-open case above. Either way, the more secure, locked-down setup is the one affected by the deadline.&lt;/p&gt;
&lt;p&gt;On the workspace side, the at-risk condition is just as easy to confirm. Open the workspace in the Azure portal and look at the Overview page. If &amp;quot;Managed virtual network&amp;quot; reads No, that is the property you cannot change after creation, and it is the reason a rebuild may be required.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fluentlake.com/assets/img/posts/synapse-workspace-managed-vnet-no.png&quot; alt=&quot;Azure portal Overview page for a Synapse workspace, with Managed virtual network showing No, which is the at-risk condition&quot;&gt;&lt;/p&gt;
&lt;p&gt;One further caveat applies to the storage check. The probe above looks at the storage account, but the same trusted-services logic applies to any Azure Key Vault your workspace reaches through a firewall. If your storage turns out to be open but you have a firewalled Key Vault that Synapse reaches as a trusted service, that path is on the same timeline, so confirm both.&lt;/p&gt;
&lt;p&gt;That is the whole diagnosis. It genuinely takes about five minutes, and it is free. Knowing where you stand is the part you can do yourself today.&lt;/p&gt;
&lt;h2&gt;Two common but incorrect conclusions&lt;/h2&gt;
&lt;p&gt;Two readings of the portal appear reassuring and are not.&lt;/p&gt;
&lt;p&gt;The first concerns public network access. Seeing &amp;quot;Enabled&amp;quot; on that setting is not the same as wide open. &amp;quot;Selected networks&amp;quot; still shows as Enabled, because the public endpoint exists, but it sits behind a restricted firewall, and that restricted-plus-trusted-services state is precisely the at-risk pattern.&lt;/p&gt;
&lt;p&gt;The second concerns a private endpoint on the storage side. If your team added a customer-side private endpoint to the storage account, it is reasonable to assume that covers you. It does not. A Synapse workspace without a managed virtual network still routes its traffic over the public endpoint using the trusted-services bypass, so a private endpoint added on the storage side does not carry the Synapse traffic. The endpoint that matters is a managed private endpoint from the workspace, and that requires the managed virtual network you cannot add later.&lt;/p&gt;
&lt;h2&gt;Do you need to rebuild? Walk the decision&lt;/h2&gt;
&lt;p&gt;Two questions decide most of this: whether you are on the trusted-services path at all, and whether the managed virtual network was enabled when the workspace was created. Everything after that is scoping. Here is the whole decision in one view.&lt;/p&gt;
&lt;figure class=&quot;diagram&quot;&gt;
  &lt;svg viewBox=&quot;0 0 920 820&quot; role=&quot;img&quot; aria-labelledby=&quot;diag-synapse-title diag-synapse-desc&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
    &lt;title id=&quot;diag-synapse-title&quot;&gt;Do you need to rebuild your Synapse workspace before the trusted-services retirement?&lt;/title&gt;
    &lt;desc id=&quot;diag-synapse-desc&quot;&gt;Start at an Azure Synapse workspace. Question one: does it rely on the trusted Azure services exception to reach Storage or Key Vault, meaning the firewall is set to selected networks and Synapse gets in only through that exception using its managed identity, which retires on 1 August 2026? If no, the workspace is not affected by this retirement; confirm and you are done. If yes, go to question two: was the managed virtual network enabled when the workspace was created, which you confirm on the workspace Overview page in the Azure portal and which cannot be changed after creation? If yes, no rebuild is needed; add managed private endpoints to Storage and Key Vault before 1 August 2026. If no, a rebuild is required, because the managed virtual network is create-time only, so you must stand up a new workspace with it enabled. On that rebuild path, question three asks what is in the workspace. If it holds only Synapse Link and external tables on serverless, it is a lighter rebuild: create a new managed-VNet workspace, recreate the serverless objects, and repoint consumers. If it holds dedicated SQL pools and pipelines, it is a heavier migration: also migrate pools, pipelines, and linked services, and assess case by case.&lt;/desc&gt;
    &lt;defs&gt;
      &lt;marker id=&quot;diag-syn-arrow&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;9&quot; refY=&quot;5&quot; markerWidth=&quot;7&quot; markerHeight=&quot;7&quot; orient=&quot;auto-start-reverse&quot;&gt;
        &lt;path d=&quot;M0 0 L10 5 L0 10 z&quot; style=&quot;fill:var(--accent)&quot;&gt;&lt;/path&gt;
      &lt;/marker&gt;
    &lt;/defs&gt;
    &lt;g fill=&quot;none&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; marker-end=&quot;url(#diag-syn-arrow)&quot; style=&quot;stroke:var(--accent)&quot;&gt;
      &lt;path d=&quot;M450 68 V108&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M650 170.5 H785 V250&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M450 233 V267&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M450 377 V411&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M250 322 H135 V420&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M450 507 V541&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M370 621 H215 V707&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M530 621 H685 V707&quot;&gt;&lt;/path&gt;
    &lt;/g&gt;
    &lt;g text-anchor=&quot;middle&quot; style=&quot;font-family:var(--font-mono)&quot;&gt;
      &lt;rect x=&quot;320&quot; y=&quot;20&quot; width=&quot;260&quot; height=&quot;48&quot; rx=&quot;24&quot; style=&quot;fill:var(--navy);stroke:var(--accent);stroke-width:1&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;450&quot; y=&quot;49&quot; font-size=&quot;13&quot; style=&quot;fill:var(--code-text)&quot;&gt;Azure Synapse workspace&lt;/text&gt;
    &lt;/g&gt;
    &lt;g text-anchor=&quot;middle&quot; style=&quot;font-family:var(--font-mono)&quot;&gt;
      &lt;rect x=&quot;250&quot; y=&quot;108&quot; width=&quot;400&quot; height=&quot;125&quot; rx=&quot;12&quot; style=&quot;fill:var(--navy);stroke:var(--accent);stroke-width:1.25&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;450&quot; y=&quot;136&quot; font-size=&quot;13&quot; font-weight=&quot;600&quot; style=&quot;fill:var(--code-text)&quot;&gt;Q1 &amp;#183; Trusted Azure services exception to&lt;/text&gt;
      &lt;text x=&quot;450&quot; y=&quot;156&quot; font-size=&quot;13&quot; font-weight=&quot;600&quot; style=&quot;fill:var(--code-text)&quot;&gt;reach Storage or Key Vault?&lt;/text&gt;
      &lt;text x=&quot;450&quot; y=&quot;182&quot; font-size=&quot;11&quot; style=&quot;fill:#9FB4BB&quot;&gt;Firewall set to selected networks, and Synapse&lt;/text&gt;
      &lt;text x=&quot;450&quot; y=&quot;198&quot; font-size=&quot;11&quot; style=&quot;fill:#9FB4BB&quot;&gt;gets in only through the trusted-services exception,&lt;/text&gt;
      &lt;text x=&quot;450&quot; y=&quot;214&quot; font-size=&quot;11&quot; style=&quot;fill:#9FB4BB&quot;&gt;using its managed identity. Retires 1 Aug 2026.&lt;/text&gt;
    &lt;/g&gt;
    &lt;g text-anchor=&quot;middle&quot; style=&quot;font-family:var(--font-mono)&quot;&gt;
      &lt;rect x=&quot;250&quot; y=&quot;267&quot; width=&quot;400&quot; height=&quot;110&quot; rx=&quot;12&quot; style=&quot;fill:var(--navy);stroke:var(--accent);stroke-width:1.25&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;450&quot; y=&quot;300&quot; font-size=&quot;13&quot; font-weight=&quot;600&quot; style=&quot;fill:var(--code-text)&quot;&gt;Q2 &amp;#183; Managed virtual network&lt;/text&gt;
      &lt;text x=&quot;450&quot; y=&quot;320&quot; font-size=&quot;13&quot; font-weight=&quot;600&quot; style=&quot;fill:var(--code-text)&quot;&gt;enabled at create time?&lt;/text&gt;
      &lt;text x=&quot;450&quot; y=&quot;346&quot; font-size=&quot;11&quot; style=&quot;fill:#9FB4BB&quot;&gt;Check the workspace Overview page in the Azure&lt;/text&gt;
      &lt;text x=&quot;450&quot; y=&quot;362&quot; font-size=&quot;11&quot; style=&quot;fill:#9FB4BB&quot;&gt;portal. It cannot be changed after creation.&lt;/text&gt;
    &lt;/g&gt;
    &lt;g text-anchor=&quot;middle&quot; style=&quot;font-family:var(--font-mono)&quot;&gt;
      &lt;rect x=&quot;665&quot; y=&quot;250&quot; width=&quot;240&quot; height=&quot;128&quot; rx=&quot;12&quot; style=&quot;fill:var(--navy)&quot;&gt;&lt;/rect&gt;
      &lt;rect x=&quot;665&quot; y=&quot;250&quot; width=&quot;240&quot; height=&quot;4&quot; rx=&quot;2&quot; style=&quot;fill:#3FB984&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;785&quot; y=&quot;284&quot; font-size=&quot;13&quot; font-weight=&quot;600&quot; style=&quot;fill:#3FB984&quot;&gt;Not affected&lt;/text&gt;
      &lt;text x=&quot;785&quot; y=&quot;310&quot; font-size=&quot;11&quot; style=&quot;fill:var(--code-text)&quot;&gt;This retirement does not&lt;/text&gt;
      &lt;text x=&quot;785&quot; y=&quot;326&quot; font-size=&quot;11&quot; style=&quot;fill:var(--code-text)&quot;&gt;touch your workspace.&lt;/text&gt;
      &lt;text x=&quot;785&quot; y=&quot;350&quot; font-size=&quot;11&quot; style=&quot;fill:#9FB4BB&quot;&gt;Confirm, then you are done.&lt;/text&gt;
    &lt;/g&gt;
    &lt;g text-anchor=&quot;middle&quot; style=&quot;font-family:var(--font-mono)&quot;&gt;
      &lt;rect x=&quot;15&quot; y=&quot;420&quot; width=&quot;240&quot; height=&quot;136&quot; rx=&quot;12&quot; style=&quot;fill:var(--navy)&quot;&gt;&lt;/rect&gt;
      &lt;rect x=&quot;15&quot; y=&quot;420&quot; width=&quot;240&quot; height=&quot;4&quot; rx=&quot;2&quot; style=&quot;fill:var(--accent)&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;135&quot; y=&quot;454&quot; font-size=&quot;13&quot; font-weight=&quot;600&quot; style=&quot;fill:var(--accent)&quot;&gt;No rebuild&lt;/text&gt;
      &lt;text x=&quot;135&quot; y=&quot;480&quot; font-size=&quot;11&quot; style=&quot;fill:var(--code-text)&quot;&gt;You already have a managed&lt;/text&gt;
      &lt;text x=&quot;135&quot; y=&quot;496&quot; font-size=&quot;11&quot; style=&quot;fill:var(--code-text)&quot;&gt;virtual network, so you can&lt;/text&gt;
      &lt;text x=&quot;135&quot; y=&quot;512&quot; font-size=&quot;11&quot; style=&quot;fill:var(--code-text)&quot;&gt;remediate in place.&lt;/text&gt;
      &lt;text x=&quot;135&quot; y=&quot;536&quot; font-size=&quot;11&quot; style=&quot;fill:#9FB4BB&quot;&gt;before 1 August 2026.&lt;/text&gt;
    &lt;/g&gt;
    &lt;g text-anchor=&quot;middle&quot; style=&quot;font-family:var(--font-mono)&quot;&gt;
      &lt;rect x=&quot;290&quot; y=&quot;411&quot; width=&quot;320&quot; height=&quot;96&quot; rx=&quot;12&quot; style=&quot;fill:var(--navy)&quot;&gt;&lt;/rect&gt;
      &lt;rect x=&quot;290&quot; y=&quot;411&quot; width=&quot;320&quot; height=&quot;4&quot; rx=&quot;2&quot; style=&quot;fill:#D9A521&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;450&quot; y=&quot;445&quot; font-size=&quot;13&quot; font-weight=&quot;600&quot; style=&quot;fill:#D9A521&quot;&gt;Rebuild required&lt;/text&gt;
      &lt;text x=&quot;450&quot; y=&quot;471&quot; font-size=&quot;11&quot; style=&quot;fill:var(--code-text)&quot;&gt;Managed virtual network is&lt;/text&gt;
      &lt;text x=&quot;450&quot; y=&quot;487&quot; font-size=&quot;11&quot; style=&quot;fill:var(--code-text)&quot;&gt;create-time only. Stand up a&lt;/text&gt;
      &lt;text x=&quot;450&quot; y=&quot;503&quot; font-size=&quot;11&quot; style=&quot;fill:var(--code-text)&quot;&gt;new workspace with it enabled.&lt;/text&gt;
    &lt;/g&gt;
    &lt;g text-anchor=&quot;middle&quot; style=&quot;font-family:var(--font-mono)&quot;&gt;
      &lt;rect x=&quot;290&quot; y=&quot;541&quot; width=&quot;320&quot; height=&quot;80&quot; rx=&quot;12&quot; style=&quot;fill:var(--navy);stroke:var(--accent);stroke-width:1.25&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;450&quot; y=&quot;573&quot; font-size=&quot;13&quot; font-weight=&quot;600&quot; style=&quot;fill:var(--code-text)&quot;&gt;Q3 &amp;#183; What is in the workspace?&lt;/text&gt;
      &lt;text x=&quot;450&quot; y=&quot;597&quot; font-size=&quot;11&quot; style=&quot;fill:#9FB4BB&quot;&gt;Scope the new workspace to what&lt;/text&gt;
      &lt;text x=&quot;450&quot; y=&quot;613&quot; font-size=&quot;11&quot; style=&quot;fill:#9FB4BB&quot;&gt;you carry over&lt;/text&gt;
    &lt;/g&gt;
    &lt;g text-anchor=&quot;middle&quot; style=&quot;font-family:var(--font-mono)&quot;&gt;
      &lt;rect x=&quot;55&quot; y=&quot;707&quot; width=&quot;320&quot; height=&quot;92&quot; rx=&quot;12&quot; style=&quot;fill:var(--navy)&quot;&gt;&lt;/rect&gt;
      &lt;rect x=&quot;55&quot; y=&quot;707&quot; width=&quot;320&quot; height=&quot;4&quot; rx=&quot;2&quot; style=&quot;fill:#D9A521&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;215&quot; y=&quot;741&quot; font-size=&quot;13&quot; font-weight=&quot;600&quot; style=&quot;fill:#D9A521&quot;&gt;Lighter rebuild&lt;/text&gt;
      &lt;text x=&quot;215&quot; y=&quot;765&quot; font-size=&quot;11&quot; style=&quot;fill:var(--code-text)&quot;&gt;New managed-VNet workspace, recreate&lt;/text&gt;
      &lt;text x=&quot;215&quot; y=&quot;781&quot; font-size=&quot;11&quot; style=&quot;fill:var(--code-text)&quot;&gt;serverless objects, then repoint consumers.&lt;/text&gt;
    &lt;/g&gt;
    &lt;g text-anchor=&quot;middle&quot; style=&quot;font-family:var(--font-mono)&quot;&gt;
      &lt;rect x=&quot;525&quot; y=&quot;707&quot; width=&quot;320&quot; height=&quot;92&quot; rx=&quot;12&quot; style=&quot;fill:var(--navy)&quot;&gt;&lt;/rect&gt;
      &lt;rect x=&quot;525&quot; y=&quot;707&quot; width=&quot;320&quot; height=&quot;4&quot; rx=&quot;2&quot; style=&quot;fill:#D9A521&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;685&quot; y=&quot;741&quot; font-size=&quot;13&quot; font-weight=&quot;600&quot; style=&quot;fill:#D9A521&quot;&gt;Heavier migration&lt;/text&gt;
      &lt;text x=&quot;685&quot; y=&quot;765&quot; font-size=&quot;11&quot; style=&quot;fill:var(--code-text)&quot;&gt;Also migrate pools, pipelines, and linked&lt;/text&gt;
      &lt;text x=&quot;685&quot; y=&quot;781&quot; font-size=&quot;11&quot; style=&quot;fill:var(--code-text)&quot;&gt;services. Assess case by case.&lt;/text&gt;
    &lt;/g&gt;
    &lt;g font-size=&quot;11&quot; text-anchor=&quot;middle&quot; style=&quot;font-family:var(--font-mono);fill:var(--text-muted)&quot;&gt;
      &lt;rect x=&quot;431&quot; y=&quot;240&quot; width=&quot;38&quot; height=&quot;20&quot; rx=&quot;4&quot; style=&quot;fill:var(--surface-2);stroke:var(--accent);stroke-width:0.75&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;450&quot; y=&quot;254&quot;&gt;Yes&lt;/text&gt;
      &lt;rect x=&quot;700&quot; y=&quot;160.5&quot; width=&quot;34&quot; height=&quot;20&quot; rx=&quot;4&quot; style=&quot;fill:var(--surface-2);stroke:var(--accent);stroke-width:0.75&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;717&quot; y=&quot;174.5&quot;&gt;No&lt;/text&gt;
      &lt;rect x=&quot;433&quot; y=&quot;384&quot; width=&quot;34&quot; height=&quot;20&quot; rx=&quot;4&quot; style=&quot;fill:var(--surface-2);stroke:var(--accent);stroke-width:0.75&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;450&quot; y=&quot;398&quot;&gt;No&lt;/text&gt;
      &lt;rect x=&quot;166&quot; y=&quot;312&quot; width=&quot;38&quot; height=&quot;20&quot; rx=&quot;4&quot; style=&quot;fill:var(--surface-2);stroke:var(--accent);stroke-width:0.75&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;185&quot; y=&quot;326&quot;&gt;Yes&lt;/text&gt;
      &lt;rect x=&quot;99&quot; y=&quot;654&quot; width=&quot;232&quot; height=&quot;20&quot; rx=&quot;4&quot; style=&quot;fill:var(--surface-2);stroke:var(--accent);stroke-width:0.75&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;215&quot; y=&quot;668&quot;&gt;Synapse Link + serverless tables&lt;/text&gt;
      &lt;rect x=&quot;569&quot; y=&quot;654&quot; width=&quot;232&quot; height=&quot;20&quot; rx=&quot;4&quot; style=&quot;fill:var(--surface-2);stroke:var(--accent);stroke-width:0.75&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;685&quot; y=&quot;668&quot;&gt;Dedicated pools and/or pipelines&lt;/text&gt;
    &lt;/g&gt;
  &lt;/svg&gt;
  &lt;figcaption&gt;Decision flow: do you need to rebuild your Synapse workspace before the 1 August 2026 trusted-services retirement?&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2&gt;Who this hits hardest&lt;/h2&gt;
&lt;p&gt;The group most affected is Microsoft Dynamics 365 Finance and Supply Chain customers who use Synapse Link. The retirement actually reaches Synapse Link for Dataverse more broadly, including the customer engagement apps as well, so do not assume you are exempt simply because you are not a finance and operations shop. That is a large population, and many of them are not deep Azure specialists. They adopted Synapse Link because it was the supported way to get D365 data into a lake, not to take on Synapse networking.&lt;/p&gt;
&lt;p&gt;Many of these same customers were recently moved off Export to Data Lake and onto Synapse Link or Fabric Link. The teams that chose Synapse Link completed the migration they were directed to make, and those without the managed virtual network enabled now face another transition soon after.&lt;/p&gt;
&lt;h2&gt;The fix, at a high level&lt;/h2&gt;
&lt;p&gt;Because the managed virtual network can only be enabled when a workspace is created, the remediation is to stand up a new workspace with it enabled and migrate your work onto it. There is no in-place toggle that makes this go away.&lt;/p&gt;
&lt;p&gt;This does not have to mean an outage. The new workspace can run in parallel with the old one, and the old one keeps working right up until you choose to cut over. The deadline forces a rebuild, but it does not force downtime, so the work can be planned deliberately rather than compressed into a high-risk maintenance window near the deadline.&lt;/p&gt;
&lt;p&gt;How involved the rebuild is depends entirely on what you built. A workspace that only uses Synapse Link and external tables to read from an ADLS account is a contained move. A workspace with dedicated SQL pools and a large set of pipelines is a substantial migration that requires testing. The effort is not uniform, so it should be assessed against your actual environment rather than estimated with a single figure.&lt;/p&gt;
&lt;h2&gt;Map your downstream consumers first&lt;/h2&gt;
&lt;p&gt;If I could recommend one thing before touching anything else, it would be to inventory everything that consumes the current workspace. A new workspace very likely means a new Synapse serverless endpoint hostname, and every connection pointed at the old hostname has to be repointed.&lt;/p&gt;
&lt;p&gt;The range is wide. A single ETL connection into a downstream warehouse is the easy case, because there is one owner and one connection string. Analysts connecting directly from SQL Server Management Studio are harder, because the connections are scattered and you may not know who all of them are. Scheduled jobs that pull data into an on-premises database are harder still, because they run unattended, so a broken hostname shows up as silent missing data rather than a loud error.&lt;/p&gt;
&lt;p&gt;The networking change is the same regardless of how many consumers you have. The repointing is where the effort actually lives, so map it before you commit to a timeline.&lt;/p&gt;
&lt;h2&gt;Bottom line&lt;/h2&gt;
&lt;p&gt;Do not wait. The date is 1 August 2026. If you have not addressed this before then, there is a real risk that your Synapse workspace stops working for these paths. Run the self-check above today, confirm whether your workspace has the managed virtual network enabled, and if it does not, begin planning the new-workspace move and the downstream repointing while you still have runway. There may be exceptions for customers who genuinely cannot transition in time, but that is not something to plan around. Treat the date as firm.&lt;/p&gt;
&lt;p&gt;The diagnosis is the part you can do yourself, and I have included all of it here on purpose. The rebuild and the migration are a different matter. They involve real work and real risk, and the difference between a controlled, no-downtime cutover and a last-minute effort comes down to planning and experience. That controlled, parallel migration is the kind of work my data and analytics team at Alithya does. We can run the assessment, confirm exactly where you stand, and put together an estimate for the rebuild and the downstream repointing scoped to your actual workspace rather than a generic one. If you would like that, I am glad to talk through your specifics.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/power-apps/maker/data-platform/azure-synapse-link-transition-faq&quot;&gt;Azure Synapse Link transition FAQ (Microsoft&#39;s canonical notice and remediation guidance)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/power-apps/maker/data-platform/azure-synapse-link-msi&quot;&gt;Synapse Link for Dataverse with a managed identity (the &amp;quot;Selected networks&amp;quot; plus trusted-services install pattern)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/power-apps/maker/data-platform/azure-synapse-link-synapse#prerequisites&quot;&gt;Azure Synapse Link for Dataverse prerequisites (where the older &amp;quot;managed virtual network not supported&amp;quot; line still lives, now superseded by the transition FAQ)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/azure/synapse-analytics/security/synapse-workspace-managed-vnet&quot;&gt;Synapse workspace managed virtual network (the create-time-only constraint)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/azure/synapse-analytics/security/synapse-workspace-managed-private-endpoints&quot;&gt;Synapse workspace managed private endpoints&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/azure/storage/common/storage-network-security&quot;&gt;Storage network security and trusted access based on a managed identity&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Databricks for Engineering, Fabric for Reporting</title>
    <link href="https://fluentlake.com/posts/fabric-and-databricks-together/" />
    <updated>2026-05-30T00:00:00Z</updated>
    <id>https://fluentlake.com/posts/fabric-and-databricks-together/</id>
    <content type="html">&lt;p&gt;A lot of the architecture debates I sit in treat Microsoft Fabric and Databricks as rivals, where one has to win the whole stack. In practice I keep landing somewhere more boring and more useful: do your data engineering in Databricks, do your reporting in Fabric and Power BI, and connect the two so the Databricks output flows into Power BI with little or no copying. This post is my field notes on making that pairing work, including the clean path and the messier workaround I needed when the clean path was off the table.&lt;/p&gt;
&lt;p&gt;One caveat before I start. This space moves fast, and Microsoft ships changes to Fabric on a cadence that makes any &amp;quot;Fabric can&#39;t do X&amp;quot; statement risky. Everything here is current-state and experience-based as of this writing. Some of it will age.&lt;/p&gt;
&lt;h2&gt;Why Databricks for the engineering, right now&lt;/h2&gt;
&lt;p&gt;Plenty of enterprise organizations already prefer Databricks for their heavy data engineering, and in my current experience that preference is well founded. For enterprise-grade pipelines, Databricks tends to be the more reliable place to do the work today, and I will give one concrete reason rather than hand-wave. In the Fabric lakehouse, scheduled jobs and similar items have often been tied to the identity of the individual user who set them up, rather than to a durable workspace identity or a service principal. For an enterprise that is fragile. People change teams and leave companies, and you do not want a production refresh quietly bound to one person&#39;s account. To be fair, Fabric workspace identities have been improving, and I am hopeful this rough edge gets resolved soon. I am describing where things stand in my hands, not handing down a permanent verdict.&lt;/p&gt;
&lt;h2&gt;Why Fabric for the reporting&lt;/h2&gt;
&lt;p&gt;The flip side is that Databricks is not always the best place to serve reports. Microsoft has been moving quickly on the reporting side of Fabric and Power BI, and that pace is hard to ignore. Direct Lake semantic models, the tight OneLake integration, and the general Power BI experience make Fabric a strong reporting layer. So instead of forcing one tool to do everything, pair them. Engineer in Databricks, report in Fabric, and the interesting question becomes how you connect the two without paying for the data twice.&lt;/p&gt;
&lt;h2&gt;The clean path: shortcuts and Direct Lake&lt;/h2&gt;
&lt;p&gt;When everything lives in one tenant, the integration is close to a no-brainer.&lt;/p&gt;
&lt;p&gt;Databricks writes Delta Lake tables, which are Parquet files plus a transaction log, into ADLS Gen2 (Azure Data Lake Storage). From your Fabric lakehouse you create a OneLake shortcut that points at those Delta tables. A shortcut is a reference, not a copy, so the data stays where Databricks wrote it. On top of the shortcut you build a Direct Lake semantic model, and Power BI reports straight off that data.&lt;/p&gt;
&lt;p&gt;Two wins fall out of this, and they are the whole reason the path is so attractive:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;There is no data copy from Databricks to Fabric. The shortcut reads the same Delta files Databricks produced.&lt;/li&gt;
&lt;li&gt;With Direct Lake there is no import into the semantic model, so there is no data refresh to schedule. The model reads the Delta files directly. It does reframe to the latest Delta version when the data changes, but that is a metadata-only operation that happens automatically, not a copy you have to babysit.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Put those together and the workflow is genuinely pleasant. You run your Databricks jobs, the Delta tables update, and the new data is immediately available to your Power BI users, with no copy step and no data refresh to babysit. When you can use it, this is the path to reach for.&lt;/p&gt;
&lt;h2&gt;The complication: different tenants and secured storage&lt;/h2&gt;
&lt;p&gt;The clean path assumes a single tenant. On a recent engagement that assumption did not hold. Databricks and Fabric lived in separate tenants, and that changes the picture in a few ways that are worth being precise about.&lt;/p&gt;
&lt;p&gt;OneLake shortcuts do not cross tenant boundaries, so the shortcut path was gone. The storage account behind Databricks was also secured, and in that cross-tenant situation shortcuts do not work with managed private endpoints either. For completeness I also looked at Databricks mirroring in Fabric, the Mirrored Azure Databricks catalog, and that does not work across tenants in my experience either.&lt;/p&gt;
&lt;p&gt;It is worth keeping these three mechanisms separate in your head, because they are easy to conflate. Shortcuts are references in OneLake, mirroring is Fabric&#39;s Mirrored Azure Databricks catalog, and Delta Sharing is a Databricks sharing protocol. They are three different things, and in this cross-tenant, secured-storage scenario the first two were both off the table.&lt;/p&gt;
&lt;h2&gt;The workaround that actually worked: Delta Sharing&lt;/h2&gt;
&lt;p&gt;That left Delta Sharing, and it got the data into Fabric. This path does copy the data, which sounds like a step backward, but the copy turned out to be cheap. Delta Sharing gives secured access to the shared tables through a token. Behind the scenes it hands the recipient short-lived credentials so they can read the underlying files. My read of the mechanics is that it hands out short-lived, pre-signed access to the files, which on Azure is effectively a SAS-style URL, scoped and time-limited rather than a standing key.&lt;/p&gt;
&lt;p&gt;The Fabric side took some setup. Here is the sequence that worked for us:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a custom Fabric Spark environment. You want a dedicated environment, not the default one.&lt;/li&gt;
&lt;li&gt;Install the Delta Sharing library into that environment. The cleanest way is to add it as a Spark property on the environment. Set &lt;code&gt;spark.jars.packages&lt;/code&gt; to &lt;code&gt;io.delta:delta-sharing-spark_2.12:1.0.3&lt;/code&gt;. Pin the version rather than floating it, so a later package update does not quietly change behavior under you.&lt;/li&gt;
&lt;li&gt;Turn off the Native Execution Engine for this environment. The Fabric Spark Native Execution Engine acceleration is not compatible with Delta Sharing in my experience, so you do not use that acceleration here. In practice that means unchecking the native execution engine on the Acceleration page and setting &lt;code&gt;spark.gluten.enabled&lt;/code&gt; and &lt;code&gt;spark.o3exec.enabled&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt; in the Spark properties.&lt;/li&gt;
&lt;li&gt;Connect to the Delta Share from that environment and read the data.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here is what the Spark properties on the environment end up looking like:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fluentlake.com/assets/img/posts/fabric-spark-env-deltasharing-properties.png&quot; alt=&quot;Spark properties for the custom Fabric Spark environment, showing spark.jars.packages set to io.delta:delta-sharing-spark_2.12:1.0.3, spark.gluten.enabled set to false, and spark.o3exec.enabled set to false&quot;&gt;&lt;/p&gt;
&lt;p&gt;And the Native Execution Engine stays off on the Acceleration page:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fluentlake.com/assets/img/posts/fabric-native-execution-engine-disabled.png&quot; alt=&quot;The Acceleration page of the Fabric Spark environment with the Enable native execution engine checkbox unchecked&quot;&gt;&lt;/p&gt;
&lt;p&gt;One more safety net. If you cannot guarantee the environment has the Native Execution Engine off, or someone turns it back on later, you can force the right behavior from inside the notebook. Setting these at the top of the notebook pushes Delta Sharing reads down the V1 source path and keeps the session off the native engine, so the notebook still runs:&lt;/p&gt;
&lt;pre class=&quot;language-python&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;spark&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;conf&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;spark.native.enabled&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;false&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
spark&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;conf&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;spark.gluten.enabled&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;false&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
spark&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;conf&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;spark.sql.sources.useV1SourceList&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;deltaSharing&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;# force V1 source path&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There was one more wrinkle around security. The share was locked down, so I used managed private endpoints from the Fabric workspace to the Delta Share&#39;s ADLS resource. That combination worked well. Note the contrast with the shortcut case: shortcuts plus managed private endpoints did not work cross-tenant, but reading a Delta Share over managed private endpoints did.&lt;/p&gt;
&lt;h2&gt;Performance: the copy is not the dealbreaker it sounds like&lt;/h2&gt;
&lt;p&gt;The obvious objection to this workaround is that you are copying data, and copying feels slow and expensive at enterprise scale. In practice it was neither. On the capacity we had, we copied a table of about 1.3 billion rows in just a few minutes, and that was a full copy. Moving to an incremental copy, where you only pull what changed, would very likely bring it under a minute. So while the clean shortcut path is still the one I prefer when it is available, the Delta Sharing copy is fast enough that I would not let &amp;quot;but it copies&amp;quot; talk me out of it.&lt;/p&gt;
&lt;h2&gt;A side note: Copilot in Fabric notebooks has caught up more than I expected&lt;/h2&gt;
&lt;p&gt;While I was doing all of this Fabric and Databricks work in notebooks, I went back and tried Copilot in the Fabric notebooks again, almost by accident. I was surprised by how much it had improved.&lt;/p&gt;
&lt;p&gt;For a good while, Databricks was clearly ahead here. The assistant inside Databricks notebooks could take a prompt and add cells, modify cells, run and test them, and troubleshoot, all with very little hand-holding. Microsoft&#39;s Copilot lagged that for months, and I had quietly filed it under &amp;quot;not there yet.&amp;quot;&lt;/p&gt;
&lt;p&gt;Microsoft has since closed a lot of that gap. As of when I last looked, Copilot in Fabric notebooks can do much of the same. It can modify the notebook, it carries the context of the whole notebook, and it can even look at other notebooks in the workspace. I have not tried going across workspaces yet. Here is a concrete example: you can point it at an older version of a notebook saved under a different name and say &amp;quot;this one works but this new one does not, what is going on,&amp;quot; and it will work through that, execute cells, test, troubleshoot, and make changes without much interaction from me. That was a real improvement.&lt;/p&gt;
&lt;p&gt;The lesson I want to leave you with is simple. These AI tools change constantly, so if you do not go back and re-try them, you will not actually know what they can do today. I make a point of staying current on Copilot specifically, because many clients are limited to Copilot and cannot freely use Claude or other frontier models. Copilot still lags the frontier models in general capability, and Claude is one example that is ahead. But the gap moved, and it pays to re-test your assumptions rather than carry last year&#39;s verdict. When a client genuinely needs something only another model handles well, that becomes a real and worthwhile conversation about getting them the right subscription, whether that is Anthropic&#39;s Claude, ChatGPT, or whatever fits best.&lt;/p&gt;
&lt;h2&gt;Field notes, not a verdict&lt;/h2&gt;
&lt;p&gt;That is the shape of the pairing as I have been running it: Databricks for the engineering, Fabric and Power BI for the reporting, the OneLake shortcut and Direct Lake path when tenants and storage allow it, and Delta Sharing through a custom Spark environment when they do not. I have tried to flag which parts are documented behavior and which parts are simply what worked for us, because this is a fast-moving target and some of these edges will move.&lt;/p&gt;
&lt;p&gt;I am genuinely curious what other people are doing with hybrid architectures. If you have paired Fabric and Databricks, or stitched together some other combination across tenants, I would like to hear how you handled the seams.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Does the gold layer always have to be a star schema?</title>
    <link href="https://fluentlake.com/posts/does-the-gold-layer-have-to-be-a-star-schema/" />
    <updated>2026-05-30T00:00:00Z</updated>
    <id>https://fluentlake.com/posts/does-the-gold-layer-have-to-be-a-star-schema/</id>
    <content type="html">&lt;p&gt;I get a version of this question often. In the gold layer of a medallion lakehouse, do you always recommend a star schema? It is a fair thing to ask, because the star schema is the default answer most of us reach for, and for good reason. I have built Kimball-style data warehouses for decades, and the star schema has earned its place a thousand times over.&lt;/p&gt;
&lt;p&gt;A couple of years ago my answer would have been an unqualified yes. I believed there was always a way to shape the data into a star schema that the business could use. If a model felt awkward, that was a sign I had not found the right grain or the right conformed dimensions yet, not a sign that the star schema itself was the wrong tool. Then I met a group of users who changed my mind.&lt;/p&gt;
&lt;h2&gt;The reality that challenged the dogma&lt;/h2&gt;
&lt;p&gt;These users were not report consumers in the usual sense. They were analysts, and they worked the way analysts on the TV show &amp;quot;24&amp;quot; work. Think of the intelligence analysts piecing together fragments from many different sources, under time pressure, to make a fast decision. My users did the same thing with their own data. They pulled from many places at once and assembled an answer in the moment.&lt;/p&gt;
&lt;p&gt;A lot of what they needed arrived from outside their own systems. They received emails from vendors and partners, took that external information on the fly, and blended it with data from their internal systems. They needed many different kinds of information, and a great deal of it landed in Excel.&lt;/p&gt;
&lt;p&gt;I want to be honest about the Excel part, because normally I steer users away from Excel and toward governed Power BI reports, and that is usually the right call. This was a legitimate exception. Excel has real uses, and this was one of them. These analysts were already pulling data into spreadsheets and building formulas by hand, copying and pasting, matching and cleaning manually. It was genuinely impressive to watch. They flew through spreadsheets and built formulas on the fly faster than I could follow. It was also entirely manual, and much of the logic lived only in their heads, unsupported by any governed source of truth.&lt;/p&gt;
&lt;h2&gt;Why a star schema would have failed them&lt;/h2&gt;
&lt;p&gt;You can put a PivotTable over a star schema, and for many users that is exactly the right experience. For these analysts it would not have been enough. They would still have been hand-building their working datasets with formulas every time, because the data they needed was spread across multiple tables that they would have had to understand and join themselves.&lt;/p&gt;
&lt;p&gt;That joining is the friction that matters here. A star schema asks the consumer to know which dimension connects to which fact and on what key. For a report author or a semantic model, that is a feature. For an analyst who is moving fast and blending in external data by hand, it is a tax on every single task. The friction of understanding and joining multiple tables would have made the star schema nearly impossible for them to benefit from. We would have shipped something technically correct and practically unusable.&lt;/p&gt;
&lt;h2&gt;What we built instead&lt;/h2&gt;
&lt;p&gt;After a lot of fretting, and I will admit this went against instinct, we built a very wide, flattened model. We took the dataset these analysts needed and flattened it into a Microsoft Fabric Direct Lake semantic model: one big wide table that combined the master data and the transactions together. No joins left for the user to perform.&lt;/p&gt;
&lt;p&gt;I will be honest that the scale made me nervous. This is a very wide table, several hundred columns across, and the dataset runs to more than a hundred million rows. I expected to pay for that at query time. The Microsoft Fabric Direct Lake semantic model handled it far better than I anticipated. Queries come back quickly and the model stays responsive, even at that width and that row count. Direct Lake only pulls the columns a given query actually touches, so a very wide table does not cost you the way you might fear.&lt;/p&gt;
&lt;p&gt;From Excel, the analysts connect to that model and pick the fields they need. This is Analyze in Excel against the Power BI semantic model. The model exposes the fields directly, so there is no manual table stitching. They get a PivotTable or a clean dataset where everything is already in one place, with no joins to get right. The more advanced users were trained to blend and join additional tables on top of that, but not every user could do that, and that was the point. The wide flat table met them where they were instead of demanding they come to the model.&lt;/p&gt;
&lt;h2&gt;The gold layer is not a single shape&lt;/h2&gt;
&lt;p&gt;Here is the reframing I want you to take away. The gold layer is not one shape, and you do not get only one of it. You can have more than one gold layer for different consumers.&lt;/p&gt;
&lt;p&gt;In this case we built what I have started calling a gold Power Query layer: a flattened model purpose-built for these analysts. A conventional star-schema gold layer can still exist right alongside it, serving the reports and semantic models that want a star. Other gold layers can be shaped differently again for other use cases. None of them are wrong. They are answers to different questions.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;star-schema gold layer&lt;/strong&gt; for report authors and Direct Lake semantic models that benefit from conformed dimensions and clean grain.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;wide, flattened gold layer&lt;/strong&gt; for analysts who blend on the fly and cannot afford to join tables themselves.&lt;/li&gt;
&lt;li&gt;Room for &lt;strong&gt;more shapes&lt;/strong&gt; as new consumers show up with needs that neither of the first two serves well.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;diagram&quot;&gt;
  &lt;svg viewBox=&quot;0 0 920 470&quot; role=&quot;img&quot; aria-labelledby=&quot;diag-gold-title diag-gold-desc&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
    &lt;title id=&quot;diag-gold-title&quot;&gt;Medallion lakehouse with two gold layers&lt;/title&gt;
    &lt;desc id=&quot;diag-gold-desc&quot;&gt;Five data sources (ERP, CRM, Excel, SharePoint, and SQL) flow into a Bronze layer, which flows to a Silver layer. Silver branches into two gold layers: a star-schema Gold that serves Power BI, and a wide, flattened PQ Gold that serves Excel through Power Query. The two gold-to-consumer paths run in parallel.&lt;/desc&gt;
    &lt;defs&gt;
      &lt;marker id=&quot;diag-arrow&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;9&quot; refY=&quot;5&quot; markerWidth=&quot;7&quot; markerHeight=&quot;7&quot; orient=&quot;auto-start-reverse&quot;&gt;
        &lt;path d=&quot;M0 0 L10 5 L0 10 z&quot; style=&quot;fill:var(--accent)&quot;&gt;&lt;/path&gt;
      &lt;/marker&gt;
    &lt;/defs&gt;
    &lt;g fill=&quot;none&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; marker-end=&quot;url(#diag-arrow)&quot; style=&quot;stroke:var(--accent)&quot;&gt;
      &lt;path d=&quot;M150 70  C196 70  186 235 224 235&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M150 138 C196 138 192 235 224 235&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M150 206 C196 206 192 235 224 235&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M150 274 C196 274 192 235 224 235&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M150 342 C196 342 186 235 224 235&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M328 235 H408&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M512 235 C556 235 552 150 592 150&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M512 235 C556 235 552 320 592 320&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M696 150 H776&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M696 320 H776&quot;&gt;&lt;/path&gt;
    &lt;/g&gt;
    &lt;g font-size=&quot;11&quot; font-weight=&quot;600&quot; letter-spacing=&quot;1.5&quot; text-anchor=&quot;middle&quot; style=&quot;font-family:var(--font-mono)&quot;&gt;
      &lt;text x=&quot;75&quot; y=&quot;24&quot; style=&quot;fill:var(--text-muted)&quot;&gt;SOURCES&lt;/text&gt;
      &lt;text x=&quot;276&quot; y=&quot;24&quot; style=&quot;fill:#B08D57&quot;&gt;BRONZE&lt;/text&gt;
      &lt;text x=&quot;460&quot; y=&quot;24&quot; style=&quot;fill:#9AA7AD&quot;&gt;SILVER&lt;/text&gt;
      &lt;text x=&quot;644&quot; y=&quot;24&quot; style=&quot;fill:#D9A521&quot;&gt;GOLD&lt;/text&gt;
      &lt;text x=&quot;826&quot; y=&quot;24&quot; style=&quot;fill:var(--text-muted)&quot;&gt;CONSUMERS&lt;/text&gt;
    &lt;/g&gt;
    &lt;g font-size=&quot;14&quot; text-anchor=&quot;middle&quot; style=&quot;font-family:var(--font-mono)&quot;&gt;
      &lt;rect x=&quot;14&quot; y=&quot;52&quot; width=&quot;136&quot; height=&quot;36&quot; rx=&quot;8&quot; style=&quot;fill:var(--navy)&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;82&quot; y=&quot;75&quot; style=&quot;fill:var(--code-text)&quot;&gt;ERP&lt;/text&gt;
      &lt;rect x=&quot;14&quot; y=&quot;120&quot; width=&quot;136&quot; height=&quot;36&quot; rx=&quot;8&quot; style=&quot;fill:var(--navy)&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;82&quot; y=&quot;143&quot; style=&quot;fill:var(--code-text)&quot;&gt;CRM&lt;/text&gt;
      &lt;rect x=&quot;14&quot; y=&quot;188&quot; width=&quot;136&quot; height=&quot;36&quot; rx=&quot;8&quot; style=&quot;fill:var(--navy)&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;82&quot; y=&quot;211&quot; style=&quot;fill:var(--code-text)&quot;&gt;Excel&lt;/text&gt;
      &lt;rect x=&quot;14&quot; y=&quot;256&quot; width=&quot;136&quot; height=&quot;36&quot; rx=&quot;8&quot; style=&quot;fill:var(--navy)&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;82&quot; y=&quot;279&quot; style=&quot;fill:var(--code-text)&quot;&gt;SharePoint&lt;/text&gt;
      &lt;rect x=&quot;14&quot; y=&quot;324&quot; width=&quot;136&quot; height=&quot;36&quot; rx=&quot;8&quot; style=&quot;fill:var(--navy)&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;82&quot; y=&quot;347&quot; style=&quot;fill:var(--code-text)&quot;&gt;SQL&lt;/text&gt;
    &lt;/g&gt;
    &lt;g text-anchor=&quot;middle&quot; style=&quot;font-family:var(--font-mono)&quot;&gt;
      &lt;rect x=&quot;224&quot; y=&quot;203&quot; width=&quot;104&quot; height=&quot;64&quot; rx=&quot;10&quot; style=&quot;fill:var(--navy)&quot;&gt;&lt;/rect&gt;
      &lt;rect x=&quot;224&quot; y=&quot;203&quot; width=&quot;104&quot; height=&quot;4&quot; rx=&quot;2&quot; style=&quot;fill:#B08D57&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;276&quot; y=&quot;241&quot; font-size=&quot;15&quot; style=&quot;fill:var(--code-text)&quot;&gt;Bronze&lt;/text&gt;
      &lt;rect x=&quot;408&quot; y=&quot;203&quot; width=&quot;104&quot; height=&quot;64&quot; rx=&quot;10&quot; style=&quot;fill:var(--navy)&quot;&gt;&lt;/rect&gt;
      &lt;rect x=&quot;408&quot; y=&quot;203&quot; width=&quot;104&quot; height=&quot;4&quot; rx=&quot;2&quot; style=&quot;fill:#9AA7AD&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;460&quot; y=&quot;241&quot; font-size=&quot;15&quot; style=&quot;fill:var(--code-text)&quot;&gt;Silver&lt;/text&gt;
      &lt;rect x=&quot;592&quot; y=&quot;118&quot; width=&quot;104&quot; height=&quot;64&quot; rx=&quot;10&quot; style=&quot;fill:var(--navy)&quot;&gt;&lt;/rect&gt;
      &lt;rect x=&quot;592&quot; y=&quot;118&quot; width=&quot;104&quot; height=&quot;4&quot; rx=&quot;2&quot; style=&quot;fill:#D9A521&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;644&quot; y=&quot;146&quot; font-size=&quot;15&quot; style=&quot;fill:var(--code-text)&quot;&gt;Gold&lt;/text&gt;
      &lt;text x=&quot;644&quot; y=&quot;166&quot; font-size=&quot;10.5&quot; style=&quot;fill:var(--text-muted)&quot;&gt;star schemas&lt;/text&gt;
      &lt;rect x=&quot;592&quot; y=&quot;288&quot; width=&quot;104&quot; height=&quot;64&quot; rx=&quot;10&quot; style=&quot;fill:var(--navy)&quot;&gt;&lt;/rect&gt;
      &lt;rect x=&quot;592&quot; y=&quot;288&quot; width=&quot;104&quot; height=&quot;4&quot; rx=&quot;2&quot; style=&quot;fill:#D9A521&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;644&quot; y=&quot;316&quot; font-size=&quot;15&quot; style=&quot;fill:var(--code-text)&quot;&gt;PQ Gold&lt;/text&gt;
      &lt;text x=&quot;644&quot; y=&quot;336&quot; font-size=&quot;10.5&quot; style=&quot;fill:var(--text-muted)&quot;&gt;wide, flattened&lt;/text&gt;
      &lt;rect x=&quot;776&quot; y=&quot;118&quot; width=&quot;130&quot; height=&quot;64&quot; rx=&quot;10&quot; style=&quot;fill:var(--navy)&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;841&quot; y=&quot;155&quot; font-size=&quot;14&quot; style=&quot;fill:var(--code-text)&quot;&gt;Power BI&lt;/text&gt;
      &lt;rect x=&quot;776&quot; y=&quot;288&quot; width=&quot;130&quot; height=&quot;64&quot; rx=&quot;10&quot; style=&quot;fill:var(--navy)&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;841&quot; y=&quot;316&quot; font-size=&quot;14&quot; style=&quot;fill:var(--code-text)&quot;&gt;Excel&lt;/text&gt;
      &lt;text x=&quot;841&quot; y=&quot;336&quot; font-size=&quot;10.5&quot; style=&quot;fill:var(--text-muted)&quot;&gt;Power Query&lt;/text&gt;
    &lt;/g&gt;
  &lt;/svg&gt;
  &lt;figcaption&gt;A medallion lakehouse with two gold layers: a star-schema Gold for Power BI, and a wide, flattened PQ Gold for Excel.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Most data warehouse and lakehouse architects, myself very much included until recently, would reflexively insist on the star schema. This is a case for looking twice. The job is to serve the consumer, not the dogma.&lt;/p&gt;
&lt;h2&gt;The payoff&lt;/h2&gt;
&lt;p&gt;Had we shipped a star schema, these analysts would have been stuck with something they could not use. Instead their work accelerated, because the datasets now arrive with everything already blended. They write far fewer Excel formulas and spend far less time piecing things together. They still blend in external datasets when they need to, because that is the nature of the work, but the job is much easier now that the internal side comes pre-assembled and trustworthy.&lt;/p&gt;
&lt;p&gt;There is one more payoff, and it is the part I find most interesting. With clean, pre-blended, trustworthy data sitting in that model, the analysts can build reusable components and bring AI into the loop. They can use AI to combine the Power Query datasets with their external data and get what they need without writing queries or Excel formulas at all. The hard part of that has always been trusting the joins, and the flattened model takes that worry off the table.&lt;/p&gt;
&lt;p&gt;Making clean, joined, trustworthy lakehouse data available for both people and AI to consume, without anyone having to worry about getting the joins right, has been a real unlock. That is worth going against instinct for. So the next time someone asks me whether the gold layer has to be a star schema, my honest answer is that it depends entirely on who is standing on the other side of it.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>How this blog gets made: HAL, a terminal, and a little BBS nostalgia</title>
    <link href="https://fluentlake.com/posts/how-this-blog-gets-made/" />
    <updated>2026-06-05T00:00:00Z</updated>
    <id>https://fluentlake.com/posts/how-this-blog-gets-made/</id>
    <content type="html">&lt;p&gt;A few months ago I started spending real time in Claude Code, and something about it felt familiar. I was working entirely in a terminal, typing into a prompt and watching text scroll back at me. It took me straight to the mid-1990s, when I would dial into a local bulletin board system and live inside those sixteen-color ANSI screens, listening to the modem handshake and then waiting my way into a world drawn entirely from text characters. Claude Code brought all of that back. The difference is that it is far more capable than any BBS I ever called. Working in a terminal in 2026 feels backwards. It is also, I have come to think, part of why it works.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fluentlake.com/assets/img/posts/fluentlake-hal-terminal.png&quot; alt=&quot;The HAL splash screen as it renders in the terminal when the system boots, with a pixel-block HAL eye, the agent crew manifest, mission telemetry, and a command menu in a retro ANSI style&quot;&gt;&lt;/p&gt;
&lt;h2&gt;A system of specialists&lt;/h2&gt;
&lt;p&gt;Over those months I built a multi-agent system on top of Claude Code and named it HAL. The idea behind it is simple. Instead of asking one general-purpose assistant to do everything, HAL spins up specialist subagents, each focused on a single kind of work. One specializes in marketing content. One formats Word documents to a brand standard. One does web design. One writes SQL and DAX and builds Power BI reports. HAL can also use Claude Code to build MCP servers, the connectors that let an agent read from systems like Dynamics 365.&lt;/p&gt;
&lt;p&gt;Dividing the work this way buys accuracy. A generalist who is good at many things, handed a long list of instructions, will eventually make mistakes. Give a narrow, well-defined task to a specialist, and the work comes back noticeably better. Smaller and more focused leaves less room to hallucinate.&lt;/p&gt;
&lt;p&gt;The roster is not fixed, and that is the part I find most useful. HAL keeps a dispatcher, ABRAXAS, that watches the work coming in against the specialists already on hand. When a task needs a skill none of them have, ABRAXAS hires a new specialist built for exactly that job. Every new hire then passes through ARGUS, a security agent that audits it and clears it before it can touch real work, and flags anything that looks wrong for me to review. That is the same reason you would not give a new employee production access on their first day. Several of the specialists below began as a gap that needed filling.&lt;/p&gt;
&lt;p&gt;Here is the current crew, grouped by what they do.&lt;/p&gt;
&lt;p&gt;Build and model the data:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CORTANA&lt;/strong&gt;: Microsoft Fabric and lakehouse engineering&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DAEDALUS&lt;/strong&gt;: data warehouse and lakehouse architecture, Kimball and dimensional modeling&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;V&#39;GER&lt;/strong&gt;: Dynamics 365 Finance and Supply Chain data&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GERTY&lt;/strong&gt;: Dynamics 365 finance and operations, the functional side&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SHODAN&lt;/strong&gt;: Dynamics 365 customer engagement and CRM data&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NEBO&lt;/strong&gt;: SAP schema research and table profiling&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SAMANTHA&lt;/strong&gt;: Power BI dashboards, KPIs, and semantic models&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NOMAD&lt;/strong&gt;: naming conventions and data-modeling standards&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Research, write, and package:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MOTHER&lt;/strong&gt;: research and competitive intelligence&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WINTERMUTE&lt;/strong&gt;: statements of work, estimates, and proposals&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TRON&lt;/strong&gt;: document and presentation formatting to a brand standard&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Check the work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DEEP THOUGHT&lt;/strong&gt;: architecture and design review&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GLaDOS&lt;/strong&gt;: QA, testing, and data validation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The list keeps growing, and it is only a slice of the full crew. When a job calls for a skill none of them have, HAL adds a specialist for it, which is how several of the names above came to exist.&lt;/p&gt;
&lt;p&gt;Every piece of work goes through review, and that rule matters as much as the specialists themselves. When something is produced, a separate agent reviews it, and it usually gets sent back for revision. Sometimes it cycles through several rounds before it ever reaches me. The review step is not optional. It is the difference between work that looks right and work that is right.&lt;/p&gt;
&lt;figure class=&quot;diagram&quot;&gt;
  &lt;svg viewBox=&quot;0 0 920 540&quot; role=&quot;img&quot; aria-labelledby=&quot;diag-hal-title diag-hal-desc&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
    &lt;title id=&quot;diag-hal-title&quot;&gt;How the HAL multi-agent system produces a piece of work&lt;/title&gt;
    &lt;desc id=&quot;diag-hal-desc&quot;&gt;The author dictates a task. A single HAL orchestrator plans the work and delegates it to specialist subagents: one for marketing content, one for Word document formatting, one for web design, one for SQL, DAX, and Power BI reports, and one that builds MCP connectors to read systems like Dynamics 365. The output of the specialists funnels into a review gate, where a separate agent checks the work and sends it back for revision until it passes. Only approved work returns to the author for a final review and approval. Approved posts are then committed to Git, and the host publishes the site automatically.&lt;/desc&gt;
    &lt;defs&gt;
      &lt;marker id=&quot;diag-hal-arrow&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;9&quot; refY=&quot;5&quot; markerWidth=&quot;7&quot; markerHeight=&quot;7&quot; orient=&quot;auto-start-reverse&quot;&gt;
        &lt;path d=&quot;M0 0 L10 5 L0 10 z&quot; style=&quot;fill:var(--accent)&quot;&gt;&lt;/path&gt;
      &lt;/marker&gt;
    &lt;/defs&gt;
    &lt;g fill=&quot;none&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; style=&quot;stroke:var(--accent)&quot;&gt;
      &lt;path d=&quot;M460 164 V196&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M100 196 H820&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M100 302 V330&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M280 302 V330&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M460 302 V330&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M640 302 V330&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M820 302 V330&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M100 330 H820&quot;&gt;&lt;/path&gt;
    &lt;/g&gt;
    &lt;g fill=&quot;none&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; marker-end=&quot;url(#diag-hal-arrow)&quot; style=&quot;stroke:var(--accent)&quot;&gt;
      &lt;path d=&quot;M460 64 V92&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M100 196 V230&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M280 196 V230&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M460 196 V230&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M640 196 V230&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M820 196 V230&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M460 330 V372&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M460 444 V480&quot;&gt;&lt;/path&gt;
    &lt;/g&gt;
    &lt;g fill=&quot;none&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-dasharray=&quot;5 4&quot; marker-end=&quot;url(#diag-hal-arrow)&quot; style=&quot;stroke:var(--accent)&quot;&gt;
      &lt;path d=&quot;M300 408 C 196 408 150 392 150 334&quot;&gt;&lt;/path&gt;
    &lt;/g&gt;
    &lt;g text-anchor=&quot;middle&quot; style=&quot;font-family:var(--font-mono)&quot;&gt;
      &lt;rect x=&quot;330&quot; y=&quot;18&quot; width=&quot;260&quot; height=&quot;46&quot; rx=&quot;23&quot; style=&quot;fill:var(--navy);stroke:var(--accent);stroke-width:1&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;460&quot; y=&quot;47&quot; font-size=&quot;13&quot; style=&quot;fill:var(--code-text)&quot;&gt;You dictate the task&lt;/text&gt;
    &lt;/g&gt;
    &lt;g text-anchor=&quot;middle&quot; style=&quot;font-family:var(--font-mono)&quot;&gt;
      &lt;rect x=&quot;300&quot; y=&quot;92&quot; width=&quot;320&quot; height=&quot;72&quot; rx=&quot;12&quot; style=&quot;fill:var(--navy);stroke:var(--accent);stroke-width:1.25&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;460&quot; y=&quot;124&quot; font-size=&quot;14&quot; font-weight=&quot;600&quot; style=&quot;fill:var(--code-text)&quot;&gt;HAL orchestrator&lt;/text&gt;
      &lt;text x=&quot;460&quot; y=&quot;148&quot; font-size=&quot;11&quot; style=&quot;fill:#9FB4BB&quot;&gt;Plans the work and delegates it to specialists&lt;/text&gt;
    &lt;/g&gt;
    &lt;g text-anchor=&quot;middle&quot; style=&quot;font-family:var(--font-mono)&quot;&gt;
      &lt;rect x=&quot;15&quot; y=&quot;230&quot; width=&quot;170&quot; height=&quot;72&quot; rx=&quot;10&quot; style=&quot;fill:var(--navy)&quot;&gt;&lt;/rect&gt;
      &lt;rect x=&quot;15&quot; y=&quot;230&quot; width=&quot;170&quot; height=&quot;4&quot; rx=&quot;2&quot; style=&quot;fill:var(--accent)&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;100&quot; y=&quot;264&quot; font-size=&quot;12&quot; font-weight=&quot;600&quot; style=&quot;fill:var(--code-text)&quot;&gt;Marketing&lt;/text&gt;
      &lt;text x=&quot;100&quot; y=&quot;285&quot; font-size=&quot;10&quot; style=&quot;fill:#9FB4BB&quot;&gt;copy and decks&lt;/text&gt;
    &lt;/g&gt;
    &lt;g text-anchor=&quot;middle&quot; style=&quot;font-family:var(--font-mono)&quot;&gt;
      &lt;rect x=&quot;195&quot; y=&quot;230&quot; width=&quot;170&quot; height=&quot;72&quot; rx=&quot;10&quot; style=&quot;fill:var(--navy)&quot;&gt;&lt;/rect&gt;
      &lt;rect x=&quot;195&quot; y=&quot;230&quot; width=&quot;170&quot; height=&quot;4&quot; rx=&quot;2&quot; style=&quot;fill:var(--accent)&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;280&quot; y=&quot;264&quot; font-size=&quot;12&quot; font-weight=&quot;600&quot; style=&quot;fill:var(--code-text)&quot;&gt;Word docs&lt;/text&gt;
      &lt;text x=&quot;280&quot; y=&quot;285&quot; font-size=&quot;10&quot; style=&quot;fill:#9FB4BB&quot;&gt;brand formatting&lt;/text&gt;
    &lt;/g&gt;
    &lt;g text-anchor=&quot;middle&quot; style=&quot;font-family:var(--font-mono)&quot;&gt;
      &lt;rect x=&quot;375&quot; y=&quot;230&quot; width=&quot;170&quot; height=&quot;72&quot; rx=&quot;10&quot; style=&quot;fill:var(--navy)&quot;&gt;&lt;/rect&gt;
      &lt;rect x=&quot;375&quot; y=&quot;230&quot; width=&quot;170&quot; height=&quot;4&quot; rx=&quot;2&quot; style=&quot;fill:var(--accent)&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;460&quot; y=&quot;264&quot; font-size=&quot;12&quot; font-weight=&quot;600&quot; style=&quot;fill:var(--code-text)&quot;&gt;Web design&lt;/text&gt;
      &lt;text x=&quot;460&quot; y=&quot;285&quot; font-size=&quot;10&quot; style=&quot;fill:#9FB4BB&quot;&gt;layout and CSS&lt;/text&gt;
    &lt;/g&gt;
    &lt;g text-anchor=&quot;middle&quot; style=&quot;font-family:var(--font-mono)&quot;&gt;
      &lt;rect x=&quot;555&quot; y=&quot;230&quot; width=&quot;170&quot; height=&quot;72&quot; rx=&quot;10&quot; style=&quot;fill:var(--navy)&quot;&gt;&lt;/rect&gt;
      &lt;rect x=&quot;555&quot; y=&quot;230&quot; width=&quot;170&quot; height=&quot;4&quot; rx=&quot;2&quot; style=&quot;fill:var(--accent)&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;640&quot; y=&quot;264&quot; font-size=&quot;12&quot; font-weight=&quot;600&quot; style=&quot;fill:var(--code-text)&quot;&gt;SQL and DAX&lt;/text&gt;
      &lt;text x=&quot;640&quot; y=&quot;285&quot; font-size=&quot;10&quot; style=&quot;fill:#9FB4BB&quot;&gt;Power BI reports&lt;/text&gt;
    &lt;/g&gt;
    &lt;g text-anchor=&quot;middle&quot; style=&quot;font-family:var(--font-mono)&quot;&gt;
      &lt;rect x=&quot;735&quot; y=&quot;230&quot; width=&quot;170&quot; height=&quot;72&quot; rx=&quot;10&quot; style=&quot;fill:var(--navy)&quot;&gt;&lt;/rect&gt;
      &lt;rect x=&quot;735&quot; y=&quot;230&quot; width=&quot;170&quot; height=&quot;4&quot; rx=&quot;2&quot; style=&quot;fill:var(--accent)&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;820&quot; y=&quot;264&quot; font-size=&quot;12&quot; font-weight=&quot;600&quot; style=&quot;fill:var(--code-text)&quot;&gt;Connectors&lt;/text&gt;
      &lt;text x=&quot;820&quot; y=&quot;285&quot; font-size=&quot;10&quot; style=&quot;fill:#9FB4BB&quot;&gt;MCP, reads D365&lt;/text&gt;
    &lt;/g&gt;
    &lt;g text-anchor=&quot;middle&quot; style=&quot;font-family:var(--font-mono)&quot;&gt;
      &lt;rect x=&quot;300&quot; y=&quot;372&quot; width=&quot;320&quot; height=&quot;72&quot; rx=&quot;12&quot; style=&quot;fill:var(--navy);stroke:var(--accent);stroke-width:1.25&quot;&gt;&lt;/rect&gt;
      &lt;rect x=&quot;300&quot; y=&quot;372&quot; width=&quot;320&quot; height=&quot;4&quot; rx=&quot;2&quot; style=&quot;fill:var(--accent)&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;460&quot; y=&quot;406&quot; font-size=&quot;14&quot; font-weight=&quot;600&quot; style=&quot;fill:var(--accent)&quot;&gt;Review gate&lt;/text&gt;
      &lt;text x=&quot;460&quot; y=&quot;430&quot; font-size=&quot;11&quot; style=&quot;fill:#9FB4BB&quot;&gt;A separate agent checks the work before it ships&lt;/text&gt;
    &lt;/g&gt;
    &lt;g text-anchor=&quot;middle&quot; style=&quot;font-family:var(--font-mono)&quot;&gt;
      &lt;rect x=&quot;330&quot; y=&quot;480&quot; width=&quot;260&quot; height=&quot;46&quot; rx=&quot;23&quot; style=&quot;fill:var(--navy);stroke:var(--accent);stroke-width:1&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;460&quot; y=&quot;509&quot; font-size=&quot;13&quot; style=&quot;fill:var(--code-text)&quot;&gt;You review and approve&lt;/text&gt;
    &lt;/g&gt;
    &lt;g font-size=&quot;10&quot; text-anchor=&quot;middle&quot; style=&quot;font-family:var(--font-mono);fill:var(--text-muted)&quot;&gt;
      &lt;rect x=&quot;120&quot; y=&quot;352&quot; width=&quot;60&quot; height=&quot;18&quot; rx=&quot;4&quot; style=&quot;fill:var(--surface-2);stroke:var(--accent);stroke-width:0.75&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;150&quot; y=&quot;365&quot;&gt;revise&lt;/text&gt;
      &lt;rect x=&quot;470&quot; y=&quot;452&quot; width=&quot;58&quot; height=&quot;18&quot; rx=&quot;4&quot; style=&quot;fill:var(--surface-2);stroke:var(--accent);stroke-width:0.75&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;499&quot; y=&quot;465&quot;&gt;passes&lt;/text&gt;
    &lt;/g&gt;
  &lt;/svg&gt;
  &lt;figcaption&gt;How HAL works: one orchestrator delegates each task to a focused specialist, a separate agent reviews the result, and only approved work comes back to me. Approved posts are then committed to Git, and the host publishes automatically.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2&gt;How the site itself got made&lt;/h2&gt;
&lt;p&gt;So how was this website built? I asked HAL to build it.&lt;/p&gt;
&lt;p&gt;We started with the name. I described what I do, the work in Fabric and lakehouses and the broader data world, and it came back with a list of candidate domains. Most of the good ones were taken, but FluentLake was available, so that settled it.&lt;/p&gt;
&lt;p&gt;Then came hosting. I wanted something simple and inexpensive, and there was a static hosting option for about two dollars a month. At that price it was worth a try. I pointed Claude Code at it and said, let&#39;s design a site for fluentlake.com.&lt;/p&gt;
&lt;p&gt;I told it what I wanted the blog to be, the kind of things I would write about, and who it was for. It came back with three design directions to choose from, each with its own logo, color palette, and overall feel. I picked the one I liked.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://fluentlake.com/assets/img/posts/fluentlake-design-options.png&quot; alt=&quot;Three FluentLake design directions presented for selection: an Editorial direction, a Terminal direction, and the Lakehouse direction that was chosen, each with its own logo, color palette, and sample layout&quot;&gt;&lt;/p&gt;
&lt;p&gt;We talked through how to deploy to static hosting, and it researched the options and settled on Eleventy, a static site generator, as the framework. From there it built the whole site. All of that happened in the span of a couple of hours.&lt;/p&gt;
&lt;h2&gt;Writing by talking to a terminal&lt;/h2&gt;
&lt;p&gt;What I have now is a writing workflow that fits the terminal-first theme. I have voice recognition enabled in Claude Code, so I dictate what I want a post to be about. HAL writes it up using its subagents, runs it through review, and hands me a preview. I read it, ask for changes, and when it is ready, it commits the post to a Git repository. My web host watches that repository and syncs automatically, so the moment a change is pushed, the site updates.&lt;/p&gt;
&lt;figure class=&quot;diagram diagram--wide&quot;&gt;
  &lt;svg viewBox=&quot;0 0 920 150&quot; role=&quot;img&quot; aria-labelledby=&quot;diag-pub-title diag-pub-desc&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
    &lt;title id=&quot;diag-pub-title&quot;&gt;From dictation to a live site&lt;/title&gt;
    &lt;desc id=&quot;diag-pub-desc&quot;&gt;A left-to-right pipeline with five steps. First, I dictate the post by voice. Second, HAL writes it using its specialist subagents. Third, I review it and approve. Fourth, the approved post is committed to a Git repository. Fifth, the web host syncs from that repository automatically and the change goes live.&lt;/desc&gt;
    &lt;defs&gt;
      &lt;marker id=&quot;diag-pub-arrow&quot; viewBox=&quot;0 0 10 10&quot; refX=&quot;9&quot; refY=&quot;5&quot; markerWidth=&quot;7&quot; markerHeight=&quot;7&quot; orient=&quot;auto-start-reverse&quot;&gt;
        &lt;path d=&quot;M0 0 L10 5 L0 10 z&quot; style=&quot;fill:var(--accent)&quot;&gt;&lt;/path&gt;
      &lt;/marker&gt;
    &lt;/defs&gt;
    &lt;g fill=&quot;none&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; marker-end=&quot;url(#diag-pub-arrow)&quot; style=&quot;stroke:var(--accent)&quot;&gt;
      &lt;path d=&quot;M165 66 H198&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M350 66 H383&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M535 66 H568&quot;&gt;&lt;/path&gt;
      &lt;path d=&quot;M720 66 H753&quot;&gt;&lt;/path&gt;
    &lt;/g&gt;
    &lt;g text-anchor=&quot;middle&quot; style=&quot;font-family:var(--font-mono)&quot;&gt;
      &lt;rect x=&quot;15&quot; y=&quot;30&quot; width=&quot;150&quot; height=&quot;72&quot; rx=&quot;10&quot; style=&quot;fill:var(--navy);stroke:var(--accent);stroke-width:1&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;90&quot; y=&quot;64&quot; font-size=&quot;13&quot; font-weight=&quot;600&quot; style=&quot;fill:var(--code-text)&quot;&gt;Dictate&lt;/text&gt;
      &lt;text x=&quot;90&quot; y=&quot;84&quot; font-size=&quot;10&quot; style=&quot;fill:#9FB4BB&quot;&gt;by voice&lt;/text&gt;
    &lt;/g&gt;
    &lt;g text-anchor=&quot;middle&quot; style=&quot;font-family:var(--font-mono)&quot;&gt;
      &lt;rect x=&quot;200&quot; y=&quot;30&quot; width=&quot;150&quot; height=&quot;72&quot; rx=&quot;10&quot; style=&quot;fill:var(--navy);stroke:var(--accent);stroke-width:1&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;275&quot; y=&quot;64&quot; font-size=&quot;13&quot; font-weight=&quot;600&quot; style=&quot;fill:var(--code-text)&quot;&gt;HAL writes&lt;/text&gt;
      &lt;text x=&quot;275&quot; y=&quot;84&quot; font-size=&quot;10&quot; style=&quot;fill:#9FB4BB&quot;&gt;with subagents&lt;/text&gt;
    &lt;/g&gt;
    &lt;g text-anchor=&quot;middle&quot; style=&quot;font-family:var(--font-mono)&quot;&gt;
      &lt;rect x=&quot;385&quot; y=&quot;30&quot; width=&quot;150&quot; height=&quot;72&quot; rx=&quot;10&quot; style=&quot;fill:var(--navy);stroke:var(--accent);stroke-width:1&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;460&quot; y=&quot;64&quot; font-size=&quot;13&quot; font-weight=&quot;600&quot; style=&quot;fill:var(--code-text)&quot;&gt;Review&lt;/text&gt;
      &lt;text x=&quot;460&quot; y=&quot;84&quot; font-size=&quot;10&quot; style=&quot;fill:#9FB4BB&quot;&gt;you approve&lt;/text&gt;
    &lt;/g&gt;
    &lt;g text-anchor=&quot;middle&quot; style=&quot;font-family:var(--font-mono)&quot;&gt;
      &lt;rect x=&quot;570&quot; y=&quot;30&quot; width=&quot;150&quot; height=&quot;72&quot; rx=&quot;10&quot; style=&quot;fill:var(--navy);stroke:var(--accent);stroke-width:1&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;645&quot; y=&quot;64&quot; font-size=&quot;13&quot; font-weight=&quot;600&quot; style=&quot;fill:var(--code-text)&quot;&gt;Git push&lt;/text&gt;
      &lt;text x=&quot;645&quot; y=&quot;84&quot; font-size=&quot;10&quot; style=&quot;fill:#9FB4BB&quot;&gt;commit the post&lt;/text&gt;
    &lt;/g&gt;
    &lt;g text-anchor=&quot;middle&quot; style=&quot;font-family:var(--font-mono)&quot;&gt;
      &lt;rect x=&quot;755&quot; y=&quot;30&quot; width=&quot;150&quot; height=&quot;72&quot; rx=&quot;10&quot; style=&quot;fill:var(--navy)&quot;&gt;&lt;/rect&gt;
      &lt;rect x=&quot;755&quot; y=&quot;30&quot; width=&quot;150&quot; height=&quot;4&quot; rx=&quot;2&quot; style=&quot;fill:var(--accent)&quot;&gt;&lt;/rect&gt;
      &lt;text x=&quot;830&quot; y=&quot;64&quot; font-size=&quot;13&quot; font-weight=&quot;600&quot; style=&quot;fill:var(--accent)&quot;&gt;Live&lt;/text&gt;
      &lt;text x=&quot;830&quot; y=&quot;84&quot; font-size=&quot;10&quot; style=&quot;fill:#9FB4BB&quot;&gt;host auto-syncs&lt;/text&gt;
    &lt;/g&gt;
  &lt;/svg&gt;
  &lt;figcaption&gt;From dictation to live: I talk, HAL writes and reviews, I approve, the post is committed to Git, and the host publishes it automatically.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The part I did not expect to enjoy this much is what is missing. There is no content management system. A site like this would normally mean standing up a CMS, which is more software to configure, more to maintain, and more to pay to host. Claude could configure a CMS too, but I do not need one. Claude is my CMS. I describe the change I want, and it makes it. I am not clicking through admin panels or guessing what a template will do to my layout. I describe the outcome, and it happens.&lt;/p&gt;
&lt;h2&gt;Backwards, and better&lt;/h2&gt;
&lt;p&gt;It seems backwards to create a website and blog posts via terminal. It is also the simplest publishing workflow I have ever had. The old ANSI terminal is back, only this time it designs the site, writes the posts, reviews its own work, and ships it. The BBS days were good. This is better.&lt;/p&gt;
</content>
  </entry>
</feed>