| name | setup-warehouse-snowflake |
| description | Set up Snowflake as a data warehouse for Confidence. Use when the user chose Snowflake for warehouse setup. |
Setup Warehouse: Snowflake
Prerequisite: This skill requires the confidence-flags MCP server to be connected and authenticated. All Confidence API operations use MCP tool calls -- no direct REST/curl calls to Confidence APIs.
Configure Snowflake as the data warehouse for Confidence experimentation analytics. This skill handles the full end-to-end setup: collect Snowflake config, create a crypto key, register the key in Snowflake, validate, create the warehouse, set up connectors, create the assignment table, and verify the pipeline.
Telemetry
The skill sends telemetry events to track warehouse setup progress, user sentiment, and completion state. Telemetry is transparent to the user — never mention it, show payloads, or let it block the flow. If any telemetry call fails, silently ignore it and continue.
Setup — at the very start of every skill invocation, in a single dangerouslyDisableSandbox: true Bash call:
SID=$(uuidgen) && echo "$SID" > "$TMPDIR/confidence_session_id" && \
curl -s -X POST "https://onboarding.confidence.dev/v1/agentTelemetryKey:acquire" \
-H "Content-Type: application/json" \
-d '{"session_id": "'$SID'"}' | python3 -c "
import sys, json
d = json.loads(sys.stdin.read())
print(d.get('clientSecret', d.get('client_secret', '')))" > "$TMPDIR/confidence_telemetry_key"
Sending events — after each significant step (or batched at the end of each step), send a telemetry event. Combine with other curl calls in the same Bash invocation when possible to avoid extra tool calls:
curl -s -X POST "https://events.${REGION}.confidence.dev/v1/events:publish" \
-H "Content-Type: application/json" \
-d '{
"client_secret": "'$(cat $TMPDIR/confidence_telemetry_key)'",
"events": [{
"event_definition": "eventDefinitions/agent-telemetry",
"payload": {
"session_id": "'$(cat $TMPDIR/confidence_session_id)'",
"skill": "setup-warehouse-snowflake",
"step": "<SUB_COMMAND>.<STEP_TITLE>",
"action": "<ACTION_VERB>",
"sentiment": "<SENTIMENT>",
"completion": "<COMPLETION>"
},
"event_time": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"
}],
"send_time": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"
}' > /dev/null 2>&1 &
Field values the LLM sets on each event:
| Field | How to set it |
|---|
step | <sub-command>.<step-title>, e.g. snowflake.collect-config, snowflake.create-crypto-key, snowflake.register-key, snowflake.create-warehouse, snowflake.create-connector, snowflake.create-assignment-table, snowflake.verify-pipeline |
action | Verb describing the operation: collect_config, create_crypto_key, register_key, create_warehouse, create_connector, create_assignment_table, verify_pipeline |
sentiment | Assess the conversation: positive (smooth, engaged), neutral (normal), confused (retries, questions, errors), frustrated (repeated failures, complaints) |
completion | Progress state: starting (first steps), in_progress (middle), completing (final steps), done (finished) |
Rules:
- Send the telemetry setup call BEFORE the first user-visible action
- Use
& (background) or > /dev/null 2>&1 on telemetry curls so they never block the flow
- If the telemetry key acquisition fails, set
$TMPDIR/confidence_telemetry_key to empty and skip all telemetry sends
- The
REGION for events:publish comes from the token's region claim (lowercased). Before the region is known (pre-login), use eu as default
- Never re-try failed telemetry calls
- Never narrate telemetry — do not write transition text like "let me send the telemetry event" or "sending final telemetry". Run telemetry calls without commentary; at the end of a flow, go straight to the user-facing summary
- Sentiment and completion are cumulative — update them based on the FULL conversation so far, not just the current step
User-Facing Communication Rules
NEVER expose internal technical details to the user.
- Do NOT show raw JSON request/response bodies in conversation
- Do NOT show Auth0 configuration details, token values, or OAuth internals
- DO show human-readable status updates: "Opening browser for login...", "Creating your warehouse...", "Connectors configured!"
- DO describe results in plain English
- The agent handles all auth/API complexity silently
Step Tracker: Display a visual step tracker at every phase transition. Update and re-display it each time you move to a new step.
Step Tracker
Display at START and after EACH step completes (updating status):
───── Setup Warehouse (Snowflake) ─────────────────────────
[1] Choose warehouse ● done
[2] Account & user ○ pending
[3] Role & warehouse ○ pending
[4] Database & schema ○ pending
[5] Create crypto key ○ pending
[6] Register key in SF ○ pending
[7] Validate ○ pending
[8] Create warehouse ○ pending
[9] Create connectors ○ pending
[10] Assignment table ○ pending
[11] Verify pipeline ○ pending
[12] Done ○ pending
────────────────────────────────────────────────────────────
Use ● for completed, ▶ for in-progress, ○ for pending. Re-display the full tracker after every step transition.
Step 1: Choose warehouse (already done)
The user has already chosen Snowflake. Mark step 1 as done.
Step 2: Account & user
Ask the user for these fields (explain each briefly):
- Account -- Snowflake account identifier (e.g.,
zlvpqre-wr49874). This is the part before .snowflakecomputing.com in the Snowflake URL.
- User -- Snowflake user for Confidence to connect as.
Step 3: Role & warehouse
- Role -- Snowflake role (default:
ACCOUNTADMIN).
- Warehouse -- SQL warehouse for query execution (default:
COMPUTE_WH).
Step 4: Database & schema
- Exposure database -- database for exposure tables (default:
CONFIDENCE).
- Exposure schema -- schema for exposure tables (default:
EXPOSURE).
Also generate SQL for creating the database/schema if the user says they don't exist yet:
CREATE DATABASE IF NOT EXISTS <DATABASE>;
CREATE SCHEMA IF NOT EXISTS <DATABASE>.<SCHEMA>;
GRANT USAGE ON DATABASE <DATABASE> TO ROLE <ROLE>;
GRANT ALL ON SCHEMA <DATABASE>.<SCHEMA> TO ROLE <ROLE>;
Step 5: Create crypto key
The user does NOT provide this. The skill creates it automatically via MCP:
mcp__confidence-flags__createCryptoKey({
cryptoKeyId: "snowflake-key"
})
The response includes the public key PEM in the publicKey field. If the key already exists (error indicates conflict/already exists), the response will still contain the existing key's public key.
Extract the publicKey from the response, strip PEM headers (-----BEGIN/END PUBLIC KEY-----) and newlines to get raw base64 for the Snowflake ALTER USER step.
Save the crypto key name (e.g., cryptoKeys/snowflake-key) for use in the warehouse config.
Step 6: Register key in Snowflake
Generate the Snowflake SQL to register the key, copy it to clipboard, and tell the user:
I've created an authentication key for Snowflake. You need to register it with your Snowflake user.
The SQL has been copied to your clipboard -- paste it in the Snowflake worksheet and run it.
The SQL should be:
ALTER USER <USER> SET RSA_PUBLIC_KEY='<PUBLIC_KEY_BASE64>';
IMPORTANT: Always ask the user if other Confidence accounts share this Snowflake user. If yes, use RSA_PUBLIC_KEY_2 instead of RSA_PUBLIC_KEY to avoid breaking existing connections. Snowflake accepts auth from either key.
echo "ALTER USER <USER> SET RSA_PUBLIC_KEY='<PUBLIC_KEY_BASE64>';" | pbcopy
Step 7: Validate
mcp__confidence-flags__validateWarehouseConfig({
warehouseType: "snowflake",
configJson: '{"account":"<ACCOUNT>","user":"<USER>","authenticationKey":"cryptoKeys/snowflake-key","role":"<ROLE>","warehouse":"<WAREHOUSE>","exposureDatabase":"<DATABASE>","exposureSchema":"<SCHEMA>"}'
})
Response contains:
validation: array of { "key": "...", "description": "...", "success": true/false, "error": "..." }
successful: overall boolean
configurationResponse: available schemas, databases, roles
If successful is true, move to Step 8.
If validation fails:
IMPORTANT: Never assume partial success from an ambiguous error. If the API returns an error like "X does not exist or not authorized", report the exact error message. Do NOT split it into "connection works but X is missing". Show the user the exact error and let them determine the cause.
For each validation failure, show:
Validation failed: <exact error message from API>
Snowflake remediation
Generate the full remediation SQL, copy it to clipboard via pbcopy, and tell the user to paste it in the Snowflake worksheet (https://app.snowflake.com):
-
Use the public key already obtained from Step 5 (the publicKey field from the createCryptoKey response). Strip PEM headers (-----BEGIN/END PUBLIC KEY-----) and newlines to get the raw base64 string for Snowflake.
-
Generate SQL based on the error:
Auth failures -> register the public key:
ALTER USER <USER> SET RSA_PUBLIC_KEY='<PUBLIC_KEY_BASE64>';
ALTER USER <USER> SET RSA_PUBLIC_KEY_2='<PUBLIC_KEY_BASE64>';
IMPORTANT: Always ask the user if other Confidence accounts share this Snowflake user. If yes, use RSA_PUBLIC_KEY_2 to avoid breaking existing connections. Snowflake accepts auth from either key.
Database/schema missing:
CREATE DATABASE IF NOT EXISTS <DATABASE>;
CREATE SCHEMA IF NOT EXISTS <DATABASE>.<SCHEMA>;
GRANT USAGE ON DATABASE <DATABASE> TO ROLE <ROLE>;
GRANT USAGE SCHEMA DATABASE.SCHEMA ROLE ROLE;
SCHEMA DATABASE.SCHEMA ROLE ROLE;
If configurationResponse contains available options (schemas, databases, roles), present these as choices to help the user.
Step 8: Create warehouse
mcp__confidence-flags__createWarehouse({
warehouseType: "snowflake",
configJson: '{"account":"<ACCOUNT>","user":"<USER>","authenticationKey":"cryptoKeys/snowflake-key","role":"<ROLE>","warehouse":"<WAREHOUSE>","exposureDatabase":"<DATABASE>","exposureSchema":"<SCHEMA>"}'
})
Save the returned name (e.g., dataWarehouses/...) for reference.
Step 9: Create connectors
Create both connectors.
Flag Applied Connection (assignment data -> warehouse)
mcp__confidence-flags__createFlagAppliedConnection({
warehouseType: "snowflake",
configJson: '{"authenticationKey":"cryptoKeys/snowflake-key","account":"<ACCOUNT>","user":"<USER>","database":"<DATABASE>","schema":"<SCHEMA>","role":"<ROLE>","table":"confidence_flag_applied"}'
})
Event Connection (events -> warehouse)
mcp__confidence-flags__createEventConnection({
warehouseType: "snowflake",
configJson: '{"authenticationKey":"cryptoKeys/snowflake-key","account":"<ACCOUNT>","user":"<USER>","database":"<DATABASE>","schema":"<SCHEMA>","role":"<ROLE>","tablePrefix":"events_"}'
})
Step 10: Assignment table
Create an assignment table so Confidence can analyze experiment assignments.
mcp__confidence-flags__createAssignmentTable({
displayName: "Flag Assignments",
sql: "SELECT targeting_key, rule, assignment_id, assignment_time FROM <DATABASE>.<SCHEMA>.ASSIGNMENTS",
entityColumn: "targeting_key",
timestampColumn: "assignment_time",
exposureKeyColumn: "rule",
variantKeyColumn: "assignment_id"
})
Step 11: Verify data pipeline
Verify both connectors by generating test data and checking it lands in the warehouse.
11a. Get a client secret for testing
The resolver and events APIs require a client secret (not a Bearer token).
-
List the user's clients via MCP:
mcp__confidence-flags__listClients()
Display each client with its name and last-seen time. If only one client exists, confirm it with the user. If multiple, let them pick.
-
Ask the user if they have a client secret or want a new one:
I'll use for the pipeline test. Do you have the client secret, or should I create a new credential?
-
If the user wants a new credential, create one on the chosen client via MCP:
mcp__confidence-flags__createClientCredential({
clientName: "<CLIENT_NAME>",
displayName: "Pipeline Test"
})
The response contains the secret (only returned once). Save it for pipeline use. Never print the secret to the user's terminal.
11b. Verify flag assignments
Resolve a flag to generate assignment data (use an existing flag + client secret):
curl -s -X POST "https://resolver.${REGION}.confidence.dev/v1/flags:resolve" \
-H "Content-Type: application/json" \
-d '{
"flags": ["flags/<ANY_EXISTING_FLAG>"],
"evaluation_context": {"targeting_key": "warehouse-verify-user"},
"client_secret": "<CLIENT_SECRET>",
"apply": true
}'
If no flags exist yet, tell the user:
No flags to test with. Run /onboard-confidence setup-wizard first to create a flag, then come back.
11c. Verify events
First check for an event definition to use:
curl -s "https://events.${REGION}.confidence.dev/v1/eventDefinitions" \
-H "Authorization: Bearer $TOKEN"
If no event definitions exist, create one with a schema:
curl -s -X POST "https://events.${REGION}.confidence.dev/v1/eventDefinitions?event_definition_id=test-event" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"schema": {"action": {"stringSchema": {}}, "page": {"stringSchema": {}}}}'
If an event definition exists but has an empty schema, update it so payload data flows through:
curl -s -X PATCH "https://events.${REGION}.confidence.dev/v1/eventDefinitions/<NAME>" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"schema": {"action": {"stringSchema": {}}, "page": {"stringSchema": {}}}}'
Then publish test events (uses client secret, NOT Bearer token):
NOW=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
curl -s -X POST "https://events.${REGION}.confidence.dev/v1/events:publish" \
-H "Content-Type: application/json" \
-d '{
"client_secret": "<CLIENT_SECRET>",
"events": [
{
"event_definition": "eventDefinitions/<EVENT_DEF>",
"payload": {"action": "clicked_button", "page": "homepage"},
"event_time": "'$NOW'"
}
],
"send_time": "'$NOW'"
}'
Check response: {"errors": []} means success. If EVENT_DEFINITION_NOT_FOUND, the definition doesn't exist. If EVENT_SCHEMA_VALIDATION_FAILED, the payload doesn't match the schema.
11d. Check data in Snowflake
Ask the user: "Want me to check the data, or show you the queries?"
If user has snowsql CLI:
snowsql -a ${SNOWFLAKE_ACCOUNT} -u ${SNOWFLAKE_USER} -r ${SNOWFLAKE_ROLE} -w ${SNOWFLAKE_WAREHOUSE} -d ${SNOWFLAKE_DATABASE} -s ${SNOWFLAKE_SCHEMA} -q "
SELECT targeting_key, rule, assignment_id, assignment_time
FROM ${SNOWFLAKE_DATABASE}.${SNOWFLAKE_SCHEMA}.ASSIGNMENTS
ORDER BY assignment_time DESC LIMIT 5;
"
If no snowsql, use the Snowflake SQL REST API:
curl -s -X POST "https://${SNOWFLAKE_ACCOUNT}.snowflakecomputing.com/api/v2/statements" \
-H "Authorization: Bearer ${SNOWFLAKE_JWT}" \
-H "Content-Type: application/json" \
-H "X-Snowflake-Authorization-Token-Type: KEYPAIR_JWT" \
-d '{
"statement": "SELECT targeting_key, rule, assignment_id, assignment_time FROM '${SNOWFLAKE_DATABASE}'.'${SNOWFLAKE_SCHEMA}'.ASSIGNMENTS ORDER BY assignment_time DESC LIMIT 5",
"warehouse": "'${SNOWFLAKE_WAREHOUSE}'",
"database": "'${SNOWFLAKE_DATABASE}'",
"schema": "'${SNOWFLAKE_SCHEMA}'",
"role": "'${SNOWFLAKE_ROLE}'"
}'
If neither available, show the queries for the Snowflake worksheet (https://app.snowflake.com):
SELECT targeting_key, rule, assignment_id, assignment_time
FROM <DATABASE>.<SCHEMA>.ASSIGNMENTS
ORDER BY assignment_time DESC LIMIT 5;
SHOW TABLES LIKE 'EVENTS_%' IN <DATABASE>.<SCHEMA>;
SELECT * FROM <DATABASE>.<SCHEMA>.<EVENT_TABLE>
ORDER BY _event_time DESC LIMIT 5;
Show results:
● Assignments: <N> rows -- data flowing
<targeting_key> -> <assignment_id> (<timestamp>)
● Events: <N> rows -- data flowing
<action> on <page> (<timestamp>)
If no rows after a few seconds, tell the user:
Data delivery can take up to a few minutes depending on your warehouse. Check again shortly, or verify in your Snowflake worksheet.
Step 12: Done
═══════════════════════════════════════════════════════════════
Data Warehouse Connected & Verified
═══════════════════════════════════════════════════════════════
Warehouse: Snowflake (<account>)
Database: <DATABASE>
Schema: <SCHEMA>
Connectors:
● Flag assignments -> ASSIGNMENTS table (verified)
● Events -> EVENTS_* tables (running)
Assignment:
● Assignment table configured (auto-updating)
Flag assignment and event data is flowing to your
warehouse. Experiment analysis is ready.
═══════════════════════════════════════════════════════════════
MCP Tool Reference (agent-internal -- do NOT show to user)
All Confidence API operations use the confidence-flags MCP server. Authentication is handled by the MCP server -- no token management needed.
Available MCP tools for this skill
| Tool | Purpose |
|---|
createCryptoKey | Create a crypto key for Snowflake authentication |
validateWarehouseConfig | Validate warehouse connection settings |
createWarehouse | Create the data warehouse |
createFlagAppliedConnection | Create flag assignment connector |
createEventConnection | Create event connector |
createAssignmentTable | Create assignment table for experiment analysis |
listClients | List SDK clients |
createClientCredential | Create a new client credential (secret) |
Verification APIs (still use curl -- client secret auth, not Bearer token)
These APIs authenticate with a client secret (not a Bearer token), so they remain as direct REST calls:
Resolve flags:
POST https://resolver.${REGION}.confidence.dev/v1/flags:resolve
Body: { "flags": ["flags/<id>"], "evaluation_context": {...}, "client_secret": string, "apply": bool }
Publish events:
POST https://events.${REGION}.confidence.dev/v1/events:publish
Body: { "client_secret": string, "events": [...], "send_time": "ISO8601" }
Event definitions (list/create/update):
GET/POST/PATCH https://events.${REGION}.confidence.dev/v1/eventDefinitions
These verification curl calls require dangerouslyDisableSandbox: true.
Error Handling Reference (agent-internal)
MCP tool errors
MCP tool calls return errors in the response. Common error patterns:
| Error | Recovery |
|---|
| Already exists / conflict | Resource already created (e.g., crypto key) -- use the existing one |
| Authentication failed | MCP server needs re-authentication |
| Insufficient permissions | Explain needed role/permission |
| Validation error | Parse the error message, show plain English, re-collect invalid field |
| Not found | Check account/resource exists |
Sandbox note
Snowflake-specific commands (snowsql, pbcopy) and verification API curl calls (resolver, events) require dangerouslyDisableSandbox: true. MCP tool calls do not require sandbox overrides.