Managing Databricks Genie Agents as Code with Databricks Declarative Automation Bundles (DAB)
Databricks Genie Agents (formerly Genie Spaces) are domain-specific, no-code chat interfaces that let business users ask natural-language questions about their data and get back SQL queries, result tables, and visualizations. As a Genie Agent grows more sophisticated, with carefully crafted instructions, SQL filter snippets, example queries, and evaluation benchmarks, a new problem emerges: how do you manage all of that configuration reliably across environments?
If your team is editing agent instructions directly in the Genie UI, you have no version history, no code review, and no reliable path to promote changes from dev to production. One wrong update and there's no rollback.
In this post I'll show how to solve that using Declarative Automation Bundles (DAB) to manage a Genie Agent and its underlying Unity Catalog metric view entirely as code.
The problem with UI-only Genie Agent management
A Genie Agent ready for production carries a surprising amount of configuration:
- Text instructions: rules governing how the agent interprets questions, resolves ambiguity, and formats responses
- SQL filter snippets: pre-built filters users can reference naturally ("show me US revenue")
- Example queries: Q&A pairs that teach the agent how to respond to common questions
- Benchmark questions: ground truth SQL used to evaluate answer quality
- Column configs: controls format assistance (how values are displayed) and entity matching (so "germany" resolves to
Germanyin the Country dimension); entity matching requires format assistance to be enabled on the column
When all of this lives only in the UI, you get:
- No audit trail for who changed what and when
- No peer review before changes hit production
- No reliable way to promote the exact same configuration from dev to prod
- No rollback if a bad instruction update breaks query behaviour
The fix: treat the Genie Agent like any other piece of software, version-controlled, reviewed, and deployed through a pipeline.
Solution overview: Declarative Automation Bundles
Declarative Automation Bundles (DAB) is Databricks' infrastructure-as-code framework. It supports jobs, pipelines, dashboards, and as of CLI v1.10, Genie spaces as first-class resources.
The approach has two key pieces:
- UC Metric View: the semantic layer that backs the Genie Agent, defined in YAML
- Genie Agent: the agent configuration (instructions, snippets, benchmarks), exported from the workspace and committed as JSON
Both are versioned in git and deployed via databricks bundle deploy.
Repository structure
The full example is on GitHub: github.com/anhhchu/genie-agent-cicd
genie-agent-cicd/
├── databricks.yml # bundle config, targets (dev/prod)
├── prebuild.py # resolves catalog/schema and writes build/
│
├── resources/
│ ├── metric_view.job.yml # job: CREATE OR REPLACE metric view
│ └── tpcds_retail.genie_space.yml # Genie Agent resource definition
│
├── src/ # source of truth — uses ${catalog}/${schema} placeholders
│ ├── metric-view.yaml # metric view dimensions and measures (edit this)
│ └── tpcds_retail.geniespace.json # Genie Agent content (edit this)
│
└── build/ # generated by prebuild.py — gitignored
├── metric-view.yaml
├── create_metric_view.sql
└── tpcds_retail.geniespace.json
Two files are the source of truth: src/metric-view.yaml defines the semantic layer (dimensions, measures, synonyms), and src/tpcds_retail.geniespace.json holds the agent configuration (instructions, SQL snippets, benchmarks). A small Python script converts the YAML into the CREATE OR REPLACE VIEW ... WITH METRICS LANGUAGE YAML DDL before deployment. The Genie Agent and metric view are both deployed via databricks bundle deploy, with the metric view applied by running the bundle job.
Example dataset
The walkthrough below uses samples.tpcds_sf1, a TPC-DS retail benchmark dataset that comes pre-loaded in every Databricks workspace. It contains three sales fact tables (store_sales, catalog_sales, web_sales) plus standard dimension tables (date_dim, item, customer). No data setup is required. You can follow every step using this dataset as-is.
The same pattern applies to your own data: replace samples.tpcds_sf1 with your source tables, and replace <your_catalog>.<your_schema> with the catalog and schema where you want the base view and metric view to live.
Prerequisites
1. Clone the repo
git clone https://github.com/anhhchu/genie-agent-cicd
cd genie-agent-cicd2. Install the Databricks CLI
The bundle commands require Databricks CLI v0.205 or later.
macOS:
brew tap databricks/tap
brew install databricksLinux / macOS (curl):
curl -fsSL https://raw.githubusercontent.com/databricks/setup-cli/main/install.sh | shWindows:
winget install Databricks.DatabricksCLIVerify the installation:
databricks --version3. Configure workspace profiles
Each target in databricks.yml maps to a named profile in ~/.databrickscfg. Set up one profile per workspace using OAuth (recommended):
# Dev workspace
databricks auth login --host https://<dev-workspace>.cloud.databricks.com --profile DEFAULT
# Prod workspace
databricks auth login --host https://<prod-workspace>.cloud.databricks.com --profile PRODBoth commands open a browser for OAuth login and write the credentials to ~/.databrickscfg:
[DEFAULT]
host = https://<dev-workspace>.cloud.databricks.com
[PROD]
host = https://<prod-workspace>.cloud.databricks.comThen update databricks.yml to reference the correct profile per target:
targets:
dev:
workspace:
profile: DEFAULT
variables:
catalog: dev_catalog
schema: genie
prod:
workspace:
profile: PROD
variables:
catalog: prod_catalog
schema: genieStep 1: Create a unified base view (multi-fact workaround)
Unity Catalog metric views currently support a single source table or view. The TPC-DS schema has three separate fact tables: store_sales, catalog_sales, and web_sales. To use all three as the basis for one metric view, first create a base view that unions them:
CREATE VIEW <your_catalog>.<your_schema>.tpcds_all_sales AS
WITH s AS (
SELECT 'Store' AS channel, ss_sold_date_sk AS sold_date_sk, ss_item_sk AS item_sk,
ss_customer_sk AS customer_sk, ss_ticket_number AS order_number,
ss_quantity AS quantity, ss_ext_sales_price AS ext_sales_price,
ss_ext_discount_amt AS ext_discount_amt, ss_net_paid AS net_paid, ss_net_profit AS net_profit
FROM samples.tpcds_sf1.store_sales
UNION ALL
SELECT 'Catalog', cs_sold_date_sk, cs_item_sk, cs_bill_customer_sk, cs_order_number,
cs_quantity, cs_ext_sales_price, cs_ext_discount_amt, cs_net_paid, cs_net_profit
FROM samples.tpcds_sf1.catalog_sales
UNION ALL
SELECT 'Web', ws_sold_date_sk, ws_item_sk, ws_bill_customer_sk, ws_order_number,
ws_quantity, ws_ext_sales_price, ws_ext_discount_amt, ws_net_paid, ws_net_profit
FROM samples.tpcds_sf1.web_sales
)
SELECT
s.channel, s.order_number, s.quantity, s.ext_sales_price,
s.ext_discount_amt, s.net_paid, s.net_profit, s.customer_sk,
d.d_date AS sold_date, d.d_year AS sold_year, d.d_moy AS sold_month, d.d_qoy AS sold_quarter,
i.i_category AS category, i.i_brand AS brand, i.i_class AS class, i.i_product_name AS product_name
FROM s
JOIN samples.tpcds_sf1.date_dim d ON s.sold_date_sk = d.d_date_sk
JOIN samples.tpcds_sf1.item i ON s.item_sk = i.i_item_skThis view does three things:
- Unions the three sales fact tables and tags each row with a
channelcolumn (Store,Catalog,Web) - Joins the date dimension to produce human-readable date columns (
sold_date,sold_year,sold_month,sold_quarter) - Joins the item dimension to pull in
category,brand,class, andproduct_name
The result is a single flat view with one row per sales line item, exactly what a metric view source needs.
Note: Metric views are designed around a single logical source. Directly joining multiple independent fact tables inside a single metric view can cause fan-out and incorrect aggregations. The officially recommended approach is to create a bridge/base SQL view that flattens the facts to a well-defined grain first — which is exactly what
tpcds_all_salesdoes. For multi-grain analysis (e.g., orders + returns at different grains), consider Dashboard Relationships instead.
Step 2: Define your UC metric view in YAML
The metric view YAML defines dimensions, measures, and their synonyms. Synonyms are critical: they teach the Genie Agent which natural-language terms map to which columns.
# src/metric-view.yaml
version: 1.1
source: <your_catalog>.<your_schema>.tpcds_all_sales
dimensions:
- name: Channel
expr: channel
comment: "Sales channel: Store, Catalog, or Web"
synonyms:
- sales channel
- division
- segment
- name: Category
expr: category
synonyms:
- product category
- department
measures:
- name: Total Sales
expr: SUM(net_paid)
comment: "Total revenue after discounts, before tax"
synonyms:
- revenue
- net sales
- sales
- name: Profit Margin
expr: "SUM(net_profit) / NULLIF(SUM(net_paid), 0)"
comment: "Net profit as a fraction of total sales (0.15 = 15%)"
synonyms:
- margin
- profitabilitySource files use ${catalog} and ${schema} as placeholders — no environment-specific values are hardcoded. When you're ready to deploy, run prebuild.py to stamp the resolved values into build/:
python3 prebuild.py --target dev
# ✓ build/ ready (target: dev, my_catalog.my_schema)The script reads catalog and schema directly from the target's variables in databricks.yml and writes substituted copies of all three files to build/. The bundle resources point to build/, and build/ is gitignored — only the placeholder source files are committed.
Step 3: Export your Genie Agent from the workspace
If you already have a Genie Agent configured in the UI, the CLI can export it directly into your bundle. First, find the space ID in the browser URL:
https://<workspace>.cloud.databricks.com/genie/spaces/<SPACE_ID>
Then run:
databricks bundle generate genie-space \
--existing-id <SPACE_ID> \
--key tpcds_retailThis generates two files:
src/tpcds_retail.geniespace.json— the full agent configurationresources/tpcds_retail.genie_space.yml— the DAB resource definition
The .geniespace.json file is structured JSON, not a raw blob, so every section is directly editable:
{
"version": 2,
"instructions": {
"text_instructions": [
{
"id": "...",
"content": ["# Agent Instructions\r\n", "..."]
}
],
"sql_snippets": {
"filters": [...],
"measures": [...],
"expressions": [...]
},
"example_question_sqls": [...]
},
"benchmarks": {
"questions": [...]
},
"data_sources": {
"tables": [
{
"identifier": "...",
"column_configs": [...]
}
]
}
}Step 4: Configure the bundle for multiple environments
databricks.yml defines targets for each environment. A single --target flag promotes the exact same configuration to a different workspace with a different catalog.
The warehouse_id variable is declared at the top level with no default — each target sets its own lookup by warehouse name, which resolves against that target's workspace at deploy time:
bundle:
name: genie_agent_cicd
engine: direct # required for genie_spaces
variables:
warehouse_id:
description: SQL warehouse used to create the metric view and Genie Agent.
targets:
dev:
default: true
mode: development
workspace:
profile: DEFAULT
variables:
catalog: dev_catalog
schema: genie
warehouse_id:
lookup:
warehouse: Serverless Starter Warehouse
prod:
mode: production
workspace:
profile: PROD
root_path: /Workspace/Users/<user>@databricks.com/.bundle/${bundle.name}/${bundle.target}
variables:
catalog: prod_catalog
schema: genie
warehouse_id:
lookup:
warehouse: <prod_warehouse_name>Step 5: Deploy
# Generate build/ for dev and deploy
python3 prebuild.py --target dev
databricks bundle deploy
databricks bundle run metric_view
# Promote to prod — prebuild stamps prod catalog/schema into build/ first
python3 prebuild.py --target prod
databricks bundle deploy --target prod
databricks bundle run metric_view --target prodThat's it. The Genie Agent and metric view are now live in the target workspace. Because prebuild.py writes to build/ each time, there's no risk of committing environment-specific values or having to manually reset between targets.
Day-2 workflows
Updating the Genie Agent instructions
Edit src/tpcds_retail.geniespace.json — for example, to add a new SQL filter snippet (use ${catalog}.${schema} for any table references):
"sql_snippets": {
"filters": [
{
"id": "...",
"display_name": "Store channel only",
"sql": ["`channel` = 'Store'"],
"synonyms": ["in-store", "brick and mortar"]
}
]
}Then:
python3 prebuild.py --target dev
databricks bundle deployUpdating a dimension or measure
Edit src/metric-view.yaml, then:
python3 prebuild.py --target dev
databricks bundle deploy
databricks bundle run metric_viewPulling UI changes back into source control
If someone made changes directly in the Genie UI (it happens), re-export:
databricks bundle generate genie-space \
--existing-id <SPACE_ID> \
--key tpcds_retailReview the diff with git diff before committing.
Managing multiple Genie Agents
Each agent uses its own --key, so multiple agents coexist in the same bundle without conflict:
databricks bundle generate genie-space \
--existing-id <SPACE_ID_B> \
--key finance_agentAdd the new resource to databricks.yml:
include:
- resources/metric_view.job.yml
- resources/tpcds_retail.genie_space.yml
- resources/finance_agent.genie_space.ymlEach agent's files are completely independent — src/finance_agent.geniespace.json and resources/finance_agent.genie_space.yml.
Why this pattern works
The metric view YAML is human-friendly. Dimensions, measures, and synonyms are readable and diff well in pull requests. A reviewer can see exactly which synonym was added or which measure expression changed.
The .geniespace.json is structured JSON, not a blob. When DAB introduced the genie-space resource type, it made the agent config a proper file rather than a stringified JSON-inside-JSON. Every section (instructions, snippets, benchmarks, column configs) is a first-class JSON object you can edit and review.
databricks.yml is the single source of environment config. Catalog and schema are declared once per target. prebuild.py reads them and stamps the values into build/ before each deploy — no hardcoded values in committed files, no manual reset when switching between dev and prod.
The separation is clean. The metric view (semantic layer) and the agent config (behaviour layer) are in separate files with separate edit workflows. Changing a synonym does not require touching the agent instructions. Adding a benchmark question does not require regenerating SQL.
Get started
The full working example is available on GitHub:
github.com/anhhchu/genie-agent-cicd
It includes the TPC-DS retail sales Genie Agent and metric view as a ready-to-deploy example. Clone it, swap in your workspace and catalog, import your own Genie Agent with bundle generate, and you have a CI/CD-ready Genie Agent in minutes.