Building the Agent, Phase by Phase
Five phases take you from a list of business questions to a shipped, evaluated agent. Every object is built by the same dbt project. Each phase states what you decide, which files change, and the exact command to run live.
New here? Start with Setup and work through the tabs in order.
Setup & the Environment Model
Confirm the ground rules and lock in the environment before you build anything. One codebase targets dev, staging, and prod, and Phase 0 pins down the names everything else references.
Prerequisites
Confirm the ground rules before Phase 0. If anything is missing, resolve it first.
| Requirement | Check |
|---|---|
| Dependencies installed | dbt deps has run (the dbt_semantic_view package is present) |
env.yml configured | Real DBT_DATABASE / DBT_WAREHOUSE per environment; profiles.yml reads them via env_var() |
| Source data exists | The tables you'll model are queryable by your role |
| Cortex Agents enabled | Account has Cortex Agents + CREATE SEMANTIC VIEW / CREATE AGENT privileges |
| External Access Integration | Exists for dbt deps package downloads |
| Snowflake CLI ≥ 3.21 | Only for the CLI path; env.yml flags need 3.21+ (snow --version) |
One codebase, three environments
The template is environment-driven. env.yml defines dev, staging, and prod; Snowflake resolves it at run time and injects values that profiles.yml reads with env_var(). You never hardcode a target; you pick an environment and the same code runs against it.
| Environment | Database | Schema | Role |
|---|---|---|---|
dev (default) | DEV_DB | CURRENT_USER() (per-developer) | CURRENT_ROLE() |
staging | STAGING_DB | CORTEX_AGENTS | SYSADMIN |
prod | PROD_DB | CORTEX_AGENTS | SYSADMIN |
Per-developer schemas in dev mean each engineer's agents, semantic views, and evaluations are isolated, so nobody overwrites anyone else while iterating.
# How the target resolves at run time
EXECUTE DBT PROJECT ... ENVIRONMENT = 'dev'
→ env.yml picks 'dev', evaluates {{ select CURRENT_USER() }}
→ injects DBT_DATABASE / DBT_SCHEMA / DBT_WAREHOUSE / DBT_ROLE
→ profiles.yml reads env_var() → target.database / schema / ...
→ everything dbt builds lands in that target; the agent macro
substitutes the same values into the agent spec
Orientation
Lock in the environment, the target, and the object names everything else references.
Confirm the environment (default dev), the resolved <db>.<schema>, and pick names for the semantic view, the agent, and the evaluation run.
Done when: environment, target, and names are confirmed and written down for reuse in every later phase.
Business Questions to a Semantic View
Capture the questions the agent must answer, then model the data to support them. The semantic view is the accuracy engine, so this is where the build really begins.
Business Questions → Semantic View
Capture the ~10 questions the agent must answer, then model the data to support them.
Edit models/sources.yml, optional models/staging/stg_*.sql, and the semantic view models/semantic_views/sv_<name>.sql. Author the clauses in this enforced order:
TABLES → RELATIONSHIPS → FACTS → DIMENSIONS → METRICS → COMMENT
→ AI_SQL_GENERATION → AI_QUESTION_CATEGORIZATION → AI_VERIFIED_QUERIES
The two AI_* clauses are optional and additive: how the agent writes SQL, and what to do with a question before SQL is attempted. See Exhibit A for the deep dive →
Build the semantic view and confirm a sample SELECT ... FROM SEMANTIC_VIEW(...) returns rows.
dbt build --select sv_<name>
AI_* clauses all live here. Exhibit A covers the high-leverage practices in full.The Agent: Orchestration, Response, Tools
Phases 2, 3, and 4 all author the same inline spec, kept in separate layers. The agent object is created once, at the end of Phase 4.
spec inside the deploy_<agent> macro in agents/<agent>.sql. Keeping orchestration, response, and tool descriptions separate is the single best way to avoid poor answers. Exhibit B is the full deep dive.Agent Reasoning → Orchestration
Define how the agent plans and routes between tools, the highest-impact accuracy lever after tool descriptions.
Phases 2–4 all author into the same place: the inline spec inside the deploy_<agent> macro in agents/<agent>.sql. The agent object is created once, at the end of Phase 4.
Put tool routing, default time windows, multi-step sequencing, conditional logic, and error/empty-result handling in instructions.orchestration. Keep tone and formatting out (that's Phase 3).
Materialize: none yet; continue to Phase 3.
Agent Response → Response Instructions
Define how the agent formats and communicates answers, separate from tool routing.
Put tone, data presentation (tables vs. charts, units), response structure by question type, disclaimers, and error-message style in instructions.response. Add 3+ representative instructions.sample_questions.
Materialize: none yet; continue to Phase 4.
Agent Tools → Tool Descriptions (create the agent)
Wire up tools and their resources, then create the agent. Tool descriptions are the single most critical factor for accuracy.
For each tool write the 4-part description: what data it accesses, when to use it, when NOT to use it, and input/format guidance. Point the Analyst tool at <<DATABASE>>.<<SCHEMA>>.SV_<NAME> and set execution_environment.warehouse to <<WAREHOUSE>>. See Exhibit B for the deep dive →
The wrapper defines the spec inline and calls create_agent (CREATE OR REPLACE). No --args needed.
dbt run-operation deploy_<agent>
# Zero-downtime edit to a live agent later:
dbt run-operation deploy_<agent> --args '{alter: true}'
Done when: <db>.<schema>.<AGENT> exists and answers a sample question.
Evaluation: The Verdict
Turn the confirmed questions and answers into a measurable evaluation run. Trust stops being a gut feeling and becomes a score you can track, compare, and hold the line on.
Evaluation → The Verdict
Turn the confirmed questions and answers into a measurable evaluation run: scores you can track, compare, and improve against.
| Metric | Ground truth? | What it measures |
|---|---|---|
answer_correctness | Yes | How closely the reply matches the expected answer |
logical_consistency | No | Whether the reasoning chain is coherent and contradiction-free |
tool_selection_accuracy | Yes | Whether the agent called the expected tools |
tool_execution_accuracy | Yes | Whether tool calls had the right inputs/outputs |
Start with AC-track rows (answer_correctness + logical_consistency); add TEA-track rows for tool metrics once the agent is stable. Aim for 15–20 questions across easy/medium/hard.
dbt seed
dbt run --select eval_dataset
dbt run-operation run_evaluation --args '{agent_name: <agent>, run_name: <run>, config_file: <config>.yml}'
-- status any time
CALL EXECUTE_AI_EVALUATION('STATUS', {'run_name': '<run>'}, NULL);
-- scores after completion
SELECT * FROM TABLE(SNOWFLAKE.LOCAL.GET_AI_EVALUATION_DATA(
'<db>', '<schema>', '<AGENT>', 'CORTEX AGENT', '<run>'));
Done when: scores return and you can identify the lowest-scoring questions to drive the next cycle.
Ship & Automate
With a verdict in hand, promote the same code to production, ship the agent where users work, and let CI/CD and scheduled tasks keep it fresh.
Closing Arguments: ship & automate
With a verdict in hand (target ≥95% answer correctness), promote, ship, and automate: same code, prod target.
| Move | How |
|---|---|
| Iterate live | dbt run-operation deploy_<agent> --args '{alter: true}' (zero-downtime) |
| Promote to prod | Re-run Phases 1, 4, 5 with --env prod (CLI) or the Workspace environment selector |
| Ship to users | Snowflake Intelligence, Microsoft Teams, the Cortex Agent REST API, or MCP |
| Automate (CI/CD) | GitHub Actions: PR builds on dev, merge deploys to prod (the .github/workflows/*.example files) |
| Schedule | Snowflake Tasks running EXECUTE DBT PROJECT ... ARGS='build --target prod' |
-- schedule daily builds + evaluation as Snowflake Tasks
CREATE OR REPLACE TASK daily_cortex_build
WAREHOUSE = ANALYTICS_WH
SCHEDULE = 'USING CRON 0 6 * * * America/Denver'
AS EXECUTE DBT PROJECT DEV_DB.CORTEX_AGENTS.CORTEX_LIFECYCLE ARGS='build --target prod';
ALTER TASK daily_cortex_build RESUME;
The CLI path (snow ≥ 3.21)
Inside a Snowsight Workspace you run dbt directly and pick the environment in the run panel. On the CLI, deploy once then execute, with two rules learned the hard way:
# deploy the project object (--default-env sets the compile/run env)
snow dbt deploy cortex_lifecycle --source . \
--default-env dev --external-access-integration dbt_ext_access --force
# build with an environment. --env MUST come BEFORE the project name;
# use the fully-qualified name so EXECUTE DBT has a database context.
snow dbt execute --env dev DB.SCHEMA.cortex_lifecycle build
# deploy the agent -- the wrapper macro carries the spec, no --args
snow dbt execute --env prod DB.SCHEMA.cortex_lifecycle run-operation deploy_<agent>
Objections (troubleshooting)
| Symptom | Ruling |
|---|---|
| Eval errors: "dataset already exists" | Remove the dataset: block from the config after the first successful run |
ground_truth rejected / wrong type | Must be VARIANT: keep PARSE_JSON(...) in eval_dataset.sql; do not switch to OBJECT_CONSTRUCT |
| Agent can't find the semantic view | Spec must reference <<DATABASE>>.<<SCHEMA>>.SV_<NAME> (tokens substituted at deploy) |
| Poor tool routing | Tighten each tools[].description: what it's for AND what it's not for |
dbt deps can't reach the hub | Provide the External Access Integration name |
snow dbt rejects --env or mangles --args | CLI older than 3.21; upgrade, or run from a Snowsight Workspace |
| "No such option '--env'" | --env was placed after the project name; it must come before |
| "session does not have a current database" | Use the fully-qualified project name <db>.<schema>.<project> |
Macro fails on load_file_contents / {% include %} | No runtime file read; specs live in the deploy_<agent> wrapper macro, not a raw .yml |