Omni is a good traditional BI tool — a well-built SaaS app. The spreadsheet surface is the best answer anyone has shipped to "the analyst wants Excel and the company wants governance", and workbook-first modeling gets a model started without a design phase nobody has time for.
Times have changed. The thing asking for a number is no longer only a person at a desk: it is an agent in a harness your team already runs, an application you ship to your own customers, a workflow that never opens a browser. The people who need answers outnumber the analysts who can get them by an order of magnitude. And what has to be right is no longer the dashboard — it is what your data means, written down in a form something other than a dashboard can read. A product shaped for the first of those describes the second badly, however well it is built.
So the reason to read this is not that Omni is bad at being Omni. It is that Omni is an app — you log into it, and the model, the skills, the surfaces and the roadmap belong to the vendor — and what you are building, whether that is an AI-first organization or a platform of your own, needs an engine. You write down what your data means as a data model, and the engine generates the rest: the dashboards, data apps and workbooks people actually use, the semantic layer that keeps them agreeing, and the pipelines and storage underneath. Omni is almost entirely that top tier, bought whole. Here it is an output — which is why the model is a file in your repository, the skills your agent follows are source you can read and change, the app on top is yours to ship, and you pay for what you run. From inside a dashboard the two look alike. They diverge the first time you want something the product does not do.
Why teams leave Omni
Fewer than leave Looker, and for different reasons. Omni is young, well built and moving fast; nobody is escaping a decade of accumulated debt. They are leaving how it is sold, not how well it works.
Everything good happens inside the app. That is coherent design, and it is why the analyst experience is good. It is also the ceiling: the model exists to serve the product's surfaces, so when the consumer is an agent in someone else's harness, or a data product you ship to your own customers, you are working outward from an app instead of building on an engine.
What is inside it is proprietary, and mostly opaque. .view and .topic are Omni's format and one vendor compiles them. Omni does sync model YAML to your git provider and can require a pull request before anything ships — more than most BI tools offer — but the CLI fetches that YAML down from Omni and your saves push back to an Omni branch, so the working copy is Omni's and git holds the record. The rest — what the modeling agent reasons from, how the dashboard builder picks a chart, what the query planner does with a topic — you see what it did, not why. When an answer looks wrong, you file a ticket instead of reading the code.
It is configurable, not extensible. You can set what Omni exposes. You cannot add a surface it does not have, change how its agent behaves, or take the part you like and run it somewhere else. Every good idea your team has about how your data should work has to fit through what the product already does.
A platform fee, and then a seat for each person. Sales-led, role-based, on a multi-tenant cloud with no self-hosted option. That prices what a person does at a desk and describes an agent's traffic badly, because agents hold no seats. It also means the bill grows with headcount rather than with use, so the people who would benefit from access are the reason not to grant it.
What you get with Credible
Open and readable the whole way down. Malloy is open source, Publisher is the open-source server we build and maintain, and the retrieval tools and modeling skills an agent uses are MIT-licensed in the same repository. You can read what a skill tells an agent to do before you trust it, change it when you disagree, and run the whole thing with no Credible account.
You own the model, the skills, and the app. The data model is Malloy text in your repository, compiled against BigQuery, Snowflake, Postgres, MySQL, Databricks, Trino or DuckDB. The skills are source. The data apps and dashboards an agent writes are code — they diff, they review, they ship with the rest of your software. Stop paying us and all three keep working.
You pay for what you run. Usage, not headcount: tokens, compute time and storage, pooled per organization. Users are unlimited on every plan, publishing is free, and MCP access costs nothing, so pointing your own agent at the model never changes the bill.
And yes, there is an app. Credible Workspaces is BI in the shape you already know — dashboards, notebooks, reports, and a chat surface over the governed model — and if you want to land somewhere familiar on day one, land there. It assumes a different reader than Omni's canvas does. Omni's surface is genuinely good for an analyst, and an analyst is who it assumes; the complaint we hear most often from teams running it is not that it is weak but that it is too much for their organization, so it stays with the handful who learned it and everyone else goes back to asking them. Credible Workspaces takes the opposite trade deliberately: ask in plain language, get a KPI, a chart or a table grounded in the model, with the query behind it one click away. The design goal is ninety per cent of what a tool like Omni can do at ten per cent of the complexity — reach over surface area, because a number nobody can get to is not worth much.
That ninety-ten is the Credible app's ceiling, not the engine's, and that gap is the point. In a closed product the missing ten per cent is a feature request: you describe what you need, you get a roadmap answer, and you wait. Here it is the next thing you build, because the data model, the retrieval tools and the agent skills are all reachable from your own code — so the last ten per cent, and the workflows well past it that no vendor would ship because they are specific to how your team works, is something you write. The app is your first surface, not your only one: a starting point, not a destination.
None of this requires a big-bang cutover, and it is less work than it looks — because you are not migrating workbooks. You are migrating the domain underneath them, and your Omni model is a good record of that domain. Unlike a decade-old LookML project, most of it is probably still in use.
How the migration works
Same five steps as any migration here. What differs is step three.
- Pick. One topic — the one behind the workbook people argue about.
- Capture. Every field the topic exposes and every field its workbooks reference, including ones defined in the workbook rather than the shared model.
- Reconcile. Resolve the shared, branch and workbook layers into one definition per field. This is the work; it has its own section below.
- Model. Base sources first, then joined sources by question, then named views for the canonical queries.
- Curate and publish.
#(doc)on every field,#(index)on the dimensions people name by value, access rules where a rule has to hold,#@ persistwhere a query is expensive.
What Credible reads
Your .view files (dimensions and measures over tables), your .topic files (views gathered into queryable units, with ai_context and default filters), and the relationships file where the global joins live. All YAML. The agent works from those files directly and inventories workbook-level extensions too, because that is where logic hides. Connecting Omni's MCP server or Model API is optional and adds live validation.
What carries over
| In Omni | In Credible |
|---|---|
| Shared model, topics, views | Malloy sources (base + joined); import/export curate exposure |
| Dimensions and measures | Carried over, filtered and ratio measures included |
Field-level sql: with ${…} refs | Resolved into Malloy expressions with explicit types |
The relationships file — relationship_type, on_sql | join_one / join_many on the source; assumed_many_to_one flagged, not converted |
primary_key: true dimensions | primary_key: on the source — what keeps symmetric aggregates correct |
| Logic split across shared / branch / workbook, and per topic | Reconciled into one canonical definition per field |
access_filters, required_access_grants, hidden_unless_access_grants | Rules in the model, enforced at the gateway on every query — field-level gating flagged for a person (full mapping) |
ai_context and descriptions | #(doc) / #(index), compressed into the concept index |
| Workbooks and dashboards | Rebuilt as data apps or notebooks |
The hard part: three layers, one definition
Every migration has one section where the work actually is. In a Looker migration it is derived tables. Here it is promotion lineage.
A field can be defined in the shared model, overridden in a branch, and overridden again in a workbook that was never promoted. All three are live. Which one is correct is not a technical question — it is a question about what the business means, and the file cannot answer it.
Layers are one axis. Topics are a second: a topic can customise a view inside that topic alone with a views: block, and topics inherit from one another through extends. So the same field can carry a different definition per layer and per topic, which is worth knowing before you count how many definitions of revenue you have.
So the conversion sorts every field into one of three cases:
| What we find | What it means | What happens |
|---|---|---|
| Defined once, in the shared model | The easy majority | Converted directly |
| Shared definition, workbook override that matches | Someone re-typed it | Converted once, override dropped, noted in the report |
| Shared definition, workbook override that differs | Two live definitions of one metric | Flagged for a person — never resolved on sight |
That third row is the one that matters, and it is why this is not a transpiler. An automated tool that picks the shared-model version is wrong whenever the workbook override was the fix, and picking the workbook version is wrong whenever it was somebody's experiment. Both failure modes produce a model that compiles and lies.
What the migration gives you instead is the list: every field where the layers disagree, what each layer says, and which workbooks depend on which. Teams routinely find that the number on the most-viewed dashboard came from an override nobody remembered making. That finding is worth the migration on its own, and you can only get it by reading every layer and every topic at once — which is exactly what you cannot do from inside a workbook.
Inline SQL and implicit types
Omni encourages field-level sql: with ${…} references and infers types. It is quick to write and it works. Moving it out means resolving those references and making the types explicit:
value_tier:
type: string
sql: |
CASE WHEN ${TABLE}.sale_price >= 100 THEN 'High'
WHEN ${TABLE}.sale_price >= 25 THEN 'Medium'
ELSE 'Low' END#(doc) Sale-price value bucket
value_tier is
pick 'High' when sale_price >= 100
pick 'Medium' when sale_price >= 25
else 'Low'The output is not shorter by much. What changes is that sale_price is now a typed reference the compiler checks rather than a string interpolated into SQL, so renaming the column breaks the build instead of the dashboard.
Default filters belong in the query
A topic can carry a removable, query-time default — status is Complete, say. It looks like model configuration. It is not: a filter a user can remove is part of the question, not part of the definition. Baking it into the source would change what total_revenue means for everyone downstream.
So those become part of a named view rather than a source-level filter, and the measure that should be filtered becomes its own measure:
#(doc) Gross merchandise value across all order items
total_revenue is sum(sale_price)
#(doc) Recognized revenue from completed items
completed_revenue is sum(sale_price) { where: status = 'Complete' }Two names for two things — which is what that filter was standing in for all along.
Access control is not a find-and-replace
Omni's access controls protect Omni. That is the right design for a BI product, and the thing to watch when you leave one: a rule that governs who can open a workbook says nothing about a query that reaches the warehouse another way.
Credible's rules live in the model and run at the gateway on every query, so the same rule covers a workspace, an agent, a dashboard and an embedded app without being restated in any of them. The mapping is mostly mechanical. One thing to watch: masking a value and controlling whether a caller sees a field at all are separate layers, and confusing them ships a model that looks restricted and isn't. Anything that gates a field rather than filtering rows gets flagged for a person to rule on.
always_where_sql on a topic is a filter a user cannot remove, and nothing in the file says whether it is a data-quality rule or a restriction somebody is relying on. Migrate it as a filter and you may quietly drop a restriction; drop it and every number moves. Those get written down and re-expressed as a rule only where they actually restrict. The access-control guide has the full mapping.
Proving the numbers match
No migration is done when the model compiles. It is done when someone trusts the number.
Two channels, used together. Omni's Model API runs the original query and we compare. Or we run Malloy against the same warehouse the topic reads and diff it against the equivalent SQL run directly there — in practice the channel that does most of the work, with or without API access. Both sides hit the same data, so any difference is a difference in logic.
When a delta shows up we confirm and flag it. We never silently reconcile it. A source of truth carries its own bugs, and a migration that quietly matches them has laundered a bug into a new system. With three layers in play, some of those deltas are the most useful output of the whole exercise.
You finish with a coverage report: every view and field marked modeled, renamed, rearchitected, deferred or skipped, each with a reason — and, for Omni specifically, every field where the layers disagreed and what was decided.
What the workbook could not ask
Matching the old workbook is the least of it. The model composes: Malloy views nest, chain and get reused as sources, so the follow-up question does not need a new topic built for it first. Every field carries its own documentation and indexed values, so an agent finds the right one instead of guessing from a name. And symmetric aggregates mean a measure cannot fan out across a one-to-many join.
You already have that in Omni. Declare a primary_key: true dimension on the view and a relationship_type on the join, and Omni protects the aggregate — it will even prompt you for the primary key when it spots fan-out. What changes is where the guarantee comes from. In Malloy the join keyword is the cardinality — join_one or join_many — so there is no parameter to leave off and nothing left assumed_many_to_one. What you declared in Omni carries over. What you can no longer do is declare nothing.
Start with one topic
Not the whole model. Pick the topic behind the workbook people actually argue about, run the five steps, and compare the numbers. You will get a real coverage report, a real sense of pace, a list of every field whose layers disagree — and a governed model you can point an agent at on day one.
Book a call, read the migration docs, or see how Credible and Omni compare.