SQL Primitives
Cascades
Multi-step operator workflows with gates, takes, validators, retries, reducers, and receipts.Operators are the SQL surface. Cascades are the multi-step execution logic inside an operator.
The term is useful because "chain", "flow", and "pipeline" are overloaded. A Cascade is specifically a SQL-callable operator plan that can branch, validate, retry, reduce, and record receipts while still behaving like a typed Postgres function from the caller's perspective.

Vocabulary#
| Word | Meaning |
|---|---|
| Operator | The SQL function users call, such as rvbbit.review_risk(text). |
| Cascade | The internal multi-step execution plan behind an operator. |
| Step | One execution unit: LLM, specialist, Python, MCP tool, SQL, or code. |
| Gate | A pre/post validation boundary. In the catalog, gates are stored in the wards column. |
| Take | One model attempt in an ensemble. Multiple takes can run to improve confidence. |
| Repair | Retry logic that asks the model or another step to fix an invalid result. |
| Reducer | Logic that turns several takes or step outputs into one typed SQL result. |
| Receipt | Cost, trace, and audit record for the call, written to rvbbit.receipts. See Receipts and Costs. |
This gives the system a cleaner story:
SQL operator -> Cascade -> steps/gates/takes/repair/reducer -> typed result + receipt
Why Cascades Matter#
Most AI application stacks move orchestration out of the database:
- fetch rows,
- call a service,
- branch in application code,
- validate elsewhere,
- write a result back,
- reconstruct cost and trace later.
RVBBIT's hook is that the orchestration can remain SQL-native:
SELECT ticket_id,
rvbbit.review_risk(body, account_tier) AS risk
FROM support_tickets
WHERE created_at >= now() - interval '1 day';
The query sees one typed function. The operator can still run a Cascade with policy gates, parallel takes, retries, tool calls, and receipts.
Shape#
input row
-> gate
-> step: classify intent
-> takes: ask multiple models or prompts
-> validate JSON
-> repair invalid outputs
-> reducer: choose or combine result
-> post gate
-> typed SQL value
-> receipt
Current SQL Surface#
The current implementation stores much of this on rvbbit.operators:
SELECT name,
shape,
return_type,
steps IS NOT NULL AS has_steps,
retry IS NOT NULL AS has_retry,
wards IS NOT NULL AS has_gates,
takes IS NOT NULL AS has_takes,
tests IS NOT NULL AS has_tests
FROM rvbbit.operators
ORDER BY name;
In the catalog, gates are stored in the wards column.
How a Cascade Is Created#
There is no create_cascade function and no separate cascades table. A Cascade
is simply an operator whose steps jsonb is non-null. You create it with the same
rvbbit.create_operator call as any operator, passing the
step plan as op_steps:
SELECT rvbbit.create_operator(
op_name => 'review_risk',
op_arg_names => ARRAY['body', 'account_tier'],
op_arg_types => ARRAY['text', 'text'],
op_return_type => 'text',
op_steps => $steps$[
{"name": "classify", "kind": "llm", "model": "openai/gpt-5.4-mini",
"system": "...", "user": "..."},
{"name": "check", "kind": "code", "fn": "validate_one_of",
"inputs": {"value": "{{ steps.classify.output }}"}}
]$steps$::jsonb);
Each step has a kind — one of llm, specialist, python, code, sql,
mcp, or n8n (invoke an external n8n workflow via its production webhook;
register the runtime first with rvbbit.register_n8n_runtime(...)) — and a
step's output is available to later steps as {{ steps.<name>.<field> }}.
Flow control (gates, ensemble takes, repair retries) is not set on
create_operator. It is attached afterward with the decorator helpers, each of
which takes a jsonb config (pass NULL to clear):
SELECT rvbbit.set_operator_wards('review_risk', '{"pre": [...], "post": [...]}');
SELECT rvbbit.set_operator_takes('review_risk', '{"factor": 3, "reduce": "vote"}');
SELECT rvbbit.set_operator_retry('review_risk', '{"until": {...}, "max_attempts": 3}');
SELECT rvbbit.judgment_purge('review_risk'); -- clear cached receipts after editing
A call then flows through pre-wards -> execute (one call, or an N-take ensemble) -> retry -> post-wards -> result. See Semantic SQL and the
SQL Reference for the full operator model and exact
signatures.
Design Guidance#
Use a Cascade when the operator needs at least one of these:
- multiple model/tool steps,
- validation before or after a model call,
- structured JSON repair,
- ensemble or voting behavior,
- cost-aware fallback,
- provenance that must be inspectable from SQL.
Do not use a Cascade when a single deterministic SQL expression or a simple operator call is enough. The point is to make complex semantic behavior observable, not to make every prompt look sophisticated.