<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Eddie Beloiu</title><description>Freelance data platform engineer in Munich. Lakehouse, ETL/ELT, migration. Built with AI, handed over to your team.</description><link>https://www.mbitai.com/</link><item><title>Persistent Claude assistants with NanoClaw: how I set them up for individuals and small teams</title><link>https://www.mbitai.com/notes/persistent-claude-assistants-with-nanoclaw/</link><guid isPermaLink="true">https://www.mbitai.com/notes/persistent-claude-assistants-with-nanoclaw/</guid><description>A self-hosted Claude assistant on a Raspberry Pi, with compounding memory via a knowledge graph and synthesised wiki pages.</description><pubDate>Tue, 28 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I keep running into the same problem when clients try to roll out a Claude-based assistant for their team: it forgets everything between sessions. Chat history is not memory. The default solution is RAG over a document store, but that retrieves text chunks, not facts. After the third client conversation that ended with &quot;okay, but how do we make it actually remember things,&quot; I started running a different setup on my own infrastructure.&lt;/p&gt;
&lt;p&gt;This is what I run, why I think it&apos;s the right shape for individuals and small teams, and how I&apos;m now setting it up for clients.&lt;/p&gt;
&lt;p&gt;The system is called NanoClaw. It&apos;s open source, written by &lt;a href=&quot;https://github.com/qwibitai/nanoclaw&quot;&gt;qwibitai&lt;/a&gt;, and the public design notes are in &lt;a href=&quot;https://gist.github.com/VivianBalakrishnan/a7d4eec3833baee4971a0ee54b08f322&quot;&gt;this gist&lt;/a&gt;. I am not the author. I run it, extend it for client setups, and write about it because almost nothing else in the self-hosted assistant space gets the memory model right.&lt;/p&gt;
&lt;h2&gt;The problem with stateless assistants&lt;/h2&gt;
&lt;p&gt;Three things are true about a default LLM assistant:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It does not remember the last conversation unless you paste the transcript back in.&lt;/li&gt;
&lt;li&gt;Long context windows do not solve this. They then to paper over it for a while, but the agent forgets the middle of the document anyway.&lt;/li&gt;
&lt;li&gt;Standard RAG retrieves chunks of raw text. If your document says &quot;I prefer dbt over Airflow for transformations,&quot; the retrieved chunk is the surrounding sentence, not the discrete fact.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For a personal assistant or a team workspace, that pattern fails the same way every time. The agent answers well in a single session, then a week later forgets that you decided not to use Snowflake stages. You burn the same conversation twice. After a month, you are not getting compounding value out of the assistant. Instead, you&apos;re paying for a stateless chatbot with extra steps.&lt;/p&gt;
&lt;p&gt;The fix is not &quot;more context window.&quot; The fix is structured memory.&lt;/p&gt;
&lt;h2&gt;The architecture&lt;/h2&gt;
&lt;p&gt;NanoClaw separates raw input, structured facts, and human-readable summaries into three layers:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Raw sources           →    mnemon graph          →    wiki pages
(transcripts,              (structured facts,         (narrative syntheses,
 articles,                  graph nodes,               human-readable,
 web clips)                 semantic retrieval)        cross-referenced)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Layer 1 Raw sources.&lt;/strong&gt; Speech transcripts in markdown, articles saved from URL ingest, mobile web clips through &lt;a href=&quot;https://obsidian.md/clipper&quot;&gt;Obsidian Web Clipper&lt;/a&gt;. Append-only, never modified after storage.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Layer 2 &lt;a href=&quot;https://github.com/mnemon-dev/mnemon&quot;&gt;mnemon&lt;/a&gt;, the knowledge graph.&lt;/strong&gt; A SQLite-backed graph database where each entry is a self-contained fact: content, category, importance score, tags, timestamp, and graph edges to related entries. Queried semantically using local vector embeddings (Ollama with &lt;code&gt;nomic-embed-text&lt;/code&gt; running on the Pi itself). Two stores: a &lt;em&gt;global&lt;/em&gt; one shared across all groups, and a &lt;em&gt;local&lt;/em&gt; one per group that only that group&apos;s agent can write to.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Layer 3 Wiki pages.&lt;/strong&gt; Synthesised markdown files compiled from mnemon facts. Not raw extracts, but full narrative pages organised into &lt;code&gt;entities/&lt;/code&gt;, &lt;code&gt;concepts/&lt;/code&gt;, and &lt;code&gt;timelines/&lt;/code&gt; subdirectories, with cross-references. The pattern follows &lt;a href=&quot;https://x.com/karpathy/status/2040470801506541998?s=20&quot;&gt;Andrej Karpathy&apos;s LLM Wiki idea&lt;/a&gt;, to extract structured knowledge from raw sources rather than indexing them whole.&lt;/p&gt;
&lt;p&gt;Every agent invocation triggers a semantic recall against the graph using the user&apos;s message as the query. Relevant facts surface automatically as a system reminder. The agent never has to decide to look something up, as the recall is free and reliable.&lt;/p&gt;
&lt;h2&gt;What&apos;s running&lt;/h2&gt;
&lt;p&gt;The full stack on a Raspberry Pi 5 (aarch64):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;NanoClaw orchestrator (Node.js + TypeScript)&lt;/td&gt;
&lt;td&gt;Message loop, container management, channel routing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Agent SDK&lt;/td&gt;
&lt;td&gt;Agent logic inside isolated Docker containers per group&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Baileys&lt;/td&gt;
&lt;td&gt;WhatsApp Web protocol, no business API needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/mnemon-dev/mnemon&quot;&gt;mnemon&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Custom CLI knowledge graph tool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ollama + &lt;code&gt;nomic-embed-text&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Local vector embeddings for semantic recall&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;whisper.cpp&lt;/td&gt;
&lt;td&gt;Local voice transcription for voice notes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/onecli/onecli&quot;&gt;OneCLI&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Credential proxy, containers never see raw API keys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQLite&lt;/td&gt;
&lt;td&gt;Message store, group registry, task scheduler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;systemd&lt;/td&gt;
&lt;td&gt;Process management&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The whole thing fits on a Raspberry Pi 5 or any similar device with 8GB RAM. No cloud-hosted services required beyond the Anthropic API itself. Voice notes never leave the device. Document content never leaves the device. The graph and the wiki are on local disk.&lt;/p&gt;
&lt;h2&gt;Why this works for you&lt;/h2&gt;
&lt;p&gt;The immediate value is the loop closing on inputs that already exist:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A WhatsApp voice note becomes a transcript, becomes a set of mnemon entries, becomes part of the wiki.&lt;/li&gt;
&lt;li&gt;An article clipped from a phone via &lt;a href=&quot;https://obsidian.md/clipper&quot;&gt;Obsidian Web Clipper&lt;/a&gt; triggers an &lt;code&gt;inotifywait&lt;/code&gt; watcher, which kicks off the ingest pipeline, which extracts facts and updates the relevant wiki pages.&lt;/li&gt;
&lt;li&gt;A scheduled task (morning briefing) runs through a bash pre-check first, it only wakes the agent if there is actually something to brief on. This keeps API costs low.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The result over time is an assistant that knows what you have read, what you have said in voice notes, and what you have decided. You can ask it &quot;what did I say about the Snowflake migration last week?&quot; and it answers from the graph, not from chat history.&lt;/p&gt;
&lt;p&gt;The wiki layer is human-editable on purpose, exactly because LLM-extracted facts need supervision.&lt;/p&gt;
&lt;h2&gt;Why this works&lt;/h2&gt;
&lt;p&gt;The multi-group isolation is the thing that makes this team-ready:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Each registered group (a WhatsApp chat, a Slack channel, a department) gets its own Docker container, filesystem, local mnemon store, and Claude session.&lt;/li&gt;
&lt;li&gt;Containers cannot read each other&apos;s memory or messages. Source identity is verified by directory path, not by message content.&lt;/li&gt;
&lt;li&gt;A runaway agent in one group cannot affect others. Container lifetime is tied to conversation activity; they shut down after idle timeout.&lt;/li&gt;
&lt;li&gt;The credential proxy means the API key never sits inside a container, so a compromised group cannot exfiltrate it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a team of five to twenty people, this is enough to set up:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A daily team briefing channel that summarises what was decided yesterday.&lt;/li&gt;
&lt;li&gt;A research channel where articles get clipped and turned into a shared wiki of &quot;what we read this quarter.&quot;&lt;/li&gt;
&lt;li&gt;A specific project channel with its own memory of decisions, blockers, and open questions, isolated from everything else.&lt;/li&gt;
&lt;li&gt;A leadership channel with its own private memory that the rest of the team&apos;s agent cannot read.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I have set this up for a small team in Munich and the most useful side effect was unexpected: the wiki became a canonical source for &quot;what did we actually decide&quot; that survived someone going on holiday. The agent stopped being the interesting part. The wiki became the interesting part.&lt;/p&gt;
&lt;h2&gt;How I set this up for clients&lt;/h2&gt;
&lt;p&gt;When I set NanoClaw up for a client, the work is roughly two phases:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 1 (week 1) install and tune.&lt;/strong&gt; Provision the Pi or a small VPS, install NanoClaw, configure channels, set up the credential proxy, integrate with the team&apos;s existing messaging. Start with the global mnemon empty and the wiki empty. Add one or two seed groups. Tune the bash pre-check scripts to keep API costs under control.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 2 (week 2) fit and handover.&lt;/strong&gt; Configure scheduled tasks (briefings, ingestion watchers). Run the team through how to use it: what the wiki is for, how to correct facts, how to add new groups. Document the maintenance ritual of about an hour a week of wiki review by someone trusted. Hand it over.&lt;/p&gt;
&lt;p&gt;This fits inside the production-readiness audit format I already offer (€4,500 fixed for one week), or as a co-build engagement at €900/day if the client wants me to run it longer and customise channels and tasks for their workflow. I am the integrator, not the author of NanoClaw. That credit goes to qwibitai. What I sell is the setup, the tuning, the team training, and the knowledge of how to keep it running cheaply.&lt;/p&gt;
&lt;h2&gt;A few opinionated bits&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Why a Raspberry Pi.&lt;/strong&gt; Because privacy-sensitive data should not leave the network. The whole point of running embeddings and transcription locally is that the inputs stay on the device. The Pi 5 has enough headroom to run &lt;code&gt;nomic-embed-text&lt;/code&gt; and &lt;code&gt;whisper.cpp base&lt;/code&gt; in the same process budget as the orchestrator. For a small team, this is plenty. For a larger team, the same code runs on a small VPS or a Mac Mini.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why Docker per group, not one process.&lt;/strong&gt; Isolation. Five years of teaching across cultures will eventually convince you that one badly-behaved actor is normal, not an edge case. Containers per group means a runaway agent in one chat cannot reach another chat&apos;s data. The cost is some overhead per active group; the benefit is a security model that does not depend on the agent being well-behaved.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why iCloud + rsync to sync wiki pages to Obsidian.&lt;/strong&gt; Because iOS git clients have been unreliable for as long as I have used them. iCloud is native to iOS, zero-config, and free. rsync from a Mac Mini bridge to the Pi is directional and battle-tested. This is a boring tools that work choice.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why the wiki layer at all.&lt;/strong&gt; Because LLM-extracted facts need supervision. The wiki gives a human a place to read, correct, and shape what the agent thinks it knows. A pure graph would be opaque; a pure wiki would not retrieve well. The split is doing real work.&lt;/p&gt;
&lt;h2&gt;What I&apos;d do differently&lt;/h2&gt;
&lt;p&gt;Three things I have learned.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One: start the bash pre-check scripts on day one.&lt;/strong&gt; It is tempting to defer cost optimisation, but a chatty agent with no pre-check will burn through API costs the first week. Spend an hour up front writing pre-checks for every scheduled task. The pre-check is allowed to be dumb (&lt;code&gt;grep&lt;/code&gt; the inbox, count the file system entries), it just needs to gate whether the agent runs at all.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Two: version the wiki from the start.&lt;/strong&gt; I did not put the wiki under git for the first two months. When I made a structural change to the synthesis prompt, I lost some context I could not easily reconstruct. The wiki is markdown files. Put it in git. Let the agent commit and review the diffs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Three: set the recall budget low and let it grow.&lt;/strong&gt; The first instinct is to inject a lot of context into every turn. The better default is to inject the top three to five facts, see if the agent&apos;s answers improve, and only widen the recall budget if you observe gaps. Wide recall is expensive and reduces the precision of what surfaces.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/VivianBalakrishnan&quot;&gt;Gists&lt;/a&gt; and the &lt;a href=&quot;https://github.com/qwibitai/nanoclaw&quot;&gt;repo at github.com/qwibitai/nanoclaw&lt;/a&gt; are the source code. If you want me to set this up for your team or your personal workflow, that is what the &lt;a href=&quot;/contact&quot;&gt;contact form&lt;/a&gt; is for.&lt;/p&gt;</content:encoded></item><item><title>3 things that break when you move Databricks notebooks to dbt</title><link>https://www.mbitai.com/notes/3-things-that-break-databricks-to-dbt/</link><guid isPermaLink="true">https://www.mbitai.com/notes/3-things-that-break-databricks-to-dbt/</guid><description>The three SQL syntax, schema reference, and state management problems I keep seeing when teams migrate from Databricks to dbt; and how to fix each one.</description><pubDate>Mon, 27 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve helped multiple teams move their data transformations from Databricks notebooks to dbt. The case for doing it is usually clear: version control, proper testing, reproducible builds. But the migration itself keeps hitting the same three snags.&lt;/p&gt;
&lt;h2&gt;1. Spark SQL syntax differences&lt;/h2&gt;
&lt;p&gt;Databricks notebooks accumulate Spark-specific SQL over time. Some of it looks like standard SQL and isn&apos;t. When you paste it into a dbt model, you get errors that aren&apos;t always obvious to diagnose.&lt;/p&gt;
&lt;p&gt;The most common examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;current_date()&lt;/code&gt; instead of &lt;code&gt;CURRENT_DATE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Date arithmetic that uses &lt;code&gt;INTERVAL 30 DAYS&lt;/code&gt; (Spark) vs &lt;code&gt;INTERVAL &apos;30&apos; DAY&lt;/code&gt; (ANSI SQL)&lt;/li&gt;
&lt;li&gt;Column names with special characters handled differently&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The fix is a translation pass before migration. Make a reference sheet of your Databricks functions and their standard SQL equivalents, and go through the notebooks systematically:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- Databricks
SELECT * FROM my_table WHERE date_col &gt; current_date() - INTERVAL 30 DAYS

-- Standard SQL (dbt)
SELECT * FROM my_table WHERE date_col &gt; CURRENT_DATE - INTERVAL &apos;30&apos; DAY
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. Missing schema references&lt;/h2&gt;
&lt;p&gt;Databricks resolves table references implicitly against the cluster&apos;s default database. dbt doesn&apos;t. When you move the same query, it fails because there&apos;s no schema prefix.&lt;/p&gt;
&lt;p&gt;Related problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hardcoded environment-specific database names&lt;/li&gt;
&lt;li&gt;Missing grants for the dbt service account&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Use dbt&apos;s &lt;code&gt;{{ ref() }}&lt;/code&gt; function and configure schema properly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- In a dbt model
SELECT *
FROM {{ ref(&apos;staging_orders&apos;) }}
WHERE order_date &gt;= &apos;{{ var(&quot;start_date&quot;) }}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# dbt_project.yml
models:
  your_project:
    staging:
      schema: staging
    marts:
      schema: analytics
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. State management and incremental logic&lt;/h2&gt;
&lt;p&gt;Databricks notebooks sometimes rely on Spark&apos;s caching between cells, or on checkpoint files on DBFS. Neither of these exists in dbt. Incremental patterns that look fine in a notebook need to be rethought.&lt;/p&gt;
&lt;p&gt;Specific failure modes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Assuming intermediate data is still cached when the next cell runs&lt;/li&gt;
&lt;li&gt;Complex incremental logic built around Spark DataFrames rather than SQL&lt;/li&gt;
&lt;li&gt;Late-arriving data that the original notebook handled manually&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;dbt&apos;s incremental materialization handles most of these cases cleanly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;{{ config(materialized=&apos;incremental&apos;) }}

SELECT
  order_id,
  customer_id,
  order_total,
  order_date
FROM {{ ref(&apos;raw_orders&apos;) }}

{% if is_incremental() %}
  WHERE order_date &gt; (SELECT MAX(order_date) FROM {{ this }})
{% endif %}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Migration approach&lt;/h2&gt;
&lt;p&gt;Don&apos;t try to convert everything at once. I usually start with the models that run most often; they have the most test coverage and the clearest business logic. Convert simple SELECT statements first, then work up to the incremental models once you have confidence in the simpler ones.&lt;/p&gt;
&lt;p&gt;Keep a migration log. Which notebooks map to which dbt models, what was changed during translation, what tests were added. That document pays for itself the first time someone asks why a model looks different from what&apos;s in the old notebook.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;What I&apos;d do differently next time: set up the dbt schema configuration before touching any SQL. Most of the schema-related failures I&apos;ve seen happened because people started converting models before the environments were properly configured.&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title>Data warehouse modernization: from SQL Server to Snowflake for a German company</title><link>https://www.mbitai.com/notes/data-warehouse-modernization-german-company/</link><guid isPermaLink="true">https://www.mbitai.com/notes/data-warehouse-modernization-german-company/</guid><description>How I migrated 250 BI users from SQL Server 2012 to Snowflake, cutting reporting latency from daily to hourly and bringing the platform into GDPR compliance.</description><pubDate>Mon, 27 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;The situation&lt;/h2&gt;
&lt;p&gt;A mid-sized German company had 250 business intelligence users across claims, underwriting, and actuarial teams, all running off a SQL Server 2012 data warehouse. The platform had been stretched past its design limits. Reports ran overnight. The concurrent user cap was 50. Above that, performance fell off. A 50TB dataset was growing 20% per year. And the system had no meaningful data lineage, which was becoming a GDPR problem the compliance team kept flagging.&lt;/p&gt;
&lt;p&gt;Specifically:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Underwriters were making decisions on yesterday&apos;s data because nothing ran in real time&lt;/li&gt;
&lt;li&gt;No audit trail or lineage for personal data&lt;/li&gt;
&lt;li&gt;Pipelines needed frequent manual intervention to stay running&lt;/li&gt;
&lt;li&gt;Hard cap of 50 concurrent users before queries started queuing&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What I did&lt;/h2&gt;
&lt;h3&gt;Assessment (weeks 1–2)&lt;/h3&gt;
&lt;p&gt;Started by cataloguing 200+ tables and mapping PII fields to understand the GDPR exposure. Mapped 50+ existing reports and dashboards before touching anything. The architecture decision to split the data into hot (Snowflake) and cold (object storage) data came out of this work.&lt;/p&gt;
&lt;h3&gt;Foundation (weeks 3–5)&lt;/h3&gt;
&lt;p&gt;Configured Snowflake with multi-cluster virtual warehouses and auto-scaling. Used Data Vault 2.0 to model the claims and policy data, which gave the audit-friendly structure GDPR requires. Row-level security and automated audit logging went in at this stage.&lt;/p&gt;
&lt;h3&gt;Migration (weeks 6–11)&lt;/h3&gt;
&lt;p&gt;Used Snowflake&apos;s native SQL Server connectors for incremental loading. Built the transformation layer in dbt. Added data validation checks at each pipeline stage, not just at the end.&lt;/p&gt;
&lt;h3&gt;Handover (weeks 12–13)&lt;/h3&gt;
&lt;p&gt;Migrated SQL Server Reporting Services dashboards to Tableau. Two workshop sessions for BI users on the self-service features. A support channel during the transition period.&lt;/p&gt;
&lt;h2&gt;Results&lt;/h2&gt;
&lt;p&gt;Reporting latency dropped from 24 hours to 2 hours. Complex actuarial queries that previously ran overnight now complete in minutes. The concurrent user limit went from 50 to 250. The queuing problem is gone.&lt;/p&gt;
&lt;p&gt;Infrastructure costs came down about 30% by moving from SQL Server&apos;s fixed licensing to Snowflake&apos;s usage-based pricing. Maintenance overhead dropped significantly once the manual data loading tasks were automated.&lt;/p&gt;
&lt;p&gt;Full GDPR compliance: data lineage, audit trails, role-based access with encryption at rest and in transit. A DPA audit that had been a recurring concern passed without issues.&lt;/p&gt;
&lt;h2&gt;What I&apos;d do differently&lt;/h2&gt;
&lt;p&gt;Data profiling at the start surfaced schema inconsistencies I underestimated. I&apos;d build more time into the assessment phase for that. I&apos;d also start user training earlier.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Client details anonymized. Metrics are from the actual project.&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title>Lakehouse migration: from Databricks to Snowflake for a European media company</title><link>https://www.mbitai.com/notes/lakehouse-migration-databricks-to-snowflake/</link><guid isPermaLink="true">https://www.mbitai.com/notes/lakehouse-migration-databricks-to-snowflake/</guid><description>How I migrated 20M+ records from Databricks to Snowflake, cutting monthly infrastructure costs by 40% and fixing years of accumulated governance debt.</description><pubDate>Mon, 27 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;The situation&lt;/h2&gt;
&lt;p&gt;A European media company had built their analytics platform on Databricks. It had worked during a period of rapid growth, but by the time I came in the monthly cost was €50K+ and the platform was ungoverned. The cost was high because clusters ran around the clock instead of spinning up on demand. There were 50+ data scientists working off 500+ tables spread across Delta Lake, Parquet, and JSON formats, with no central catalog and no clear lineage.&lt;/p&gt;
&lt;p&gt;The harder problem was trust. When quarterly reports took two hours and the numbers sometimes differed depending on who ran them, teams started routing around the platform instead of using it. That&apos;s usually where things stand when I get called in.&lt;/p&gt;
&lt;h2&gt;What I did&lt;/h2&gt;
&lt;h3&gt;Assessment (weeks 1–4)&lt;/h3&gt;
&lt;p&gt;Profiled all 200+ notebooks to understand compute vs. storage usage patterns. Built a cost model comparing the two platforms under realistic usage assumptions. Designed a hybrid architecture: Snowflake as the compute layer over the existing S3 storage, which avoided duplicating data and reduced migration risk.&lt;/p&gt;
&lt;h3&gt;Foundation and tooling (weeks 5–10)&lt;/h3&gt;
&lt;p&gt;Set up multi-cluster Snowflake warehouses with separate resource pools for ETL, analytics, and reporting workloads. Built migration pipelines with dbt and Airflow. Added data validation and reconciliation at each step. Most migrations fail here because teams assume data arrived correctly without checking.&lt;/p&gt;
&lt;h3&gt;Incremental migration (weeks 11–18)&lt;/h3&gt;
&lt;p&gt;Started with high-traffic datasets: user behavior and content metadata. Both platforms ran in parallel through the transition. I decommissioned Databricks notebooks gradually as Snowflake equivalents proved out, rather than doing a hard cutover.&lt;/p&gt;
&lt;h3&gt;Optimisation (weeks 19–22)&lt;/h3&gt;
&lt;p&gt;Right-sized compute based on actual usage patterns. Added materialized views for common reporting aggregations. Set up cost monitoring and alerts.&lt;/p&gt;
&lt;h2&gt;Results&lt;/h2&gt;
&lt;p&gt;Monthly infrastructure dropped from €50K to around €30K (a 40% reduction). Most of the savings came from moving away from always-on clusters to Snowflake&apos;s per-second billing.&lt;/p&gt;
&lt;p&gt;Query performance roughly tripled on typical analytical workloads. Quarterly reports went from two hours to fifteen minutes. Concurrent analyst capacity doubled.&lt;/p&gt;
&lt;p&gt;Data lineage is now fully tracked. There&apos;s a central catalog. The self-service analytics that had been the original promise of the Databricks setup started getting used once people could find data and trust it.&lt;/p&gt;
&lt;h2&gt;What I&apos;d do differently&lt;/h2&gt;
&lt;p&gt;I spent more time on the cost model at the start than I needed to. The architecture decision was right, but I could have reached it faster and spent that time on better tooling for schema evolution edge cases. There were Delta Lake schema changes mid-migration that required manual intervention I hadn&apos;t fully planned for.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Client details anonymized. Metrics are from the actual project.&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title>Three failure modes I&apos;ve seen in enterprise lakehouses (and the cheap fixes)</title><link>https://www.mbitai.com/notes/three-failure-modes-in-enterprise-lakehouses/</link><guid isPermaLink="true">https://www.mbitai.com/notes/three-failure-modes-in-enterprise-lakehouses/</guid><description>Three patterns that derail lakehouses in production: the swamp lakehouse, the performance mirage, and the metadata ghost town.</description><pubDate>Wed, 22 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve reviewed enough enterprise lakehouse implementations to have opinions about where they tend to go wrong. Three patterns come up more than anything else. Each is fixable if you catch it early.&lt;/p&gt;
&lt;h2&gt;Failure mode 1: the swamp lakehouse&lt;/h2&gt;
&lt;p&gt;Symptom: everything goes into the lakehouse with no distinction between raw, cleaned, and curated data. Users can&apos;t trust anything because they don&apos;t know what state it&apos;s in.&lt;/p&gt;
&lt;p&gt;Root cause: no zoning strategy. Teams treat the lakehouse as a dumping ground rather than implementing the medallion architecture properly.&lt;/p&gt;
&lt;p&gt;The fix is clear zone boundaries with automated validation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bronze: raw ingestion (immutable, exactly as received)&lt;/li&gt;
&lt;li&gt;Silver: cleaned and validated (business rules applied, basic quality checks)&lt;/li&gt;
&lt;li&gt;Gold: business-ready (dimensionally modeled, performance optimized)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Add zone enforcement at the pipeline level:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- Example: prevent silver-to-gold promotion without quality checks
CREATE OR REPLACE TRUSTED SILVER_TO_GOLD_CHECK AS
CASE
  WHEN (SELECT failed_checks FROM silver_quality_metrics WHERE table_name = CURRENT_TABLE()) = 0
  THEN &apos;ALLOW&apos;
  ELSE BLOCK
END;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Failure mode 2: the performance mirage&lt;/h2&gt;
&lt;p&gt;Symptom: works fine in development with 10GB datasets, crawls in production with 1TB. Costs explode because patterns that look reasonable at small scale don&apos;t hold up.&lt;/p&gt;
&lt;p&gt;Root cause: development uses unrealistic data volumes. Teams optimise for developer convenience rather than production economics.&lt;/p&gt;
&lt;p&gt;The fix: production-realistic testing from the start.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Clone the production schema with 1% of real data (statistically valid sample)&lt;/li&gt;
&lt;li&gt;Automate performance regression testing in CI/CD&lt;/li&gt;
&lt;li&gt;Set cost monitoring alerts at 80% of budget&lt;/li&gt;
&lt;li&gt;Require performance justification for any new pipeline&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;One thing worth remembering: a pipeline that&apos;s 2x slower but 10x cheaper to run is often the better business choice.&lt;/p&gt;
&lt;h2&gt;Failure mode 3: the metadata ghost town&lt;/h2&gt;
&lt;p&gt;Symptom: data exists but nobody knows what it means, where it came from, or how to use it correctly. This leads to misinterpretation and wrong business decisions.&lt;/p&gt;
&lt;p&gt;Root cause: metadata management treated as an afterthought. No investment in documentation, lineage, or a semantic layer.&lt;/p&gt;
&lt;p&gt;Lightweight but effective fixes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Automatically capture technical metadata (schema, size, update frequency)&lt;/li&gt;
&lt;li&gt;Require business owners to add semantic descriptions during data onboarding&lt;/li&gt;
&lt;li&gt;Use open-source tools like Amundsen or DataHub for discovery&lt;/li&gt;
&lt;li&gt;Implement simple data contract validation at pipeline boundaries&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The most effective technique I&apos;ve seen: a &quot;data passport&quot; that travels with each dataset, updated automatically by pipelines and manually enriched by data owners.&lt;/p&gt;
&lt;h2&gt;Spotting them early&lt;/h2&gt;
&lt;p&gt;The implementation work is mostly straightforward once you know what you&apos;re looking for. What takes time to develop is pattern recognition, catching these before they cause damage rather than remediating after the fact. When I start a new engagement, I can usually identify the problem within the first week from three tells:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Missing zone enforcement in pipeline orchestration&lt;/li&gt;
&lt;li&gt;Performance tests that only run on tiny datasets&lt;/li&gt;
&lt;li&gt;Zero business documentation on core datasets&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Catching these early typically saves three to six months of remediation time. The loss of executive trust that follows a failed data platform launch is much harder to recover from than fixing the architecture before anyone notices.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;What I&apos;d do differently next time: I would create a standardised lakehouse health check that runs in the first two weeks of any engagement, providing a clear, actionable scorecard before significant resources are committed.&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title>GDPR-aligned RAG: a checklist that survived three audits</title><link>https://www.mbitai.com/notes/gdpr-aligned-rag-checklist/</link><guid isPermaLink="true">https://www.mbitai.com/notes/gdpr-aligned-rag-checklist/</guid><description>GDPR checklist for RAG systems: data inventory, vector storage, deletion pipelines, and audit trails; tested across three DACH audits.</description><pubDate>Mon, 20 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Most RAG tutorials stop at &quot;make it work.&quot; Chunk some documents, embed them, store in a vector database, retrieve context for your LLM. That&apos;s fine for a prototype. In production, GDPR creates constraints that typical RAG architectures don&apos;t account for.&lt;/p&gt;
&lt;p&gt;After helping three different DACH enterprises get their RAG implementations through GDPR audits, I&apos;ve settled on a checklist that covers the gaps auditors actually flag.&lt;/p&gt;
&lt;h2&gt;Where GDPR and RAG conflict&lt;/h2&gt;
&lt;p&gt;The tension is specific. GDPR requires data minimization, purpose limitation, storage limitation, data subject rights (access, rectify, erase), and accountability. Standard RAG implementations violate at least three of these by default: the vector database usually contains more than you need, deletion is an afterthought, and there&apos;s no audit trail for what got retrieved.&lt;/p&gt;
&lt;h2&gt;My GDPR-aligned RAG checklist&lt;/h2&gt;
&lt;h3&gt;1. Data inventory and classification&lt;/h3&gt;
&lt;p&gt;Before building anything, know what you&apos;re working with:&lt;/p&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Document all data sources going into your RAG system&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Classify data by sensitivity (PII, special categories, business confidential)&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Identify which data requires GDPR protections&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Document legal basis for processing each data type&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. Purpose-limited system design&lt;/h3&gt;
&lt;p&gt;Your RAG system should have a clearly defined, documented purpose:&lt;/p&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Write a specific purpose statement for your RAG system&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Ensure all data processing aligns with this purpose&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Prevent function creep through technical controls&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Regularly review purpose alignment&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. Data minimization in chunking&lt;/h3&gt;
&lt;p&gt;How you prepare data for embedding matters:&lt;/p&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Chunk documents at semantic boundaries (not fixed sizes)&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Remove or pseudonymize unnecessary PII during preprocessing&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Consider summary-based approaches for sensitive documents&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Log what data was excluded and why&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. Secure embedding and storage&lt;/h3&gt;
&lt;p&gt;Where and how you store vectors has privacy implications:&lt;/p&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Use encrypted vector databases (at rest and in transit)&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Implement access controls and audit logging&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Consider on-prem or private cloud for highly sensitive data&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Regularly test encryption key rotation procedures&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5. Retrieval privacy controls&lt;/h3&gt;
&lt;p&gt;What gets retrieved affects what the LLM sees:&lt;/p&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Implement relevance thresholds to limit unnecessary data exposure&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Consider hybrid search (keyword + vector) for precision&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Log retrievals for audit purposes (anonymized)&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Implement retrieval rate limiting to prevent scraping&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;6. Generation guardrails&lt;/h3&gt;
&lt;p&gt;The LLM output needs protection too:&lt;/p&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Implement output filtering for accidental PII disclosure&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Use prompt engineering to discourage PII generation&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Consider confidence scoring for responses&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Provide clear attribution to source documents&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;7. Data subject rights implementation&lt;/h3&gt;
&lt;p&gt;Make it possible to honor GDPR rights:&lt;/p&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Build deletion pipelines that remove data from all systems (including backups)&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Implement data portability exports in standard formats&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Create access request workflows that show what data is in your RAG&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Design rectification processes for inaccurate data&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;8. Documentation and accountability&lt;/h3&gt;
&lt;p&gt;Prove your compliance:&lt;/p&gt;
&lt;ul class=&quot;contains-task-list&quot;&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Maintain a data flow diagram showing PII through your RAG system&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Document all technical and organizational measures&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Conduct regular DPIAs (Data Protection Impact Assessments)&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot;&gt;&lt;input type=&quot;checkbox&quot; disabled&gt; Train your team on GDPR responsibilities for AI systems&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Technical implementation notes&lt;/h2&gt;
&lt;h3&gt;Vector database choices&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;PGVector with pgcrypto: good balance of features and security if you&apos;re already on PostgreSQL&lt;/li&gt;
&lt;li&gt;Weaviate: strong security features including RBAC and encryption&lt;/li&gt;
&lt;li&gt;Milvus: enterprise-grade with good security controls&lt;/li&gt;
&lt;li&gt;Avoid public vector databases with unclear data handling practices&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Preprocessing pipeline example&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Pseudocode for GDPR-aware preprocessing
def prepare_document_for_rag(doc):
    # 1. Identify document type and sensitivity
    sensitivity = classify_document_sensitivity(doc)

    # 2. Apply appropriate transformations
    if sensitivity == &quot;high&quot;:
        # Remove or pseudonymize direct identifiers
        doc = remove_pii(doc)
        doc = pseudonymize_identifiers(doc)

    # 3. Create semantic chunks
    chunks = semantic_chunk(doc, max_tokens=512)

    # 4. Add metadata for auditing
    for chunk in chunks:
        chunk.metadata.update({
            &quot;source_document_id&quot;: doc.id,
            &quot;chunk_index&quot;: chunk.index,
            &quot;processing_timestamp&quot;: datetime.utcnow(),
            &quot;sensitivity_level&quot;: sensitivity
        })

    return chunks
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Audit trail essentials&lt;/h3&gt;
&lt;p&gt;Every RAG interaction should leave a trace:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When was the data accessed?&lt;/li&gt;
&lt;li&gt;What specific chunks were retrieved?&lt;/li&gt;
&lt;li&gt;What was the user query?&lt;/li&gt;
&lt;li&gt;What model was used for generation?&lt;/li&gt;
&lt;li&gt;What was the final output (hashed for storage)?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What auditors actually flagged&lt;/h2&gt;
&lt;p&gt;Things auditors liked: clear data flow diagrams showing PII handling, automated deletion pipelines tested quarterly, purpose statements reviewed and signed off, encryption key management with rotation procedures, staff training completion records.&lt;/p&gt;
&lt;p&gt;Common findings: vague purpose statements like &quot;to improve customer service,&quot; no documentation on what data goes into the vector database, no process for honoring deletion requests, insufficient access controls on vector databases, missing legal basis documentation.&lt;/p&gt;
&lt;h2&gt;Putting it together&lt;/h2&gt;
&lt;p&gt;The systems that pass audits are the ones where you can clearly explain what data goes in and why, how it&apos;s protected throughout its lifecycle, how you honor data subject rights, and how you demonstrate ongoing compliance. Start with your data inventory, build purpose-limited systems, implement audit trails. The technical implementation follows from there.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;What I&apos;d do differently next time: I would implement automated compliance testing in the CI/CD pipeline earlier, checking for common configuration mistakes before they reach production rather than finding them during an audit.&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title>Why I built ECL: synthesis over retrieval for enterprise knowledge</title><link>https://www.mbitai.com/notes/ecl-synthesis-over-retrieval/</link><guid isPermaLink="true">https://www.mbitai.com/notes/ecl-synthesis-over-retrieval/</guid><description>RAG returns the closest document. ECL returns the reasoning your experts use, cited and conflict-aware. Open source, built on Andy Chen&apos;s pattern.</description><pubDate>Sat, 21 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I have shipped enough RAG systems to know what they cannot do. They retrieve the closest matching document. They do not tell you that two documents disagree, that one of them is wrong, or that you should not be answering the question in the first place.&lt;/p&gt;
&lt;p&gt;That gap is what I built &lt;a href=&quot;https://github.com/TMFNK/Enterprise-Context-Layer&quot;&gt;Enterprise Context Layer (ECL)&lt;/a&gt; for. It is open source under GPL-3.0 and I have been running it on a client platform for the past few months. The architecture work that used to take months took days, and the answers the system gives are ones the client&apos;s senior engineers actually trust.&lt;/p&gt;
&lt;p&gt;This post is about why I built it, what it borrows from, and where the RAG pattern hits a wall.&lt;/p&gt;
&lt;h2&gt;Where RAG breaks&lt;/h2&gt;
&lt;p&gt;The RAG promise is straightforward. Chunk your documents, embed them, retrieve the top-k chunks for a query, hand them to an LLM. For &quot;what does the policy say about X&quot; it works fine. For most real semantic questions inside a company, it does not.&lt;/p&gt;
&lt;p&gt;Take one I keep seeing in the wild: &lt;em&gt;how long do we keep customer data after churn?&lt;/em&gt; The answer lives in four places. A legal policy document. An engineering deletion runbook. A Slack thread from eight months ago. The head of one senior engineer who has rebuilt the pipeline twice. They do not all agree.&lt;/p&gt;
&lt;p&gt;A retrieval system returns the closest match. It does not flag the conflict, does not know which source has authority, and does not know that this question should probably be routed to the security team rather than answered to a customer at all. The retrieval layer treats every chunk as equally valid. That is the bug.&lt;/p&gt;
&lt;p&gt;In my GDPR audit work I have watched this fail audits three times. Auditors do not want &quot;the closest paragraph.&quot; They want a cited, reasoned answer with a defensible chain back to the primary source. RAG cannot give them that without bolting on machinery that ends up looking suspiciously like ECL.&lt;/p&gt;
&lt;h2&gt;What ECL actually is&lt;/h2&gt;
&lt;p&gt;ECL is a git repo of markdown files. LLM agents read the company&apos;s source systems (Slack, Jira, Confluence, code, calls) and write synthesised, cited knowledge into the repo. Every claim has an inline citation. Every conflict between sources is documented in place rather than silently picked. The git history is the audit trail.&lt;/p&gt;
&lt;p&gt;The original idea is Andy Chen&apos;s, from his Substack piece &lt;a href=&quot;https://andychen32.substack.com/p/the-enterprise-context-layer&quot;&gt;The Enterprise Context Layer&lt;/a&gt;. He built the first production version at Abnormal Security. The line from his post that stuck with me: retrieval and synthesis are different problems. Glean finds the best matching document. ECL builds the reasoning framework an expert uses, and tells you which questions should never be answered at all.&lt;/p&gt;
&lt;p&gt;The version I open-sourced takes Andy&apos;s pattern and combines it with two other ideas that solve problems his original write-up did not fully cover.&lt;/p&gt;
&lt;h2&gt;What I added&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;The C-Compiler pattern, for parallel agents.&lt;/strong&gt; Nicholas Carlini&apos;s &lt;a href=&quot;https://www.anthropic.com/engineering/building-c-compiler&quot;&gt;Building a C Compiler with Parallel Claudes&lt;/a&gt; at Anthropic showed how to coordinate multiple Claude agents without a message broker. Each task is a YAML file. An agent claims a task by writing a &lt;code&gt;.LOCKED&lt;/code&gt; sidecar and pushing to git. If the push is rejected, another agent got there first. Git&apos;s push rejection is the mutex.&lt;/p&gt;
&lt;p&gt;I wanted ECL to scale to ten or twenty workers running on whatever compute is cheap that day, without a central coordinator. The C-Compiler pattern gave me that for free. No Redis, no RabbitMQ, no Kubernetes operator. Just git.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Superpowers, for process discipline.&lt;/strong&gt; Jesse Vincent&apos;s &lt;a href=&quot;https://github.com/obra/superpowers&quot;&gt;Superpowers&lt;/a&gt; makes the case that agent quality is more about process than prompt. Before any non-trivial task, an agent loads a &lt;code&gt;SKILL.md&lt;/code&gt; file that defines the mandatory workflow for that task type.&lt;/p&gt;
&lt;p&gt;I store team workflows as skill files inside the ECL itself. The &quot;how we close a deal&quot; workflow lives in &lt;code&gt;domains/skills/closing-a-deal/SKILL.md&lt;/code&gt;. It has citations, a &lt;code&gt;last_verified&lt;/code&gt; date, and gets re-flagged when the underlying process changes. Process becomes a first-class, versioned, citable artifact. That is the part I think most enterprise teams will not see coming, and the part that has paid off most for the client I built this for.&lt;/p&gt;
&lt;h2&gt;Why the result holds up where RAG does not&lt;/h2&gt;
&lt;p&gt;A few things fall out of the design that I did not have to design for.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Conflicts are visible.&lt;/strong&gt; When two sources disagree, the topic file documents both, cites both, and either resolves the conflict with a note or routes the question. Nothing is silently picked. This is what auditors want. It is also what senior engineers want, because it preserves the disagreement instead of pretending it did not exist.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sensitive questions get routed, not answered.&lt;/strong&gt; Each topic file can carry a routing note. &quot;Do not answer data deletion timeline questions directly in customer-facing contexts. Route to the security team.&quot; That instruction sits next to the cited answer, so any agent or human reading the file sees both. RAG has no equivalent. RAG always answers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The system gets smarter from use.&lt;/strong&gt; There is a file called &lt;code&gt;meta/how-to-get-accurate-information.md&lt;/code&gt;. It starts empty. Agents add to it as they discover stale sources, unreliable APIs, and questions that should always be escalated. After a few hundred runs it becomes a dense, experience-grounded guide to the company&apos;s specific information landscape. You do not pre-fill it. Invented wisdom is worse than none.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The audit trail is git.&lt;/strong&gt; Every synthesis is a commit. Every conflict resolution is a commit. Every error is committed before the agent process can crash. I have watched this pay off in two audits already. Showing an auditor &lt;code&gt;git log&lt;/code&gt; for a topic file is a different conversation than showing them a vector database.&lt;/p&gt;
&lt;h2&gt;What the client deployment looked like&lt;/h2&gt;
&lt;p&gt;Here&apos;s what I can say:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We replaced an early-stage RAG prototype with an ECL repo seeded over a 90-minute interview.&lt;/li&gt;
&lt;li&gt;Worker agents populated the first three domains in two days.&lt;/li&gt;
&lt;li&gt;Architecture decisions that previously needed three or four meetings to reach consensus on were resolved by reading the relevant topic file, because the conflict between two source systems was already documented and routed.&lt;/li&gt;
&lt;li&gt;The architecture phase finished in roughly half the time I had budgeted for it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The headline result was that the team stopped re-litigating the same questions. The ECL made the company&apos;s existing disagreements legible. That alone changed how the client&apos;s engineers worked.&lt;/p&gt;
&lt;h2&gt;Is it production-ready&lt;/h2&gt;
&lt;p&gt;I would call it solid beta, edging into prod-ready. Andy ran the original at scale at Abnormal. The C-Compiler coordination pattern is tested at Anthropic. The Superpowers skill model has thousands of users. My contribution is the integration and the implementation template. That part has been running on a real client platform without me having to babysit it.&lt;/p&gt;
&lt;p&gt;The repo is at &lt;a href=&quot;https://github.com/TMFNK/Enterprise-Context-Layer&quot;&gt;github.com/TMFNK/Enterprise-Context-Layer&lt;/a&gt;. It is GPL-3.0. There are three README files, one for humans, one for agents, one for engineers who want the design rationale. If you give the agent README to Claude Code or CODEX and tell it to follow the Quick-Start Checklist, it will interview you and scaffold the repo for your company.&lt;/p&gt;
&lt;h2&gt;What I&apos;d do differently next time&lt;/h2&gt;
&lt;p&gt;Three things.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One: write the routing rules before the agents run.&lt;/strong&gt; I let the first client deployment populate domain files before we had a finalised list of sensitive topics. The agents wrote good content, but some of it should have been a routing note, not an answer. We had to go back and edit... Define what must not be answered before you let agents answer anything.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Two: keep &lt;code&gt;how-to-get-accurate-information.md&lt;/code&gt; empty.&lt;/strong&gt; I was tempted the first time to seed it with my guesses about which sources were reliable. Well, I was wrong about most of them. The file is more useful when it grows from real agent experience than from my upfront assumptions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Three: budget for the interview.&lt;/strong&gt; The 90-minute interview that bootstraps the ECL is essential. The quality of every later answer depends on the domain mapping and source authority hierarchy that come out of that conversation. Treat it like the discovery phase of an audit, not like onboarding paperwork.&lt;/p&gt;</content:encoded></item></channel></rss>