Why raw access isn't enough
Pointing an AI agent at raw warehouse tables is the same as handing someone a database connection with no data platform in front of it. You get results, but you have no control over what gets queried, no guarantee the definitions are consistent, and no isolation between the agent's workload and everything else running on that compute. It's not a starting point. It's a known failure mode.
The design constraint was clear from the start. The agent would query through the same governed interface built for human analysts: curated models, defined metrics, explicit access controls. Anything less wasn't a simpler version of a data platform. It was no data platform at all.
Isolate everything
The first decision is physical separation. The agentic layer lives in its own database, runs on its own compute, and is entirely separate from the warehouse your ETL and BI tools use. This isn't just about security. It means an expensive agent query can't saturate the compute your production pipelines depend on, and a misconfigured grant in the agentic layer can't accidentally expose something in the main analytics database.
In practice this means a dedicated database, a dedicated warehouse that auto-suspends when idle, and two roles, one for building and one for querying, with nothing shared between them.
Two roles, one job each
The access model has exactly two roles. The first is for the data team, and it can create objects, manage grants, and configure the agentic infrastructure. The second is what the AI agent authenticates as at runtime. It can only read, and only from an explicit allowlist.
The agent cannot query a table it was never granted access to. That boundary lives at the database level, not in a system prompt that could be overridden or ignored in a future conversation.
What the agent can and can't see
The agent role has an explicit allowlist. Every table outside that list returns an access error, not a result. The list is deliberately narrow: user behaviour data, product usage metrics, and sales call records. Revenue tables, billing data, and raw source tables are never granted.
A curated data layer between agent and source
Even within the allowlisted tables, the agent doesn't query raw data directly. Every approved table goes through a curated layer first: a set of models built specifically for AI consumers that pre-join context, strip sensitive columns, and expose only the fields the agent genuinely needs.
Semantic views: one definition per metric
On top of the curated data layer sits a semantic layer: views that add business-friendly names, metric definitions, and natural language descriptions so the agent understands the data without guessing from column names or inventing its own logic.
The critical property is that every metric is defined exactly once. Total consults, activation rate, average generation time, whatever it is, every consumer of the semantic layer gets the same definition. There's no version of "activation rate" that calculates differently depending on which tool is querying. The agent reads the semantic view, and the semantic view enforces the definition.
Connecting Claude to Snowflake: the OAuth layer
Everything above describes what the agent role is allowed to touch once it's authenticated. The other half of the problem is authentication itself: how Claude actually establishes who it's acting as, without a shared secret sitting in a config file somewhere.
Snowflake's answer is a security integration: a first-class object that turns Claude into a proper OAuth client rather than a service account with a password. It's the same pattern behind "sign in with Google" on any consumer app, just pointed at a warehouse instead of a website. Claude requests a short-lived token through a standard authorization flow, that token is scoped to exactly one pre-approved role, and nothing about it is a static credential sitting somewhere waiting to be copied.
CREATE OR REPLACE SECURITY INTEGRATION claude_code_mcp_oauth
TYPE = OAUTH
OAUTH_CLIENT = CUSTOM
ENABLED = TRUE
PRE_AUTHORIZED_ROLES_LIST = ('AGENTIC_ANALYTICS_ANALYST')
OAUTH_CLIENT_TYPE = 'CONFIDENTIAL'
OAUTH_REDIRECT_URI = 'https://claude.ai/api/mcp/auth_callback'
OAUTH_ENFORCE_PKCE = TRUE
The one line that matters most for everything discussed earlier is the pre-authorized role. It ties this integration directly back to the agent role from the access model above, at the authentication layer rather than just the grant layer. Claude can only ever walk away with a token scoped to that one role. There's no path where a misconfiguration hands out a token for the builder role instead, because the integration itself was never given the ability to issue one.
Authentication and authorization are two separate boundaries, and it's worth keeping them separate in your head. The security integration decides who Claude can prove it is. The role and grant system decides what that identity is allowed to see once it's in. Neither one substitutes for the other, and a governed data platform needs both.
What this prevents
Three failure modes become structurally impossible with this design, not just unlikely:
The whole pattern is version-controlled in Terraform: roles, grants, semantic view definitions, warehouse config. That means access controls are reviewable in a pull request like any other infrastructure change, not managed through a UI where history is hard to audit.
The principle that generalises
The components ended up being straightforward: a dedicated role scoped to an explicit allowlist, a curated data layer that pre-filters before the agent sees anything, a semantic layer that enforces metric definitions, and isolated compute. What took time was the design: deciding what the agent should and shouldn't see, and making that decision live in the infrastructure rather than in a conversation.
What makes it hold up over time is that none of the boundaries depend on the agent behaving correctly. They're enforced by the database, the role system, and the data model. The agent can't accidentally or deliberately step outside them, and when a new team member asks "what can the agent see?", the answer lives in version-controlled Terraform, not someone's memory.