# How to Build an AI Context Layer for Your Data Warehouse

> Source: <https://getbruin.com/blog/build-ai-context-layer-data-warehouse/>
> Published: 2026-08-21 00:00:00+00:00

**Quick answer:** to give an AI agent real context about your warehouse, install the [Bruin CLI](https://github.com/bruin-data/bruin), map your tables into local files with `bruin import database`

, then run `bruin ai enhance`

to fill those files with descriptions, data quality checks, and tags. Both commands are free and open source, they run on your machine, and the result is a folder of plain YAML you can review in a pull request. You do not need to be a Bruin user, and you do not need to move your pipelines.

Every text-to-SQL demo works on the demo schema. Then you point the same agent at a real warehouse with 400 tables, five naming conventions, and a `status`

column that stores integers, and the answers quietly go wrong.

The model is not the problem. The problem is that the agent can see your schema and nothing else. It knows a column is called `gmv`

and typed `DECIMAL`

. It does not know that `gmv`

excludes refunds, that `status = 3`

means refunded, that `orders.total_amount`

does not reconcile with the line items, or that `country_code`

is ISO alpha-2 rather than a free-text country name.

That missing information is the context layer. This guide builds one in two commands.

A directory of YAML files, one per table, that looks like this:

Three things make this useful rather than decorative:

**It is text in git.** Reviewable in a PR, greppable, diffable, and readable by any agent that can open a file. No catalog UI, no API, no export step.**The checks are executable.** `not_null`

, `unique`

, and `accepted_values`

are not comments. Bruin can run them against the table, so the documentation gets tested instead of rotting.**It is yours.** Wrong description? Edit the line. There is no metadata service that owns the truth and no vendor that has to agree with you.

An agent writing SQL needs to answer four questions before it writes a line:

- Which table has this?
- What does this column actually mean?
- What values are legal, and what do they encode?
- How do these tables join?

A raw `INFORMATION_SCHEMA`

dump answers question one, badly, and none of the others. So the agent guesses. Guessing produces SQL that runs, returns a number, and is wrong - which is the worst failure mode available, because nothing errors and someone puts the number in a deck.

The context layer answers all four in a form the agent reads before querying. This is the same reason coding agents work well on code: the metadata and the artifact live in the same repository, in text.

You need three things.

**1. The Bruin CLI.** Open source, Apache 2.0, single binary:

Verify it:

**2. An AI coding CLI.** `bruin ai enhance`

drives an agent you already have installed. Any one of these works:

| Provider | Install | Flag |
|---|
| Claude Code | `curl -fsSL https://claude.ai/install.sh | bash` | `--claude` |
| Codex | See the [Codex docs](https://developers.openai.com/codex/cli) | `--codex` |
| OpenCode | See [opencode.ai](https://opencode.ai) | `--opencode` |
| Cursor | `cursor-agent` CLI | `--cursor` |

Bruin auto-detects what is installed, so the flag is only needed when you have more than one and want to pick.

**3. Read access to your warehouse.** `SELECT`

on the schema you want to map, plus permission to read its metadata. That is it - no write access, no admin role.

You are not building a pipeline, so skip the platform-specific templates. An empty project is all the context layer needs:

That gives you a `bruin/`

folder containing `ai-analyst/`

- a `pipeline.yml`

with everything commented out, and an `assets/`

directory waiting to be filled.

Two paths matter for the rest of this guide, and mixing them up is the most common mistake:

`.bruin.yml`

lives at the project root and holds credentials. It does not exist yet; it is created the first time you add a connection or validate the project, and it is gitignored.`ai-analyst/`

is the pipeline folder. It contains `pipeline.yml`

, and it is the path you pass to `import`

and `enhance`

.

Add a connection with the interactive wizard:

It asks for the connection type, a name, and the credentials that type needs, then writes `.bruin.yml`

for you. Or create the file yourself - it is small:

Confirm it works before going further:

If you do not know which schema you want, ask the warehouse:

This is the first half of the context layer: turn every table into a file.

The result is one file per table, organised by schema:

Each file is a skeleton with the table name, the asset type derived from your connection, and every column with its real database type:

Accurate, and still not useful to an agent. That is expected - this step is the inventory, not the knowledge.

| Flag | What it does |
|---|
`--connection` , `-c` | Connection name from `.bruin.yml` . Omit it to pick from an interactive list. |
`--schema` , `-s` | Import a single schema. Start here. |
`--schemas` | Repeat per schema (`--schemas raw --schemas analytics` ). BigQuery only. |
`--no-columns` , `-n` | Skip column metadata. Faster, and much less useful. |
`--ingestr` | Generate runnable [ingestr](https://github.com/bruin-data/ingestr) assets that replicate the source instead of metadata-only placeholders. |
`--destination` | Destination platform for `--ingestr` assets, for example `duckdb` . |
`--environment` , `--env` | Target a specific environment from `.bruin.yml` . |

Supported sources: Snowflake, BigQuery, PostgreSQL, Redshift, Athena, Databricks, DuckDB, ClickHouse, Azure Synapse, MS SQL Server, and MongoDB.

**Start with one schema.** Import your most-queried schema, run the next step, read the output, and decide whether you like it before pointing this at 400 tables.

Now the second half - filling those skeletons with meaning:

Set `--model`

on the first run. The CLI's built-in default for Claude Code is `claude-sonnet-4-20250514`

, which has reached end of life, so the bare command currently fails on every asset with `There's an issue with the selected model`

.

You can point it at a whole pipeline folder, a single schema folder, or one file. It processes 5 assets in parallel by default.

Each asset goes through the same stages, and the command narrates them:

(The step numbering in that output is inconsistent, which is cosmetic.) What actually happens per asset:

**Fill columns** - re-reads the schema and adds any column that is missing from the file.**AI enhancement** - queries the warehouse for column statistics (row counts, null counts, distinct counts, min/max ranges), then hands the schema plus those statistics to your AI CLI to write descriptions, checks, and tags.**Format** - normalises the YAML.**Validate** - parses the result. If the AI produced something invalid, the file is reverted rather than left broken.

Then a summary:

Expect a few minutes for 15-20 tables, and 10 minutes or more for 50+.

| Flag | What it does |
|---|
`--model` | Pick the model, for example `--model claude-sonnet-5` . Set it on the first run: the built-in default for Claude Code is `claude-sonnet-4-20250514` , which has reached end of life. |
`--claude` / `--codex` / `--opencode` / `--cursor` | Force a provider when several CLIs are installed. |
`--concurrency` | Assets enhanced in parallel. Default 5. Lower it if you hit rate limits. |
`--system-prompt` | Append your own instructions to the default enhancement prompt. |
`--environment` , `--env` | Target a specific environment. |
`--output` , `-o` | `plain` or `json` . |
`--debug` | Print the full agent conversation. Use this when output looks wrong. |

`--system-prompt`

is the underrated one. It is where your house rules go:

Here is real output from the run above, on the `orders`

table, trimmed for length. Nothing in the input file said anything beyond column names and types:

Three things happened there that a schema dump cannot do.

It **inferred the join graph** - `customer_id`

got a `foreign_key`

block pointing at `ecommerce.customers`

, from naming and cardinality alone.

It **found a real data problem**. `total_amount`

does not equal the sum of its line items in this dataset. The AI checked, noticed, and wrote the caveat into the description with instructions on which table to trust for which question. An agent reading this will not silently produce two different revenue numbers depending on which table it picked.

It **encoded the enum**. `status`

got an `accepted_values`

check listing the four states it observed, so both the agent and the pipeline now know what is legal.

Bruin pre-fetches statistics so check selection is grounded in the data rather than in vibes:

| Check | When it gets applied |
|---|
`not_null` | Columns with zero nulls observed, IDs, required fields |
`unique` | Distinct count equals row count |
`positive` / `non_negative` | Amounts, prices, quantities, counts |
`accepted_values` | Low-cardinality enum-like columns: status, type, category |
`pattern` | Formatted strings such as emails |
`min` / `max` | Numeric columns with a clear observed range |

The generated context is a strong first draft written by something that has never spoken to your finance team. Read it.

**The **`unique`

trap. A column can be unique in today's snapshot and not unique by design. `ticker`

in a quarterly financials table is the classic case: unique in a single-quarter extract, one row per quarter forever after. Bruin only adds `unique`

when the statistics support it, which is exactly why a coincidence can slip through. Delete any `unique`

check that is true by accident.

**Business meaning it cannot know.** The AI can tell that `status`

has four values. It cannot tell you that `refunded`

is set by a nightly job with a 24-hour lag. Add that yourself.

**Silent renames.** If two teams both maintain a `revenue`

column with different definitions, the AI will describe each in isolation. Reconciling them is a human decision. This is where a [glossary](https://getbruin.com/docs/bruin/getting-started/glossary.html) earns its keep: define the entity once and have assets `extends`

it.

Editing is cheap because these are files. Fix the line, commit, move on.

Then prove the documentation is true:

If you have write access to run checks, `bruin run`

executes them against the real tables. A `not_null`

check that fails is documentation caught lying, which is the entire point of making the checks executable.

Re-run the same command after a schema change:

It is additive, not destructive. Existing descriptions and checks are left alone - a second pass on our demo pipeline reported `No changes made.`

for the table it had nothing to add to, and for the others it only filled in fields it had skipped the first time, such as `meta`

, `domains`

, and `primary_key`

. Nothing was duplicated and nothing human-written was overwritten. Your edits survive.

That property is what makes this CI-friendly. A weekly job that runs `import database`

followed by `ai enhance`

and opens a PR with the diff turns documentation drift into a reviewable change instead of a slow decay. Because the output is YAML, the diff is readable: a new column shows up as a new column, not as a re-rendered catalog page.

The context layer is useful the moment it exists - any agent with filesystem access can read `assets/`

. To let it query as well, Bruin ships an MCP server:

Register it with your agent. For Claude Code:

Or in a `mcp.json`

for Cursor and friends:

Now the agent reads the context layer from the repository and runs queries through Bruin's connections, so credentials stay in `.bruin.yml`

and never reach the model. One more useful command while you are here:

The skill name is required - a bare `bruin ai skills`

opens an interactive picker and fails outright when the terminal is not interactive. `all`

installs the full set into `.agents/skills`

and writes an `AGENTS.md`

at the project root, teaching your coding agent how Bruin projects are structured.

If you would rather follow this as a guided tutorial with per-warehouse setup for BigQuery, Snowflake, Redshift, Databricks, ClickHouse, Postgres, and SQL Server, work through [Build an AI Context Layer](/learn/ai-context-layer) in Bruin Academy.

The two steps here are steps one and two of building your own AI data analyst. The full path - connections, context, agent setup, and the harder context problems - is written up in the [AI data analyst course](/learn/ai-data-analyst), and the reasoning behind open-sourcing it is in [Building an AI Data Analyst Sucks](/blog/build-your-own-ai-data-analyst/).

`error: There's an issue with the selected model`

You dropped `--model`

, so the CLI fell back to `claude-sonnet-4-20250514`

, which has reached end of life. Add `--model claude-sonnet-5`

back, and on Codex or OpenCode use that provider's model name instead. Check that you are on a recent CLI too: `bruin --version`

.

`No AI CLI detected`

`bruin ai enhance`

needs one of Claude Code, Codex, OpenCode, or Cursor's `cursor-agent`

on your `PATH`

. Install one, confirm with `which claude`

, then re-run.

`unknown command "enhance"`

The command is `bruin ai enhance`

. The `ai`

subcommand is easy to drop.

**Import fails with permission denied**

The connection needs `SELECT`

on the target schema and access to its metadata. For BigQuery that means two roles, not one: `BigQuery Data Viewer`

on the dataset plus `BigQuery Job User`

on the project, because both `bruin query`

and `ai enhance`

submit query jobs. Data Viewer alone reads metadata but cannot run a query.

`--schemas`

does nothing

It only works on BigQuery, and it repeats rather than taking a comma-separated list: `--schemas raw --schemas analytics`

. On other warehouses, run `--schema`

once per schema into the same pipeline folder.

**It hangs on a large schema**

Enhance is doing real work per asset. Lower `--concurrency`

if you are hitting rate limits, split the work by schema folder, and remember you can re-run safely.

**Descriptions are confidently wrong**

Three fixes, in order of leverage: pass your rules with `--system-prompt`

, edit the file, or write an `AGENTS.md`

telling the agent how to interpret the ambiguous parts.

Two commands, a warehouse you can read, and roughly ten minutes of waiting produce a context layer that lives in git, gets reviewed like code, and carries executable checks. Whatever you point at it next - Claude Code, Cursor, a Slack bot, your own agent - starts from what your data means rather than from what its column names look like.

The tooling is [open source](https://github.com/bruin-data/bruin) and runs locally, so the cost of finding out whether your agent gets better is one schema and one afternoon.

An AI context layer is a machine-readable description of your tables: what each table represents, what every column means, which values are valid, and how tables relate. An AI agent reads it before writing SQL, so it stops guessing at column names and business meaning. With Bruin the context layer is a folder of plain YAML asset files in your git repository, which means you can review it in a pull request and edit it with any text editor.

Install the free, open-source Bruin CLI, import your schema with `bruin import database --connection <name> --schema <schema> <pipeline-path>`

, then run `bruin ai enhance <pipeline-path>`

. The enhance command pulls column statistics from your warehouse and uses your local AI CLI - Claude Code, Codex, OpenCode, or Cursor - to write descriptions, data quality checks, and tags directly into each asset file.

Yes. The Bruin CLI is open source under Apache 2.0, and both `bruin import database`

and `bruin ai enhance`

run locally with no Bruin account, signup, or credit card. You need one AI coding CLI installed and you pay your AI provider for the tokens it uses.

It sends schema plus aggregate column statistics such as row counts, null counts, distinct counts, and min/max ranges, along with the small samples needed to infer things like accepted values. It does not replicate your tables. Everything goes through the AI CLI already installed on your machine, so the traffic path is the one your coding agent already uses.

Snowflake, BigQuery, PostgreSQL, Redshift, Athena, Databricks, DuckDB, ClickHouse, Azure Synapse, MS SQL Server, and MongoDB. The generated asset type follows the connection, for example `sf.source`

, `bq.source`

, or `pg.source`

.

No. Import and enhance only read warehouse metadata and write YAML into a local folder. Keep dbt, Airflow, Fivetran, or hand-written SQL exactly where they are and use the context layer purely as documentation for your AI agent. If you later want the checks to run on a schedule, that is what `bruin run`

and Bruin Cloud are for.

A catalog stores metadata in a hosted service and exposes it through a UI and an API, which means an agent needs an integration to read it and a human needs a login to fix it. This context layer is text files next to your code: agents read them directly, humans edit them in a PR, and the quality checks are executable rather than descriptive.
