Snowflake

Snowflake Cortex Agent SDK: Building AI Agents on Enterprise Data with Python

A practical guide to the Snowflake Cortex Agent SDK, covering Python setup, Cortex Analyst semantic modeling, Cortex Search, streaming, background execution, security, and the governance considerations that make AI agents reliable on enterprise data.

Amit SinghOctober 17, 202617 min read
Amit Singh - Founder, DE Copilot | Data Platform Architect and Lead Data Engineer

Most enterprise AI agent projects stall at the same point: the agent can answer questions about public knowledge, but it cannot answer questions about your data. The moment you need it to reason over a claims table, a customer dimension, or a policy ledger, you are back to writing custom retrieval logic, managing credentials, and hoping the model does not hallucinate a column name.

Snowflake's Cortex Agent SDK, which entered Public Preview on October 13, 2026, is a direct response to that problem. It gives Python applications a structured way to invoke Cortex Analyst for structured data reasoning and Cortex Search for unstructured document retrieval - both within the same agent loop, both governed by Snowflake's existing access controls.

This article is a practical walkthrough of the SDK: what it does, how to set it up, and what you need to think about before deploying it against production data.

All table names, schema paths, and example values in this article are synthetic demonstration material. No employer, client, internal platform, or production data is referenced. Code examples are illustrative; test against your own Snowflake environment before deploying. Estimated reading time: 14-16 minutes

What the Cortex Agent SDK Actually Is

Before getting into setup, it is worth being precise about what the SDK is and what it is not.

The Cortex Agent SDK is a Python library that lets your application send a natural-language query to a Cortex Agent endpoint and receive a structured, streamed response. The agent, running inside Snowflake, decides which tools to call - Cortex Analyst for SQL-answerable questions, Cortex Search for document retrieval - and returns both the answer and the reasoning trace.

It is not a general-purpose LLM orchestration framework. It does not replace LangChain or LlamaIndex for arbitrary tool use. Its scope is deliberate: governed reasoning over data that lives in Snowflake, using semantic models and search services that you define and control.

The distinction from the REST-based Cortex Agents API is also worth noting. The REST API has been available for some time. The SDK wraps that API with Python-native ergonomics: streaming generators, async support, typed response objects, and conversation state management. If you are already calling the REST API directly, the SDK reduces boilerplate. If you are starting fresh, the SDK is the right entry point.


Architecture Overview

A Cortex Agent SDK application has four layers:

Your Python Application

|

v

Cortex Agent SDK (snowflake-cortex-agent-sdk)

|

v

Cortex Agent Endpoint (Snowflake-hosted, REST)

/ \

/ \

v v

Cortex Cortex

Analyst Search

| |

v v

Semantic Search

Model Service

(.yaml) (indexed

docs)

Your application sends a message. The SDK delivers it to the agent endpoint. The agent selects tools, calls them, and streams the response back. Cortex Analyst translates natural language into SQL against your semantic model and executes it. Cortex Search retrieves relevant document chunks from an indexed corpus. The agent synthesizes both into a coherent answer.

The semantic model is the critical governance layer. It defines which tables and columns the agent can reason about, what the business terms mean, and what filters are always applied. Without a well-defined semantic model, Cortex Analyst is guessing at your schema. With one, it is constrained to the vocabulary and logic you have approved.


Prerequisites

Before writing any Python, you need three things in place inside Snowflake:

1. A Cortex Analyst semantic model

A .yaml file stored in a Snowflake stage that describes your tables, columns, business metrics, and relationships. This is not optional. Cortex Analyst will not generate SQL without a semantic model to reason against.

2. A Cortex Search service

A search service created over a table or set of documents you want the agent to retrieve from. This is optional if your use case is purely structured-data reasoning, but most enterprise agents need both.

3. A Cortex Agent

An agent object created in Snowflake that references your semantic model and search service. The agent has a name, a set of tools, and an optional system prompt.

If you have not created these yet, the Cortex Analyst documentation and Cortex Search documentation cover the setup steps. This article assumes they exist and focuses on the SDK layer.


Python Environment Setup

Install the SDK

pip install snowflake-cortex-agent-sdk

The SDK requires Python 3.9 or above. It depends on snowflake-connector-python for authentication and httpx for streaming HTTP. Both are installed automatically.

Authenticate

The SDK uses the same authentication mechanisms as the Snowflake Python connector. For local development, key-pair authentication or browser-based SSO are the most practical options. For deployed applications, use a service account with key-pair authentication or Snowflake's OAuth integration.

from snowflake.cortex.agent import CortexAgentClient

from snowflake.connector import connect

connection = connect(

account="your-account-identifier",

user="your-service-user",

private_key_file="/path/to/rsa_key.p8",

private_key_file_pwd=None, # or your passphrase

warehouse="ANALYST_WH",

database="CLAIMS_DB",

schema="ANALYTICS",

role="CORTEX_AGENT_ROLE",

)

client = CortexAgentClient(connection=connection)

The role you specify must have USAGE on the warehouse, SELECT on the tables referenced in your semantic model, and USAGE on the Cortex Agent object. Principle of least privilege applies here: the agent role should not have write access to any table it reasons about.


Defining the Semantic Model

The semantic model is a .yaml file that tells Cortex Analyst what your data means. For a synthetic insurance claims dataset, a minimal model looks like this:

name: claims_analytics

description: >

Semantic model for P&C insurance claims analytics.

Covers open claims, closed claims, reserve amounts,

and adjuster performance metrics.

tables:

- name: FACT_CLAIMS

description: One row per claim. Grain is claim_id.

base_table:

database: CLAIMS_DB

schema: ANALYTICS

table: FACT_CLAIMS

columns:

- name: claim_id

description: Unique claim identifier. Format CLM-YYYY-NNNNNN.

data_type: VARCHAR

- name: policy_number

description: Associated policy. Foreign key to DIM_POLICIES.

data_type: VARCHAR

- name: date_of_loss

description: Date the loss event occurred. ISO 8601.

data_type: DATE

- name: claimed_amount

description: Amount claimed by the policyholder in USD.

data_type: NUMBER

- name: reserve_amount

description: Current reserve set by the adjuster in USD.

data_type: NUMBER

- name: claim_status

description: >

Current status. Valid values: OPEN, CLOSED, PENDING_REVIEW,

SUBROGATION, DENIED.

data_type: VARCHAR

- name: adjuster_id

description: Assigned adjuster. Foreign key to DIM_ADJUSTERS.

data_type: VARCHAR

- name: DIM_ADJUSTERS

description: Adjuster reference data.

base_table:

database: CLAIMS_DB

schema: ANALYTICS

table: DIM_ADJUSTERS

columns:

- name: adjuster_id

data_type: VARCHAR

- name: adjuster_name

data_type: VARCHAR

- name: region

data_type: VARCHAR

metrics:

- name: total_claimed_amount

description: Sum of claimed_amount across all claims in scope.

expr: SUM(claimed_amount)

data_type: NUMBER

- name: average_reserve_per_open_claim

description: Average reserve amount for claims with status OPEN.

expr: AVG(reserve_amount)

filters:

- expr: "claim_status = 'OPEN'"

data_type: NUMBER

- name: open_claim_count

description: Count of claims currently in OPEN status.

expr: COUNT(claim_id)

filters:

- expr: "claim_status = 'OPEN'"

data_type: NUMBER

The quality of this file determines the quality of every answer the agent produces. Vague column descriptions produce vague SQL. Precise descriptions with valid-value lists and business-rule filters produce SQL you can trust. This is where the governance work actually happens - not in the agent configuration, but in the semantic model.


Sending a Query: Synchronous

For request-response patterns where you do not need streaming, the synchronous interface is straightforward:

response = client.run(

agent_name="CLAIMS_ANALYTICS_AGENT",

messages=[

{

"role": "user",

"content": "What is the total claimed amount for open claims in the Northeast region this quarter?"

}

]

)

print(response.answer)

print(response.sql) # The SQL Cortex Analyst generated

print(response.citations) # Source references from Cortex Search, if invoked

The response.sql field is particularly useful for auditability. You can log it, display it to the user, or run it through your own validation layer before trusting the answer. This is one of the meaningful differences between Cortex Analyst-backed agents and black-box LLM responses: the reasoning is inspectable.


Sending a Query: Streaming

For user-facing applications where latency matters, streaming returns tokens as they are generated rather than waiting for the full response:

stream = client.stream(

agent_name="CLAIMS_ANALYTICS_AGENT",

messages=[

{

"role": "user",

"content": "Summarize the top five adjusters by open claim count and average reserve."

}

]

)

for chunk in stream:

if chunk.type == "text":

print(chunk.content, end="", flush=True)

elif chunk.type == "sql":

# Cortex Analyst emitted the SQL it is about to execute

print(f"\n[SQL]: {chunk.content}\n")

elif chunk.type == "citation":

print(f"\n[Source]: {chunk.content}")

The chunk types let your application handle different parts of the response differently. You might display text inline, render SQL in a collapsible code block, and show citations as footnotes. The streaming interface gives you that control.


Multi-Turn Conversation Management

The SDK maintains conversation context across turns by passing the message history back with each request. This is the same pattern used by most LLM APIs:

conversation = []

def ask(question: str) -> str:

conversation.append({"role": "user", "content": question})

response = client.run(

agent_name="CLAIMS_ANALYTICS_AGENT",

messages=conversation

)

conversation.append({"role": "assistant", "content": response.answer})

return response.answer

Turn 1

print(ask("How many open claims do we have in the Northeast?"))

Turn 2 - agent has context from turn 1

print(ask("Break that down by adjuster."))

Turn 3 - agent still has context

print(ask("Which of those adjusters has the highest average reserve?"))

The agent uses the conversation history to resolve pronouns and implicit references. "Break that down" in turn 2 refers to the Northeast open claims from turn 1. This works because the semantic model constrains the scope - the agent is not free-associating across your entire database, it is reasoning within the tables and metrics you defined.

For long-running sessions, you will want to implement a context window management strategy. The agent endpoint has a token limit on the message history it accepts. A sliding window that retains the last N turns, or a summarization step that compresses older turns, is the standard approach.


Background Execution for Long-Running Queries

Some analytical queries take time - aggregations over large fact tables, complex joins, or queries that trigger warehouse scaling. The SDK supports background execution for these cases:

job = client.submit(

agent_name="CLAIMS_ANALYTICS_AGENT",

messages=[

{

"role": "user",

"content": "Generate a full reserve adequacy report for all open claims filed in the last 90 days, grouped by region and claim type."

}

]

)

print(f"Job submitted: {job.job_id}")

Poll for completion

import time

while not job.is_complete():

time.sleep(5)

job.refresh()

result = job.result()

print(result.answer)

Background jobs are appropriate when the query is expected to run for more than a few seconds and the user does not need to wait for a streaming response. The job ID can be stored and the result retrieved later, which is useful for scheduled report generation or batch analytical workflows.


Combining Cortex Analyst and Cortex Search

The real value of the agent architecture is the ability to answer questions that require both structured data and unstructured documents. A claims analyst might ask: "What is the total reserve for claims where the adjuster notes mention roof damage?"

Answering that question requires:

  • ›Cortex Search to retrieve claims where adjuster notes contain "roof damage"
  • ›Cortex Analyst to sum the reserve amounts for those claim IDs

The agent handles this routing automatically. You do not write the orchestration logic. You define the tools - the semantic model and the search service - and the agent decides which to call and in what order.

To enable both tools, your agent definition in Snowflake references both:

-- Illustrative DDL; verify against current Snowflake documentation

CREATE OR REPLACE CORTEX AGENT CLAIMS_ANALYTICS_AGENT

TOOLS = (

CORTEX_ANALYST_TOOL(

SEMANTIC_MODEL = '@CLAIMS_DB.ANALYTICS.MODELS/claims_analytics.yaml'

),

CORTEX_SEARCH_TOOL(

SERVICE = CLAIMS_DB.ANALYTICS.ADJUSTER_NOTES_SEARCH

)

)

SYSTEM_PROMPT = 'You are a claims analytics assistant. Answer questions about P&C insurance claims using the provided data tools. Always cite the SQL or document source for your answer. Do not speculate about data that is not in the tools.';

The system prompt is worth treating carefully. It sets the agent's behavior, constrains its scope, and establishes the citation requirement. A system prompt that says "always cite your source" produces auditable answers. One that says "be helpful" produces answers that may be correct but are harder to verify.


Security and Access Control

The Cortex Agent SDK inherits Snowflake's access control model. The connection you pass to CortexAgentClient determines what the agent can see. If the role has SELECT on FACT_CLAIMS but not on DIM_POLICIES, the agent cannot join to the policy dimension - even if the semantic model references it.

This is the correct behavior. It means your existing row-level security policies, column masking policies, and object-level grants apply to agent queries exactly as they apply to direct SQL queries. There is no separate agent permission layer to maintain.

A few specific considerations:

Row access policies apply to the tables the agent queries. If a row access policy restricts an adjuster to their own region's claims, the agent will only see that region's data when running under that adjuster's credentials. This is the right behavior for user-facing agent applications. Column masking policies apply to sensitive columns. If policy_number is masked for non-privileged roles, the agent will see the masked value. Design your semantic model to avoid asking the agent to reason about masked columns where the masking would produce incorrect aggregations. Network policies apply to the connection. If your Snowflake account has network policies restricting access by IP range, the machine running the SDK must be within the allowed range. Secrets management: Do not embed Snowflake credentials in application code. Use environment variables, a secrets manager, or Snowflake's OAuth integration. The private key file path in the connection example above should point to a file that is not committed to source control.

Semantic Accuracy and Validation

The most common failure mode for Cortex Analyst-backed agents is not a technical failure - it is a semantic one. The agent generates syntactically valid SQL that answers the wrong question because the semantic model was ambiguous.

Consider a column named amount. Is that the claimed amount, the paid amount, the reserve amount, or the subrogation recovery? If the semantic model does not distinguish them clearly, the agent will guess. Sometimes it will guess correctly. Sometimes it will not.

The practices that reduce semantic errors:

Use precise, unambiguous column descriptions. "Amount claimed by the policyholder at first notice of loss, before any adjustment, in USD" is better than "claim amount." List valid values for categorical columns. A claim_status column with the description "Current status. Valid values: OPEN, CLOSED, PENDING_REVIEW, SUBROGATION, DENIED" constrains the agent's filter generation. Define metrics explicitly rather than relying on the agent to construct them. A total_claimed_amount metric defined as SUM(claimed_amount) is more reliable than asking the agent to figure out the right aggregation. Log and review the generated SQL. The response.sql field gives you the SQL the agent produced. Build a logging layer that captures it, and periodically review it for correctness. Incorrect SQL that produces a plausible-looking answer is harder to catch than an error. Test with known-answer questions. Before deploying an agent to users, build a test suite of questions with known correct answers. Run the agent against them and compare. This is the closest equivalent to unit testing for semantic accuracy.

Observability and Cost

Observability

The SDK does not include built-in observability tooling. You are responsible for logging the inputs, outputs, SQL, citations, and latency of each agent call. A minimal logging wrapper:

import logging

import time

from dataclasses import dataclass, field

from typing import Optional

logger = logging.getLogger("cortex_agent")

@dataclass

class AgentCallRecord:

agent_name: str

question: str

answer: Optional[str] = None

sql: Optional[str] = None

latency_ms: Optional[float] = None

error: Optional[str] = None

turn: int = 1

def ask_with_logging(

client: "CortexAgentClient",

agent_name: str,

messages: list,

turn: int = 1

) -> "AgentResponse":

question = messages[-1]["content"] if messages else ""

record = AgentCallRecord(agent_name=agent_name, question=question, turn=turn)

start = time.monotonic()

try:

response = client.run(agent_name=agent_name, messages=messages)

record.answer = response.answer

record.sql = response.sql

record.latency_ms = (time.monotonic() - start) * 1000

logger.info("agent_call", extra={"record": record.__dict__})

return response

except Exception as exc:

record.error = str(exc)

record.latency_ms = (time.monotonic() - start) * 1000

logger.error("agent_call_error", extra={"record": record.__dict__})

raise

Structured logging to a system like Snowflake itself (via a log table), Datadog, or your preferred observability platform gives you the data you need to understand agent behavior over time.

Cost

Cortex Agent SDK calls consume Snowflake credits through two mechanisms: the warehouse compute used to execute the SQL that Cortex Analyst generates, and the Cortex AI token consumption for the LLM reasoning steps.

The warehouse cost is the same as running the equivalent SQL directly. The AI token cost depends on the length of the conversation history, the complexity of the question, and whether Cortex Search is invoked. Longer conversation histories cost more because the full history is sent with each request.

Practical cost controls:

  • •Use a dedicated warehouse for agent queries with auto-suspend set to a short interval (60-120 seconds).
  • •Implement conversation history limits to cap token consumption per session.
  • •Monitor SNOWFLAKE.ACCOUNT_USAGE.CORTEX_ANALYST_USAGE_HISTORY and the equivalent Cortex Search usage view for per-query cost attribution.
  • •Consider caching responses for identical questions in read-heavy applications.

What This Means for Governed Data Delivery

The Cortex Agent SDK is a useful capability, but it does not change the fundamental requirement for governed data delivery. An agent that reasons over a poorly defined semantic model produces unreliable answers. An agent that has access to columns it should not see creates a data governance risk. An agent whose SQL is never reviewed can silently answer the wrong question for months.

The practices that make agents reliable are the same practices that make any data product reliable: precise definitions, explicit business rules, access controls that match the data's sensitivity, and a validation layer that catches errors before they reach users.

The semantic model is the place where that governance work happens for Cortex Analyst. It is the equivalent of a source-to-target mapping for the agent layer - a document that says what the data means, what the valid values are, and what the business rules require. Building it carefully is not optional work that can be deferred. It is the work that determines whether the agent is useful or dangerous.


FAQ

What is the Snowflake Cortex Agent SDK?

A Python library that lets applications send natural-language queries to a Cortex Agent endpoint and receive structured, streamed responses. The agent uses Cortex Analyst for SQL-based structured data reasoning and Cortex Search for document retrieval.

How does the Cortex Agent SDK differ from the Cortex Agents REST API?

The REST API is the underlying service. The SDK wraps it with Python-native ergonomics: streaming generators, typed response objects, async support, and conversation state management. The SDK is the recommended entry point for new Python applications.

Does the Cortex Agent SDK support Python?

Yes. Python 3.9 and above. The SDK is a Python package installed via pip. It uses the Snowflake Python connector for authentication.

Is the Cortex Agent SDK production-ready?

As of October 13, 2026, the SDK is in Public Preview. Public Preview features are generally stable and suitable for non-critical workloads, but Snowflake may make breaking changes before General Availability. Review the Snowflake feature lifecycle documentation and your organization's policy on preview features before deploying to production.

Can the agent write data or only read it?

Cortex Analyst generates SELECT statements only. The agent cannot insert, update, delete, or modify data. The role you use for the connection should have SELECT only on the relevant tables.

What happens if the agent cannot answer a question?

The agent returns a response indicating it could not find relevant information in the available tools. It does not fabricate an answer from general knowledge - the system prompt and tool constraints prevent that. You should test this behavior with out-of-scope questions before deploying.


Official References


The Cortex Agent SDK entered Public Preview on October 13, 2026. Verify current feature availability, API signatures, and credit consumption rates against the official Snowflake documentation before building production workloads.

Try DE Copilot

Upload a real STTM and generate Snowflake DDL, SQL, data dictionaries, DQ rules, and AI analysis in under a minute.