Skip to main content
The generated search tool handles filter, sort, limit. Some questions need more than that:
  • “the five nearest stores” — needs a distance calculated from the caller’s location
  • “total spend by tier” — needs grouping and a sum
  • “plans expiring in the next 30 days, cheapest first” — needs date arithmetic
A stored query is SQL you write once, save on a table, and turn on. It becomes an agent tool like any other.
Writing and enabling stored queries needs the custom_data:manage permission. Reading and running them doesn’t — an agent uses a query someone else validated.

Writing one

Open a table, go to Queries, and click New query.
Three things to know before you write anything: Your table is a table. Refer to it by name — FROM stores s — and its columns are real typed columns, not JSON. latitude is a number, not text. Parameters arrive in args. Declare them below the editor and join args to read them. It’s one row with one column per parameter, already the right type:
Don’t write LIMIT. The Row limit box applies it. That’s your “nearest N”.

Parameters

Each parameter has a name and a type, and both matter: they become the generated tool’s parameters, and they’re the only place the type is stated. The SQL can’t say that lat is a number, or that it exists. The third column in each parameter row is a test value, used only by Run. It’s never saved.
A parameter with no test value runs as NULL, which is not the same call a real agent makes. The result panel tells you which ones ran as NULL — read it. A query that looks right with a missing parameter can behave completely differently when an agent actually supplies one.

Run before you save

Run executes against your live rows and shows what comes back. Nothing runs on keystroke. Three kinds of result:
  • Rows — what an agent would get
  • “Ran without errors and matched no rows” — valid query, nothing matched. Not a failure.
  • Did not validate — with the reason, verbatim, and a note explaining the rule behind it

What a query may do

Anything that reads one table and computes over it. Maths, CASE, aggregates, window functions, string and date functions, GROUP BY, ORDER BY. What it may not do, and why: If a query is refused, the message names the specific function or table that was rejected.
Need the current date? Pass it as a parameter. The workflow or agent calling the query knows what “today” is; the query deliberately doesn’t.

Saving, and the four states

A query is always saved, even if it doesn’t validate — a draft you’re mid-way through isn’t something to throw away. What changes is its state: Only a query that is validated and switched on becomes an agent tool. Re-saving a working query that now fails also switches it off, so a live agent never keeps a tool the platform has stopped vouching for.

When a query goes stale

Editing the table’s schema marks every query on it as needing revalidation, because their columns may no longer exist. The card tells you which column went missing:
This reads region, which the schema no longer declares.
Open it, and save again to revalidate.

Using one

Once switched on, a query appears on the Agent tools tab as <query_name>_<table>. Copy it into an agent like any other tool. Workflows can call it too — add an action, pick Custom Data → Run Saved Query, choose the table and the query, and supply the arguments. Useful when you want the query’s result to feed later steps rather than going straight to the model.

A note on how arguments are handled

Values you pass are handed to the database as data, never pasted into the SQL text. A caller value that happens to look like SQL is just a value. You don’t need to escape or sanitise anything.