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.

1 · Setup 2 · Semantic View 3 · The Agent 4 · Evaluation 5 · Ship
The contract: before each phase, decide whether to infer values from your data or specify them. At the end of each build phase, run the materialize command and confirm success. Never advance past a failing gate.

New here? Start with Setup and work through the tabs in order.

Step 1 of 5

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.

RequirementCheck
Dependencies installeddbt deps has run (the dbt_semantic_view package is present)
env.yml configuredReal DBT_DATABASE / DBT_WAREHOUSE per environment; profiles.yml reads them via env_var()
Source data existsThe tables you'll model are queryable by your role
Cortex Agents enabledAccount has Cortex Agents + CREATE SEMANTIC VIEW / CREATE AGENT privileges
External Access IntegrationExists for dbt deps package downloads
Snowflake CLI ≥ 3.21Only 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.

EnvironmentDatabaseSchemaRole
dev (default)DEV_DBCURRENT_USER() (per-developer)CURRENT_ROLE()
stagingSTAGING_DBCORTEX_AGENTSSYSADMIN
prodPROD_DBCORTEX_AGENTSSYSADMIN

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
The contract: before each phase, decide whether to infer values from your data or specify them. At the end of each build phase, run the materialize command and confirm success. Never advance past a failing gate.
0

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.

Gate  Infer these from your environment/conventions, or specify them explicitly?

Done when: environment, target, and names are confirmed and written down for reuse in every later phase.

Step 2 of 5

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.

1

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 →

Gate  Infer dimensions/metrics and the AI_* rules from your sources, or specify them?
Demo · materialize

Build the semantic view and confirm a sample SELECT ... FROM SEMANTIC_VIEW(...) returns rows.

dbt build --select sv_<name>
Why this matters: the semantic view is where Cortex Analyst gets its accuracy. Business names, curated synonyms, verified queries, and the two AI_* clauses all live here. Exhibit A covers the high-leverage practices in full.
Step 3 of 5

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.

One spec, three layers: Phases 2–4 all edit the inline 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.
2

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).

Gate  Infer orchestration logic from your questions and tools, or specify it?

Materialize: none yet; continue to Phase 3.

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.

Rule of thumb: if the instruction affects what the agent does or which tool it picks, it's orchestration. If it affects how the output looks, it's response.
Gate  Infer response style from the use case and audience, or specify it?

Materialize: none yet; continue to Phase 4.

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 →

Gate  Infer tool descriptions from the semantic view and use case, or specify them?
Demo · create the agent

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.

Step 4 of 5

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.

5

Evaluation → The Verdict

Turn the confirmed questions and answers into a measurable evaluation run: scores you can track, compare, and improve against.

MetricGround truth?What it measures
answer_correctnessYesHow closely the reply matches the expected answer
logical_consistencyNoWhether the reasoning chain is coherent and contradiction-free
tool_selection_accuracyYesWhether the agent called the expected tools
tool_execution_accuracyYesWhether 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.

Gate  Infer ground-truth answers from your data, or specify them?
Demo · run the evaluation
dbt seed
dbt run --select eval_dataset
dbt run-operation run_evaluation --args '{agent_name: <agent>, run_name: <run>, config_file: <config>.yml}'
Demo · check status & scores
-- 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.

Visual
Drop the evaluation results here: the scores table or a bar of answer-correctness by question. This is the "verdict" money shot.
Step 5 of 5

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.

MoveHow
Iterate livedbt run-operation deploy_<agent> --args '{alter: true}' (zero-downtime)
Promote to prodRe-run Phases 1, 4, 5 with --env prod (CLI) or the Workspace environment selector
Ship to usersSnowflake 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)
ScheduleSnowflake 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)

SymptomRuling
Eval errors: "dataset already exists"Remove the dataset: block from the config after the first successful run
ground_truth rejected / wrong typeMust be VARIANT: keep PARSE_JSON(...) in eval_dataset.sql; do not switch to OBJECT_CONSTRUCT
Agent can't find the semantic viewSpec must reference <<DATABASE>>.<<SCHEMA>>.SV_<NAME> (tokens substituted at deploy)
Poor tool routingTighten each tools[].description: what it's for AND what it's not for
dbt deps can't reach the hubProvide the External Access Integration name
snow dbt rejects --env or mangles --argsCLI 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
That's the lifecycle. Business questions became a governed, evaluated, shipped agent, all built by one dbt project. Head back to The Case for the summary, or revisit any step from the tabs above.