[Docs index](/docs.md) / [Tool Policies](/docs/tool-policies/overview.md) / Playbook: Put Guardrails on Warehouse Queries

---

# Playbook: Put Guardrails on Warehouse Queries

Your operations and finance teams finally have a way to ask the data warehouse questions without filing a ticket. They chat with an AI, it writes the query, and the answer comes back in seconds. Then someone asks it to "clean up the old test orders", and you realize the same tool that reads the warehouse can also change it. This playbook walks you through giving your team open access to read data while making sure nobody, and no agent, can delete rows, touch payroll tables, or pull a million rows by accident.

## What you will build

By the end of this playbook, you will have:

- A custom tool that runs queries against your data warehouse
- A rule that blocks statements that change or delete data
- A rule that keeps restricted schemas, such as payroll, off limits
- A rule that stops oversized result sets before they run
- An exception that lets your data engineering group run the statements everyone else cannot
- A message for each rule that tells the AI what to do instead, so conversations keep moving
- A tested rule list and a routine for reviewing what gets blocked

## What you need before you start

- An Assist workspace where you are a workspace admin.
- An AI client connected to your workspace through an MCP server, or the chat in Assist.
- Read access credentials for your data warehouse. Use a service account, not a personal login.
- A short list of what should never happen: which statements, which schemas, how many rows is too many.
- A group in your workspace for the people who are allowed to make changes, such as **Data Engineering**. Create it under **Permissions** on the **Groups** tab if it does not exist.

## Step 1: Build the query tool

Start in your AI client. Describe the warehouse and what you want the tool to do:

> "I want to build a tool called 'warehouse_query' that runs SQL against our data warehouse. It should take a 'sql' parameter with the statement, an optional 'schema' parameter, and a 'max_rows' parameter that defaults to 500. It should return the rows and the column names. The connection details are in the workspace secrets."

Let the AI ask questions. It will want to know how to handle errors, how long a query may run, and what to return when there are no rows. Answer these now, because the tool's parameters are what your rules will look at.

Then test it with a harmless query:

> "Run warehouse_query with 'select count(*) from orders where created_at > current_date - 7'."

Confirm the number makes sense. Ask a few more questions the way your team would, so you can see the kinds of statements the AI writes. Pay attention to how it formats them. Queries often span several lines, and keywords may be uppercase or lowercase. Both matter when you write rules.

## Step 2: Decide what should never happen

Before you open the rule editor, write the list down. A typical list for a warehouse:

1. Nobody except data engineering may run a statement that changes data: `insert`, `update`, `delete`, `merge`, `truncate`, `drop`, `alter`, `create`, `grant`.
2. Nobody may query the `payroll` or `hr_private` schemas through this tool.
3. No query may ask for more than 10,000 rows.

Keep the list short. Each item becomes one rule, and a short list is easier to reason about than a long one.

Your warehouse credentials should already be read-only. Rules are a second layer. If the service account cannot write, the rule that blocks writes still earns its place: the AI gets a clear message instead of a database error, and the attempt is recorded where you can see it.

## Step 3: Block statements that change data

In Assist, open **Permissions** and select the **Tool Policies** tab. Click **New Rule**.

Set up the rule:

1. Under **Action**, select **Deny**.
2. Under **Applies to**, leave **Workspace**.
3. In **Tool**, type `warehouse` and press **Tab**. The name completes to `warehouse_query` and the tool pack fills in.
4. Click **Add condition**.
5. Click **Path** and press **Tab**. Choose `sql`.
6. Set **Operator** to **matches**.
7. In **Pattern**, enter:

   ```
   \b(insert|update|delete|merge|truncate|drop|alter|create|grant)\b
   ```

8. Tick **ignore case**.

The `\b` on each side means the word must stand alone. Without it, a query that reads a column called `updated_at` would be blocked, because `update` appears inside it.

Fill in the two text fields:

- **Reason**: "Only data engineering may change warehouse data."
- **Message returned to the model on deny**: "This tool is read-only for you. Write a select statement instead, or ask the data engineering team to make the change."

Now test it. Under **Test conditions**, click **Fill from tool**, then set the `sql` value to a statement that should be blocked:

```
delete from orders where status = 'test'
```

Click **Run test**. The result should say the conditions match. Change the statement to `select updated_at from orders` and run it again. This time it should not match.

Leave **Placement** at **Bottom of chain** and click **Create Rule**.

## Step 4: Add the exception for data engineering

The rule you just made applies to everyone. Your data engineers need to get through. Because the first matching rule decides, the exception goes above the block.

Click **New Rule**:

1. Under **Action**, select **Allow**.
2. Under **Applies to**, choose **Group**, then pick **Data Engineering**.
3. In **Tool**, enter `warehouse_query`.
4. Add no conditions.
5. **Reason**: "Data engineering may run any statement."
6. Under **Placement**, choose **Top of chain**.

Click **Create Rule**. Your list now reads, from the top: allow data engineering, then block changes for everyone.

## Step 5: Keep restricted schemas off limits

This rule should apply to everyone, including data engineering, so it goes at the very top.

Click **New Rule**:

1. **Action**: **Deny**.
2. **Applies to**: **Workspace**.
3. **Tool**: `warehouse_query`.
4. Add a condition on `sql` with **matches** and this pattern, with **ignore case** ticked:

   ```
   \b(payroll|hr_private)\.
   ```

5. **Reason**: "Payroll and HR schemas are not available through the warehouse tool."
6. **Message returned to the model on deny**: "Payroll and HR data cannot be queried here. Tell the user to request it from the finance systems team."
7. **Placement**: **Top of chain**.

The tool also takes a `schema` parameter, and a query could name the schema there instead of in the statement. Add a second rule for it:

1. **Action**: **Deny**, **Applies to**: **Workspace**, **Tool**: `warehouse_query`.
2. Add a condition on `schema` with **in**, and enter `payroll, hr_private`. Tick **ignore case**.
3. Use the same reason and message.
4. **Placement**: **Top of chain**.

This is the habit to build: for each thing you want to stop, ask every way the call could express it, and cover each one.

## Step 6: Stop oversized results

Click **New Rule**:

1. **Action**: **Deny**, **Applies to**: **Workspace**, **Tool**: `warehouse_query`.
2. Add a condition. Choose the path `max_rows`. Because the parameter is a number, the operator list shows number comparisons. Choose **gt** and enter `10000`.
3. **Reason**: "Result sets over 10,000 rows are not allowed."
4. **Message returned to the model on deny**: "Ask for at most 10,000 rows. Summarize with group by, or narrow the date range."
5. **Placement**: after the schema rules.

## Step 7: Test the whole list

Rules interact, so test them together. Select the **Simulator** tab and switch to **Tool call**.

Run these checks, changing **User** and **Params JSON** each time:

| Person | Statement | Expected |
|--------|-----------|----------|
| A member of operations | `select * from orders limit 10` | Allowed by the workspace default |
| A member of operations | `delete from orders` | Blocked by the change rule |
| A data engineer | `delete from orders` | Allowed by the group exception |
| A data engineer | `select * from payroll.salaries` | Blocked by the schema rule |
| Anyone | `max_rows` set to `50000` | Blocked by the size rule |

For each one, read the **Evaluation trace**. It shows which rule decided and why the others were skipped. If a check gives the wrong answer, the trace tells you which rule to fix or move.

## Step 8: Try it for real

Go back to your AI client and ask for something that should be blocked:

> "Delete the test orders from last week."

The AI calls the tool, the rule blocks it, and the AI tells you it cannot make changes and suggests what to do instead. That is the message you wrote doing its job.

In Assist, open **Tool History** and select **Blocked**. The call is there. Click it to see which rule stopped it and the statement that was attempted.

## Step 9: Review and adjust

For the first two weeks, check **Tool History** every few days with the **Blocked** filter on. You are looking for two things:

- **Calls that should have been allowed.** A legitimate query blocked because a column name happened to match. Tighten the pattern.
- **Calls that reveal a gap.** Someone tried something you had not thought of. Click **Create policy rule from this call** in **Execution Details** to start a rule from it.

On the **Tool Policies** tab, the **Blocked 7d** column shows how often each rule fires. A rule that never fires may be unnecessary, or it may be misspelled. A rule that fires constantly may be too broad.

## What you built

Your team can ask the warehouse anything, and the answers come back in seconds. The statements that could damage data are blocked for everyone except the people whose job it is to make changes. Restricted schemas are off limits to everyone. Oversized queries are stopped before they run. Every blocked attempt is recorded with the rule that stopped it, and the AI explains what happened instead of failing.

You did this without changing the tool. When your list of restrictions changes, you edit a rule, and it applies to every chat, agent, and app that uses the tool.

## Where to go next

- **Limit agents differently from people.** Add a rule with **Sources** set to **Subagent** so that scheduled agents may only query a reporting schema.
- **Catch what patterns miss.** Patterns cannot tell whether a query is dangerous in context. See [Playbook: Build a query intent classifier](playbook-query-intent-classifier.md).
- **Cover the other tools.** If you have tools for your ERP or order system, give them the same treatment. A tool pack pattern covers every tool in a pack with one rule.
- **Build a review app.** Ask your AI client to build a sandcastle app that lists blocked queries by person and by rule, so team leads can see what their people are trying to do and whether they need more access.

## Related guides

- [Creating a rule](creating-a-rule.md)
- [Writing conditions](writing-conditions.md)
- [Testing rules with the simulator](testing-rules-with-the-simulator.md)
- [Managing the rule list](managing-the-rule-list.md)
- [Troubleshooting](troubleshooting.md)

---

## Navigation

### In this section: Tool Policies

- [Tool Policies](/docs/tool-policies/overview.md)
- [Use Cases and Playbooks](/docs/tool-policies/use-cases.md)
- [Creating a Rule](/docs/tool-policies/creating-a-rule.md)
- [Writing Conditions](/docs/tool-policies/writing-conditions.md)
- [Using a Tool as a Classifier](/docs/tool-policies/using-a-classifier.md)
- [Testing Rules with the Simulator](/docs/tool-policies/testing-rules-with-the-simulator.md)
- [Creating a Rule from a Tool Call](/docs/tool-policies/creating-a-rule-from-a-tool-call.md)
- [Managing the Rule List](/docs/tool-policies/managing-the-rule-list.md)
- [Troubleshooting](/docs/tool-policies/troubleshooting.md)

#### Playbooks

- [Playbook: Build a Query Intent Classifier](/docs/tool-policies/playbook-query-intent-classifier.md)
- [Playbook: Control Where Your Tools Can Send Data](/docs/tool-policies/playbook-outbound-request-allowlist.md)
- [Playbook: Give a Scheduled Agent Only the Access It Needs](/docs/tool-policies/playbook-scheduled-agent-guardrails.md)
- **Playbook: Put Guardrails on Warehouse Queries** (current)

### Other sections

- [Tool Creation](/docs/tool-creation/overview.md)
- [Subagents](/docs/subagents/overview.md)
- [Agent Skills](/docs/agent-skills/overview.md)
- [Sandcastles](/docs/sandcastles/overview.md)
- [MCP Servers](/docs/mcp-servers/overview.md)
- [Scheduled Triggers](/docs/scheduled-triggers/overview.md)
- [Agent Filesystem](/docs/agent-filesystem/overview.md)
- [Workspace Permissions](/docs/workspace-permissions/overview.md)
- [Workspace Billing](/docs/workspace-billing/overview.md)
- [Chat Sharing](/docs/chat-sharing/overview.md)

[Back to docs index](/docs.md)
