Vertex Data

Building a Slack AI Agent That Can Actually Query Your Warehouse

A practitioner’s guide to shipping a Slack AI agent that runs real warehouse queries—scoped, grounded on dbt, cost-safe, and evaluated for drift.

Eric Provencio, Principal Analytics Engineer at Vertex Data Consulting
Eric Provencio
7 min read

Your leaders want answers where work already happens, and they want them fast—ask in Slack, get real numbers from the warehouse. You can ship that. The blueprint: a least-privilege Slack app with OAuth, an agent loop with tool calls, warehouse access that’s read-only by default, query cost guards, and human-in-the-loop for anything that writes. Ground the agent on your dbt docs and semantic layer so it uses your definitions, not the model’s guesses. Run evals to spot drift. Below is the architecture we deploy for clients and the exact pieces you can copy. If you want help standing it up or hardening it for production threads, see our service overview for Slack AI agents built for analytics.

Architecture: scopes, OAuth, and the agent loop

Start with a Slack app in your org. Use OAuth and only the scopes you truly need. In most teams, we use a bot user and these reads/writes for threads where the agent operates inside one workspace:

  • app_mentions:read, channels:history, groups:history, im:history
  • chat:write, commands, users:read, team:read
  • Optional: files:write for chart attachments

Use the Web API via OAuth V2; the Slack Developer Docs list exact meanings. Keep tokens in a secret manager, not in code. A simple agent loop looks like this:

# Python (FastAPI or Flask behind a Slash command or event subscription)
# Pseudocode: message -> plan -> tools -> SQL -> result -> summary

def handle_message(event):
    q = normalize(event.text)
    intent = classify(q)
    ctx = retrieve_docs(q)  # dbt docs, glossary, data contracts
    plan = propose_plan(q, ctx)
    for step in plan:
        if step.tool == "query":
            sql = text_to_sql(q, ctx)
            sql = enforce_limits(sql)
            rows = run_sql(sql)
            audit_log(event.user, sql, rows)
        elif step.tool == "chart":
            img = render_chart(rows)
        elif step.tool == "request_approval":
            enqueue_human_review(event.channel, step.payload)
    answer = compose_answer(q, rows, ctx)
    return post_message(event.channel, answer, thread_ts=event.ts)

The loop is simple on paper; the hard parts are tool design, safe SQL, and grounding so the agent doesn’t invent metrics. We’ll tackle those next.

Warehouse access that can’t burn you

Give the agent read-only credentials and a role that touches only curated schemas. Example Snowflake setup:

-- Snowflake: create a minimal read role
CREATE ROLE AI_AGENT_READ;
GRANT USAGE ON WAREHOUSE ANALYTICS_WH TO ROLE AI_AGENT_READ;
GRANT USAGE ON DATABASE PROD TO ROLE AI_AGENT_READ;
GRANT USAGE ON SCHEMA PROD.MART TO ROLE AI_AGENT_READ;
GRANT SELECT ON ALL TABLES IN SCHEMA PROD.MART TO ROLE AI_AGENT_READ;
GRANT SELECT ON FUTURE TABLES IN SCHEMA PROD.MART TO ROLE AI_AGENT_READ;
CREATE USER AI_AGENT PASSWORD = '...' DEFAULT_ROLE = AI_AGENT_READ;
GRANT ROLE AI_AGENT_READ TO USER AI_AGENT;

Enforce row and time limits at the connector, not just in prompts:

# Python: add LIMIT, timeout, and block risky statements
MAX_ROWS = 5000
QUERY_TIMEOUT_S = 20

BLOCKLIST = ["UPDATE", "DELETE", "INSERT", "MERGE", "DROP", "ALTER"]

def enforce_limits(sql: str) -> str:
    upper = sql.upper()
    if any(word in upper for word in BLOCKLIST):
        raise ValueError("Write-like SQL blocked")
    if " LIMIT " not in upper:
        sql = f"{sql.strip()} LIMIT {MAX_ROWS}"
    return sql

def run_sql(sql):
    with snowflake.connect(..., role="AI_AGENT_READ", warehouse="ANALYTICS_WH") as cxn:
        cxn.cursor().execute(f"ALTER SESSION SET STATEMENT_TIMEOUT_IN_SECONDS={QUERY_TIMEOUT_S}")
        return cxn.cursor().execute(sql).fetchmany(MAX_ROWS)

Test against your heaviest mart tables. For example, run the agent on a 40M-row orders table on an X-Small warehouse and log timeouts. Don’t guess costs—measure with your query history and warehouse credits to set sane defaults.

Grounding on dbt docs and a semantic layer

Text prompts aren’t a contract. Your agent should read your canonical metric definitions and model documentation. Expose dbt metadata (manifest + catalog) and, if you use the semantic layer, align queries to those metrics.

# dbt metric example (YAML)
models:
  - name: fct_orders
    description: Fact table of orders
    columns:
      - name: order_id
        tests: [unique, not_null]
      - name: order_total
        description: Total value in USD

metrics:
  - name: gross_revenue
    model: ref('fct_orders')
    type: sum
    sql: order_total
    timestamp: order_created_at
    time_grains: [day, week, month]
    filters:
      - field: is_test_order
        operator: '='
        value: 'false'
# Python: load dbt docs for retrieval grounding
import json, pathlib

MANIFEST = json.loads(pathlib.Path('target/manifest.json').read_text())
CATALOG = json.loads(pathlib.Path('target/catalog.json').read_text())

def glossary():
    terms = {}
    for name, node in MANIFEST['nodes'].items():
        if node.get('description'):
            terms[node['name']] = node['description']
    return terms

Feed the glossary and metric YAML to retrieval so the agent asks for gross_revenue by month instead of inventing a sum on a similarly named column. For teams using a central semantic layer, route through it to guarantee consistent aggregations and dimensions. This is where a little upfront plumbing prevents week‑two drift.

Tools the agent can call—and how to gate them

Keep tools explicit and typed. At minimum: query_sql, explain_sql, render_chart, and request_approval for anything that changes systems. Writes should always trigger human review.

# Tool schema (JSON Schema-like contract)
TOOLS = {
  "query_sql": {
    "description": "Run a read-only SQL against curated schemas",
    "params": {
      "type": "object",
      "properties": {
        "sql": {"type": "string"},
        "purpose": {"type": "string"}
      },
      "required": ["sql"]
    }
  },
  "request_approval": {
    "description": "Ask a human to approve a side-effecting action",
    "params": {
      "type": "object",
      "properties": {
        "action": {"type": "string"},
        "payload": {"type": "object"}
      },
      "required": ["action"]
    }
  }
}
# Human-in-the-loop prompt to the channel

def enqueue_human_review(channel, payload):
    msg = (
        "Proposed action requires approval.\n"
        f"Action: {payload['action']}\n"
        "React with ✅ to proceed, 🛑 to cancel."
    )
    post_message(channel, msg)
    # a simple state machine waits for reactions then runs the integration

This pattern keeps your bot helpful while insulating production systems. For report attachments, render a chart server-side and post the image with a short summary. If you want deeper orchestration or routing across many tools, we build that as part of our internal AI platforms.

Evals and drift detection you can run weekly

Ship with a test pack. Store canonical question → expected SQL → expected shape → acceptable tolerance. Run it on a schedule and alert when behavior drifts. Keep golden answers small enough to compute quickly and update them when business logic changes.

# Minimal eval harness
import yaml, time

suite = yaml.safe_load(open('evals.yaml'))

failures = []
for case in suite['cases']:
    sql = text_to_sql(case['question'], case.get('context', {}))
    sql = enforce_limits(sql)
    rows = run_sql(sql)
    if not case['assert'](rows):
        failures.append({"case": case['id'], "sql": sql, "rows": rows[:5]})

print({"passed": len(suite['cases']) - len(failures), "failed": len(failures)})
# evals.yaml (sketch)
cases:
  - id: revenue_month
    question: "What is gross revenue by month this year?"
    assert: !!python/name:assert_revenue_month  # implement in harness

Track latency, tool counts, and the fraction of answers that needed human approval. If you change the llm or prompt, run the suite first, then canary to a single team. Repeat the eval weekly; it catches the quiet regressions that appear as your schemas evolve.

What text-to-SQL still gets wrong (and how to contain it)

Even with grounding, you’ll see predictable misses:

  • Joins on similarly named keys across marts, causing fan-out
  • Time zones and week boundaries disagreeing with finance calendars
  • Null handling and returned zero rows misread as “no data”
  • Aggregations on pre-aggregated tables
  • Filters that bypass your “is_test_order = false” convention

Mitigations that hold up in production:

  • Route queries through models in curated schemas only
  • Force GROUP BY on named time grains from your metrics YAML
  • Reject queries that mix tables across domains without a documented relationship
  • Attach an explain_sql step: the agent must show the final SQL before execution
  • Always return the SQL and a short summary with the answer so humans can spot issues
# Guardrails in generation
ALLOWED_SCHEMAS = {"PROD.MART", "PROD.DIM"}

def validate_tables(sql):
    tables = extract_tables(sql)
    if not all(tbl.split('.')[0] + '.' + tbl.split('.')[1] in ALLOWED_SCHEMAS for tbl in tables):
        raise ValueError("Unapproved schema in query")

Containment beats perfection. If the agent is uncertain, have it ask a clarifying question in the thread and prefer fewer, smaller queries over one giant guess.

Build vs. buy: custom agent or Agentforce and friends

There are credible off-the-shelf options. Salesforce is shipping Agentforce and a growing set of AI features. If your warehouse, Sales Cloud objects, and approvals live together, that can be a fast path. If your facts live in Snowflake/BigQuery and your team needs analytics answers inside Slack, a custom build keeps control over SQL and governance.

OptionStrengthsTrade-offs
Custom agent in SlackExact metric semantics (dbt), read-only SQL, tailored toolsOwn the plumbing, on-call, and evals
AgentforceTight with Salesforce objects, approvals, and CanvasWarehouse joins may require extra integration work
Marketplace AI appsFast start, minimal setupLimited control over schemas, fewer safety levers

Questions to ask in your org: Will answers live inside Slack threads or in dashboards linked from Canvas? Do we need enterprise search across knowledge plus metrics? Which customer data must appear in channel without leaking PII? Will one slack agent serve more than one slack workspace? If you want a partner who has wired this to the flow of work, we document approaches on our AI agents articles page.

FAQ: short, practical answers

Can you suggest a few names for my Slack app?

Pick a noun that signals scope: Ledger, Atlas, Pulse, Beacon. Avoid “data” in the name; the icon and description can carry that.

Can Agentic AI in Slack resolve issues end-to-end without humans?

For reads and small automations, yes. For writes or cross-system changes, gate actions with an approval tool. That keeps risk low while still saving time.

Can I build custom agentic ai agents for Slack, and how?

Yes—use an event subscription, a small web service, and the tool pattern above. Ground on dbt and add evals before broad rollout.

Can’t get enough AI in the agent experience?

Add explain_sql, chart suggestions, and a one-line summary beside the full answer. Just keep prompts short and measure latency.

Do I need developer skills to build a Dust agent for Slack?

Non-developers can prototype, but production needs an engineer for OAuth, roles, and cost controls. A slack developer can take it the last mile.

Do Slack AI agents work for external customer support, or only internal teams?

They work for both, but external use raises auth and privacy needs. Keep PII masked and restrict channels.

Do Slack AI agents learn from past conversations?

They can store anonymized thread context or embed Q&A pairs. Retention should be short and configurable within slack.

Do Slack AI agents work across all my Slack workspaces?

Use an Enterprise Grid app if you need multi-org install. Keep data access separated per workspace and audit tokens.

Does AI in Slack work globally?

Yes, but watch data residency. Confirm where tokens and logs live, and align with your compliance posture.

Does Slack use customer data to train LLMs?

Review current policy from the vendor and your legal team. Many enterprise settings limit training on your slack data.

Does Slackbot work globally?

Yes, with network allowances. Latency varies; cache small lookups locally to keep the bot snappy.

Don’t have a paid plan?

You can still prototype with a free tier, but some APIs, retention, and security controls require paid.

Do we need a personal ai agent for work or a shared one?

Start with a shared channel agent. Later, consider a personal ai agent that knows an individual’s role and teams.

Where does an agent in slack fit with enterprise search?

Use search to retrieve docs and policies; use the agent for computation over curated data. They complement each other.

How do we keep the agent inside slack safe?

Limit scopes, use read-only roles, enforce limits, and route write actions through approvals in specific slack channels.

Which API endpoints matter most?

chat.postMessage, events for mentions, and file upload for charts are usually enough to start.

What is the right “ai assistant” shape?

A thread-native helper that posts the SQL it ran, a short summary, and links to the dashboard for deep dive.

When to ship, and when to call in help

If you’ve got OAuth, a read role, dbt docs, and a small eval pack, you can pilot to one team this week. If you want a hardened slack ai agent with cost guards, governance, and training for your analysts, we’ve done it many times—see our Slack AI agents service and broader internal AI platform work. We’ll meet you within slack, in your threads, and hand back a system your team can own.


Ready to move? Share your workspace constraints, warehouse, and target metrics, and we’ll scope a build-or-boost plan. Start a project with Vertex.

About the author

Eric Provencio Analytics engineer who has built and run production data platforms for Disney, Hulu, Nike, Peloton, Gopuff, and Kaplan. Founded Vertex Data Consulting to do the deep work most data teams never find time for: dbt Cloud migrations, repo performance, Airflow reliability, and AI agents that actually touch the stack.

Keep reading

Start a conversation

Tell us what's slowing your data team down.

We maintain a small client roster on purpose. If we're the wrong fit, we'll say so — and usually we know somebody who isn't.

  • Replies within 2 business days
  • NDA before specifics
  • Fixed-scope first engagement, retainer if it works
What do you want help with? *

We reply within 2 business days if there's a fit. No newsletters, ever.