Engineering

10 MIN READ

Model the Meaning First. Let It Build the Pipeline.

The modern data stack set the order we model in a decade ago, for consumers who no longer dominate: pipeline first, meaning last. Flip it. Write down what your data means first, in one language -- and let the engine build the transformations, tables, governance, and context underneath it.

Oliver Larsson

Oliver Larsson

Solutions Engineer @ Credible · Sep 8, 2026

Inside the AI Analytics Engine made the case that the layers of the modern data stack are the problem, and that the way out is a split: one language for what your data means, one engine that owns how it is served. You own "the what". The engine owns "the how".

That split changes something most data teams have not caught up with yet: the order you build a model in.

We've been modeling data backwards for a decade.

You know the order, whether you built it or waited on it. Raw tables land. A data team cleans them, joins them, and reshapes them, one layer at a time, until a mart is ready to serve. If you were on that team, you wrote the models and sequenced the steps. If you weren't, you filed a ticket and waited for the number to show up in a dashboard. And then -- at the very end, if there's time, if the sprint allows -- someone writes down what any of it actually means. A YAML description here. A wiki page there. A definition of "active customer" that was true when it was written.

This wasn't a mistake. It was a rational answer to the moment that produced it. The consumers were dashboards, refreshed nightly, viewed weekly, by people who carried the missing context in their heads. When the "revenue" tile looked off, an analyst squinted at it, remembered that finance excludes intercompany transfers, and mentally corrected. The model could be approximate because a person stood between the model and the decision.

Every semantic layer ever shipped inherited that order. Build the pipeline first, describe it after. Which means every semantic layer ever shipped inherited the same failure mode: the data's meaning is the last thing you get to and the first thing that goes stale. The pipeline changes on Tuesday; the description catches up never.

Agents broke it

Not gently.

A model that was good enough for a dashboard becomes visibly wrong the moment an agent consumes it. The agent doesn't view the marts weekly -- it queries them a thousand times a day. It doesn't squint at a suspicious number -- it answers confidently either way. It doesn't carry the tribal knowledge that status_v2 is the authoritative column and that a status of 2 means "cancelled, don't contact." It reads what's there and believes it.

Here's what that looks like in practice. Your marts have a table called revenue_daily. Everyone on the data team knows it's booked revenue, not recognized -- fine for the Monday standup dashboard, where the one person who cares about the distinction knows to ask. Then an agent gets pointed at the warehouse, a customer asks it a question, and it quotes booked revenue as recognized revenue in a customer-facing answer, with a confident sentence wrapped around it. The model didn't get worse. The tolerance for its fuzziness went to zero.

This is the shift: a person fills gaps in the model with judgment, an agent fills them with unfounded confidence. Weak models used to degrade gracefully. Now they fail loudly, at volume, in production.

So the fix isn't a better description bolted onto the end of the pipeline. Descriptions at the end of the pipeline are what just failed. The fix is to change the order.

Flip it: the data model comes first

Write down what your data means first, as a data model. The entities. The metrics. The relationships and their cardinality. The business rules and the edge cases -- trial churn doesn't count, intercompany transfers are excluded, the fiscal year starts in February.

Then let the engine derive what sits underneath it: the transformations, the materialized tables, the access enforcement, the context every agent and application reads from.

Meaning stops being documentation of the pipeline and becomes the specification for it. It stops being the output and becomes the input.

This is what "declarative" was always pointing at. dbt made "the how" versioned and testable -- a real achievement, and the reason data teams work like software teams today. But "the what" still lived downstream: in YAML descriptions that drift from the SQL they describe, in BI-layer definitions that disagree with the warehouse, in the heads of the two people who were there when the metric was defined. Putting the data model first makes "the what" primary -- and makes everything else derivable.

Here's what that looks like when the data model is code.

The model is the pipeline

Take the classic mart-building exercise: order metrics, joined to customers and line items, with a business rule for customer segments and a materialized table so downstream queries are fast. In the inherited order, that's three or four staging models, an intermediate model, a mart, a YAML file declaring tests and descriptions, and a config block for materialization -- the meaning scattered across all of them and fully stated in none.

Written down first, in Malloy, the open-source language the engine speaks:

#@ persist name="order_metrics" refresh="incremental"
source: order_metrics is warehouse.table('orders') extend {
  join_one: customers is warehouse.table('customers') with customer_id
  join_many: items is warehouse.table('order_items') on order_id = items.order_id

  dimension:
    #(doc) Drives onboarding campaign eligibility.
    customer_segment is customers.signup_date ?
      pick 'new' when >= now - 30 days
      else 'established'

  measure:
    #(doc) Revenue at order placement. Recognized revenue lives in finance.recognized_revenue.
    booked_revenue is sum(order_total)
    order_count is count()
}

Read what this one file asserts. Every order has exactly one customer and many line items -- cardinality isn't a hint or a hope, it's a declared fact the compiler enforces, so joined totals never double-count -- correct by construction. The segment rule isn't a CASE statement copy-pasted into six queries -- it's defined once, and its #(doc) doesn't restate the logic (the model already says it); it captures what code can't: what depends on this definition. Revenue is named booked_revenue, so the distinction that burned the agent in the old order is unmissable in the name itself -- the doc just points to where recognized lives. And #@ persist is the only mention of storage in the file. The annotation is the intent; the engine is the execution. It builds the table, tracks what depends on it, and refreshes it incrementally -- new rows, not full rebuilds.

The transformation, the materialization, the documentation, the relationships, the business rule -- none of them were written about the model after the fact, in a second language or a separate system. They are the model. There is nothing to drift, because there is no second copy.

The edge cases -- the part of meaning that always died in the wiki -- live in the model too:

source: customers is warehouse.table('customers') extend {
  dimension:
    #(doc) Trial churners never converted, so they don't count.
    is_active is status = 'active' and not churned_in_trial

  measure:
    active_customers is count() { where: is_active }
}

That's the rule that used to live in one analyst's head, get rediscovered quarterly, and silently disagree between the board deck and the product dashboard. Now it's a named, documented, versioned definition. When an agent is asked "how many active customers do we have," it doesn't guess at what active means. The engine hands it that definition as context -- the same definition the dashboard uses, the same one the API serves. One data model, delivered to every surface, produces consistent answers everywhere.

And when meaning changes -- because it will, faster than ever now that everyone with an AI in a side tab is discovering in real time what a metric should mean -- it changes in one place, in a diff someone reviews, and everything derived from it moves together: the tables, the docs, the index, the access enforcement, as one versioned package. You can see how "revenue" was defined in March versus today. You can roll back a bad change. Try that with a wiki page.

It also changes who can change the model. In the old order the model was the pipeline, so only the people who could write the pipeline could touch it. A new metric meant a ticket, and the ticket meant plumbing the data through every layer of the medallion architecture -- bronze, silver, gold -- before anyone could use it. Weeks of work for one definition. When the meaning comes first, the part of the model that non-engineers know best -- what counts as active, which revenue is which, the rule the data alone can't settle -- is written down in one readable place, and that place can reach all the way down to the raw operational data. A domain expert describes a change in plain language, an agent drafts it into the model, and the engine builds whatever the change needs underneath.

Because the model is code in a repository, each of those people works in the tools they already use. The analyst asks in the app. The agent commits the change as a draft. An engineer pulls the branch into their own editor, reviews the diff, and approves it, or the team decides the author can approve their own. When it merges, it publishes to every surface. Shared meaning is a team effort. Meaning first is what lets the whole team work on one model -- and git-backed modeling is what lets them do it without leaving their own tools.

The objections, taken seriously

"You can't know the meaning up front." You know more than you think. You know your entities. You know your core metrics, even if the edge cases are still being argued about. And the argument itself is the point: better to have it at the front, in a reviewable definition, than to have it silently five different ways in five different marts. The data model isn't a waterfall spec -- it's a living record. Every disambiguation, every corrected join, every edge case an analyst catches folds back in as a definition. And the engine helps: it watches which questions found the right concepts and which missed, and surfaces the misses as proposed fixes to the model -- a better #(doc) here, a missing #(index) there -- for a person to review. It's versioned like code because it is code.

"Performance still needs engineering." It does -- and the engine does it. Materialization is an annotation on the model, not a parallel system with its own config language. When agent traffic makes a source expensive, #@ persist makes it a managed table and #@ preaggregate keeps the hot rollups warm. The engine decides what to serve each question from; the answer never changes, only the route to it. The performance work is still real work. It just stops being the thing the model is made of.

"This is just semantic layers again." Every prior semantic layer sat at the end of the pipeline and described what the pipeline had already built. That's why they all rotted the same way: the pipeline moved, the layer lagged, trust evaporated. Position is the whole point. A semantic layer downstream of the transformations is documentation. A data model upstream of them is the source the transformations are derived from. Same idea, opposite architecture -- and the difference is whether there is an engine on the other side of it.

Meaning at the front

Look at what's commoditized. Charting -- an agent draws any chart from a sentence. Storage -- solved, cheap, rented. Query engines -- pick any three, they're all fast. The mechanical middle of the data stack keeps collapsing, and every collapse makes the same thing more visible: the one input no engine and no agent can supply is what your data actually means.

Meaning is the product. You don't build the product last, and you don't build it alone: the people who know what the data means are spread across the business, and a model built meaning-first is one all of them can improve.

A stack of hand-written steps decays: every schema change, every departure, every vendor swap chips away at what the data means. A declared data model appreciates: write the data's meaning down once, and the engine gets faster, cheaper, and more accurate on its own.

Write it down first. Version it like the code it is. Let the engine derive the pipeline underneath it -- and let every agent, dashboard, and application read from the same model, so the answer is the same everywhere the question gets asked.

More from Credible

Engineering

21 MIN READ

Inside the AI Analytics Engine

The AI Analytics Engine is not another data platform. It is a paradigm shift: software moved from hand-written assembly to compilers to managed runtimes -- and that same move is now happening in data. You write down what your data means, in one language. The engine derives the rest: pipelines, optimized storage, retrieval and context for every agent, governance on every query. And the modeling, analysis, dashboards, data apps, and agent skills ship in the open, so you can tune the engine to your business.

Kyle Nesbit

Kyle Nesbit

CEO & Founder @ Credible

AI & ML

13 MIN READ

The 4 Questions to Ask Before Deploying Semantic Layers in Production

A detailed comparison between the Credible and dbt semantic layers, drawn from two production agentic-analytics deployments and organized around four questions to ask of any solution: what can an agent ask, what can it know, what can't it do, and how does the platform run.

Adam Ribaudo

Adam Ribaudo

Founder, Noise to Signal

Open Source

14 MIN READ

A governed dataset end to end: Claude Code and Malloy on real CVE data

The whole series, run end to end on one messy, real dataset: 320,000 public security vulnerabilities across six overlapping feeds and severity scales that disagree. An agent builds the model, the definitions get locked, a loaded question gets a defensible answer, and a data app ships it.

Ofer Mendelevitch

Ofer Mendelevitch

DevRel @ Credible